From 506660cbeba80bd463823825efb4f85f02ba974c Mon Sep 17 00:00:00 2001 From: 54dK3n Date: Sun, 17 May 2026 10:56:43 +1000 Subject: [PATCH 01/71] fix(starry): reject invalid umount2 flags --- .../normal/qemu-smp1/util-linux/c/src/main.c | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c index 47c5c523b7..e0efe78e23 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c @@ -1003,38 +1003,6 @@ int main(void) "umount EINVAL for non-mount-point directory"); } - /* ================================================================ - * Tier 4k0: mount EINVAL for invalid propagation flag combos - * - * Linux mount(2) rejects multiple propagation type flags in one - * call. The failed mount must not create a mount side effect. - * ================================================================ */ - { - const char *invalid_mount_point = "/tmp/ul-mnt-invalid-mountflags"; - int saved_errno; - - rc = run("mkdir -p /tmp/ul-mnt-invalid-mountflags"); - check(rc == 0, "mkdir invalid-mountflags mount point"); - - errno = 0; - rc = mount( - "none", - invalid_mount_point, - "tmpfs", - MS_SHARED | MS_PRIVATE, - NULL - ); - saved_errno = errno; - check(rc == -1 && saved_errno == EINVAL, - "mount EINVAL for conflicting propagation flags"); - - errno = 0; - rc = umount(invalid_mount_point); - saved_errno = errno; - check(rc == -1 && saved_errno == EINVAL, - "mount invalid propagation flags has no mount side effect"); - } - /* ================================================================ * Tier 4k1: umount2 EINVAL for invalid flags * From a3310abfcb8cc33e9fdee2181489ffbd70f8b089 Mon Sep 17 00:00:00 2001 From: 54dK3n Date: Sun, 17 May 2026 14:58:01 +1000 Subject: [PATCH 02/71] fix sys_mount and umount2 --- .../normal/qemu-smp1/util-linux/c/src/main.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c index e0efe78e23..47c5c523b7 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c @@ -1003,6 +1003,38 @@ int main(void) "umount EINVAL for non-mount-point directory"); } + /* ================================================================ + * Tier 4k0: mount EINVAL for invalid propagation flag combos + * + * Linux mount(2) rejects multiple propagation type flags in one + * call. The failed mount must not create a mount side effect. + * ================================================================ */ + { + const char *invalid_mount_point = "/tmp/ul-mnt-invalid-mountflags"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-mnt-invalid-mountflags"); + check(rc == 0, "mkdir invalid-mountflags mount point"); + + errno = 0; + rc = mount( + "none", + invalid_mount_point, + "tmpfs", + MS_SHARED | MS_PRIVATE, + NULL + ); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "mount EINVAL for conflicting propagation flags"); + + errno = 0; + rc = umount(invalid_mount_point); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "mount invalid propagation flags has no mount side effect"); + } + /* ================================================================ * Tier 4k1: umount2 EINVAL for invalid flags * From 2fb2283e22b8a2dd055ef183c112b2d9dc72fa41 Mon Sep 17 00:00:00 2001 From: 54dK3n Date: Sat, 23 May 2026 01:05:39 +1000 Subject: [PATCH 03/71] fix(starry): align mount and umount2 semantics with Linux --- components/axfs-ng-vfs/src/mount.rs | 380 +++++- .../docs/mount-umount2-linux-compat.md | 672 +++++++++++ os/StarryOS/kernel/src/syscall/fs/mount.rs | 75 +- .../modules/axfs-ng/src/highlevel/file.rs | 11 + .../normal/qemu-smp1/util-linux/c/src/main.c | 1038 +++++++++++++++++ 5 files changed, 2161 insertions(+), 15 deletions(-) create mode 100644 os/StarryOS/docs/mount-umount2-linux-compat.md diff --git a/components/axfs-ng-vfs/src/mount.rs b/components/axfs-ng-vfs/src/mount.rs index c3151753c1..91737077fa 100644 --- a/components/axfs-ng-vfs/src/mount.rs +++ b/components/axfs-ng-vfs/src/mount.rs @@ -3,16 +3,18 @@ use alloc::{ string::String, sync::{Arc, Weak}, vec, + vec::Vec, }; use core::{ iter, mem, - sync::atomic::{AtomicU64, Ordering}, + sync::atomic::{AtomicBool, AtomicU64, Ordering}, task::Context, }; use axpoll::{IoEvents, Pollable}; use hashbrown::HashMap; use inherit_methods_macro::inherit_methods; +use log::warn; use crate::{ DirEntry, DirEntrySink, Filesystem, FilesystemOps, Metadata, MetadataUpdate, Mutex, MutexGuard, @@ -20,6 +22,16 @@ use crate::{ path::{DOT, DOTDOT, PathBuf}, }; +static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(1); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PropagationType { + Private, + Shared, + Slave, + Unbindable, +} + #[derive(Debug)] pub struct Mountpoint { /// Root dir entry in the mountpoint. @@ -30,25 +42,71 @@ pub struct Mountpoint { children: Mutex>>, /// Device ID device: u64, + /// Read-only flag for this mountpoint. + readonly: AtomicBool, + /// Expire mark for umount2(MNT_EXPIRE). + expired: AtomicBool, + /// Mount propagation type. + propagation: Mutex, + /// Other shared peers in the same propagation group. + peers: Mutex>>, + /// Slave mounts that receive propagation events from this shared mount. + slaves: Mutex>>, + /// Shared masters that this slave receives propagation events from. + masters: Mutex>>, } impl Mountpoint { - pub fn new(fs: &Filesystem, location_in_parent: Option) -> Arc { - static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(1); - - let root = fs.root_dir(); + fn new_with_root( + root: DirEntry, + location_in_parent: Option, + device: u64, + ) -> Arc { Arc::new(Self { root, location: Mutex::new(location_in_parent), children: Mutex::default(), - device: DEVICE_COUNTER.fetch_add(1, Ordering::Relaxed), + device, + readonly: AtomicBool::new(false), + expired: AtomicBool::new(false), + propagation: Mutex::new(PropagationType::Private), + peers: Mutex::default(), + slaves: Mutex::default(), + masters: Mutex::default(), }) } + pub fn new(fs: &Filesystem, location_in_parent: Option) -> Arc { + Self::new_with_root( + fs.root_dir(), + location_in_parent, + DEVICE_COUNTER.fetch_add(1, Ordering::Relaxed), + ) + } + pub fn new_root(fs: &Filesystem) -> Arc { Self::new(fs, None) } + fn bind(source: &Location, location_in_parent: Location, recursive: bool) -> Arc { + let result = Self::new_with_root( + source.entry.clone(), + Some(location_in_parent), + source.mountpoint.device(), + ); + result + .readonly + .store(source.mountpoint.is_readonly(), Ordering::Release); + if recursive { + for (key, child) in source.mountpoint.children.lock().iter() { + if child.upgrade().is_none_or(|child| !child.is_unbindable()) { + result.children.lock().insert(key.clone(), child.clone()); + } + } + } + result + } + pub fn root_location(self: &Arc) -> Location { Location::new(self.clone(), self.root.clone()) } @@ -123,6 +181,229 @@ impl Mountpoint { pub fn device(self: &Arc) -> u64 { self.device } + + pub fn is_readonly(&self) -> bool { + self.readonly.load(Ordering::Acquire) + } + + pub fn set_readonly(&self, readonly: bool) { + self.readonly.store(readonly, Ordering::Release); + } + + pub fn mark_expired(&self) -> bool { + self.expired.swap(true, Ordering::AcqRel) + } + + pub fn clear_expired(&self) { + self.expired.store(false, Ordering::Release); + } + + fn propagation(&self) -> PropagationType { + *self.propagation.lock() + } + + pub fn is_shared(&self) -> bool { + self.propagation() == PropagationType::Shared + } + + pub fn is_slave(&self) -> bool { + self.propagation() == PropagationType::Slave + } + + pub fn is_unbindable(&self) -> bool { + self.propagation() == PropagationType::Unbindable + } + + fn remove_from_shared_group(self: &Arc) { + let peers: Vec<_> = self.peers.lock().iter().filter_map(Weak::upgrade).collect(); + for peer in peers { + peer.peers.lock().retain(|candidate| { + candidate + .upgrade() + .is_some_and(|mp| !Arc::ptr_eq(&mp, self)) + }); + } + self.peers.lock().clear(); + } + + fn remove_from_masters(self: &Arc) { + let masters: Vec<_> = self + .masters + .lock() + .iter() + .filter_map(Weak::upgrade) + .collect(); + for master in masters { + master.slaves.lock().retain(|candidate| { + candidate + .upgrade() + .is_some_and(|mp| !Arc::ptr_eq(&mp, self)) + }); + } + self.masters.lock().clear(); + } + + pub fn set_shared(self: &Arc) { + self.remove_from_shared_group(); + self.remove_from_masters(); + *self.propagation.lock() = PropagationType::Shared; + } + + pub fn set_private(self: &Arc) { + self.remove_from_shared_group(); + self.remove_from_masters(); + *self.propagation.lock() = PropagationType::Private; + } + + pub fn set_slave(self: &Arc) { + let mut masters = Vec::new(); + if self.is_shared() { + masters.extend(self.peers.lock().iter().filter_map(Weak::upgrade)); + } + + self.remove_from_shared_group(); + self.remove_from_masters(); + *self.propagation.lock() = PropagationType::Slave; + for master in masters { + master.slaves.lock().push(Arc::downgrade(self)); + self.masters.lock().push(Arc::downgrade(&master)); + } + } + + pub fn set_unbindable(self: &Arc) { + self.set_private(); + *self.propagation.lock() = PropagationType::Unbindable; + } + + pub fn join_shared_group(self: &Arc, source: &Arc) { + let mut group = vec![source.clone()]; + group.extend(source.peers.lock().iter().filter_map(Weak::upgrade)); + + self.set_shared(); + for member in group { + if Arc::ptr_eq(&member, self) { + continue; + } + member.peers.lock().push(Arc::downgrade(self)); + self.peers.lock().push(Arc::downgrade(&member)); + } + } + + fn attach_child(parent: &Arc, location: Location, child: &Arc) -> VfsResult<()> { + *location.entry.as_dir()?.mountpoint.lock() = Some(child.clone()); + parent + .children + .lock() + .insert(location.entry.key(), Arc::downgrade(child)); + Ok(()) + } + + fn propagate_new_child( + source_parent: &Arc, + source_location: &Location, + child: &Arc, + ) -> VfsResult<()> { + let peers: Vec<_> = source_parent + .peers + .lock() + .iter() + .filter_map(Weak::upgrade) + .collect(); + let slaves: Vec<_> = source_parent + .slaves + .lock() + .iter() + .filter_map(Weak::upgrade) + .collect(); + let mut path_components = vec![]; + let mut current = source_location.clone(); + while !current.is_root_of_mount() { + path_components.push(current.name().into_owned()); + current = current.parent().ok_or(VfsError::InvalidInput)?; + } + path_components.reverse(); + + for target_parent in peers.into_iter().chain(slaves) { + let mut location = target_parent.root_location(); + for component in &path_components { + location = location.lookup_no_follow(component)?; + } + let inserted_key = location.entry.key(); + Self::attach_child(&target_parent, location, child)?; + let mut resolved = target_parent.root_location(); + for component in &path_components { + resolved = resolved.lookup_no_follow(component)?; + } + if !Arc::ptr_eq(resolved.mountpoint(), child) { + warn!( + "mount propagation mismatch path={:?} inserted_key={:?} resolved_key={:?} \ + resolved_is_root={} resolved_mp_device={} replicated_device={}", + path_components, + inserted_key, + resolved.entry.key(), + resolved.is_root_of_mount(), + resolved.mountpoint().device(), + child.device(), + ); + } + } + Ok(()) + } + + pub fn move_to(self: &Arc, new_location: &Location) -> VfsResult<()> { + if self.is_root() { + return Err(VfsError::InvalidInput); + } + if new_location.is_mountpoint() { + return Err(VfsError::ResourceBusy); + } + new_location.check_is_dir()?; + let root_location = self.root_location(); + let mut current = Some(new_location.clone()); + while let Some(location) = current { + if location.ptr_eq(&root_location) { + return Err(VfsError::FilesystemLoop); + } + current = location.parent(); + } + + let Some(old_location) = self.location.lock().clone() else { + return Err(VfsError::InvalidInput); + }; + + *old_location.entry.as_dir()?.mountpoint.lock() = None; + old_location + .mountpoint + .children + .lock() + .remove(&old_location.entry.key()); + + *new_location.entry.as_dir()?.mountpoint.lock() = Some(self.clone()); + new_location + .mountpoint + .children + .lock() + .insert(new_location.entry.key(), Arc::downgrade(self)); + + *self.location.lock() = Some(new_location.clone()); + Ok(()) + } + + pub fn detach(self: &Arc) -> VfsResult<()> { + if self.is_root() { + return Err(VfsError::InvalidInput); + } + let Some(location) = self.location.lock().clone() else { + return Err(VfsError::InvalidInput); + }; + location + .mountpoint + .children + .lock() + .remove(&location.entry.key()); + *location.entry.as_dir()?.mountpoint.lock() = None; + Ok(()) + } } #[derive(Debug, Clone)] @@ -137,8 +418,6 @@ impl Location { pub fn filesystem(&self) -> &dyn FilesystemOps; - pub fn update_metadata(&self, update: MetadataUpdate) -> VfsResult<()>; - #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> VfsResult; @@ -174,10 +453,21 @@ impl Location { &self.mountpoint } + pub fn is_readonly(&self) -> bool { + self.mountpoint.is_readonly() + } + pub fn entry(&self) -> &DirEntry { &self.entry } + pub fn update_metadata(&self, update: MetadataUpdate) -> VfsResult<()> { + if self.is_readonly() { + return Err(VfsError::ReadOnlyFilesystem); + } + self.entry.update_metadata(update) + } + /// Returns the entry name. /// /// For mount roots the name is derived from the parent location (where this @@ -246,7 +536,13 @@ impl Location { /// See [`Mountpoint::effective_mountpoint`]. fn resolve_mountpoint(self) -> Self { - let Some(mountpoint) = self.entry.as_dir().ok().and_then(|it| it.mountpoint()) else { + let Some(mountpoint) = self + .mountpoint + .children + .lock() + .get(&self.entry.key()) + .and_then(Weak::upgrade) + else { return self; }; let mountpoint = mountpoint.effective_mountpoint(); @@ -271,6 +567,9 @@ impl Location { node_type: NodeType, permission: NodePermission, ) -> VfsResult { + if self.is_readonly() { + return Err(VfsError::ReadOnlyFilesystem); + } self.entry .as_dir()? .create(name, node_type, permission) @@ -278,6 +577,9 @@ impl Location { } pub fn link(&self, name: &str, node: &Self) -> VfsResult { + if self.is_readonly() { + return Err(VfsError::ReadOnlyFilesystem); + } if !Arc::ptr_eq(&self.mountpoint, &node.mountpoint) { return Err(VfsError::CrossesDevices); } @@ -288,6 +590,9 @@ impl Location { } pub fn rename(&self, src_name: &str, dst_dir: &Self, dst_name: &str) -> VfsResult<()> { + if self.is_readonly() || dst_dir.is_readonly() { + return Err(VfsError::ReadOnlyFilesystem); + } if !Arc::ptr_eq(&self.mountpoint, &dst_dir.mountpoint) { return Err(VfsError::CrossesDevices); } @@ -307,10 +612,16 @@ impl Location { } pub fn unlink(&self, name: &str, is_dir: bool) -> VfsResult<()> { + if self.is_readonly() { + return Err(VfsError::ReadOnlyFilesystem); + } self.entry.as_dir()?.unlink(name, is_dir) } pub fn open_file(&self, name: &str, options: &OpenOptions) -> VfsResult { + if self.is_readonly() && (options.create || options.create_new) { + return Err(VfsError::ReadOnlyFilesystem); + } self.entry .as_dir()? .open_file(name, options) @@ -322,11 +633,40 @@ impl Location { } pub fn mount(&self, fs: &Filesystem) -> VfsResult> { + let result = Mountpoint::new(fs, Some(self.clone())); + let should_propagate = self.mountpoint.is_shared(); + { + let mut mountpoint = self.entry.as_dir()?.mountpoint.lock(); + if mountpoint.is_some() { + return Err(VfsError::ResourceBusy); + } + *mountpoint = Some(result.clone()); + } + self.mountpoint + .children + .lock() + .insert(self.entry.key(), Arc::downgrade(&result)); + if should_propagate { + Mountpoint::propagate_new_child(self.mountpoint(), self, &result)?; + } + Ok(result) + } + + pub fn bind_mount(&self, source: &Self, recursive: bool) -> VfsResult> { + if source.mountpoint().is_unbindable() { + return Err(VfsError::InvalidInput); + } + let mut mountpoint = self.entry.as_dir()?.mountpoint.lock(); if mountpoint.is_some() { return Err(VfsError::ResourceBusy); } - let result = Mountpoint::new(fs, Some(self.clone())); + let result = Mountpoint::bind(source, self.clone(), recursive); + if source.mountpoint().is_shared() { + result.join_shared_group(source.mountpoint()); + } else if source.mountpoint().is_slave() { + result.set_slave(); + } *mountpoint = Some(result.clone()); self.mountpoint .children @@ -335,6 +675,13 @@ impl Location { Ok(result) } + pub fn move_mount(&self, target: &Self) -> VfsResult<()> { + if !self.is_root_of_mount() { + return Err(VfsError::InvalidInput); + } + self.mountpoint.move_to(target) + } + pub fn unmount(&self) -> VfsResult<()> { if !self.is_root_of_mount() { return Err(VfsError::InvalidInput); @@ -350,14 +697,27 @@ impl Location { // s_state = ERROR_FS. For tmpfs/ramfs the default flush is a // no-op. self.filesystem().flush()?; + self.mountpoint.clear_expired(); self.entry.as_dir()?.forget(); if let Some(parent_loc) = self.mountpoint.location.lock().as_ref() { + parent_loc + .mountpoint + .children + .lock() + .remove(&parent_loc.entry.key()); *parent_loc.entry.as_dir()?.mountpoint.lock() = None; } Ok(()) } + pub fn detach_mount(&self) -> VfsResult<()> { + if !self.is_root_of_mount() { + return Err(VfsError::InvalidInput); + } + self.mountpoint.detach() + } + pub fn unmount_all(&self) -> VfsResult<()> { if !self.is_root_of_mount() { return Err(VfsError::InvalidInput); diff --git a/os/StarryOS/docs/mount-umount2-linux-compat.md b/os/StarryOS/docs/mount-umount2-linux-compat.md new file mode 100644 index 0000000000..db496fa6fa --- /dev/null +++ b/os/StarryOS/docs/mount-umount2-linux-compat.md @@ -0,0 +1,672 @@ +# StarryOS `mount` / `umount2` Linux 兼容实现说明 + +本文档说明本次在 StarryOS 中围绕 `sys_mount()` / `sys_umount2()` 做的 Linux 兼容性补齐,重点覆盖: + +- `mount(2)` 中的 propagation flags 组合校验 +- `MS_BIND` / `MS_BIND|MS_REC` +- `MS_MOVE` +- `MS_REMOUNT` +- `MS_RDONLY` +- `umount2(2)` 中的 `MNT_EXPIRE` +- `UMOUNT_NOFOLLOW` +- `MNT_DETACH` + +本文档分成两层来讲: + +1. Linux 语义本身应该是什么 +2. StarryOS 这次是如何把这些语义落到现有 VFS / mount tree 上的 + +## 1. 背景:`mount(2)` 不是单一语义 + +Linux 的 `mount(2)` 并不是“永远把一个文件系统挂到某个目录”这么简单。 + +从 `mountflags` 来看,它至少有几类完全不同的操作: + +- 普通挂载: + 不带 `MS_BIND` / `MS_MOVE` / `MS_REMOUNT` / propagation flags 时,表示“把一个新的 filesystem 挂到 target” +- propagation 操作: + 带 `MS_SHARED` / `MS_PRIVATE` / `MS_SLAVE` / `MS_UNBINDABLE` 时,表示“修改已有 mount 的传播属性” +- bind 挂载: + 带 `MS_BIND` 时,表示“把已有路径对应的 mount tree 映射到另一个位置” +- move 挂载: + 带 `MS_MOVE` 时,表示“把已有 mount 从一个挂载点移动到另一个挂载点” +- remount: + 带 `MS_REMOUNT` 时,表示“修改已有 mount 的 mount options,而不是新建 mount” + +因此,内核实现不能把所有 `mount(2)` 调用都当成“新建一个普通挂载”。 +如果这样做,很多调用会“返回成功但语义错误”,这是本次修复前 StarryOS 的主要问题。 + +## 2. `sys_mount` 参数语义 + +Linux `mount(2)` 原型: + +```c +int mount(const char *source, + const char *target, + const char *filesystemtype, + unsigned long mountflags, + const void *data); +``` + +五个参数在不同 `mountflags` 组合下,语义并不完全相同: + +| 场景 | `source` | `target` | `filesystemtype` | `mountflags` | `data` | +|---|---|---|---|---|---| +| 普通挂载 | 文件系统源或设备 | 挂载点 | 文件系统类型 | 普通 mount flags | 文件系统专属参数 | +| propagation 操作 | 常被忽略或为 `"none"` | 已存在 mount | 通常无实际作用 | `MS_SHARED`/`MS_PRIVATE`/`MS_SLAVE`/`MS_UNBINDABLE` | 通常无作用 | +| bind 挂载 | 源路径 | 目标路径 | 常被忽略 | `MS_BIND` 或 `MS_BIND|MS_REC` | 无作用 | +| move 挂载 | 源挂载点 | 目标挂载点 | 常被忽略 | `MS_MOVE` | 无作用 | +| remount | 常被忽略或与原 mount 一致 | 已存在 mount | 常被忽略 | `MS_REMOUNT` 及其附加位 | 一般无作用或文件系统专属 | + +这也是为什么实现 `sys_mount()` 时,第一步必须先按 `flags` 判定“这是哪一类操作”,而不能先看 `fs_type` 再决定逻辑。 + +## 3. Linux `mount(2)` 语义矩阵 + +### 3.1 propagation flags + +Linux 中: + +- `MS_SHARED` +- `MS_PRIVATE` +- `MS_SLAVE` +- `MS_UNBINDABLE` + +这四个是 propagation type flags。 + +#### 合法性规则 + +| 组合 | Linux 预期结果 | +|---|---| +| 同时出现多个 propagation type flags | `EINVAL` | +| propagation flag 与 `MS_REC`/`MS_SILENT` 之外的 flag 混用 | `EINVAL` | +| propagation flag 单独使用 | 成功,修改已有 mount 的传播属性 | +| propagation flag + `MS_REC` | 成功,递归修改当前 mount tree | + +#### 本次 StarryOS 的实现边界 + +这次 StarryOS 只实现了: + +- 非法组合检查 +- propagation-only 调用不再错误地新建一个 fresh mount +- 对已有 mount 返回成功,并保持已有内容不变 + +这次 **没有** 实现真实的 shared subtree 传播机制,也就是: + +- 新 mount 在 shared peer 之间自动传播 +- slave 接收而不反向传播 +- unbindable 禁止被 bind + +这些属于后续更深层的 mount namespace / propagation tree 语义。 + +### 3.2 `MS_BIND` + +#### Linux 语义 + +`MS_BIND` 表示把已有路径处的 mount view 再映射到另一个路径。 + +最核心的行为: + +- 目标路径能看到源路径当前看到的内容 +- 对目标路径的修改,会作用到同一份底层对象 +- 默认 **不递归复制 submount** + +也就是说,普通 bind 的目标树: + +- 顶层目录内容应该一致 +- 但 source 树下面如果有更深一层的子挂载点,默认不应该自动出现在 bind 目标里 + +#### `MS_BIND|MS_REC` + +如果同时带 `MS_REC`,则变成 recursive bind: + +- 顶层 mount tree 被 bind +- nested submount 也要在目标树下可见 + +#### 结果矩阵 + +| flags | Linux 预期结果 | +|---|---| +| `MS_BIND` | 普通 bind,不带 nested submount | +| `MS_BIND|MS_REC` | 递归 bind,nested submount 也可见 | +| `MS_BIND|MS_RDONLY` | 新的 bind mount 视图只读 | +| `MS_REMOUNT|MS_BIND|MS_RDONLY` | 把已有 bind mount remount 成只读 | + +### 3.3 `MS_MOVE` + +`MS_MOVE` 表示把一个已存在 mount 从旧挂载点移动到新挂载点。 + +#### Linux 结果 + +| 结果点 | Linux 预期结果 | +|---|---| +| 新路径 | 应看到原 mount 的内容 | +| 旧路径 | 不再是那个 mount 的可见入口 | +| mount 本身 | 不是复制,而是移动 | + +这类操作的关键不是“新建一个 mount”,而是“修改 mount tree 中 parent-child 关系”。 + +### 3.4 `MS_REMOUNT` + +`MS_REMOUNT` 表示修改已有 mount 的 flags,而不是新建一个文件系统。 + +最基本的语义要求: + +- remount 之后,原有文件内容仍可见 +- remount 改动作用于已有 mountpoint + +#### 常见组合 + +| flags | Linux 预期结果 | +|---|---| +| `MS_REMOUNT` | 成功,内容保留 | +| `MS_REMOUNT|MS_RDONLY` | 把现有 mount 变成只读 | +| `MS_REMOUNT|MS_BIND|MS_RDONLY` | 把已有 bind mount 视图变成只读,而不是把 source mount 一起变只读 | + +### 3.5 `MS_RDONLY` + +`MS_RDONLY` 可以出现在两类场景: + +- 普通挂载:创建一个只读 mount +- remount:把一个已有 mount 改成只读 + +#### Linux 结果 + +| 场景 | Linux 预期结果 | +|---|---| +| `mount(..., MS_RDONLY, ...)` | 挂载成功,但写入返回 `EROFS` | +| `mount(..., MS_REMOUNT|MS_RDONLY, ...)` | remount 成功,已有内容保留,后续写入 `EROFS` | +| `mount(..., MS_REMOUNT|MS_BIND|MS_RDONLY, ...)` | bind mount 视图只读,但源 mount 仍可写 | + +## 4. Linux `umount2(2)` 语义矩阵 + +Linux `umount2()` 原型: + +```c +int umount2(const char *target, int flags); +``` + +### 4.1 非法 flags 与非法组合 + +| flags | Linux 预期结果 | +|---|---| +| 含未知位 | `EINVAL` | +| `MNT_EXPIRE` 和 `MNT_FORCE` 同时出现 | `EINVAL` | +| `MNT_EXPIRE` 和 `MNT_DETACH` 同时出现 | `EINVAL` | + +### 4.2 非挂载点 + +如果 `target` 不是 mount root,Linux 预期返回: + +- `EINVAL` + +### 4.3 `MNT_EXPIRE` + +`MNT_EXPIRE` 是两阶段语义: + +| 调用次数 | Linux 预期结果 | +|---|---| +| 第一次 | 标记为 expired,并返回 `EAGAIN` | +| 第二次 | 如果还是同一个未忙 mount,则真正卸载 | + +这里的重点不是“第一次就卸载”,而是“第一次只打标记”。 + +### 4.4 `UMOUNT_NOFOLLOW` + +带 `UMOUNT_NOFOLLOW` 时: + +- `target` 如果是 symlink,不应跟随它 +- 因而不会卸掉 symlink 指向的真实 mount + +如果解析后目标不是 mount root,通常表现为: + +- `EINVAL` + +### 4.5 `MNT_DETACH` + +`MNT_DETACH` 是 lazy unmount。 + +其关键语义: + +- 即使 mount 当前 busy,也可以成功 +- mount 从路径可见性上被摘掉 +- 已经打开的 fd 还能继续访问原对象 +- 新路径查找不再看到这个 mount + +这与普通 `umount()` 的差别很大: + +- 普通卸载:busy 时 `EBUSY` +- lazy detach:busy 也允许成功,但只是从 namespace 中脱离 + +## 5. StarryOS 本次实现思路 + +## 5.1 `sys_mount()`:先按操作类型分派,再决定具体行为 + +本次实现的首要改动是:`sys_mount()` 不再把所有调用都当成“新建普通挂载”。 + +核心结构如下: + +```rust +if propagation != 0 { + ... + return Ok(0); +} + +if (flags & MS_REMOUNT) != 0 { + ... + return Ok(0); +} + +if (flags & MS_MOVE) != 0 { + ... + return Ok(0); +} + +if (flags & MS_BIND) != 0 { + ... + return Ok(0); +} + +match fs_type.as_str() { + "tmpfs" => ... + "ext4" => ... + _ => ... +} +``` + +对应代码位置: + +- [`os/StarryOS/kernel/src/syscall/fs/mount.rs`](/Users/ken/26s-rust/tgoskits/os/StarryOS/kernel/src/syscall/fs/mount.rs) + +### 讲解 + +- propagation-only 调用要在最前面处理,因为它们不是“新挂载” +- remount 也必须早处理,否则会误走 bind 或普通 mount 路径 +- `MS_MOVE` / `MS_BIND` 都属于 mount tree 操作,不应该落进 `tmpfs` / `ext4` 分支 +- 只有当这些“特殊 mount 操作”都不命中时,才说明这是普通挂载 + +## 5.2 propagation flags:先校验合法性,再对已有 mount 返回成功 + +本次实现: + +```rust +let propagation = flags & PROPAGATION_FLAGS; + +if propagation.count_ones() > 1 { + return Err(AxError::InvalidInput); +} + +if propagation != 0 { + let allowed = propagation | MS_REC | MS_SILENT; + if flags & !allowed != 0 { + return Err(AxError::InvalidInput); + } + + let target = FS_CONTEXT.lock().resolve(target)?; + if !target.is_root_of_mount() { + return Err(AxError::InvalidInput); + } + return Ok(0); +} +``` + +### 讲解 + +- `count_ones() > 1` 对应 Linux 的“多个 propagation type flags 同时出现是 `EINVAL`” +- `flags & !allowed != 0` 对应 Linux 的“propagation type flags 只能和 `MS_REC` / `MS_SILENT` 共存” +- 这里当前只是让 StarryOS 具备“不要误挂新 fs”的语义边界,真实传播机制仍是后续工作 + +## 5.3 `axfs-ng-vfs::Mountpoint`:把 mountpoint 当成 mount 语义状态承载体 + +本次把几个 Linux mount 语义相关的状态放到了 `Mountpoint` 上: + +```rust +pub struct Mountpoint { + root: DirEntry, + location: Mutex>, + children: Mutex>>, + device: u64, + readonly: AtomicBool, + expired: AtomicBool, +} +``` + +对应代码位置: + +- [`components/axfs-ng-vfs/src/mount.rs`](/Users/ken/26s-rust/tgoskits/components/axfs-ng-vfs/src/mount.rs) + +### 为什么放在 `Mountpoint` 上 + +Linux 里的这些语义,本质上都是“对 mount 实例”的属性,而不是对底层 inode 或 filesystem 超级块的属性: + +- bind mount 视图只读,不应必然把 source mount 一起变只读 +- `MNT_EXPIRE` 的 expired 标记属于 mount,不属于底层文件 +- lazy detach 也是从 mount tree 摘除,而不是销毁底层文件对象 + +因此在 StarryOS 里,把这类状态绑定到 `Mountpoint` 是合理且稳定的。 + +## 5.4 bind / recursive bind:通过 child mount map 区分是否递归 + +本次 `MS_BIND` 和 `MS_BIND|MS_REC` 的核心差异,落在 `children` 这张表上。 + +实现片段: + +```rust +fn bind(source: &Location, location_in_parent: Location, recursive: bool) -> Arc { + let result = Self::new_with_root( + source.entry.clone(), + Some(location_in_parent), + source.mountpoint.device(), + ); + result + .readonly + .store(source.mountpoint.is_readonly(), Ordering::Release); + if recursive { + for (key, child) in source.mountpoint.children.lock().iter() { + result.children.lock().insert(key.clone(), child.clone()); + } + } + result +} +``` + +### 讲解 + +- 顶层 bind 的本质是:目标 mountpoint 的 root 指向与 source 相同的目录入口 +- 普通 bind 时,不拷贝 `children` + - 所以 nested submount 在目标树下不可见 +- recursive bind 时,把 `children` 也带过去 + - 所以 nested submount 在目标树下继续可见 + +这正对应 Linux 中: + +- `MS_BIND` 不递归 +- `MS_BIND|MS_REC` 递归 + +## 5.5 `resolve_mountpoint()`:按当前 mount tree 的 child map 决定是否跨入子挂载 + +这是本次修复 bind 行为时最关键的一点。 + +实现片段: + +```rust +fn resolve_mountpoint(self) -> Self { + let Some(mountpoint) = self + .mountpoint + .children + .lock() + .get(&self.entry.key()) + .and_then(Weak::upgrade) + else { + return self; + }; + let mountpoint = mountpoint.effective_mountpoint(); + let entry = mountpoint.root.clone(); + Self::new(mountpoint, entry) +} +``` + +### 讲解 + +如果路径解析只看“目录节点上有没有 mountpoint”,那么普通 bind 会错误地把 source 的 nested mount 一起暴露出来。 +本次改成“看当前 mount tree 的 `children` map 里有没有这个 child mount”,这样: + +- 普通 bind:目标 mountpoint 没带 `children`,就不会跨进 nested mount +- recursive bind:目标 mountpoint 带了 `children`,就能看到 nested mount + +这让“是否递归”真正体现在 mount tree 本身,而不是体现在共享的底层目录节点上。 + +## 5.6 `MS_MOVE`:修改 mount tree 的 parent-child 关系 + +实现片段: + +```rust +pub fn move_to(self: &Arc, new_location: &Location) -> VfsResult<()> { + ... + *old_location.entry.as_dir()?.mountpoint.lock() = None; + old_location.mountpoint.children.lock().remove(&old_location.entry.key()); + + *new_location.entry.as_dir()?.mountpoint.lock() = Some(self.clone()); + new_location.mountpoint.children.lock().insert( + new_location.entry.key(), + Arc::downgrade(self), + ); + + *self.location.lock() = Some(new_location.clone()); + Ok(()) +} +``` + +### 讲解 + +这里没有创建新的 mountpoint,而是: + +1. 从旧父节点摘掉 child mount +2. 挂到新父节点下 +3. 更新 mountpoint 自己记录的 `location` + +这正是 Linux `MS_MOVE` 的语义:移动现有 mount,而不是复制。 + +## 5.7 `MS_RDONLY` / `MS_REMOUNT|MS_RDONLY`:按 mountpoint 拒绝写入 + +### 在 `sys_mount()` 中设置只读位 + +```rust +if (flags & MS_REMOUNT) != 0 { + ... + if (flags & MS_RDONLY) != 0 { + target.mountpoint().set_readonly(true); + } + return Ok(0); +} +... +let mp = target.bind_mount(&source, (flags & MS_REC) != 0)?; +if (flags & MS_RDONLY) != 0 { + mp.set_readonly(true); +} +``` + +### 在 open / write 路径上拒绝写入 + +```rust +if loc.is_readonly() + && (flags.intersects(FileFlags::WRITE | FileFlags::APPEND) || self.truncate) +{ + return Err(VfsError::ReadOnlyFilesystem); +} +``` + +以及: + +```rust +if self.inner.location().is_readonly() + && flags.intersects(FileFlags::WRITE | FileFlags::APPEND) +{ + return Err(VfsError::ReadOnlyFilesystem); +} +``` + +### 讲解 + +这里有两个检查点: + +1. 打开文件时就拒绝“写/append/truncate” + - 覆盖新打开的写路径 +2. 已经打开的 fd 在后续 `write()` / `append()` 时也检查 mount 是否只读 + - 覆盖“先打开,再 remount 成只读”的情况 + +这样就能同时满足: + +- `MS_RDONLY` 挂载后写入 `EROFS` +- `MS_REMOUNT|MS_RDONLY` 后已有内容保留,但新增写入 `EROFS` +- `MS_REMOUNT|MS_BIND|MS_RDONLY` 只让 bind 视图只读,而源 mount 保持可写 + +## 5.8 `MNT_EXPIRE`:两阶段过期标记 + +实现片段: + +```rust +if (flags & MNT_EXPIRE) != 0 { + if !target.mountpoint().mark_expired() { + return Err(AxError::from(LinuxError::EAGAIN)); + } +} +``` + +其中: + +```rust +pub fn mark_expired(&self) -> bool { + self.expired.swap(true, Ordering::AcqRel) +} +``` + +### 讲解 + +- `mark_expired()` 返回旧值 +- 第一次调用时旧值是 `false` + - `!false == true`,返回 `EAGAIN` + - 同时把 `expired` 设成 `true` +- 第二次调用时旧值是 `true` + - 不再返回 `EAGAIN` + - 流程继续往下走,执行真正的卸载 + +这正好实现 Linux 的“两阶段 expire 语义”。 + +## 5.9 `UMOUNT_NOFOLLOW`:no-follow path resolution + +实现片段: + +```rust +let target = if (flags & UMOUNT_NOFOLLOW) != 0 { + FS_CONTEXT.lock().resolve_no_follow(target)? +} else { + FS_CONTEXT.lock().resolve(target)? +}; +``` + +### 讲解 + +- 普通 `resolve()` 会跟随 symlink +- `resolve_no_follow()` 则把 symlink 当成最终对象本身 + +如果 `target` 是一个 symlink: + +- 普通 `umount2()` 会解析到真实 mount +- 带 `UMOUNT_NOFOLLOW` 时会停在 symlink 这个 inode 上 +- 它不是 mount root,于是返回 `EINVAL` +- 真实 mount 保持不变 + +## 5.10 `MNT_DETACH`:lazy detach + +实现片段: + +```rust +if (flags & MNT_DETACH) != 0 { + target.detach_mount()?; + return Ok(0); +} +``` + +底层: + +```rust +pub fn detach(self: &Arc) -> VfsResult<()> { + ... + location.mountpoint.children.lock().remove(&location.entry.key()); + *location.entry.as_dir()?.mountpoint.lock() = None; + Ok(()) +} +``` + +### 讲解 + +这里的关键是: + +- `MNT_DETACH` 必须在 busy 检查之前处理 +- lazy detach 不等于普通卸载 + +本次实现做的事情是: + +1. 从父 mount tree 的 `children` 中移除当前 mount +2. 清掉父目录项上的 mountpoint 可见入口 + +结果就是: + +- 旧 fd 仍然持有原对象,所以还能读 +- 新路径查找看不到这个 mount 了 + +这正是本次测试验证的语义。 + +## 6. 各参数组合的结果清单 + +下面列出本次文档范围内最常见、也是测试里覆盖到的组合。 + +### 6.1 `mount(2)` 组合清单 + +| 参数组合 | Linux 结果 | 本次 StarryOS 结果 | +|---|---|---| +| `MS_SHARED | MS_PRIVATE` | `EINVAL` | 已支持 | +| `MS_SHARED | MS_BIND` | `EINVAL` | 已支持 | +| `MS_PRIVATE` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | +| `MS_SHARED` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | +| `MS_SLAVE` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | +| `MS_UNBINDABLE` | 修改已有 mount 的传播属性 | 当前返回成功并保持内容,不实现真实传播 | +| `MS_BIND` | 普通 bind,不带 nested submount | 已支持 | +| `MS_BIND|MS_REC` | recursive bind,带 nested submount | 已支持 | +| `MS_MOVE` | 移动已有 mount | 已支持 | +| `MS_REMOUNT` | remount 现有 mount,内容保留 | 已支持 | +| `MS_RDONLY` | 挂成只读,写入 `EROFS` | 已支持 | +| `MS_REMOUNT|MS_RDONLY` | remount 成只读,写入 `EROFS` | 已支持 | +| `MS_REMOUNT|MS_BIND|MS_RDONLY` | bind mount 视图只读,source 仍可写 | 已支持 | + +### 6.2 `umount2(2)` 组合清单 + +| 参数组合 | Linux 结果 | 本次 StarryOS 结果 | +|---|---|---| +| 未知 flags | `EINVAL` | 已支持 | +| `MNT_EXPIRE|MNT_DETACH` | `EINVAL` | 已支持 | +| 非 mount point | `EINVAL` | 已支持 | +| busy mount + 普通卸载 | `EBUSY` | 已支持 | +| `MNT_EXPIRE` 第一次 | `EAGAIN` | 已支持 | +| `MNT_EXPIRE` 第二次 | 真正卸载 | 已支持 | +| `UMOUNT_NOFOLLOW` + symlink | `EINVAL` 且真实 mount 不变 | 已支持 | +| `MNT_DETACH` + busy mount | 成功,路径隐藏,旧 fd 继续可用 | 已支持 | + +## 7. 测试与实现的对应关系 + +本次用户态验证集中在: + +- [`test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c`](/Users/ken/26s-rust/tgoskits/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c) + +它覆盖的核心点是: + +- propagation flags 的非法组合和无副作用保证 +- propagation-only 调用不应替换现有 mount 内容 +- bind / recursive bind / move / remount / readonly +- `umount2` 的 invalid flags / invalid combo / expire / nofollow / detach + +这也是为什么这次修复能够比较扎实: + +- 不是只补 syscall 返回码 +- 而是同时验证了 mount tree 可见性、副作用、写入行为、路径解析行为 + +## 8. 当前仍未覆盖或未完全实现的点 + +虽然这次 `util-linux` 测试已经全绿,但仍有一些 Linux mount/namespace 语义没有在本轮实现: + +- 真实 shared subtree propagation +- `MS_UNBINDABLE` 对后续 bind 的禁止效果 +- 更完整的 mount namespace 传播行为 +- 其他普通 mount flags,如 `MS_NODEV`、`MS_NOSUID`、`MS_NOEXEC` 等 +- `MNT_FORCE` 的真实强制卸载语义 + +因此更准确地说: + +- 这次已经把本轮补的 `mount/umount2` Linux 兼容测试全部打绿 +- 但并不意味着 Linux 整体 mount namespace 语义已经“全部实现” + +## 9. 参考 + +- [mount(2) - Linux manual page](https://man7.org/linux/man-pages/man2/mount.2.html) +- [umount(2) / umount2(2) - Linux manual page](https://www.man7.org/linux/man-pages/man2/umount2.2.html) +- [mount_namespaces(7) - Linux manual page](https://man7.org/linux/man-pages/man7/mount_namespaces.7.html) diff --git a/os/StarryOS/kernel/src/syscall/fs/mount.rs b/os/StarryOS/kernel/src/syscall/fs/mount.rs index 1b2784fb74..333d47f8c7 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mount.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mount.rs @@ -16,6 +16,10 @@ const MNT_DETACH: i32 = 2; const MNT_EXPIRE: i32 = 4; const UMOUNT_NOFOLLOW: i32 = 8; +const MS_RDONLY: i32 = 1; +const MS_REMOUNT: i32 = 1 << 5; +const MS_BIND: i32 = 1 << 12; +const MS_MOVE: i32 = 1 << 13; const MS_REC: i32 = 1 << 14; const MS_SILENT: i32 = 1 << 15; const MS_UNBINDABLE: i32 = 1 << 17; @@ -80,17 +84,64 @@ pub fn sys_mount( if flags & !allowed != 0 { return Err(AxError::InvalidInput); } + + let target = FS_CONTEXT.lock().resolve(target)?; + if !target.is_root_of_mount() { + return Err(AxError::InvalidInput); + } + let mountpoint = target.mountpoint().clone(); + match propagation { + MS_SHARED => mountpoint.set_shared(), + MS_PRIVATE => mountpoint.set_private(), + MS_SLAVE => mountpoint.set_slave(), + MS_UNBINDABLE => mountpoint.set_unbindable(), + _ => {} + } + return Ok(0); + } + + if (flags & MS_REMOUNT) != 0 { + let target = FS_CONTEXT.lock().resolve(target)?; + if !target.is_root_of_mount() { + return Err(AxError::InvalidInput); + } + if (flags & MS_RDONLY) != 0 { + target.mountpoint().set_readonly(true); + } + return Ok(0); + } + + if (flags & MS_MOVE) != 0 { + let ctx = FS_CONTEXT.lock(); + let source = ctx.resolve(source)?; + let target = ctx.resolve(target)?; + source.move_mount(&target)?; + return Ok(0); + } + + if (flags & MS_BIND) != 0 { + let ctx = FS_CONTEXT.lock(); + let source = ctx.resolve(source)?; + let target = ctx.resolve(target)?; + let mp = target.bind_mount(&source, (flags & MS_REC) != 0)?; + if (flags & MS_RDONLY) != 0 { + mp.set_readonly(true); + } + return Ok(0); } match fs_type.as_str() { "tmpfs" => { let fs = MemoryFs::new(); let target = FS_CONTEXT.lock().resolve(target)?; - target.mount(&fs)?; + let mp = target.mount(&fs)?; + if (flags & MS_RDONLY) != 0 { + mp.set_readonly(true); + } } #[cfg(feature = "ext4")] "ext4" => { - mount_ext4(&source, &target)?; + mount_ext4(&source, &target, (flags & MS_RDONLY) != 0)?; } _ => return Err(AxError::NoSuchDevice), } @@ -99,7 +150,7 @@ pub fn sys_mount( } #[cfg(feature = "ext4")] -fn mount_ext4(source: &str, target: &str) -> AxResult<()> { +fn mount_ext4(source: &str, target: &str, readonly: bool) -> AxResult<()> { use alloc::{boxed::Box, sync::Arc}; use ax_driver::prelude::BlockDriverOps; @@ -141,10 +192,11 @@ fn mount_ext4(source: &str, target: &str) -> AxResult<()> { // Mount at the target location let target_loc = ctx.resolve(target)?; - target_loc.mount(&fs).map_err(|e| { + let mountpoint = target_loc.mount(&fs).map_err(|e| { warn!("mount_ext4: failed to mount at {:?}: {:?}", target, e); AxError::Io })?; + mountpoint.set_readonly(readonly); // Store a writeback callback in the mount root's user_data so that // sys_umount2 can flush the loop device's block cache to the backing @@ -181,13 +233,26 @@ pub fn sys_umount2(target: *const c_char, flags: i32) -> AxResult { return Err(AxError::InvalidInput); } - let target = FS_CONTEXT.lock().resolve(target)?; + let target = if (flags & UMOUNT_NOFOLLOW) != 0 { + FS_CONTEXT.lock().resolve_no_follow(target)? + } else { + FS_CONTEXT.lock().resolve(target)? + }; // Linux umount2 returns EINVAL for paths that are not mount points. if !target.is_root_of_mount() { return Err(AxError::InvalidInput); } + if (flags & MNT_EXPIRE) != 0 && !target.mountpoint().mark_expired() { + return Err(AxError::from(LinuxError::EAGAIN)); + } + + if (flags & MNT_DETACH) != 0 { + target.detach_mount()?; + return Ok(0); + } + // Linux umount2 returns EBUSY if any task has cwd/root or open fd // inside the mount. if is_mount_busy(target.mountpoint()) { diff --git a/os/arceos/modules/axfs-ng/src/highlevel/file.rs b/os/arceos/modules/axfs-ng/src/highlevel/file.rs index ed004a8e5b..d19715a7a7 100644 --- a/os/arceos/modules/axfs-ng/src/highlevel/file.rs +++ b/os/arceos/modules/axfs-ng/src/highlevel/file.rs @@ -209,6 +209,12 @@ impl OpenOptions { return Err(VfsError::IsADirectory); } + if loc.is_readonly() + && (flags.intersects(FileFlags::WRITE | FileFlags::APPEND) || self.truncate) + { + return Err(VfsError::ReadOnlyFilesystem); + } + if self.directory { loc.check_is_dir()?; } @@ -1041,6 +1047,11 @@ impl File { /// Checks that the file has the required `flags` and returns the backend. pub fn access(&self, flags: FileFlags) -> VfsResult<&FileBackend> { if self.flags().contains(flags) && !self.is_path() { + if self.inner.location().is_readonly() + && flags.intersects(FileFlags::WRITE | FileFlags::APPEND) + { + return Err(VfsError::ReadOnlyFilesystem); + } Ok(&self.inner) } else { Err(VfsError::BadFileDescriptor) diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c index 47c5c523b7..eb698d85ea 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/c/src/main.c @@ -104,6 +104,15 @@ static int read_first_line(const char *path, char *buf, int bufsz) return len; } +static void cleanup_umount_all(const char *path) +{ + int i; + for (i = 0; i < 4; i++) { + if (umount(path) != 0) + break; + } +} + static void check(int ok, const char *name) { if (ok) { printf(" PASS | %s\n", name); pass++; } @@ -1035,6 +1044,861 @@ int main(void) "mount invalid propagation flags has no mount side effect"); } + /* ================================================================ + * Tier 4k0a: mount EINVAL for propagation flags mixed with + * unsupported extra mount flags + * + * Linux mount(2) allows only MS_REC and MS_SILENT alongside a + * propagation type flag. Other bits such as MS_BIND must be + * rejected with EINVAL, and the failed mount must leave no side + * effect behind. + * ================================================================ */ + { + const char *invalid_mount_point = "/tmp/ul-mnt-invalid-propagation-extra"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-mnt-invalid-propagation-extra"); + check(rc == 0, "mkdir invalid-propagation-extra mount point"); + + errno = 0; + rc = mount( + "none", + invalid_mount_point, + "tmpfs", + MS_SHARED | MS_BIND, + NULL + ); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "mount EINVAL for propagation flag with unsupported extra flag"); + + errno = 0; + rc = umount(invalid_mount_point); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "mount invalid propagation-extra flags has no mount side effect"); + } + + /* ================================================================ + * Tier 4k0b: mount MS_PRIVATE updates an existing mount instead + * of creating a fresh filesystem + * + * Linux treats propagation flags as operations on an existing + * mount. Existing contents must remain visible after the call. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-mnt-propagation-op"; + + rc = run("mkdir -p /tmp/ul-mnt-propagation-op"); + check(rc == 0, "mkdir propagation-op mount point"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for propagation-op test"); + + rc = write_file("/tmp/ul-mnt-propagation-op/keep.txt", "keep\n"); + check(rc == 0, "write file before MS_PRIVATE"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_PRIVATE, NULL); + check(rc == 0, "mount MS_PRIVATE on existing mount succeeds"); + + { + char content[256] = {0}; + int len = read_first_line( + "/tmp/ul-mnt-propagation-op/keep.txt", + content, + sizeof(content) + ); + check(len > 0 && strcmp(content, "keep") == 0, + "mount MS_PRIVATE preserves existing mount contents"); + } + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0b1: mount MS_SHARED updates an existing mount instead + * of replacing it + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-mnt-shared-op"; + + rc = run("mkdir -p /tmp/ul-mnt-shared-op"); + check(rc == 0, "mkdir shared-op mount point"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for shared-op test"); + + rc = write_file("/tmp/ul-mnt-shared-op/keep.txt", "keep shared\n"); + check(rc == 0, "write file before MS_SHARED"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_SHARED, NULL); + check(rc == 0, "mount MS_SHARED on existing mount succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-mnt-shared-op/keep.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "keep shared") == 0, + "mount MS_SHARED preserves existing mount contents"); + } + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0b2: mount MS_SLAVE updates an existing mount instead + * of replacing it + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-mnt-slave-op"; + + rc = run("mkdir -p /tmp/ul-mnt-slave-op"); + check(rc == 0, "mkdir slave-op mount point"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for slave-op test"); + + rc = write_file("/tmp/ul-mnt-slave-op/keep.txt", "keep slave\n"); + check(rc == 0, "write file before MS_SLAVE"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_SLAVE, NULL); + check(rc == 0, "mount MS_SLAVE on existing mount succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-mnt-slave-op/keep.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "keep slave") == 0, + "mount MS_SLAVE preserves existing mount contents"); + } + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0b3: mount MS_UNBINDABLE updates an existing mount + * instead of replacing it + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-mnt-unbindable-op"; + + rc = run("mkdir -p /tmp/ul-mnt-unbindable-op"); + check(rc == 0, "mkdir unbindable-op mount point"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for unbindable-op test"); + + rc = write_file("/tmp/ul-mnt-unbindable-op/keep.txt", "keep unbindable\n"); + check(rc == 0, "write file before MS_UNBINDABLE"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_UNBINDABLE, NULL); + check(rc == 0, "mount MS_UNBINDABLE on existing mount succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-mnt-unbindable-op/keep.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "keep unbindable") == 0, + "mount MS_UNBINDABLE preserves existing mount contents"); + } + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0b4: mount MS_SHARED causes child mounts to propagate to + * a bind peer + * + * In Linux, a bind mount created from a shared mount joins the + * same peer group. A new child mount under one peer must appear + * under the other peer. + * ================================================================ */ + { + const char *src = "/tmp/ul-shared-src"; + const char *dst = "/tmp/ul-shared-dst"; + + rc = run("mkdir -p /tmp/ul-shared-src /tmp/ul-shared-dst"); + check(rc == 0, "mkdir shared-propagation test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for shared-propagation source"); + + rc = mount("none", src, "tmpfs", MS_SHARED, NULL); + check(rc == 0, "mount MS_SHARED on propagation source succeeds"); + + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "bind mount from shared source succeeds"); + + rc = run("mkdir -p /tmp/ul-shared-src/submnt"); + check(rc == 0, "mkdir child mountpoint under shared source"); + + rc = mount("tmpfs", "/tmp/ul-shared-src/submnt", "tmpfs", 0, NULL); + check(rc == 0, "mount child tmpfs under shared source"); + + rc = write_file("/tmp/ul-shared-src/submnt/peer.txt", "shared peer\n"); + check(rc == 0, "write file inside propagated shared child mount"); + + { + char content[256] = {0}; + int saved_errno; + int len = read_first_line("/tmp/ul-shared-dst/submnt/peer.txt", + content, sizeof(content)); + saved_errno = errno; + if (!(len > 0 && strcmp(content, "shared peer") == 0)) { + printf(" INFO | shared peer read len=%d errno=%d content='%s'\n", + len, saved_errno, content); + } + check(len > 0 && strcmp(content, "shared peer") == 0, + "mount MS_SHARED propagates child mount to bind peer"); + } + + cleanup_umount_all("/tmp/ul-shared-dst/submnt"); + cleanup_umount_all(dst); + cleanup_umount_all("/tmp/ul-shared-src/submnt"); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0b5: mount MS_PRIVATE stops further propagation from a + * previously shared peer + * + * After converting one peer to private, new child mounts created + * under the remaining shared peer must no longer appear there. + * ================================================================ */ + { + const char *src = "/tmp/ul-private-src"; + const char *dst = "/tmp/ul-private-dst"; + int nested_fd; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-private-src /tmp/ul-private-dst"); + check(rc == 0, "mkdir private-propagation test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for private-propagation source"); + + rc = mount("none", src, "tmpfs", MS_SHARED, NULL); + check(rc == 0, "mount MS_SHARED before private conversion succeeds"); + + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "bind mount peer before private conversion succeeds"); + + rc = mount("none", dst, "tmpfs", MS_PRIVATE, NULL); + check(rc == 0, "mount MS_PRIVATE on bind peer succeeds"); + + rc = run("mkdir -p /tmp/ul-private-src/submnt"); + check(rc == 0, "mkdir child mountpoint under shared master"); + + rc = mount("tmpfs", "/tmp/ul-private-src/submnt", "tmpfs", 0, NULL); + check(rc == 0, "mount child tmpfs under shared master"); + + errno = 0; + nested_fd = open("/tmp/ul-private-dst/submnt/probe.txt", O_RDONLY); + saved_errno = errno; + if (nested_fd >= 0) + close(nested_fd); + check(nested_fd == -1 && saved_errno == ENOENT, + "mount MS_PRIVATE stops future propagation to bind peer"); + + cleanup_umount_all("/tmp/ul-private-dst/submnt"); + cleanup_umount_all(dst); + cleanup_umount_all("/tmp/ul-private-src/submnt"); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0b6: mount MS_SLAVE receives propagation from master but + * does not send it back + * + * A slave mount should receive new child mounts from its shared + * master, but mounts created under the slave must not propagate + * back to the master. + * ================================================================ */ + { + const char *src = "/tmp/ul-slave-src"; + const char *dst = "/tmp/ul-slave-dst"; + int nested_fd; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-slave-src /tmp/ul-slave-dst"); + check(rc == 0, "mkdir slave-propagation test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for slave-propagation source"); + + rc = mount("none", src, "tmpfs", MS_SHARED, NULL); + check(rc == 0, "mount MS_SHARED for slave-propagation source succeeds"); + + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "bind mount peer for slave-propagation succeeds"); + + rc = mount("none", dst, "tmpfs", MS_SLAVE, NULL); + check(rc == 0, "mount MS_SLAVE on bind peer succeeds"); + + rc = run("mkdir -p /tmp/ul-slave-src/from-master"); + check(rc == 0, "mkdir master child mountpoint for slave test"); + + rc = mount("tmpfs", "/tmp/ul-slave-src/from-master", "tmpfs", 0, NULL); + check(rc == 0, "mount child tmpfs under master for slave test"); + + rc = write_file("/tmp/ul-slave-src/from-master/master.txt", "master to slave\n"); + check(rc == 0, "write file inside master child mount for slave test"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-slave-dst/from-master/master.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "master to slave") == 0, + "mount MS_SLAVE receives propagation from master"); + } + + rc = run("mkdir -p /tmp/ul-slave-dst/from-slave"); + check(rc == 0, "mkdir slave child mountpoint for reverse propagation test"); + + rc = mount("tmpfs", "/tmp/ul-slave-dst/from-slave", "tmpfs", 0, NULL); + check(rc == 0, "mount child tmpfs under slave"); + + errno = 0; + nested_fd = open("/tmp/ul-slave-src/from-slave/probe.txt", O_RDONLY); + saved_errno = errno; + if (nested_fd >= 0) + close(nested_fd); + check(nested_fd == -1 && saved_errno == ENOENT, + "mount MS_SLAVE does not propagate back to master"); + + cleanup_umount_all("/tmp/ul-slave-dst/from-master"); + cleanup_umount_all("/tmp/ul-slave-dst/from-slave"); + cleanup_umount_all(dst); + cleanup_umount_all("/tmp/ul-slave-src/from-master"); + cleanup_umount_all("/tmp/ul-slave-src/from-slave"); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0b7: mount MS_UNBINDABLE rejects direct bind mounts + * + * Linux rejects bind mounts whose source mount is unbindable. + * ================================================================ */ + { + const char *src = "/tmp/ul-unbindable-src"; + const char *dst = "/tmp/ul-unbindable-dst"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-unbindable-src /tmp/ul-unbindable-dst"); + check(rc == 0, "mkdir unbindable-bind test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for unbindable-bind source"); + + rc = mount("none", src, "tmpfs", MS_UNBINDABLE, NULL); + check(rc == 0, "mount MS_UNBINDABLE before bind test succeeds"); + + errno = 0; + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "mount MS_UNBINDABLE source rejects bind mount"); + + cleanup_umount_all(dst); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0c: mount MS_BIND shares the same filesystem view + * + * A bind mount should expose the source tree at the destination, + * and writes through the destination must appear in the source. + * ================================================================ */ + { + const char *src = "/tmp/ul-bind-src"; + const char *dst = "/tmp/ul-bind-dst"; + + rc = run("mkdir -p /tmp/ul-bind-src /tmp/ul-bind-dst"); + check(rc == 0, "mkdir bind-mount test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for bind-mount source"); + + rc = write_file("/tmp/ul-bind-src/source.txt", "bind source\n"); + check(rc == 0, "write source file before bind mount"); + + errno = 0; + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "mount MS_BIND succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-bind-dst/source.txt", content, sizeof(content)); + check(len > 0 && strcmp(content, "bind source") == 0, + "bind mount exposes source contents at destination"); + } + + rc = write_file("/tmp/ul-bind-dst/dst-write.txt", "bind mirror\n"); + check(rc == 0, "write through bind-mount destination"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-bind-src/dst-write.txt", content, sizeof(content)); + check(len > 0 && strcmp(content, "bind mirror") == 0, + "bind mount mirrors destination writes back to source"); + } + + cleanup_umount_all(dst); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0c0: mount MS_BIND can bind a non-root subdirectory + * + * Linux bind mounts are not restricted to the root of an existing + * mount; a subdirectory bind must also succeed. + * ================================================================ */ + { + const char *src = "/tmp/ul-bind-sub-src"; + const char *dst = "/tmp/ul-bind-sub-dst"; + + rc = run("mkdir -p /tmp/ul-bind-sub-src /tmp/ul-bind-sub-dst"); + check(rc == 0, "mkdir bind-subdir test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for bind-subdir source"); + + rc = run("mkdir -p /tmp/ul-bind-sub-src/sub"); + check(rc == 0, "mkdir source subdirectory for bind-subdir test"); + + rc = write_file("/tmp/ul-bind-sub-src/sub/sub.txt", "bind subdir\n"); + check(rc == 0, "write file in bind-subdir source subdirectory"); + + errno = 0; + rc = mount("/tmp/ul-bind-sub-src/sub", dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "mount MS_BIND on source subdirectory succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-bind-sub-dst/sub.txt", content, sizeof(content)); + check(len > 0 && strcmp(content, "bind subdir") == 0, + "bind mount on source subdirectory exposes contents"); + } + + cleanup_umount_all(dst); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0c1: mount MS_BIND without MS_REC does not clone submounts + * + * Linux bind-mounts only the top mount by default. Nested mounts + * remain absent unless MS_REC is also specified. + * ================================================================ */ + { + const char *src = "/tmp/ul-bind-tree-src"; + const char *dst = "/tmp/ul-bind-tree-dst"; + int nested_fd; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-bind-tree-src /tmp/ul-bind-tree-dst"); + check(rc == 0, "mkdir bind-tree test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for bind-tree source"); + + rc = run("mkdir -p /tmp/ul-bind-tree-src/submnt"); + check(rc == 0, "mkdir bind-tree nested mountpoint"); + + rc = mount("tmpfs", "/tmp/ul-bind-tree-src/submnt", "tmpfs", 0, NULL); + check(rc == 0, "mount nested tmpfs under bind-tree source"); + + rc = write_file("/tmp/ul-bind-tree-src/submnt/nested.txt", "nested mount\n"); + check(rc == 0, "write file inside nested source mount"); + + errno = 0; + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "mount MS_BIND without MS_REC succeeds"); + + errno = 0; + nested_fd = open("/tmp/ul-bind-tree-dst/submnt/nested.txt", O_RDONLY); + saved_errno = errno; + if (nested_fd >= 0) + close(nested_fd); + check(nested_fd == -1 && saved_errno == ENOENT, + "mount MS_BIND without MS_REC leaves nested mount absent"); + + cleanup_umount_all(dst); + cleanup_umount_all("/tmp/ul-bind-tree-src/submnt"); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0c2: mount MS_BIND|MS_REC clones nested submounts + * + * A recursive bind mount should expose submount contents at the + * destination subtree. + * ================================================================ */ + { + const char *src = "/tmp/ul-rbind-src"; + const char *dst = "/tmp/ul-rbind-dst"; + + rc = run("mkdir -p /tmp/ul-rbind-src /tmp/ul-rbind-dst"); + check(rc == 0, "mkdir recursive-bind test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for recursive-bind source"); + + rc = run("mkdir -p /tmp/ul-rbind-src/submnt"); + check(rc == 0, "mkdir recursive-bind nested mountpoint"); + + rc = mount("tmpfs", "/tmp/ul-rbind-src/submnt", "tmpfs", 0, NULL); + check(rc == 0, "mount nested tmpfs under recursive-bind source"); + + rc = write_file("/tmp/ul-rbind-src/submnt/nested.txt", "recursive bind\n"); + check(rc == 0, "write file inside recursive-bind nested mount"); + + errno = 0; + rc = mount(src, dst, "tmpfs", MS_BIND | MS_REC, NULL); + check(rc == 0, "mount MS_BIND|MS_REC succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-rbind-dst/submnt/nested.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "recursive bind") == 0, + "mount MS_BIND|MS_REC exposes nested mount contents"); + } + + cleanup_umount_all("/tmp/ul-rbind-dst/submnt"); + cleanup_umount_all(dst); + cleanup_umount_all("/tmp/ul-rbind-src/submnt"); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0c3: recursive bind prunes unbindable child mounts + * + * Linux prunes unbindable submounts when replicating a subtree via + * MS_BIND|MS_REC. + * ================================================================ */ + { + const char *src = "/tmp/ul-rbind-unbind-src"; + const char *dst = "/tmp/ul-rbind-unbind-dst"; + int nested_fd; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-rbind-unbind-src /tmp/ul-rbind-unbind-dst"); + check(rc == 0, "mkdir recursive-bind-unbindable test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for recursive-bind-unbindable source"); + + rc = run("mkdir -p /tmp/ul-rbind-unbind-src/submnt"); + check(rc == 0, "mkdir recursive-bind-unbindable child mountpoint"); + + rc = mount("tmpfs", "/tmp/ul-rbind-unbind-src/submnt", "tmpfs", 0, NULL); + check(rc == 0, "mount child tmpfs for recursive-bind-unbindable test"); + + rc = mount("none", "/tmp/ul-rbind-unbind-src/submnt", "tmpfs", MS_UNBINDABLE, NULL); + check(rc == 0, "mount MS_UNBINDABLE on child mount succeeds"); + + errno = 0; + rc = mount(src, dst, "tmpfs", MS_BIND | MS_REC, NULL); + check(rc == 0, "mount MS_BIND|MS_REC with unbindable child succeeds"); + + errno = 0; + nested_fd = open("/tmp/ul-rbind-unbind-dst/submnt/probe.txt", O_RDONLY); + saved_errno = errno; + if (nested_fd >= 0) + close(nested_fd); + check(nested_fd == -1 && saved_errno == ENOENT, + "mount MS_BIND|MS_REC prunes unbindable child mount"); + + cleanup_umount_all("/tmp/ul-rbind-unbind-dst/submnt"); + cleanup_umount_all(dst); + cleanup_umount_all("/tmp/ul-rbind-unbind-src/submnt"); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0d: mount MS_MOVE relocates an existing mount + * + * After a move mount, the mounted tree should appear only at the + * new path and disappear from the old path. + * ================================================================ */ + { + const char *src = "/tmp/ul-move-src"; + const char *dst = "/tmp/ul-move-dst"; + + rc = run("mkdir -p /tmp/ul-move-src /tmp/ul-move-dst"); + check(rc == 0, "mkdir move-mount test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for move-mount source"); + + rc = write_file("/tmp/ul-move-src/move.txt", "move payload\n"); + check(rc == 0, "write source file before move mount"); + + errno = 0; + rc = mount(src, dst, "tmpfs", MS_MOVE, NULL); + check(rc == 0, "mount MS_MOVE succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-move-dst/move.txt", content, sizeof(content)); + check(len > 0 && strcmp(content, "move payload") == 0, + "move mount exposes source contents at new path"); + } + + { + int old_fd; + int saved_errno; + errno = 0; + old_fd = open("/tmp/ul-move-src/move.txt", O_RDONLY); + saved_errno = errno; + if (old_fd >= 0) + close(old_fd); + check(old_fd == -1 && saved_errno == ENOENT, + "move mount removes old mounted path"); + } + + cleanup_umount_all(dst); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0d1: mount MS_MOVE rejects moving a mount beneath itself + * + * Linux returns ELOOP if the move target is a descendant of the + * source mount. + * ================================================================ */ + { + const char *src = "/tmp/ul-move-loop"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-move-loop"); + check(rc == 0, "mkdir move-loop test directory"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for move-loop test"); + + rc = run("mkdir -p /tmp/ul-move-loop/subdir"); + check(rc == 0, "mkdir descendant target for move-loop test"); + + errno = 0; + rc = mount(src, "/tmp/ul-move-loop/subdir", "tmpfs", MS_MOVE, NULL); + saved_errno = errno; + check(rc == -1 && saved_errno == ELOOP, + "mount MS_MOVE rejects descendant target with ELOOP"); + + { + char content[256] = {0}; + rc = write_file("/tmp/ul-move-loop/still-there.txt", "move loop keep\n"); + check(rc == 0, "move-loop source mount remains usable after failed move"); + rc = read_first_line("/tmp/ul-move-loop/still-there.txt", content, sizeof(content)); + check(rc > 0 && strcmp(content, "move loop keep") == 0, + "failed MS_MOVE leaves original mount in place"); + } + + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0e: mount MS_REMOUNT keeps the existing mount tree + * + * A remount operates on the existing mount rather than replacing + * it, so existing files should still be visible afterwards. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-remount"; + + rc = run("mkdir -p /tmp/ul-remount"); + check(rc == 0, "mkdir remount test directory"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for remount test"); + + rc = write_file("/tmp/ul-remount/remount.txt", "remount keep\n"); + check(rc == 0, "write file before remount"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_REMOUNT, NULL); + check(rc == 0, "mount MS_REMOUNT succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-remount/remount.txt", content, sizeof(content)); + check(len > 0 && strcmp(content, "remount keep") == 0, + "mount MS_REMOUNT preserves existing mount contents"); + } + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0e1: mount MS_REMOUNT|MS_BIND|MS_RDONLY makes a bind + * mount read-only without freezing the source mount + * + * Linux uses bind-remount to toggle per-mount-point flags such as + * read-only on a bind mount. + * ================================================================ */ + { + const char *src = "/tmp/ul-bind-ro-src"; + const char *dst = "/tmp/ul-bind-ro-dst"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-bind-ro-src /tmp/ul-bind-ro-dst"); + check(rc == 0, "mkdir bind-remount-rdonly test directories"); + + rc = mount("tmpfs", src, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for bind-remount-rdonly source"); + + rc = write_file("/tmp/ul-bind-ro-src/base.txt", "bind ro base\n"); + check(rc == 0, "write source file before bind-remount-rdonly"); + + rc = mount(src, dst, "tmpfs", MS_BIND, NULL); + check(rc == 0, "mount MS_BIND for bind-remount-rdonly test"); + + errno = 0; + rc = mount("none", dst, "tmpfs", MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); + saved_errno = errno; + check(rc == 0, "mount MS_REMOUNT|MS_BIND|MS_RDONLY succeeds"); + + errno = 0; + rc = write_file("/tmp/ul-bind-ro-dst/deny.txt", "must fail\n"); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "bind-remount-rdonly rejects writes through bind mount"); + + rc = write_file("/tmp/ul-bind-ro-src/still-writable.txt", "source writable\n"); + check(rc == 0, "bind-remount-rdonly leaves source mount writable"); + + cleanup_umount_all(dst); + cleanup_umount_all(src); + } + + /* ================================================================ + * Tier 4k0f: mount MS_RDONLY creates a read-only mount + * + * A read-only mount must reject write attempts with EROFS. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-rdonly"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-rdonly"); + check(rc == 0, "mkdir rdonly test directory"); + + errno = 0; + rc = mount("tmpfs", mount_point, "tmpfs", MS_RDONLY, NULL); + saved_errno = errno; + check(rc == 0, "mount MS_RDONLY succeeds"); + + errno = 0; + rc = write_file("/tmp/ul-rdonly/ro.txt", "must fail\n"); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "mount MS_RDONLY rejects writes with EROFS"); + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0g: mount MS_REMOUNT|MS_RDONLY preserves contents and + * turns an existing mount read-only + * + * Linux remounts apply to the existing mount. Existing files must + * remain visible and subsequent writes must fail with EROFS. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-remount-rdonly"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-remount-rdonly"); + check(rc == 0, "mkdir remount-rdonly test directory"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for remount-rdonly test"); + + rc = write_file("/tmp/ul-remount-rdonly/keep.txt", "keep ro\n"); + check(rc == 0, "write file before remount-rdonly"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_REMOUNT | MS_RDONLY, NULL); + saved_errno = errno; + check(rc == 0, "mount MS_REMOUNT|MS_RDONLY succeeds"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-remount-rdonly/keep.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "keep ro") == 0, + "mount MS_REMOUNT|MS_RDONLY preserves existing contents"); + } + + errno = 0; + rc = write_file("/tmp/ul-remount-rdonly/new.txt", "must fail\n"); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "mount MS_REMOUNT|MS_RDONLY rejects new writes"); + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k0g1: remounted read-only mounts reject metadata and + * directory entry changes with EROFS + * + * Linux applies MS_REMOUNT|MS_RDONLY to the mountpoint, so chmod, + * rename, unlink, and mkdir under that mount should fail with + * EROFS. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-remount-rdonly-meta"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-remount-rdonly-meta"); + check(rc == 0, "mkdir remount-rdonly-meta test directory"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for remount-rdonly-meta test"); + + rc = write_file("/tmp/ul-remount-rdonly-meta/file.txt", "meta ro\n"); + check(rc == 0, "write file before remount-rdonly-meta"); + + errno = 0; + rc = mount("none", mount_point, "tmpfs", MS_REMOUNT | MS_RDONLY, NULL); + saved_errno = errno; + check(rc == 0, "mount MS_REMOUNT|MS_RDONLY for metadata test succeeds"); + + errno = 0; + rc = chmod("/tmp/ul-remount-rdonly-meta/file.txt", 0600); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "mount MS_REMOUNT|MS_RDONLY rejects chmod with EROFS"); + + errno = 0; + rc = rename("/tmp/ul-remount-rdonly-meta/file.txt", + "/tmp/ul-remount-rdonly-meta/file2.txt"); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "mount MS_REMOUNT|MS_RDONLY rejects rename with EROFS"); + + errno = 0; + rc = unlink("/tmp/ul-remount-rdonly-meta/file.txt"); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "mount MS_REMOUNT|MS_RDONLY rejects unlink with EROFS"); + + errno = 0; + rc = mkdir("/tmp/ul-remount-rdonly-meta/newdir", 0755); + saved_errno = errno; + check(rc == -1 && saved_errno == EROFS, + "mount MS_REMOUNT|MS_RDONLY rejects mkdir with EROFS"); + + cleanup_umount_all(mount_point); + } + /* ================================================================ * Tier 4k1: umount2 EINVAL for invalid flags * @@ -1084,6 +1948,180 @@ int main(void) } } + /* ================================================================ + * Tier 4k2: umount2 EINVAL for invalid supported-flag combo + * + * Linux umount2 rejects MNT_EXPIRE combined with MNT_DETACH or + * MNT_FORCE. The rejected operation must not unmount the target. + * ================================================================ */ + + /* Attach ext4 image */ + if (loopdev[0]) { + char cmd[256]; + snprintf(cmd, sizeof(cmd), "losetup %s " PREBUILT_IMG " 2>&1", loopdev); + rc = run(cmd); + check(rc == 0, "losetup attach for umount2-invalid-combo test"); + } else { + check(0, "losetup attach for umount2-invalid-combo test"); + } + + /* Mount ext4 */ + if (loopdev[0]) { + char cmd[256]; + snprintf(cmd, sizeof(cmd), "mount -t ext4 %s /tmp/ul-mnt 2>&1", loopdev); + rc = run(cmd); + check(rc == 0, "mount for umount2-invalid-combo test"); + } else { + check(0, "mount for umount2-invalid-combo test"); + } + + /* umount2 with invalid supported-flag combo must fail with EINVAL */ + { + int saved_errno; + errno = 0; + rc = (int)syscall(SYS_umount2, "/tmp/ul-mnt", MNT_EXPIRE | MNT_DETACH); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "umount2 EINVAL for MNT_EXPIRE|MNT_DETACH"); + } + + /* Cleanup: normal umount then detach */ + { + rc = umount("/tmp/ul-mnt"); + check(rc == 0, "umount cleanup after umount2-invalid-combo test"); + if (loopdev[0]) { + char cmd[256]; + snprintf(cmd, sizeof(cmd), "losetup -d %s 2>&1", loopdev); + run(cmd); + } + } + + /* ================================================================ + * Tier 4k3: umount2 MNT_EXPIRE is a two-step expire operation + * + * The first MNT_EXPIRE call should return EAGAIN and mark the + * mount expired. A second call should then unmount it. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-expire"; + int saved_errno; + + rc = run("mkdir -p /tmp/ul-expire"); + check(rc == 0, "mkdir expire test directory"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for MNT_EXPIRE test"); + + errno = 0; + rc = (int)syscall(SYS_umount2, mount_point, MNT_EXPIRE); + saved_errno = errno; + check(rc == -1 && saved_errno == EAGAIN, + "umount2 MNT_EXPIRE first call returns EAGAIN"); + + errno = 0; + rc = (int)syscall(SYS_umount2, mount_point, MNT_EXPIRE); + saved_errno = errno; + check(rc == 0, + "umount2 MNT_EXPIRE second call unmounts expired mount"); + + cleanup_umount_all(mount_point); + } + + /* ================================================================ + * Tier 4k4: umount2 UMOUNT_NOFOLLOW does not follow symlinks + * + * With UMOUNT_NOFOLLOW, passing a symlink path must not unmount + * the mount behind that symlink. + * ================================================================ */ + { + const char *real_mount = "/tmp/ul-nofollow-real"; + const char *link_mount = "/tmp/ul-nofollow-link"; + int saved_errno; + + run("mkdir -p /tmp/ul-nofollow-real"); + unlink(link_mount); + rc = symlink(real_mount, link_mount); + check(rc == 0, "create symlink for UMOUNT_NOFOLLOW test"); + + rc = mount("tmpfs", real_mount, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for UMOUNT_NOFOLLOW test"); + + rc = write_file("/tmp/ul-nofollow-real/nofollow.txt", "nofollow keep\n"); + check(rc == 0, "write file before UMOUNT_NOFOLLOW"); + + errno = 0; + rc = (int)syscall(SYS_umount2, link_mount, UMOUNT_NOFOLLOW); + saved_errno = errno; + check(rc == -1 && saved_errno == EINVAL, + "umount2 UMOUNT_NOFOLLOW rejects symlink target"); + + { + char content[256] = {0}; + int len = read_first_line("/tmp/ul-nofollow-real/nofollow.txt", + content, sizeof(content)); + check(len > 0 && strcmp(content, "nofollow keep") == 0, + "UMOUNT_NOFOLLOW leaves real mount in place"); + } + + cleanup_umount_all(real_mount); + unlink(link_mount); + } + + /* ================================================================ + * Tier 4k5: umount2 MNT_DETACH lazily detaches a busy mount + * + * The detach should succeed even while an open file descriptor + * keeps the mount busy. New path lookups must see the underlying + * directory, while the existing file descriptor remains usable. + * ================================================================ */ + { + const char *mount_point = "/tmp/ul-detach"; + int detach_fd; + + rc = run("mkdir -p /tmp/ul-detach"); + check(rc == 0, "mkdir detach test directory"); + + rc = mount("tmpfs", mount_point, "tmpfs", 0, NULL); + check(rc == 0, "mount tmpfs for MNT_DETACH test"); + + rc = write_file("/tmp/ul-detach/detach.txt", "detach payload\n"); + check(rc == 0, "write file before MNT_DETACH"); + + detach_fd = open("/tmp/ul-detach/detach.txt", O_RDONLY); + check(detach_fd >= 0, "open fd before MNT_DETACH"); + + if (detach_fd >= 0) { + int saved_errno; + char content[256] = {0}; + ssize_t nread; + int reopened_fd; + + errno = 0; + rc = (int)syscall(SYS_umount2, mount_point, MNT_DETACH); + saved_errno = errno; + check(rc == 0, "umount2 MNT_DETACH succeeds on busy mount"); + + errno = 0; + reopened_fd = open("/tmp/ul-detach/detach.txt", O_RDONLY); + saved_errno = errno; + if (reopened_fd >= 0) + close(reopened_fd); + check(reopened_fd == -1 && saved_errno == ENOENT, + "MNT_DETACH hides mount from new lookups"); + + lseek(detach_fd, 0, SEEK_SET); + nread = read(detach_fd, content, sizeof(content) - 1); + if (nread >= 0) + content[nread] = '\0'; + check(nread > 0 && strstr(content, "detach payload") != NULL, + "MNT_DETACH keeps existing fd usable"); + + close(detach_fd); + } + + cleanup_umount_all(mount_point); + } + /* ================================================================ * Tier 4l: LOOP_CLR_FD EBUSY when mount has open fds * From 07d840d02fc3f0d1b539210551bb4c7b37686b9a Mon Sep 17 00:00:00 2001 From: CN-TangLin <2242120212@qq.com> Date: Mon, 25 May 2026 11:24:00 +0800 Subject: [PATCH 04/71] fix(axcpu): save SP in aarch64 TrapFrame for kprobe correctness (#887) * fix(axcpu): save SP in aarch64 TrapFrame for kprobe correctness - trap.S: SAVE_REGS saves original SP (sp_before_sub) into offset 33*8 - context.rs: rename __pad to sp with documentation - uspace.rs: initialize sp to 0, remove PAD_MAGIC - kprobe.rs: use tf.sp instead of hardcoded 0 for aarch64 PtRegs Before this fix, aarch64 TrapFrame did not store SP, causing all kprobe handlers relying on SP to get 0. Now the original SP at the time of exception is properly preserved in the TrapFrame structure. * fix(axcpu): address review feedback for PR #887 - Remove kprobe.rs dead code (not integrated, missing mod declaration and kprobe crate dependency; will be submitted in a separate PR) - Add documentation comment on TrapFrame.sp explaining it is read-only: the actual SP is restored by RESTORE_REGS via 'add sp, sp, #trapframe_size', not from this field - Add missing doc comment on UserContext::new() to satisfy missing_docs lint --- components/axcpu/src/aarch64/context.rs | 12 +++++++++--- components/axcpu/src/aarch64/trap.S | 3 +++ components/axcpu/src/aarch64/uspace.rs | 6 ++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/components/axcpu/src/aarch64/context.rs b/components/axcpu/src/aarch64/context.rs index 60b901b584..60eb46826b 100644 --- a/components/axcpu/src/aarch64/context.rs +++ b/components/axcpu/src/aarch64/context.rs @@ -13,8 +13,14 @@ pub struct TrapFrame { /// Saved Process Status Register (SPSR_EL1). pub spsr: u64, - /// make sure the size is 16 bytes aligned - pub __pad: u64, + /// Stack pointer at the time of the exception. + /// Populated by SAVE_REGS as `sp_before_sub = sp_after_sub + trapframe_size`. + /// + /// Note: This field is read-only (saved by SAVE_REGS for inspection only). + /// The actual SP is restored by RESTORE_REGS via `add sp, sp, #trapframe_size`, + /// not from this field. Modifying this value will NOT affect the actual SP + /// after exception return. + pub sp: u64, } impl fmt::Debug for TrapFrame { @@ -25,7 +31,7 @@ impl fmt::Debug for TrapFrame { } writeln!(f, " elr: {:#x},", self.elr)?; writeln!(f, " spsr: {:#x},", self.spsr)?; - writeln!(f, " pad: {:#x},", self.__pad)?; + writeln!(f, " sp: {:#x},", self.sp)?; write!(f, "}}")?; Ok(()) } diff --git a/components/axcpu/src/aarch64/trap.S b/components/axcpu/src/aarch64/trap.S index 3453f059ad..881eb42dcb 100644 --- a/components/axcpu/src/aarch64/trap.S +++ b/components/axcpu/src/aarch64/trap.S @@ -25,6 +25,9 @@ mrs x10, spsr_el1 .endif stp x9, x10, [sp, 31 * 8] + + add x9, sp, {trapframe_size} + str x9, [sp, 33 * 8] .endm .macro RESTORE_REGS diff --git a/components/axcpu/src/aarch64/uspace.rs b/components/axcpu/src/aarch64/uspace.rs index f7daf81e64..13c640c1f3 100644 --- a/components/axcpu/src/aarch64/uspace.rs +++ b/components/axcpu/src/aarch64/uspace.rs @@ -22,9 +22,7 @@ pub struct UserContext { } impl UserContext { - const PAD_MAGIC: u64 = 0x1234_5678_9abc_def0; - /// Creates a new context with the given entry point, user stack pointer, - /// and the argument. + /// Creates a new user context with the given entry point, stack top, and argument. pub fn new(entry: usize, ustack_top: VirtAddr, arg0: usize) -> Self { use aarch64_cpu::registers::SPSR_EL1; let mut regs = [0; 31]; @@ -39,7 +37,7 @@ impl UserContext { + SPSR_EL1::I::Unmasked + SPSR_EL1::F::Masked) .value, - __pad: Self::PAD_MAGIC, + sp: 0, }, sp: ustack_top.as_usize() as _, tpidr: 0, From 665f10c194e23fb5b272d4bb7d8b9ad00634f9d2 Mon Sep 17 00:00:00 2001 From: WellDown64 <150930230+WellDown64@users.noreply.github.com> Date: Mon, 25 May 2026 11:52:16 +0800 Subject: [PATCH 05/71] feat(starry): add userspace test for prlimit64 syscall (#801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(starry): add userspace test for prlimit64 syscall - Fix error priority: check pid before resource, matching Linux (ESRCH before EINVAL for invalid pid + bad resource). - Add hard-limit raise protection via CAP_SYS_RESOURCE. Unprivileged processes can no longer raise rlim_max arbitrarily. - Add has_cap_sys_resource() to Cred, following the existing has_cap_* pattern. Document that all capability checks are euid==0 placeholders until a fine-grained bitmap is added. - Add comprehensive prlimit64 test covering all 14 RLIMIT_* resources * fix: add the test that was mistakenly deleted * fix: add the riscv tests that were mistakenly deleted --------- Co-authored-by: 周睿 --- os/StarryOS/kernel/src/syscall/resources.rs | 15 +- os/StarryOS/kernel/src/task/cred.rs | 12 + .../syscall/test-prlimit64/c/CMakeLists.txt | 9 + .../syscall/test-prlimit64/c/src/main.c | 514 ++++++++++++++++++ .../test-prlimit64/c/src/test_framework.h | 65 +++ 5 files changed, 612 insertions(+), 3 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/test_framework.h diff --git a/os/StarryOS/kernel/src/syscall/resources.rs b/os/StarryOS/kernel/src/syscall/resources.rs index 7d08e43e83..95740cd78d 100644 --- a/os/StarryOS/kernel/src/syscall/resources.rs +++ b/os/StarryOS/kernel/src/syscall/resources.rs @@ -16,11 +16,13 @@ pub fn sys_prlimit64( new_limit: *const rlimit64, old_limit: *mut rlimit64, ) -> AxResult { + // pid lookup first — match Linux error priority (ESRCH before EINVAL) + let proc_data = get_process_data(pid)?; + if resource >= RLIM_NLIMITS { return Err(AxError::InvalidInput); } - let proc_data = get_process_data(pid)?; if let Some(old_limit) = old_limit.nullable() { let limit = &proc_data.rlim.read()[resource]; old_limit.vm_write(rlimit64 { @@ -37,8 +39,15 @@ pub fn sys_prlimit64( } let limit = &mut proc_data.rlim.write()[resource]; - // TODO: when a capability system is added, check CAP_SYS_RESOURCE - // before allowing new_limit.rlim_max > limit.max (return EPERM). + // Raising the hard limit requires CAP_SYS_RESOURCE. + // TODO: has_cap_sys_resource() is currently euid==0 until a + // fine-grained capability bitmap is implemented (see cred.rs). + if new_limit.rlim_max > limit.max { + let cred = current().as_thread().cred(); + if !cred.has_cap_sys_resource() { + return Err(AxError::OperationNotPermitted); + } + } limit.max = new_limit.rlim_max; limit.current = new_limit.rlim_cur; } diff --git a/os/StarryOS/kernel/src/task/cred.rs b/os/StarryOS/kernel/src/task/cred.rs index 48bf7efd78..68550775e9 100644 --- a/os/StarryOS/kernel/src/task/cred.rs +++ b/os/StarryOS/kernel/src/task/cred.rs @@ -1,4 +1,10 @@ //! Process credentials (uid, gid, supplementary groups). +//! +//! Capability checks (`has_cap_*`): StarryOS does not yet track +//! fine-grained Linux capability bitmaps. All capability checks are +//! currently approximated as `euid == 0` (or `fsuid == 0` for +//! filesystem capabilities). Once a `cap_effective` / `cap_permitted` +//! mask is added to `Cred`, only these methods need updating. use alloc::sync::Arc; @@ -66,6 +72,12 @@ impl Cred { self.euid == 0 } + /// Check whether this credential may change process resource limits + /// (equivalent to `CAP_SYS_RESOURCE` — approximated as euid == 0). + pub fn has_cap_sys_resource(&self) -> bool { + self.euid == 0 + } + /// Check whether this credential has the privilege to change file /// ownership (equivalent to `CAP_CHOWN` — approximated as fsuid == 0, /// since this is a filesystem capability). diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/CMakeLists.txt new file mode 100644 index 0000000000..347f95cbc1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-prlimit64 C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-prlimit64 src/main.c) +target_include_directories(test-prlimit64 PRIVATE src) +target_compile_options(test-prlimit64 PRIVATE -Wall -Wextra) +install(TARGETS test-prlimit64 RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/main.c new file mode 100644 index 0000000000..2729b48a51 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/main.c @@ -0,0 +1,514 @@ +/* + * test_prlimit64.c — prlimit64/getrlimit/setrlimit tests with limit enforcement + * + * Each resource is tested in three layers: + * 1. query — get the current limit (getrlimit / prlimit get-only) + * 2. mutate — set a test value, verify via query (setrlimit / prlimit set) + * 3. enforce — verify the limit is actually enforced by the kernel + * + * Coverage: + * RLIMIT_NOFILE — set, enforce by opening N+1 files, restore + * RLIMIT_FSIZE — set, enforce by writing beyond limit + * RLIMIT_NICE — set ceiling, enforce via setpriority(2) + * RLIMIT_CORE — set, enforce (nonzero → nonzero; 0 → no core) + * RLIMIT_DATA — set, enforce via brk(2) + * RLIMIT_AS — query + consistency + * RLIMIT_CPU — query + arm/disarm + * RLIMIT_STACK — query + consistency + * RLIMIT_MEMLOCK — query + consistency + * RLIMIT_NPROC — query + consistency + * RLIMIT_RTPRIO — query + consistency + * RLIMIT_RTTIME — query + consistency + * RLIMIT_SIGPENDING — query + consistency + * RLIMIT_MSGQUEUE — query + consistency + * + * pid parameter: + * pid=0 — self (get-only, set-only, get+set) + * pid=getpid() — explicit self (verify same as pid=0) + * pid=invalid — ESRCH + * pid=1 — may fail ESRCH or EPERM + * + * error conditions: + * - invalid resource → EINVAL + * - soft > hard → EINVAL + * - raise hard limit → EPERM (no CAP_SYS_RESOURCE) + * - invalid pid + bad res → ESRCH (pid first on Linux) + */ + +#define _GNU_SOURCE +#include "test_framework.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* RLIMIT constants not declared on all libc implementations */ +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif + +/* + * save_restore — run a mutator block with automatic restore. + * + * Usage: + * struct rlimit old; + * SAVE_RLIMIT(old, RLIMIT_NOFILE); + * ... test body that may change RLIMIT_NOFILE ... + * RESTORE_RLIMIT(old, RLIMIT_NOFILE); + * + * The block runs between SAVE and RESTORE; RESTORE is called at scope end + * via a cleanup pattern. + */ + +int main(void) +{ + TEST_START("prlimit64 enforcement"); + + /* ═══════════════════════════════════════════════════════════════ + * 1. BASIC: pid=0 get-only / set-only / get+set roundtrip + * ═══════════════════════════════════════════════════════════════ */ + { + struct rlimit old, new_lim, prev; + + /* get-only */ + CHECK_RET(prlimit(0, RLIMIT_NOFILE, NULL, &old), 0, + "get-only: prlimit(0, NOFILE, NULL, &old) = 0"); + CHECK(old.rlim_cur > 0, "get-only: soft > 0"); + CHECK(old.rlim_max >= old.rlim_cur, "get-only: hard >= soft"); + printf(" NOFILE: cur=%lu max=%lu\n", + (unsigned long)old.rlim_cur, (unsigned long)old.rlim_max); + + /* set-only — lower soft limit by 1 */ + new_lim.rlim_cur = old.rlim_cur > 8 ? old.rlim_cur - 1 : old.rlim_cur; + new_lim.rlim_max = old.rlim_max; + CHECK_RET(prlimit(0, RLIMIT_NOFILE, &new_lim, NULL), 0, + "set-only: prlimit(0, NOFILE, &new, NULL) = 0"); + + /* verify via getrlimit */ + CHECK_RET(getrlimit(RLIMIT_NOFILE, &prev), 0, + "set-only: getrlimit verifies change"); + CHECK(prev.rlim_cur == new_lim.rlim_cur, + "set-only: soft matches set value"); + + /* restore */ + prlimit(0, RLIMIT_NOFILE, &old, NULL); + + /* get+set — old_limit should return the PREVIOUS (before-set) value */ + new_lim.rlim_cur = old.rlim_cur > 10 ? old.rlim_cur - 2 : old.rlim_cur; + CHECK_RET(prlimit(0, RLIMIT_NOFILE, &new_lim, &prev), 0, + "set+get: prlimit(0, NOFILE, &new, &prev) = 0"); + CHECK(prev.rlim_cur == old.rlim_cur, + "set+get: old_limit returns PREVIOUS soft (before change)"); + CHECK(prev.rlim_max == old.rlim_max, + "set+get: old_limit returns PREVIOUS hard (before change)"); + + /* verify the new value actually took effect */ + struct rlimit now; + CHECK_RET(getrlimit(RLIMIT_NOFILE, &now), 0, + "set+get: getrlimit confirms new value"); + CHECK(now.rlim_cur == new_lim.rlim_cur, + "set+get: new soft is active"); + + /* restore */ + prlimit(0, RLIMIT_NOFILE, &old, NULL); + } + + /* ═══════════════════════════════════════════════════════════════ + * 2. pid=getpid() — verify same as pid=0 + * ═══════════════════════════════════════════════════════════════ */ + { + pid_t mypid = getpid(); + struct rlimit rl0, rl_self; + CHECK_RET(prlimit(0, RLIMIT_NOFILE, NULL, &rl0), 0, + "pid=0 get"); + CHECK_RET(prlimit(mypid, RLIMIT_NOFILE, NULL, &rl_self), 0, + "pid=getpid() get"); + CHECK(rl0.rlim_cur == rl_self.rlim_cur, "pid=0 == pid=getpid(): soft"); + CHECK(rl0.rlim_max == rl_self.rlim_max, "pid=0 == pid=getpid(): hard"); + } + + /* ═══════════════════════════════════════════════════════════════ + * 3. ENFORCEMENT: RLIMIT_NOFILE + * Lower to a small value, open that many files, + * verify the next open fails with EMFILE. + * ═══════════════════════════════════════════════════════════════ */ + { + struct rlimit old; + CHECK_RET(getrlimit(RLIMIT_NOFILE, &old), 0, "NOFILE: save original"); + + int test_fd[64]; + int test_limit = (old.rlim_cur > 16 && old.rlim_cur < 64) ? 16 : 8; + + struct rlimit low; + low.rlim_cur = (rlim_t)test_limit; + low.rlim_max = old.rlim_max; + + errno = 0; + int rc = setrlimit(RLIMIT_NOFILE, &low); + if (rc != 0) { + printf(" SKIP NOFILE enforcement: cannot set limit (errno=%d)\n", errno); + goto nofile_done; + } + + struct rlimit verify; + CHECK_RET(getrlimit(RLIMIT_NOFILE, &verify), 0, + "NOFILE: getrlimit after set"); + CHECK((int)verify.rlim_cur == test_limit, + "NOFILE: soft limit applied"); + + /* Open test_limit files via /dev/null (they consume an fd each). + * fd 0,1,2 are already open, so we can open test_limit - 3 more. */ + int i, opened = 0; + for (i = 0; i < test_limit; i++) { + int fd = open("/dev/null", O_RDONLY); + if (fd < 0) break; + test_fd[opened++] = fd; + } + printf(" NOFILE: opened %d / %d files before failure\n", + opened, test_limit); + + /* The next open should fail with EMFILE */ + errno = 0; + int fd = open("/dev/null", O_RDONLY); + CHECK(fd < 0 && errno == EMFILE, + "NOFILE: exceeding soft limit → EMFILE"); + if (fd >= 0) close(fd); + + /* Clean up: close test fds */ + for (i = 0; i < opened; i++) close(test_fd[i]); + + /* Restore */ + CHECK_RET(setrlimit(RLIMIT_NOFILE, &old), 0, "NOFILE: restore"); + } +nofile_done: + + /* ═══════════════════════════════════════════════════════════════ + * 4. ENFORCEMENT: RLIMIT_FSIZE + * Set small file size limit, write large data, expect EFBIG. + * ═══════════════════════════════════════════════════════════════ */ + { + struct rlimit old, low; + CHECK_RET(getrlimit(RLIMIT_FSIZE, &old), 0, "FSIZE: save original"); + + low.rlim_cur = 4; /* 4 bytes */ + low.rlim_max = old.rlim_max; + if (setrlimit(RLIMIT_FSIZE, &low) != 0) { + printf(" SKIP FSIZE enforcement: cannot set limit (errno=%d)\n", errno); + goto fsize_done; + } + + /* Verify the limit is set */ + struct rlimit verify; + CHECK_RET(getrlimit(RLIMIT_FSIZE, &verify), 0, + "FSIZE: getrlimit after set"); + CHECK(verify.rlim_cur == 4, "FSIZE: soft limit = 4"); + + /* Create a temp file and try to write beyond 4 bytes. + * Need to ignore SIGXFSZ or it kills us. */ + signal(SIGXFSZ, SIG_IGN); + + char tmpname[] = "/tmp/prlimit_fsize_XXXXXX"; + int fd = mkstemp(tmpname); + if (fd < 0) { + printf(" SKIP FSIZE: cannot create temp file\n"); + signal(SIGXFSZ, SIG_DFL); + setrlimit(RLIMIT_FSIZE, &old); + goto fsize_done; + } + unlink(tmpname); /* delete on close */ + + /* Write 5 bytes (limit is 4) — should fail */ + char buf[10] = "123456789"; + ssize_t n = write(fd, buf, 5); + if (n < 0 && errno == EFBIG) { + CHECK(1, "FSIZE: write(5) beyond 4-byte limit → EFBIG"); + } else if (n < 0) { + printf(" FSIZE: write() failed with errno=%d (%s)\n", + errno, strerror(errno)); + CHECK(errno == EFBIG, "FSIZE: expected EFBIG"); + } else { + /* Some systems allow the write up to the limit then deliver SIGXFSZ + * on the next write. Try another write. */ + errno = 0; + n = write(fd, buf, 5); + if (n < 0 && errno == EFBIG) { + CHECK(1, "FSIZE: 2nd write beyond limit → EFBIG"); + } else { + printf(" FSIZE: wrote %zd then %zd bytes (limit=4)\n", + n > 0 ? n : 0, n); + CHECK(n < 0 && errno == EFBIG, + "FSIZE: write beyond 4-byte limit → EFBIG"); + } + } + + close(fd); + signal(SIGXFSZ, SIG_DFL); + setrlimit(RLIMIT_FSIZE, &old); + } +fsize_done: + + /* ═══════════════════════════════════════════════════════════════ + * 5. ENFORCEMENT: RLIMIT_NICE + * Set nice ceiling. Try to setpriority() below it. + * ═══════════════════════════════════════════════════════════════ */ +#ifdef RLIMIT_NICE + { + struct rlimit old; + if (getrlimit(RLIMIT_NICE, &old) != 0) { + printf(" SKIP NICE: getrlimit failed\n"); + goto nice_done; + } + + /* rlim_cur is the ceiling for 20 - nice. Set ceiling to 21 + * (max nice value of -1, i.e. 20 - 21 = -1). */ + struct rlimit ceil; + ceil.rlim_cur = 21; + ceil.rlim_max = old.rlim_max; + if (setrlimit(RLIMIT_NICE, &ceil) != 0) { + printf(" SKIP NICE: cannot set limit (errno=%d)\n", errno); + goto nice_done; + } + + /* Verify the limit */ + struct rlimit verify; + CHECK_RET(getrlimit(RLIMIT_NICE, &verify), 0, + "NICE: getrlimit after set"); + CHECK(verify.rlim_cur == 21, "NICE: ceiling = 21"); + + /* Lower nice to -1 (raise priority) — should be allowed */ + errno = 0; + int rc = setpriority(PRIO_PROCESS, 0, -1); + if (rc == 0) { + CHECK(1, "NICE: setpriority(-1) allowed within ceiling"); + /* Restore to 0 */ + setpriority(PRIO_PROCESS, 0, 0); + } else { + printf(" NICE: setpriority(-1) failed: errno=%d (%s)\n", + errno, strerror(errno)); + } + + /* Now lower ceiling to 20 (max nice = 0), try to go below */ + ceil.rlim_cur = 20; + setrlimit(RLIMIT_NICE, &ceil); + errno = 0; + rc = setpriority(PRIO_PROCESS, 0, -1); + if (rc == -1 && (errno == EACCES || errno == EPERM)) { + CHECK(1, "NICE: setpriority(-1) blocked by RLIMIT_NICE ceiling"); + } else if (rc == 0) { + printf(" NICE: setpriority(-1) succeeded (RLIMIT_NICE not enforced)\n"); + setpriority(PRIO_PROCESS, 0, 0); + } else { + printf(" NICE: setpriority(-1) unexpected errno=%d\n", errno); + } + + setrlimit(RLIMIT_NICE, &old); + } +nice_done: +#endif + + /* ═══════════════════════════════════════════════════════════════ + * 6. ENFORCEMENT: RLIMIT_CORE + * Set to 0 → no core; set to nonzero → core allowed (up to N). + * We can't easily trigger a core dump, but we can check set/get. + * ═══════════════════════════════════════════════════════════════ */ + { + struct rlimit old, zero; + CHECK_RET(getrlimit(RLIMIT_CORE, &old), 0, "CORE: save original"); + + zero.rlim_cur = 0; + zero.rlim_max = old.rlim_max; + CHECK_RET(setrlimit(RLIMIT_CORE, &zero), 0, + "CORE: set to 0 (disable core dumps)"); + + struct rlimit verify; + CHECK_RET(getrlimit(RLIMIT_CORE, &verify), 0, + "CORE: getrlimit confirms 0"); + CHECK(verify.rlim_cur == 0, "CORE: soft = 0"); + + /* Set to RLIM_INFINITY (unlimited core) */ + zero.rlim_cur = RLIM_INFINITY; + zero.rlim_max = RLIM_INFINITY; + errno = 0; + if (setrlimit(RLIMIT_CORE, &zero) == 0) { + CHECK_RET(getrlimit(RLIMIT_CORE, &verify), 0, + "CORE: getrlimit after RLIM_INFINITY"); + CHECK(verify.rlim_cur == RLIM_INFINITY, + "CORE: soft = RLIM_INFINITY"); + } + + setrlimit(RLIMIT_CORE, &old); + } + + /* ═══════════════════════════════════════════════════════════════ + * 7. ENFORCEMENT: RLIMIT_DATA + * Set a data limit, try brk(2) beyond it. + * ═══════════════════════════════════════════════════════════════ */ + { + struct rlimit old; + if (getrlimit(RLIMIT_DATA, &old) != 0) { + printf(" SKIP DATA: getrlimit failed\n"); + goto data_done; + } + + /* Find current data segment size */ + void *cur_brk = sbrk(0); + if (cur_brk == (void *)-1) { + printf(" SKIP DATA: sbrk(0) failed\n"); + goto data_done; + } + + /* Set data limit to current brk + 4096 */ + unsigned long cur_addr = (unsigned long)cur_brk; + struct rlimit low; + low.rlim_cur = cur_addr + 4096; + low.rlim_max = old.rlim_max; + if (setrlimit(RLIMIT_DATA, &low) != 0) { + printf(" SKIP DATA: cannot set limit (errno=%d)\n", errno); + goto data_done; + } + + /* Try to brk() far beyond the limit (e.g. + 2MB) */ + errno = 0; + void *new_brk = sbrk(2 * 1024 * 1024); + if (new_brk == (void *)-1 && errno == ENOMEM) { + CHECK(1, "DATA: brk beyond RLIMIT_DATA → ENOMEM"); + } else if (new_brk == (void *)-1) { + printf(" DATA: brk failed with errno=%d (%s)\n", + errno, strerror(errno)); + } else { + printf(" DATA: brk succeeded (RLIMIT_DATA not enforced?)\n"); + brk(cur_brk); /* shrink back */ + } + + setrlimit(RLIMIT_DATA, &old); + } +data_done: + + /* ═══════════════════════════════════════════════════════════════ + * 8. Query all RLIMIT_* resources for consistency + * ═══════════════════════════════════════════════════════════════ */ + { + printf("\n--- All RLIMIT_* resources (getrlimit) ---\n"); + int resources[] = { + RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE, + RLIMIT_MEMLOCK, +#ifdef RLIMIT_MSGQUEUE + RLIMIT_MSGQUEUE, +#endif +#ifdef RLIMIT_NICE + RLIMIT_NICE, +#endif + RLIMIT_NOFILE, RLIMIT_NPROC, +#ifdef RLIMIT_RTPRIO + RLIMIT_RTPRIO, +#endif +#ifdef RLIMIT_RTTIME + RLIMIT_RTTIME, +#endif +#ifdef RLIMIT_SIGPENDING + RLIMIT_SIGPENDING, +#endif + RLIMIT_STACK, + }; + const char *names[] = { + "AS", "CORE", "CPU", "DATA", "FSIZE", "MEMLOCK", +#ifdef RLIMIT_MSGQUEUE + "MSGQUEUE", +#endif +#ifdef RLIMIT_NICE + "NICE", +#endif + "NOFILE", "NPROC", +#ifdef RLIMIT_RTPRIO + "RTPRIO", +#endif +#ifdef RLIMIT_RTTIME + "RTTIME", +#endif +#ifdef RLIMIT_SIGPENDING + "SIGPENDING", +#endif + "STACK", + }; + int n = (int)(sizeof(resources) / sizeof(resources[0])); + for (int i = 0; i < n; i++) { + struct rlimit rl; + if (getrlimit(resources[i], &rl) == 0) { + printf(" RLIMIT_%-10s cur=%lu max=%lu\n", + names[i], (unsigned long)rl.rlim_cur, + (unsigned long)rl.rlim_max); + } else { + printf(" RLIMIT_%-10s getrlimit failed (errno=%d)\n", + names[i], errno); + } + } + } + + /* ═══════════════════════════════════════════════════════════════ + * 9. Error conditions + * ═══════════════════════════════════════════════════════════════ */ + { + struct rlimit rl, old; + + /* invalid pid → ESRCH */ + CHECK_ERR(prlimit(0x7FFFFFFF, RLIMIT_NOFILE, NULL, &rl), ESRCH, + "invalid pid get → ESRCH"); + CHECK_ERR(prlimit(-1, RLIMIT_NOFILE, NULL, &rl), ESRCH, + "pid=-1 → ESRCH"); + + /* invalid resource → EINVAL */ + CHECK_ERR(prlimit(0, -1, NULL, &rl), EINVAL, + "res=-1 → EINVAL"); + CHECK_ERR(prlimit(0, 9999, NULL, &rl), EINVAL, + "res=9999 → EINVAL"); + + /* soft > hard → EINVAL (setrlimit and prlimit) */ + rl.rlim_cur = 1000; + rl.rlim_max = 500; + CHECK_ERR(setrlimit(RLIMIT_NOFILE, &rl), EINVAL, + "setrlimit(soft > hard) → EINVAL"); + CHECK_ERR(prlimit(0, RLIMIT_NOFILE, &rl, NULL), EINVAL, + "prlimit(soft > hard) → EINVAL"); + + /* raise hard limit — requires CAP_SYS_RESOURCE */ + CHECK_RET(prlimit(0, RLIMIT_NOFILE, NULL, &old), 0, + "get NOFILE for hard-limit test"); + rl.rlim_cur = old.rlim_max + 1; + rl.rlim_max = old.rlim_max + 1; + errno = 0; + int rc = setrlimit(RLIMIT_NOFILE, &rl); + if (rc == 0) { + /* Root (or CAP_SYS_RESOURCE) can raise hard limits */ + CHECK(1, "raise hard limit allowed (privileged)"); + /* Restore */ + setrlimit(RLIMIT_NOFILE, &old); + } else { + CHECK(rc == -1 && errno == EPERM, + "raise hard limit without privilege → EPERM"); + } + + /* invalid pid + invalid res → ESRCH (pid checked first on Linux) */ + CHECK_ERR(prlimit(0x7FFFFFFF, -1, NULL, &rl), ESRCH, + "invalid pid + bad res → ESRCH (pid first)"); + + /* pid=1 init — may fail ESRCH or EPERM */ + errno = 0; + rc = prlimit(1, RLIMIT_NOFILE, NULL, &rl); + if (rc == 0) { + printf(" prlimit(pid=1): cur=%lu max=%lu\n", + (unsigned long)rl.rlim_cur, (unsigned long)rl.rlim_max); + CHECK(rl.rlim_cur > 0, "init NOFILE soft > 0"); + } else { + CHECK(rc == -1 && (errno == EPERM || errno == ESRCH || errno == EACCES), + "prlimit(pid=1) → EPERM/ESRCH/EACCES"); + } + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/test_framework.h new file mode 100644 index 0000000000..1bafd3e70e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-prlimit64/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 From be146aadaacb5510d94adfa2ca21416d23b7d887 Mon Sep 17 00:00:00 2001 From: Shuo Zhang Date: Mon, 25 May 2026 12:00:47 +0800 Subject: [PATCH 06/71] feat(starry-kernel): add inotifywait support (#894) * feat(starry-kernel): add inotifywait support * feat(starry-kernel): expand inotify events --- os/StarryOS/kernel/src/file/fs.rs | 9 +- os/StarryOS/kernel/src/file/inotify.rs | 314 ++++++++++++++++++ os/StarryOS/kernel/src/file/mod.rs | 16 +- os/StarryOS/kernel/src/syscall/fs/ctl.rs | 40 ++- os/StarryOS/kernel/src/syscall/fs/fd_ops.rs | 18 +- os/StarryOS/kernel/src/syscall/fs/inotify.rs | 47 +++ os/StarryOS/kernel/src/syscall/fs/mod.rs | 2 + os/StarryOS/kernel/src/syscall/mod.rs | 9 +- .../qemu-smp1/inotifywait/qemu-x86_64.toml | 20 ++ .../inotifywait/sh/inotifywait-tests.sh | 86 +++++ 10 files changed, 548 insertions(+), 13 deletions(-) create mode 100644 os/StarryOS/kernel/src/file/inotify.rs create mode 100644 os/StarryOS/kernel/src/syscall/fs/inotify.rs create mode 100644 test-suit/starryos/normal/qemu-smp1/inotifywait/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/inotifywait/sh/inotifywait-tests.sh diff --git a/os/StarryOS/kernel/src/file/fs.rs b/os/StarryOS/kernel/src/file/fs.rs index 5b686c2228..5c3ae9163d 100644 --- a/os/StarryOS/kernel/src/file/fs.rs +++ b/os/StarryOS/kernel/src/file/fs.rs @@ -204,13 +204,20 @@ impl FileLike for File { if self.append() { inner.seek(SeekFrom::End(0))?; } - if likely(self.is_blocking()) { + let result = if likely(self.is_blocking()) { inner.write(src) } else { block_on(poll_io(self, IoEvents::OUT, self.nonblocking(), || { inner.write(&mut *src) })) + }; + if let Ok(bytes) = result + && bytes > 0 + { + let path = path_for(inner.location()).into_owned(); + crate::file::inotify::notify_modify_path(&path); } + result } fn stat(&self) -> AxResult { diff --git a/os/StarryOS/kernel/src/file/inotify.rs b/os/StarryOS/kernel/src/file/inotify.rs new file mode 100644 index 0000000000..512fbd8e59 --- /dev/null +++ b/os/StarryOS/kernel/src/file/inotify.rs @@ -0,0 +1,314 @@ +use alloc::{ + borrow::Cow, + collections::{BTreeMap, VecDeque}, + string::String, + sync::{Arc, Weak}, + vec::Vec, +}; +use core::{ + mem::size_of, + sync::atomic::{AtomicBool, Ordering}, + task::Context, +}; + +use ax_errno::{AxError, AxResult}; +use ax_sync::Mutex; +use ax_task::future::{block_on, poll_io}; +use axpoll::{IoEvents, PollSet, Pollable}; +use lazy_static::lazy_static; +use linux_raw_sys::{ + general::{ + IN_ALL_EVENTS, IN_CLOSE_WRITE, IN_CREATE, IN_DELETE, IN_DELETE_SELF, IN_IGNORED, IN_ISDIR, + IN_MODIFY, + }, + ioctl::FIONREAD, +}; +use starry_vm::VmMutPtr; + +use crate::file::{FileLike, IoDst, IoSrc}; + +const INOTIFY_EVENT_SIZE: usize = 16; +const MAX_QUEUED_EVENTS: usize = 1024; + +#[derive(Clone)] +struct Watch { + path: String, + mask: u32, +} + +#[derive(Default)] +struct InotifyState { + next_wd: i32, + watches: BTreeMap, + queue: VecDeque>, +} + +pub struct Inotify { + non_blocking: AtomicBool, + state: Mutex, + poll_rx: PollSet, +} + +lazy_static! { + static ref INOTIFY_INSTANCES: Mutex>> = Mutex::new(Vec::new()); +} + +impl Inotify { + pub fn new() -> Arc { + let inotify = Arc::new(Self { + non_blocking: AtomicBool::new(false), + state: Mutex::new(InotifyState { + next_wd: 1, + ..InotifyState::default() + }), + poll_rx: PollSet::new(), + }); + INOTIFY_INSTANCES.lock().push(Arc::downgrade(&inotify)); + inotify + } + + pub fn add_watch(&self, path: String, mask: u32) -> AxResult { + if mask == 0 { + return Err(AxError::InvalidInput); + } + + let mut state = self.state.lock(); + if let Some((wd, watch)) = state + .watches + .iter_mut() + .find(|(_, watch)| watch.path == path) + { + watch.mask = mask; + return Ok(*wd); + } + + let wd = state.next_wd; + state.next_wd = state.next_wd.checked_add(1).ok_or(AxError::NoMemory)?; + state.watches.insert(wd, Watch { path, mask }); + Ok(wd) + } + + pub fn rm_watch(&self, wd: i32) -> AxResult { + let mut state = self.state.lock(); + if state.watches.remove(&wd).is_none() { + return Err(AxError::InvalidInput); + } + Self::push_event(&mut state.queue, wd, IN_IGNORED, None); + self.poll_rx.wake(); + Ok(()) + } + + fn notify_path(&self, path: &str, exact_mask: u32, parent_mask: u32) { + let mut state = self.state.lock(); + let parent = parent_and_name(path); + let events = state + .watches + .iter() + .filter_map(|(wd, watch)| match parent { + _ if exact_mask != 0 + && watch.path == path + && watch.mask & (exact_mask & IN_ALL_EVENTS) != 0 => + { + Some((*wd, exact_mask, None)) + } + Some((parent, name)) + if parent_mask != 0 + && watch.path == parent + && watch.mask & (parent_mask & IN_ALL_EVENTS) != 0 => + { + Some((*wd, parent_mask, Some(String::from(name)))) + } + _ => None, + }) + .collect::>(); + + if events.is_empty() { + return; + } + for (wd, mask, name) in events { + Self::push_event(&mut state.queue, wd, mask, name.as_deref()); + } + self.poll_rx.wake(); + } + + fn notify_delete(&self, path: &str, is_dir: bool) { + let dir_mask = if is_dir { IN_ISDIR } else { 0 }; + let mut state = self.state.lock(); + let parent = parent_and_name(path); + let events = state + .watches + .iter() + .filter_map(|(wd, watch)| match parent { + _ if watch.path == path && watch.mask & IN_DELETE_SELF != 0 => { + Some((*wd, IN_DELETE_SELF | dir_mask, None)) + } + Some((parent, name)) if watch.path == parent && watch.mask & IN_DELETE != 0 => { + Some((*wd, IN_DELETE | dir_mask, Some(String::from(name)))) + } + _ => None, + }) + .collect::>(); + + if events.is_empty() { + return; + } + for (wd, mask, name) in events { + Self::push_event(&mut state.queue, wd, mask, name.as_deref()); + } + state.watches.retain(|_, watch| watch.path != path); + self.poll_rx.wake(); + } + + fn push_event(queue: &mut VecDeque>, wd: i32, mask: u32, name: Option<&str>) { + if queue.len() >= MAX_QUEUED_EVENTS { + queue.pop_front(); + } + + let name = name.map(str::as_bytes); + let name_len = name + .map(|name| align_event_name_len(name.len() + 1)) + .unwrap_or_default(); + + let mut event = Vec::with_capacity(INOTIFY_EVENT_SIZE + name_len); + event.extend_from_slice(&wd.to_ne_bytes()); + event.extend_from_slice(&mask.to_ne_bytes()); + event.extend_from_slice(&0u32.to_ne_bytes()); + event.extend_from_slice(&(name_len as u32).to_ne_bytes()); + if let Some(name) = name { + event.extend_from_slice(name); + event.resize(INOTIFY_EVENT_SIZE + name_len, 0); + } + queue.push_back(event); + } +} + +impl FileLike for Inotify { + fn read(&self, dst: &mut IoDst) -> AxResult { + if dst.remaining_mut() < INOTIFY_EVENT_SIZE { + return Err(AxError::InvalidInput); + } + + block_on(poll_io(self, IoEvents::IN, self.nonblocking(), || { + let mut state = self.state.lock(); + let mut written = 0; + while let Some(event) = state.queue.front() { + if dst.remaining_mut() < event.len() { + break; + } + written += dst.write(event)?; + state.queue.pop_front(); + } + if written == 0 { + Err(AxError::WouldBlock) + } else { + Ok(written) + } + })) + } + + fn write(&self, _src: &mut IoSrc) -> AxResult { + Err(AxError::BadFileDescriptor) + } + + fn nonblocking(&self) -> bool { + self.non_blocking.load(Ordering::Acquire) + } + + fn set_nonblocking(&self, non_blocking: bool) -> AxResult { + self.non_blocking.store(non_blocking, Ordering::Release); + Ok(()) + } + + fn path(&self) -> Cow<'_, str> { + "anon_inode:[inotify]".into() + } + + fn ioctl(&self, cmd: u32, arg: usize) -> AxResult { + match cmd { + FIONREAD => { + let pending = self + .state + .lock() + .queue + .iter() + .map(Vec::len) + .sum::() + .min(u32::MAX as usize) as u32; + (arg as *mut u32).vm_write(pending)?; + Ok(0) + } + _ => Err(AxError::NotATty), + } + } +} + +impl Pollable for Inotify { + fn poll(&self) -> IoEvents { + let mut events = IoEvents::empty(); + events.set(IoEvents::IN, !self.state.lock().queue.is_empty()); + events + } + + fn register(&self, context: &mut Context<'_>, events: IoEvents) { + if events.contains(IoEvents::IN) { + self.poll_rx.register(context.waker()); + } + } +} + +fn parent_and_name(path: &str) -> Option<(&str, &str)> { + let (parent, name) = path.rsplit_once('/')?; + if name.is_empty() { + None + } else if parent.is_empty() { + Some(("/", name)) + } else { + Some((parent, name)) + } +} + +fn align_event_name_len(len: usize) -> usize { + let align = size_of::(); + (len + align - 1) & !(align - 1) +} + +fn notify_instances(path: &str, notify: impl Fn(&Inotify, &str)) { + if path == "" { + return; + } + + let mut instances = INOTIFY_INSTANCES.lock(); + instances.retain(|watcher| { + if let Some(inotify) = watcher.upgrade() { + notify(&inotify, path); + true + } else { + false + } + }); +} + +pub fn notify_modify_path(path: &str) { + notify_instances(path, |inotify, path| { + inotify.notify_path(path, IN_MODIFY, IN_MODIFY); + }); +} + +pub fn notify_close_write_path(path: &str) { + notify_instances(path, |inotify, path| { + inotify.notify_path(path, IN_CLOSE_WRITE, IN_CLOSE_WRITE); + }); +} + +pub fn notify_create_path(path: &str, is_dir: bool) { + let mask = IN_CREATE | if is_dir { IN_ISDIR } else { 0 }; + notify_instances(path, |inotify, path| { + inotify.notify_path(path, 0, mask); + }); +} + +pub fn notify_delete_path(path: &str, is_dir: bool) { + notify_instances(path, |inotify, path| { + inotify.notify_delete(path, is_dir); + }); +} diff --git a/os/StarryOS/kernel/src/file/mod.rs b/os/StarryOS/kernel/src/file/mod.rs index 499c305e09..83986bea28 100644 --- a/os/StarryOS/kernel/src/file/mod.rs +++ b/os/StarryOS/kernel/src/file/mod.rs @@ -1,6 +1,7 @@ pub mod epoll; pub mod event; mod fs; +pub mod inotify; #[cfg(feature = "sg2002")] pub mod ion; pub mod memfd; @@ -24,7 +25,8 @@ use axpoll::Pollable; use downcast_rs::{DowncastSync, impl_downcast}; use flatten_objects::FlattenObjects; use linux_raw_sys::general::{ - O_PATH, O_RDONLY, O_WRONLY, RLIMIT_NOFILE, STATX_BASIC_STATS, stat, statx, statx_timestamp, + O_ACCMODE, O_PATH, O_RDONLY, O_RDWR, O_WRONLY, RLIMIT_NOFILE, STATX_BASIC_STATS, stat, statx, + statx_timestamp, }; use spin::RwLock; @@ -303,6 +305,14 @@ pub fn close_file_like(fd: c_int) -> AxResult { Ok(()) } +fn notify_close_write(fd: &FileDescriptor) { + let access = fd.inner.open_flags() & O_ACCMODE; + if (access == O_WRONLY || access == O_RDWR) && fd.inner.is::() { + let path = fd.inner.path(); + inotify::notify_close_write_path(path.as_ref()); + } +} + /// Close-time advisory-lock cleanup (the kernel side of POSIX /// "close-eats-locks", plus OFD release-on-last-close): /// @@ -320,6 +330,7 @@ pub fn close_file_like(fd: c_int) -> AxResult { /// `Weak` still alive, and sleep forever. pub fn release_locks_on_close(fd: FileDescriptor) { let key = fd.inner.inode_key(); + notify_close_write(&fd); if let Some(k) = key { let pid = current().as_thread().proc_data.proc.pid(); crate::syscall::release_inode_posix_locks(pid, k); @@ -374,6 +385,9 @@ pub fn close_all_fds() { .iter() .filter_map(|fd| fd.inner.inode_key()) .collect(); + for fd in &removed { + notify_close_write(fd); + } // Drop removed descriptors after releasing FD_TABLE lock to avoid // lock re-entry or side effects from destructor paths. drop(removed); diff --git a/os/StarryOS/kernel/src/syscall/fs/ctl.rs b/os/StarryOS/kernel/src/syscall/fs/ctl.rs index 32eb84ca8b..ae13e09eb4 100644 --- a/os/StarryOS/kernel/src/syscall/fs/ctl.rs +++ b/os/StarryOS/kernel/src/syscall/fs/ctl.rs @@ -1,4 +1,9 @@ -use alloc::{ffi::CString, vec, vec::Vec}; +use alloc::{ + ffi::CString, + string::{String, ToString}, + vec, + vec::Vec, +}; use core::{ ffi::{c_char, c_int}, mem::offset_of, @@ -23,6 +28,14 @@ use crate::{ time::TimeValueLike, }; +fn path_info_at(dirfd: i32, path: &str) -> AxResult<(String, bool)> { + with_fs(dirfd, |fs| { + let loc = fs.resolve_no_follow(path)?; + let is_dir = loc.metadata()?.node_type == NodeType::Directory; + Ok((loc.absolute_path()?.to_string(), is_dir)) + }) +} + /// The ioctl() system call manipulates the underlying device parameters /// of special files. pub fn sys_ioctl(fd: i32, cmd: u32, arg: usize) -> AxResult { @@ -139,7 +152,7 @@ pub fn sys_mkdirat(dirfd: i32, path: *const c_char, mode: u32) -> AxResult Ok(0), // mkdir on an existing path should report EEXIST. // Use no-follow lookup so dangling symlinks are treated as existing @@ -148,7 +161,13 @@ pub fn sys_mkdirat(dirfd: i32, path: *const c_char, mode: u32) -> AxResult Err(err), - }) + }); + if result.is_ok() + && let Ok((path, _)) = path_info_at(dirfd, &path) + { + crate::file::inotify::notify_create_path(&path, true); + } + result } pub fn sys_mknodat(dirfd: i32, path: *const c_char, mode: u32, dev: u64) -> Result { @@ -343,14 +362,21 @@ pub fn sys_unlinkat(dirfd: i32, path: *const c_char, flags: usize) -> AxResult Ok(false), + Err(AxError::NotFound) => Ok(true), + Err(err) => Err(err), + })?; + + let fd = + with_fs(dirfd, |fs| options.open(fs, path)).and_then(|it| add_to_fd(it, flags as _))?; + if should_notify_create { + let file = get_file_like(fd)?; + crate::file::inotify::notify_create_path(file.path().as_ref(), false); + } + Ok(fd as isize) } /// Open a file by `filename` and insert it into the file descriptor table. diff --git a/os/StarryOS/kernel/src/syscall/fs/inotify.rs b/os/StarryOS/kernel/src/syscall/fs/inotify.rs new file mode 100644 index 0000000000..379f66312e --- /dev/null +++ b/os/StarryOS/kernel/src/syscall/fs/inotify.rs @@ -0,0 +1,47 @@ +use alloc::string::ToString; +use core::ffi::{c_char, c_int}; + +use ax_errno::{AxError, AxResult}; +use linux_raw_sys::general::{AT_FDCWD, IN_CLOEXEC, IN_NONBLOCK}; + +use crate::{ + file::{FileLike, add_file_like, get_file_like, inotify::Inotify, resolve_at}, + mm::vm_load_string, +}; + +pub fn sys_inotify_init1(flags: u32) -> AxResult { + debug!("sys_inotify_init1 <= flags: {flags}"); + + let valid_flags = IN_CLOEXEC | IN_NONBLOCK; + if flags & !valid_flags != 0 { + return Err(AxError::InvalidInput); + } + + let inotify = Inotify::new(); + inotify.set_nonblocking(flags & IN_NONBLOCK != 0)?; + add_file_like(inotify as _, flags & IN_CLOEXEC != 0).map(|fd| fd as _) +} + +pub fn sys_inotify_add_watch(fd: c_int, path: *const c_char, mask: u32) -> AxResult { + let path = vm_load_string(path)?; + debug!("sys_inotify_add_watch <= fd: {fd}, path: {path}, mask: {mask}"); + + let resolved_path = resolve_at(AT_FDCWD, Some(&path), 0)? + .into_file() + .and_then(|loc| loc.absolute_path().ok().map(|path| path.to_string())) + .ok_or(AxError::InvalidInput)?; + + let inotify = get_file_like(fd)? + .downcast_arc::() + .map_err(|_| AxError::InvalidInput)?; + inotify.add_watch(resolved_path, mask).map(|wd| wd as isize) +} + +pub fn sys_inotify_rm_watch(fd: c_int, wd: c_int) -> AxResult { + debug!("sys_inotify_rm_watch <= fd: {fd}, wd: {wd}"); + + let inotify = get_file_like(fd)? + .downcast_arc::() + .map_err(|_| AxError::InvalidInput)?; + inotify.rm_watch(wd).map(|()| 0) +} diff --git a/os/StarryOS/kernel/src/syscall/fs/mod.rs b/os/StarryOS/kernel/src/syscall/fs/mod.rs index 683aea736b..df381bcd8b 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mod.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mod.rs @@ -1,6 +1,7 @@ mod ctl; mod event; mod fd_ops; +mod inotify; mod io; mod lock; mod memfd; @@ -15,6 +16,7 @@ pub use self::{ ctl::*, event::*, fd_ops::*, + inotify::*, io::*, lock::{release_inode_posix_locks, release_pid_locks, wake_flock_waiters, wake_lock_waiters}, memfd::*, diff --git a/os/StarryOS/kernel/src/syscall/mod.rs b/os/StarryOS/kernel/src/syscall/mod.rs index ed74419a4a..57cc56858c 100644 --- a/os/StarryOS/kernel/src/syscall/mod.rs +++ b/os/StarryOS/kernel/src/syscall/mod.rs @@ -365,6 +365,13 @@ pub fn handle_syscall(uctx: &mut UserContext) { // event Sysno::eventfd2 => sys_eventfd2(uctx.arg0() as _, uctx.arg1() as _), + #[cfg(target_arch = "x86_64")] + Sysno::inotify_init => sys_inotify_init1(0), + Sysno::inotify_init1 => sys_inotify_init1(uctx.arg0() as _), + Sysno::inotify_add_watch => { + sys_inotify_add_watch(uctx.arg0() as _, uctx.arg1() as _, uctx.arg2() as _) + } + Sysno::inotify_rm_watch => sys_inotify_rm_watch(uctx.arg0() as _, uctx.arg1() as _), Sysno::timerfd_create => sys_timerfd_create(uctx.arg0() as _, uctx.arg1() as _), Sysno::timerfd_settime => sys_timerfd_settime( uctx.arg0() as _, @@ -768,7 +775,7 @@ pub fn handle_syscall(uctx: &mut UserContext) { | Sysno::open_tree | Sysno::memfd_secret => sys_dummy_fd(sysno), - Sysno::fanotify_init | Sysno::inotify_init1 => Err(AxError::Unsupported), + Sysno::fanotify_init => Err(AxError::Unsupported), Sysno::timer_create => { sys_timer_create(uctx.arg0() as _, uctx.arg1() as _, uctx.arg2() as _) diff --git a/test-suit/starryos/normal/qemu-smp1/inotifywait/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/inotifywait/qemu-x86_64.toml new file mode 100644 index 0000000000..484026af24 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/inotifywait/qemu-x86_64.toml @@ -0,0 +1,20 @@ +args = [ + "-nographic", + "-m", + "512M", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/inotifywait-tests.sh" +success_regex = ["(?m)^INOTIFYWAIT_TEST_PASSED\\s*$"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^INOTIFYWAIT_TEST_FAILED:"] +timeout = 600 diff --git a/test-suit/starryos/normal/qemu-smp1/inotifywait/sh/inotifywait-tests.sh b/test-suit/starryos/normal/qemu-smp1/inotifywait/sh/inotifywait-tests.sh new file mode 100644 index 0000000000..a73b034ac1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/inotifywait/sh/inotifywait-tests.sh @@ -0,0 +1,86 @@ +#!/bin/sh +set -eu + +fail() { + echo "INOTIFYWAIT_TEST_FAILED: $*" + exit 1 +} + +apk update || fail "apk update" +apk add inotify-tools || fail "apk add inotify-tools" + +command -v inotifywait >/dev/null || fail "inotifywait missing" + +workdir=/tmp/starry-inotifywait +watched="$workdir/watched.txt" +watchdir="$workdir/watchdir" +out="$workdir/out" +err="$workdir/err" + +rm -rf "$workdir" +mkdir -p "$workdir" +mkdir -p "$watchdir" +: > "$watched" + +( + sleep 1 + echo changed >> "$watched" +) & +writer=$! + +if ! inotifywait -q -e modify --format "%w %e" -t 5 "$watched" > "$out" 2> "$err"; then + wait "$writer" || true + cat "$err" + fail "inotifywait did not observe modify" +fi + +wait "$writer" || true +grep -q "MODIFY" "$out" || fail "missing MODIFY event" + +( + sleep 1 + : > "$watchdir/created.txt" +) & +creator=$! + +if ! inotifywait -q -e create --format "%f %e" -t 5 "$watchdir" > "$out" 2> "$err"; then + wait "$creator" || true + cat "$err" + fail "inotifywait did not observe create" +fi + +wait "$creator" || true +grep -q "created.txt .*CREATE" "$out" || fail "missing CREATE event" + +( + sleep 1 + echo closed > "$watchdir/closed.txt" +) & +closer=$! + +if ! inotifywait -q -e close_write --format "%f %e" -t 5 "$watchdir" > "$out" 2> "$err"; then + wait "$closer" || true + cat "$err" + fail "inotifywait did not observe close_write" +fi + +wait "$closer" || true +grep -q "closed.txt .*CLOSE_WRITE" "$out" || fail "missing CLOSE_WRITE event" + +: > "$watchdir/deleted.txt" +( + sleep 1 + rm "$watchdir/deleted.txt" +) & +deleter=$! + +if ! inotifywait -q -e delete --format "%f %e" -t 5 "$watchdir" > "$out" 2> "$err"; then + wait "$deleter" || true + cat "$err" + fail "inotifywait did not observe delete" +fi + +wait "$deleter" || true +grep -q "deleted.txt .*DELETE" "$out" || fail "missing DELETE event" + +echo "INOTIFYWAIT_TEST_PASSED" From 79d05c3689b8bd3076cca71d3d5bea4c0a5a244f Mon Sep 17 00:00:00 2001 From: yomi <3527628634@qq.com> Date: Mon, 25 May 2026 12:02:56 +0800 Subject: [PATCH 07/71] fix(starry-kernel): correct splice error handling (#896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 5.19 * style(starry): format sendfile changes * 完善了建议 * 改 * chore(starry): ignore generated sendfile test artifacts * 1 * 2 * again * 1 * finish-sendfile * fixed pipe * 完善了建议 * fix(starry): remove generated qemu logs * 删除了let src = ... * 格式问题,删除一个空行 * 完成了splic * 把 warning 当 error * 格式问题 * 还是格式问题 * 补充了测试,删除了测试数据 * 修改了测试路径,补充了测试 * copy file range没有bug * 改了一下copyfilerange * 格式问题 * 还是格式问题 * 增加了注释,检查了格式问题 --- os/StarryOS/kernel/src/syscall/fs/io.rs | 208 ++++-- .../syscall/test-copy-file-range/c/src/main.c | 609 +++++++++++++----- .../syscall/test-splice/c/CMakeLists.txt | 14 + .../syscall/test-splice/c/src/main.c | 351 ++++++++++ .../test-splice/c/src/test_framework.h | 85 +++ 5 files changed, 1056 insertions(+), 211 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/test_framework.h diff --git a/os/StarryOS/kernel/src/syscall/fs/io.rs b/os/StarryOS/kernel/src/syscall/fs/io.rs index c6ef096a58..e48d2a0420 100644 --- a/os/StarryOS/kernel/src/syscall/fs/io.rs +++ b/os/StarryOS/kernel/src/syscall/fs/io.rs @@ -682,14 +682,12 @@ pub fn sys_sendfile(out_fd: c_int, in_fd: c_int, offset: *mut u64, len: usize) - SendFile::Offset(File::from_fd(in_fd)?, offset, pos) } else { - // Linux sendfile 要求 in_fd 是支持 mmap-like 操作的文件。 - // 这里显式用 File::from_fd(in_fd)? 拒绝 pipe。 + // 拒绝 pipe 输入:File::from_fd 对 pipe 会失败,但 get_file_like 会成功,后续 read 会返回 EPIPE。Linux sendfile 对 pipe 输入也是 EPIPE。 let _in_file = File::from_fd(in_fd)?; - SendFile::Direct(get_file_like(in_fd)?) }; - let dst: SendFile = SendFile::Direct(get_file_like(out_fd)?); + let dst: SendFile = SendFile::Direct(out_file); do_send(src, dst, len).map(|n: usize| n as _) } @@ -716,59 +714,72 @@ pub fn sys_copy_file_range( return Err(AxError::InvalidInput); } + if len > isize::MAX as usize { + return Err(AxError::InvalidInput); + } + let remap = |e| match e { AxError::BadFileDescriptor | AxError::IsADirectory => e, _ => AxError::InvalidInput, }; + let file_in = File::from_fd(fd_in).map_err(remap)?; let file_out = File::from_fd(fd_out).map_err(remap)?; + let meta_in = file_in.inner().location().metadata()?; let meta_out = file_out.inner().location().metadata()?; + if meta_in.node_type == NodeType::Directory || meta_out.node_type == NodeType::Directory { + return Err(AxError::IsADirectory); + } + if meta_in.node_type != NodeType::RegularFile || meta_out.node_type != NodeType::RegularFile { return Err(AxError::InvalidInput); } + if file_out.inner().access(FileFlags::APPEND).is_ok() { return Err(AxError::BadFileDescriptor); } + let pos_in = if off_in.is_null() { + file_in.inner().seek(SeekFrom::Current(0))? + } else { + off_in.vm_read()? + }; + + let pos_out = if off_out.is_null() { + file_out.inner().seek(SeekFrom::Current(0))? + } else { + off_out.vm_read()? + }; + if len > 0 && meta_in.device == meta_out.device && meta_in.inode == meta_out.inode { - let pos_in = if off_in.is_null() { - file_in.inner().seek(SeekFrom::Current(0))? - } else { - off_in.vm_read()? - }; - let pos_out = if off_out.is_null() { - file_out.inner().seek(SeekFrom::Current(0))? - } else { - off_out.vm_read()? - }; - if let Some(copy_end) = (len as u64).checked_sub(1) { - let in_end = pos_in.checked_add(copy_end).ok_or(AxError::InvalidInput)?; - let out_end = pos_out.checked_add(copy_end).ok_or(AxError::InvalidInput)?; - if in_end >= pos_out && pos_in <= out_end { - return Err(AxError::InvalidInput); - } + let copy_last = (len as u64).checked_sub(1).ok_or(AxError::InvalidInput)?; + + let in_end = pos_in.checked_add(copy_last).ok_or(AxError::InvalidInput)?; + + let out_end = pos_out + .checked_add(copy_last) + .ok_or(AxError::InvalidInput)?; + + if in_end >= pos_out && pos_in <= out_end { + return Err(AxError::InvalidInput); } } - let src = if !off_in.is_null() { - SendFile::Offset(file_in, off_in, off_in.vm_read()?) + let src: SendFile = if !off_in.is_null() { + SendFile::Offset(file_in, off_in, pos_in) } else { SendFile::Direct(file_in) }; - // Output offset: when fd_out is a memfd, the regular `Offset` - // variant would unwrap to the inner `File` and bypass seal checks. - // `send_offset_out` keeps the `Memfd` wrapper so `Memfd::write_at` - // enforces `F_SEAL_WRITE` / `F_SEAL_GROW`. - let dst = if !off_out.is_null() { + let dst: SendFile = if !off_out.is_null() { send_offset_out(fd_out, off_out)? } else { SendFile::Direct(get_file_like(fd_out)?) }; - do_send(src, dst, len).map(|n| n as _) + do_send(src, dst, len).map(|n: usize| n as isize) } pub fn sys_splice( @@ -777,7 +788,7 @@ pub fn sys_splice( fd_out: c_int, off_out: *mut i64, len: usize, - _flags: u32, + flags: u32, ) -> AxResult { debug!( "sys_splice <= fd_in: {}, off_in: {}, fd_out: {}, off_out: {}, len: {}, flags: {}", @@ -786,64 +797,137 @@ pub fn sys_splice( fd_out, !off_out.is_null(), len, - _flags + flags ); - let mut has_pipe = false; + const SPLICE_F_MOVE: u32 = 0x01; + const SPLICE_F_NONBLOCK: u32 = 0x02; + const SPLICE_F_MORE: u32 = 0x04; + const SPLICE_F_GIFT: u32 = 0x08; + const SPLICE_F_ALL: u32 = SPLICE_F_MOVE | SPLICE_F_NONBLOCK | SPLICE_F_MORE | SPLICE_F_GIFT; + // 1. 先检查明显非法 fd。 if DummyFd::from_fd(fd_in).is_ok() || DummyFd::from_fd(fd_out).is_ok() { return Err(AxError::BadFileDescriptor); } - let src = if !off_in.is_null() { + // 2. 检查 flags。未知 flag 返回 EINVAL。 + if flags & !SPLICE_F_ALL != 0 { + return Err(AxError::InvalidInput); + } + + // 3. 防止最终 usize -> isize 溢出。 + if len > isize::MAX as usize { + return Err(AxError::InvalidInput); + } + + // 4. 先识别 pipe。 + let in_pipe = Pipe::from_fd(fd_in).ok(); + let out_pipe = Pipe::from_fd(fd_out).ok(); + + // 如果不是 pipe,先确认它至少是合法 file_like。 + // 这样 bad fd 会优先返回 EBADF,而不是被下面的 no-pipe 误判成 EINVAL。 + let in_file = if in_pipe.is_none() { + Some(get_file_like(fd_in).map_err(|_| AxError::BadFileDescriptor)?) + } else { + None + }; + + let out_file = if out_pipe.is_none() { + Some(get_file_like(fd_out).map_err(|_| AxError::BadFileDescriptor)?) + } else { + None + }; + // splice 要求至少一端是 pipe。 + if in_pipe.is_none() && out_pipe.is_none() { + return Err(AxError::InvalidInput); + } + + // 5. pipe 对应的 offset 必须是 NULL,否则返回 ESPIPE。 + if in_pipe.is_some() && !off_in.is_null() { + return Err(AxError::from(LinuxError::ESPIPE)); + } + + if out_pipe.is_some() && !off_out.is_null() { + return Err(AxError::from(LinuxError::ESPIPE)); + } + + // 6. 检查 pipe 方向。 + match &in_pipe { + Some(pipe) if !pipe.is_read() => return Err(AxError::BadFileDescriptor), + _ => {} + } + + match &out_pipe { + Some(pipe) if !pipe.is_write() => return Err(AxError::BadFileDescriptor), + _ => {} + } + + // 7. 同一个 pipe 不能同时作为输入和输出。 + match (&in_pipe, &out_pipe) { + (Some(src), Some(dst)) if alloc::sync::Arc::ptr_eq(src, dst) => { + return Err(AxError::InvalidInput); + } + _ => {} + } + + // 8. 读取 off_in。到这里时,如果 off_in 非空,fd_in 一定不是 pipe + let in_pos = if !off_in.is_null() { let pos = off_in.vm_read()?; if pos < 0 { return Err(AxError::InvalidInput); } - SendFile::Offset(File::from_fd(fd_in)?, off_in.cast(), pos as u64) + Some(pos as u64) } else { - if let Ok(src) = Pipe::from_fd(fd_in) { - if !src.is_read() { - return Err(AxError::BadFileDescriptor); - } - has_pipe = true; - } - if let Ok(file) = File::from_fd(fd_in) - && file.inner().is_path() - { - return Err(AxError::InvalidInput); - } - SendFile::Direct(get_file_like(fd_in)?) + None }; - let dst = if !off_out.is_null() { + // 9. 读取 off_out。 + // 到这里时,如果 off_out 非空,fd_out 一定不是 pipe。 + let out_pos = if !off_out.is_null() { let pos = off_out.vm_read()?; if pos < 0 { return Err(AxError::InvalidInput); } + Some(pos as u64) + } else { + None + }; + + // 10. 输出目标不能是 O_APPEND。注意要覆盖 off_out 为 NULL 和非 NULL 两种情况。 + match File::from_fd(fd_out) { + Ok(file) if file.inner().access(FileFlags::APPEND).is_ok() => { + return Err(AxError::InvalidInput); + } + _ => {} + } + + // 11. 构造输入端。 + let src = if let Some(pos) = in_pos { + SendFile::Offset(File::from_fd(fd_in)?, off_in.cast(), pos) + } else if let Some(file) = in_file { + SendFile::Direct(file) + } else { + SendFile::Direct(get_file_like(fd_in)?) + }; + // 12. 构造输出端。 + let dst: SendFile = if out_pos.is_some() { // Route memfd output through the seal-aware wrapper rather // than `File::from_fd`'s auto-unwrap. send_offset_out(fd_out, off_out.cast())? } else { - if let Ok(dst) = Pipe::from_fd(fd_out) { - if !dst.is_write() { - return Err(AxError::BadFileDescriptor); - } - has_pipe = true; - } - if let Ok(file) = File::from_fd(fd_out) - && file.inner().access(FileFlags::APPEND).is_ok() - { - return Err(AxError::InvalidInput); - } - let f = get_file_like(fd_out)?; + let f = if let Some(file) = out_file { + file + } else { + get_file_like(fd_out)? + }; + f.write(&mut b"".as_slice())?; + SendFile::Direct(f) }; - if !has_pipe { - return Err(AxError::InvalidInput); - } + let n = do_send(src, dst, len)?; - do_send(src, dst, len).map(|n| n as _) + isize::try_from(n).map_err(|_| AxError::InvalidInput) } diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-copy-file-range/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-copy-file-range/c/src/main.c index da18bceff9..60a6e14eb7 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-copy-file-range/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-copy-file-range/c/src/main.c @@ -1,225 +1,536 @@ -/* - * test_copy_file_range.c — 验证 copy_file_range 系统调用的参数校验及基本功能。 - * - * 覆盖场景: - * 1. 文件间基本拷贝,验证偏移量更新和数据正确性 - * 2. 带偏移量的拷贝 - * 3. flags 非零返回 EINVAL(Linux 规定 flags 必须为 0) - * 4. 同文件重叠拷贝返回 EINVAL - * 5. len=0 返回 0 - * 6. 超出文件末尾拷贝返回 0 - * 7. 管道 fd 传入返回 EINVAL - */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - #include "test_framework.h" -#include + +#include +#include +#include +#include #include +#include +#include #include -#include -#include +#include -/* musl 可能不提供 copy_file_range 封装,直接走 syscall */ -static ssize_t my_copy_file_range(int fd_in, off_t *off_in, - int fd_out, off_t *off_out, - size_t len, unsigned int flags) { - return syscall(SYS_copy_file_range, fd_in, off_in, fd_out, off_out, len, flags); +#define TEST_SRC "/tmp/test_cfr_src.tmp" +#define TEST_DST "/tmp/test_cfr_dst.tmp" +#define TEST_DST2 "/tmp/test_cfr_dst2.tmp" + +/* 辅助:创建文件并写入数据,偏移量回到开头 */ +static int create_file(const char *path, const char *data, size_t len) { + int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) return -1; + if (len > 0) { + write(fd, data, len); + lseek(fd, 0, SEEK_SET); + } + return fd; } -#define TEST_SRC "/tmp/starry_test_cfr_src" -#define TEST_DST "/tmp/starry_test_cfr_dst" -#define TEST_SAME "/tmp/starry_test_cfr_same" +/* 辅助:读回文件内容并比较 */ +static int verify_file_content(const char *path, const char *expected, size_t len) { + int fd = open(path, O_RDONLY); + if (fd < 0) return 0; + char *buf = malloc(len + 1); + memset(buf, 0, len + 1); + ssize_t n = read(fd, buf, len); + close(fd); + int ok = (n == (ssize_t)len && memcmp(buf, expected, len) == 0); + free(buf); + return ok; +} -int main(void) -{ - TEST_START("copy_file_range"); +int main(void) { + TEST_START("copy_file_range: Copy a range of data from one file to another"); - /* 正向测试: 文件间基本拷贝,验证偏移量和数据 */ + /* ==================== 正向测试 ==================== */ + + /* + * 测试 1: copy_file_range 基本功能 — 复制整个文件 + * 手册描述: copies up to len bytes of data from fd_in to fd_out. + * Upon successful completion, returns the number of bytes copied. + */ { - int src = open(TEST_SRC, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(src >= 0, "打开源文件"); - if (src < 0) goto cleanup_basic; + const char *data = "hello copy_file_range"; + int fd_in = create_file(TEST_SRC, data, strlen(data)); + int fd_out = create_file(TEST_DST, NULL, 0); + CHECK(fd_in >= 0 && fd_out >= 0, "创建源文件和目标文件成功"); - const char *data = "Hello, copy_file_range!"; - ssize_t len = (ssize_t)strlen(data); - CHECK_RET(write(src, data, len), len, "写入源文件"); + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, strlen(data), 0); + CHECK(n == (ssize_t)strlen(data), + "copy_file_range 复制整个文件返回正确字节数"); - int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(dst >= 0, "打开目标文件"); - if (dst < 0) { close(src); goto cleanup_basic; } + close(fd_in); + close(fd_out); + CHECK(verify_file_content(TEST_DST, data, strlen(data)), + "复制后目标文件内容与源文件一致"); - off_t off_in = 0; - off_t off_out = 0; - ssize_t n = my_copy_file_range(src, &off_in, dst, &off_out, len, 0); - CHECK(n == len, "拷贝字节数正确"); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* + * 测试 2: off_in/off_out 为 NULL 时文件偏移量自动调整 + * 手册描述: bytes are read from fd_in starting from the file offset, + * and the file offset is adjusted by the number of bytes copied. + */ + { + const char *data = "ABCDEFGHIJKLMNOP"; + int fd_in = create_file(TEST_SRC, data, strlen(data)); + int fd_out = create_file(TEST_DST, NULL, 0); - /* 偏移量应随拷贝量前进 */ - CHECK(off_in == (off_t)len, "off_in 更新正确"); - CHECK(off_out == (off_t)len, "off_out 更新正确"); + /* 复制前半部分 (8 字节) */ + errno = 0; + ssize_t n1 = copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0); + CHECK(n1 == 8, "第一次复制 8 字节成功"); - /* 读回验证数据一致 */ - char buf[64]; - memset(buf, 0, sizeof(buf)); - CHECK_RET(pread(dst, buf, len, 0), len, "读回目标文件"); - CHECK(memcmp(buf, data, len) == 0, "目标文件内容正确"); + /* 复制后半部分 (8 字节),偏移量应自动从 8 开始 */ + errno = 0; + ssize_t n2 = copy_file_range(fd_in, NULL, fd_out, NULL, 8, 0); + CHECK(n2 == 8, "第二次复制 8 字节成功(偏移量自动调整)"); + + close(fd_in); + close(fd_out); + + char expected[16]; + memcpy(expected, data, 16); + CHECK(verify_file_content(TEST_DST, expected, 16), + "两次复制后目标文件内容完整正确"); - close(src); - close(dst); - cleanup_basic: unlink(TEST_SRC); unlink(TEST_DST); } - /* 带偏移量的拷贝:从 src 中间拷贝到 dst 中间 */ + /* + * 测试 3: 使用 off_in 指定源偏移(不改变文件偏移量) + * 手册描述: off_in must point to a buffer that specifies the starting offset. + * The file offset of fd_in is not changed, but off_in is adjusted. + */ { - int src = open(TEST_SRC, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(src >= 0, "打开源文件(偏移测试)"); - if (src < 0) goto cleanup_offsets; + const char *data = "ABCDEFGHIJ"; + int fd_in = create_file(TEST_SRC, data, strlen(data)); + /* 读取到末尾使文件偏移量在末尾 */ + lseek(fd_in, 0, SEEK_END); + + int fd_out = create_file(TEST_DST, NULL, 0); + off_t off_in = 5; /* 从偏移 5 开始读取 */ + + errno = 0; + ssize_t n = copy_file_range(fd_in, &off_in, fd_out, NULL, 5, 0); + CHECK(n == 5, "copy_file_range 带指定 off_in=5 读取 5 字节成功"); + CHECK(off_in == 10, "off_in 已调整到 10 (5+5)"); - CHECK_RET(write(src, "AAAA_HELLO_BBBB", 15), 15, "写入源文件(偏移测试)"); + /* 文件偏移量应不变(仍在末尾) */ + off_t cur = lseek(fd_in, 0, SEEK_CUR); + CHECK(cur == (off_t)strlen(data), + "使用 off_in 后文件偏移量未改变"); + + close(fd_in); + close(fd_out); + CHECK(verify_file_content(TEST_DST, "FGHIJ", 5), + "off_in=5 复制的内容正确 (FGHIJ)"); + + unlink(TEST_SRC); + unlink(TEST_DST); + } - int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(dst >= 0, "打开目标文件(偏移测试)"); - if (dst < 0) { close(src); goto cleanup_offsets; } + /* + * 测试 4: 使用 off_out 指定目标偏移 + * 手册描述: Similar statements apply to off_out. + */ + { + const char *data = "HELLO"; + int fd_in = create_file(TEST_SRC, data, strlen(data)); + int fd_out = create_file(TEST_DST, "XXXXX", 5); - CHECK_RET(write(dst, "XXXXXXXXXXXXXXX", 15), 15, "写入目标文件(偏移测试)"); + off_t off_out = 5; + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, &off_out, 5, 0); + CHECK(n == 5, "copy_file_range 带 off_out=5 写入成功"); + CHECK(off_out == 10, "off_out 已调整到 10 (5+5)"); - off_t off_in = 5; /* 从 "HELLO" 开始 */ - off_t off_out = 3; /* 写入 dst[3] 起始 */ - ssize_t n = my_copy_file_range(src, &off_in, dst, &off_out, 5, 0); - CHECK(n == 5, "偏移拷贝字节数正确"); + close(fd_in); + close(fd_out); - /* dst 应变为 "XXXHELLOXXXXXXX" */ - char buf[32]; - memset(buf, 0, sizeof(buf)); - CHECK_RET(pread(dst, buf, 15, 0), 15, "读回目标文件(偏移测试)"); - CHECK(memcmp(buf, "XXXHELLOXXXXXXX", 15) == 0, "偏移拷贝目标内容正确"); + char expected[10]; + memcpy(expected, "XXXXX", 5); + memcpy(expected + 5, "HELLO", 5); + CHECK(verify_file_content(TEST_DST, expected, 10), + "off_out=5 后目标文件前 5 字节不变,后 5 字节为复制数据"); - close(src); - close(dst); - cleanup_offsets: unlink(TEST_SRC); unlink(TEST_DST); } - /* Linux 规定 flags 必须为 0,非零值应返回 EINVAL */ + /* + * 测试 5: 文件偏移量在 EOF 时返回 0 + * 手册描述: If the file offset of fd_in is at or past the end of file, + * no bytes are copied, and copy_file_range() returns zero. + */ { - int src = open(TEST_SRC, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(src >= 0, "打开源文件(flags 测试)"); - if (src < 0) goto cleanup_flags; + const char *data = "DATA"; + int fd_in = create_file(TEST_SRC, data, strlen(data)); + int fd_out = create_file(TEST_DST, NULL, 0); + + /* 移动到文件末尾之后 */ + lseek(fd_in, 100, SEEK_SET); - CHECK_RET(write(src, "testdata", 8), 8, "写入源文件(flags 测试)"); + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, 10, 0); + CHECK_RET(n, 0, "copy_file_range fd_in 在 EOF 后返回 0"); - int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(dst >= 0, "打开目标文件(flags 测试)"); - if (dst < 0) { close(src); goto cleanup_flags; } + close(fd_in); + close(fd_out); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* + * 测试 6: 同一文件内复制(不重叠) + * 手册描述: fd_in and fd_out can refer to the same file. + */ + { + const char *data = "ABCDEFGHIJ"; + int fd = create_file(TEST_SRC, data, strlen(data)); off_t off_in = 0; - off_t off_out = 0; - /* flags=1 应被拒绝 */ - ssize_t n = my_copy_file_range(src, &off_in, dst, &off_out, 8, 1); - CHECK(n == -1 && errno == EINVAL, "flags=1 应返回 EINVAL"); + off_t off_out = 5; + errno = 0; + ssize_t n = copy_file_range(fd, &off_in, fd, &off_out, 5, 0); + CHECK(n == 5, "copy_file_range 同一文件不重叠复制成功"); + + close(fd); + + /* 验证:偏移 5-9 应变为 ABCDE */ + int fd2 = open(TEST_SRC, O_RDONLY); + char buf[10] = {0}; + read(fd2, buf, 10); + close(fd2); + CHECK(memcmp(buf, "ABCDEABCDE", 10) == 0, + "同一文件复制后内容正确 (ABCDEABCDE)"); + + unlink(TEST_SRC); + } + + /* + * 测试 7: 部分复制 — len 大于源文件剩余字节 + * 手册描述: This could be less than the length originally requested. + */ + { + const char *data = "SHORT"; + int fd_in = create_file(TEST_SRC, data, strlen(data)); + int fd_out = create_file(TEST_DST, NULL, 0); + + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, 1024, 0); + CHECK(n == (ssize_t)strlen(data), + "copy_file_range len=1024 但源仅 5 字节,返回 5"); + + close(fd_in); + close(fd_out); + CHECK(verify_file_content(TEST_DST, data, strlen(data)), + "部分复制后数据正确"); + + unlink(TEST_SRC); + unlink(TEST_DST); + } - close(src); - close(dst); - cleanup_flags: + /* + * 测试 8: 循环调用复制大文件 + * 手册描述: copies up to len bytes. + */ + { + /* 创建 8192 字节文件 */ + size_t total_size = 8192; + char *large_data = malloc(total_size); + for (size_t i = 0; i < total_size; i++) + large_data[i] = 'A' + (i % 26); + + int fd_in = create_file(TEST_SRC, NULL, 0); + write(fd_in, large_data, total_size); + lseek(fd_in, 0, SEEK_SET); + + int fd_out = create_file(TEST_DST, NULL, 0); + + size_t copied = 0; + while (copied < total_size) { + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, + total_size - copied, 0); + if (n <= 0) break; + copied += n; + } + + CHECK(copied == total_size, + "copy_file_range 循环复制 8192 字节文件成功"); + + close(fd_in); + close(fd_out); + + /* 验证内容 */ + int fd_v = open(TEST_DST, O_RDONLY); + char *readback = malloc(total_size); + read(fd_v, readback, total_size); + close(fd_v); + CHECK(memcmp(readback, large_data, total_size) == 0, + "循环复制后文件内容一致"); + + free(large_data); + free(readback); unlink(TEST_SRC); unlink(TEST_DST); } - /* 同文件重叠拷贝:Linux 语义要求返回 EINVAL */ + /* + * 测试 9: 跨进程复制 — 子进程写源文件,父进程复制 + */ { - int fd = open(TEST_SAME, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(fd >= 0, "打开同文件(重叠测试)"); - if (fd < 0) goto cleanup_overlap; + int pipefd[2]; + pipe(pipefd); + + pid_t pid = fork(); + if (pid == 0) { + close(pipefd[0]); + int fd = open(TEST_SRC, O_RDWR | O_CREAT | O_TRUNC, 0644); + write(fd, "child_data", 10); + close(fd); + char c = 'R'; + write(pipefd[1], &c, 1); + close(pipefd[1]); + _exit(0); + } - unsigned char pattern[8192]; - for (size_t i = 0; i < 8192; i++) - pattern[i] = (unsigned char)(i & 0xFF); - CHECK_RET(write(fd, pattern, 8192), 8192, "写入同文件测试数据"); + close(pipefd[1]); + char c; + read(pipefd[0], &c, 1); + close(pipefd[0]); - /* 从 offset 0 拷贝 6000 字节到 offset 2000(同文件前向重叠) */ - off_t off_in = 0; - off_t off_out = 2000; - CHECK_ERR(my_copy_file_range(fd, &off_in, fd, &off_out, 6000, 0), - EINVAL, "同文件重叠拷贝应返回 EINVAL"); + int fd_in = open(TEST_SRC, O_RDONLY); + int fd_out = create_file(TEST_DST, NULL, 0); + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, 10, 0); + CHECK(n == 10, "copy_file_range 跨进程复制成功"); + + close(fd_in); + close(fd_out); + CHECK(verify_file_content(TEST_DST, "child_data", 10), + "跨进程复制后内容正确"); + + int status; + waitpid(pid, &status, 0); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* + * 测试 10: 复制空文件(源文件大小为 0) + * 手册描述: If the file offset of fd_in is at or past EOF, returns zero. + */ + { + int fd_in = create_file(TEST_SRC, NULL, 0); + int fd_out = create_file(TEST_DST, NULL, 0); + + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, 100, 0); + CHECK_RET(n, 0, "copy_file_range 空源文件返回 0"); + + close(fd_in); + close(fd_out); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* ==================== 反向测试 ==================== */ + + /* + * 测试 11: EBADF — 无效的文件描述符 + * 手册描述: One or more file descriptors are not valid. + */ + { + int fd = create_file(TEST_DST, NULL, 0); + + CHECK_ERR(copy_file_range(-1, NULL, fd, NULL, 10, 0), + EBADF, "copy_file_range fd_in=-1 返回 EBADF"); + + CHECK_ERR(copy_file_range(fd, NULL, -1, NULL, 10, 0), + EBADF, "copy_file_range fd_out=-1 返回 EBADF"); + + CHECK_ERR(copy_file_range(-1, NULL, -1, NULL, 10, 0), + EBADF, "copy_file_range 两个 fd 均=-1 返回 EBADF"); close(fd); - cleanup_overlap: - unlink(TEST_SAME); + unlink(TEST_DST); } - /* len=0 应成功返回 0 */ + /* + * 测试 12: EBADF — fd_in 不可读 + * 手册描述: fd_in is not open for reading. + */ { - int src = open(TEST_SRC, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(src >= 0, "打开源文件(零长度测试)"); - if (src < 0) goto cleanup_zero; + int fd_in = open(TEST_SRC, O_WRONLY | O_CREAT | O_TRUNC, 0644); + int fd_out = create_file(TEST_DST, NULL, 0); - CHECK_RET(write(src, "data", 4), 4, "写入源文件(零长度测试)"); + CHECK_ERR(copy_file_range(fd_in, NULL, fd_out, NULL, 10, 0), + EBADF, "copy_file_range fd_in 不可读 (O_WRONLY) 返回 EBADF"); - int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(dst >= 0, "打开目标文件(零长度测试)"); - if (dst < 0) { close(src); goto cleanup_zero; } + close(fd_in); + close(fd_out); + unlink(TEST_SRC); + unlink(TEST_DST); + } - off_t off_in = 0; - off_t off_out = 0; - CHECK_RET(my_copy_file_range(src, &off_in, dst, &off_out, 0, 0), 0, "len=0 返回 0"); + /* + * 测试 13: EBADF — fd_out 不可写 + * 手册描述: fd_out is not open for writing. + */ + { + int fd_in = create_file(TEST_SRC, "DATA", 4); + int fd_out = open(TEST_DST, O_RDONLY | O_CREAT, 0644); - close(src); - close(dst); - cleanup_zero: + CHECK_ERR(copy_file_range(fd_in, NULL, fd_out, NULL, 10, 0), + EBADF, "copy_file_range fd_out 不可写 (O_RDONLY) 返回 EBADF"); + + close(fd_in); + close(fd_out); unlink(TEST_SRC); unlink(TEST_DST); } - /* 从文件末尾之后读取,应返回 0(无数据) */ + /* + * 测试 14: EBADF — fd_out 以 O_APPEND 打开 + * 手册描述: The O_APPEND flag is set for fd_out. + */ { - int src = open(TEST_SRC, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(src >= 0, "打开源文件(越界测试)"); - if (src < 0) goto cleanup_eof; + int fd_in = create_file(TEST_SRC, "DATA", 4); + int fd_out = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0644); + + CHECK_ERR(copy_file_range(fd_in, NULL, fd_out, NULL, 10, 0), + EBADF, "copy_file_range fd_out O_APPEND 返回 EBADF"); - CHECK_RET(write(src, "short", 5), 5, "写入源文件(越界测试)"); + close(fd_in); + close(fd_out); + unlink(TEST_SRC); + unlink(TEST_DST); + } - int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(dst >= 0, "打开目标文件(越界测试)"); - if (dst < 0) { close(src); goto cleanup_eof; } + /* + * 测试 15: EINVAL — flags 不为 0 + * 手册描述: The flags argument is not 0. + */ + { + int fd_in = create_file(TEST_SRC, "DATA", 4); + int fd_out = create_file(TEST_DST, NULL, 0); - off_t off_in = 100; /* 超过 EOF */ - off_t off_out = 0; - CHECK_RET(my_copy_file_range(src, &off_in, dst, &off_out, 1024, 0), 0, "超出 EOF 返回 0"); + CHECK_ERR(copy_file_range(fd_in, NULL, fd_out, NULL, 10, 1), + EINVAL, "copy_file_range flags=1 返回 EINVAL"); - close(src); - close(dst); - cleanup_eof: + close(fd_in); + close(fd_out); unlink(TEST_SRC); unlink(TEST_DST); } - /* 管道 fd 应返回 EINVAL,copy_file_range 只接受普通文件 */ + /* + * 测试 16: EINVAL — 同一文件且范围重叠 + * 手册描述: fd_in and fd_out refer to the same file and the source + * and target ranges overlap. + */ { - int pipefd[2]; - int ret = pipe(pipefd); - CHECK(ret == 0, "创建管道"); - if (ret != 0) goto cleanup_pipe; + const char *data = "ABCDEFGHIJ"; + int fd = create_file(TEST_SRC, data, strlen(data)); - int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); - CHECK(dst >= 0, "打开目标文件(管道测试)"); - if (dst < 0) { close(pipefd[0]); close(pipefd[1]); goto cleanup_pipe; } + /* off_in=0, off_out=3, len=5 → [0..4] 和 [3..7] 重叠 */ + off_t off_in = 0; + off_t off_out = 3; + CHECK_ERR(copy_file_range(fd, &off_in, fd, &off_out, 5, 0), + EINVAL, "copy_file_range 同一文件范围重叠返回 EINVAL"); - CHECK_RET(write(pipefd[1], "pipedata", 8), 8, "写入管道数据"); + close(fd); + unlink(TEST_SRC); + } - off_t off_out = 0; - ssize_t n = my_copy_file_range(pipefd[0], NULL, dst, &off_out, 8, 0); - CHECK(n == -1 && errno == EINVAL, "管道作为输入应返回 EINVAL"); + /* + * 测试 17: EINVAL — fd 之一不是常规文件(使用管道) + * 手册描述: Either fd_in or fd_out is not a regular file. + */ + { + int pipefd[2]; + pipe(pipefd); + int fd = create_file(TEST_DST, NULL, 0); + + CHECK_ERR(copy_file_range(pipefd[0], NULL, fd, NULL, 10, 0), + EINVAL, "copy_file_range fd_in=管道 返回 EINVAL"); + + CHECK_ERR(copy_file_range(fd, NULL, pipefd[1], NULL, 10, 0), + EINVAL, "copy_file_range fd_out=管道 返回 EINVAL"); close(pipefd[0]); close(pipefd[1]); - close(dst); - cleanup_pipe: + close(fd); + unlink(TEST_DST); + } + + /* + * 测试 18: EISDIR — fd 指向目录 + * 手册描述: Either fd_in or fd_out refers to a directory. + */ + { + int dir_fd = open("/tmp", O_RDONLY); + int fd_out = create_file(TEST_DST, NULL, 0); + int fd_in = create_file(TEST_SRC, "X", 1); + + CHECK_ERR(copy_file_range(dir_fd, NULL, fd_out, NULL, 10, 0), + EISDIR, "copy_file_range fd_in=目录 返回 EISDIR"); + + CHECK_ERR(copy_file_range(fd_in, NULL, dir_fd, NULL, 10, 0), + EISDIR, "copy_file_range fd_out=目录 返回 EISDIR"); + + close(dir_fd); + close(fd_in); + close(fd_out); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* + * 测试 19: EBADF — 已关闭的 fd + */ + { + int fd_in = create_file(TEST_SRC, "DATA", 4); + int fd_out = create_file(TEST_DST, NULL, 0); + close(fd_in); + + CHECK_ERR(copy_file_range(fd_in, NULL, fd_out, NULL, 10, 0), + EBADF, "copy_file_range 已关闭的 fd_in 返回 EBADF"); + + close(fd_out); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* + * 测试 20: EINVAL — len 为 0 + * 边界条件:len=0 + */ + { + int fd_in = create_file(TEST_SRC, "DATA", 4); + int fd_out = create_file(TEST_DST, NULL, 0); + + errno = 0; + ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, 0, 0); + /* len=0 可返回 0 或 EINVAL,取决于内核版本 */ + CHECK(n == 0 || (n == -1 && errno == EINVAL), + "copy_file_range len=0 返回 0 或 EINVAL (均为合理行为)"); + + close(fd_in); + close(fd_out); + unlink(TEST_SRC); unlink(TEST_DST); } + /* ==================== 清理 ==================== */ + unlink(TEST_SRC); + unlink(TEST_DST); + unlink(TEST_DST2); + TEST_DONE(); -} +} \ No newline at end of file diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/CMakeLists.txt new file mode 100644 index 0000000000..915bd43dba --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.20) + +project(test-splice C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-splice src/main.c) +include_directories(test-splice PRIVATE src) +target_compile_options(test-splice PRIVATE -Wall -Wextra -Werror) + +install(TARGETS test-splice RUNTIME DESTINATION usr/bin/starry-test-suit) + diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/main.c new file mode 100644 index 0000000000..50bce83caa --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/main.c @@ -0,0 +1,351 @@ +/* + * test_splice.c — 验证 splice 系统调用的参数校验及基本功能。 + * + * 覆盖场景: + * 1. regular file -> regular file 返回 EINVAL + * 2. file -> pipe 数据传输,验证 off_in 更新 + * 3. pipe -> file 数据传输,验证 off_out 更新 + * 4. pipe -> pipe 数据传输 + * 5. fd_in 是 pipe 且 off_in 非 NULL 返回 ESPIPE + * 6. fd_out 是 pipe 且 off_out 非 NULL 返回 ESPIPE + * 7. unknown flags 返回 EINVAL + * 8. EOF 后 splice 返回 0,off_in 不变 + * 9. len=0 返回 0,off_in 不变 + * 10. bad input fd 返回 EBADF + * 11. bad output fd 返回 EBADF + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include "test_framework.h" +#include +#include +#include +#include +#include + +#ifndef SPLICE_F_MOVE +#define SPLICE_F_MOVE 1 +#endif + +static ssize_t my_splice(int fd_in, off_t *off_in, + int fd_out, off_t *off_out, + size_t len, unsigned int flags) +{ + return syscall(SYS_splice, fd_in, off_in, fd_out, off_out, len, flags); +} + +#define TEST_SRC "/tmp/starry_test_splice_src" +#define TEST_DST "/tmp/starry_test_splice_dst" + +static int reset_file(const char *path, const char *data) +{ + int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644); + CHECK(fd >= 0, "open reset file"); + if (fd < 0) { + return -1; + } + + size_t len = strlen(data); + CHECK_RET(write(fd, data, len), (ssize_t)len, "write all data"); + + return fd; +} + +static int read_file(const char *path, char *buf, size_t size) +{ + int fd = open(path, O_RDONLY); + CHECK(fd >= 0, "open file for read"); + if (fd < 0) { + return -1; + } + + ssize_t n = read(fd, buf, size); + CHECK(n >= 0, "read file content"); + + close(fd); + return (int)n; +} + +int main(void) +{ + TEST_START("splice"); + + /* regular file -> regular file must fail with EINVAL */ + { + int src = reset_file(TEST_SRC, "abcdef"); + int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); + CHECK(dst >= 0, "open destination file"); + + if (src >= 0 && dst >= 0) { + ssize_t n = my_splice(src, NULL, dst, NULL, 3, 0); + CHECK(n == -1, "regular file to regular file should fail"); + CHECK(errno == EINVAL, "regular file to regular file should return EINVAL"); + } + + if (src >= 0) close(src); + if (dst >= 0) close(dst); + unlink(TEST_SRC); + unlink(TEST_DST); + } + + /* file -> pipe with off_in */ + { + int src = reset_file(TEST_SRC, "abcdef"); + int pipefd[2]; + CHECK(src >= 0, "open source file"); + CHECK(pipe(pipefd) == 0, "create pipe"); + + if (src >= 0 && pipefd[0] >= 0 && pipefd[1] >= 0) { + off_t off_in = 1; + ssize_t n = my_splice(src, &off_in, pipefd[1], NULL, 3, 0); + CHECK(n == 3, "file to pipe should splice 3 bytes"); + CHECK(off_in == 4, "off_in should be updated"); + + off_t cur = lseek(src, 0, SEEK_CUR); + CHECK(cur == 0, "fd_in offset should not change when off_in is not NULL"); + + char buf[8] = {0}; + CHECK_RET(read(pipefd[0], buf, 3), 3, "read 3 bytes from pipe"); + CHECK(memcmp(buf, "bcd", 3) == 0, "pipe content should be bcd"); + } + + if (src >= 0) close(src); + close(pipefd[0]); + close(pipefd[1]); + unlink(TEST_SRC); + } + + /* pipe -> file with off_out */ + { + int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); + CHECK(dst >= 0, "open destination file"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + + CHECK_RET(write(pipefd[1], "abc", 3), 3, "write all data"); + + if (dst >= 0) { + off_t off_out = 0; + ssize_t n = my_splice(pipefd[0], NULL, dst, &off_out, 3, 0); + CHECK(n == 3, "pipe to file should splice 3 bytes"); + CHECK(off_out == 3, "off_out should be updated"); + + char buf[8] = {0}; + int r = read_file(TEST_DST, buf, sizeof(buf)); + CHECK(r >= 3 && memcmp(buf, "abc", 3) == 0, "destination file content should be abc"); + } + + close(pipefd[0]); + close(pipefd[1]); + if (dst >= 0) close(dst); + unlink(TEST_DST); + } + + /* pipe -> pipe */ + { + int in_pipe[2]; + int out_pipe[2]; + + CHECK(pipe(in_pipe) == 0, "create input pipe"); + CHECK(pipe(out_pipe) == 0, "create output pipe"); + + CHECK_RET(write(in_pipe[1], "xyz", 3), 3, "write all data"); + + ssize_t n = my_splice(in_pipe[0], NULL, out_pipe[1], NULL, 3, 0); + CHECK(n == 3, "pipe to pipe should splice 3 bytes"); + + char buf[8] = {0}; + CHECK_RET(read(out_pipe[0], buf, 3), 3, "read 3 bytes from output pipe"); + CHECK(memcmp(buf, "xyz", 3) == 0, "output pipe content should be xyz"); + + close(in_pipe[0]); + close(in_pipe[1]); + close(out_pipe[0]); + close(out_pipe[1]); + } + + /* fd_in is pipe and off_in is not NULL -> ESPIPE */ + { + int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); + CHECK(dst >= 0, "open destination file"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + CHECK_RET(write(pipefd[1], "abc", 3), 3, "write all data"); + + off_t off_in = 0; + ssize_t n = my_splice(pipefd[0], &off_in, dst, NULL, 3, 0); + CHECK(n == -1, "fd_in is pipe and off_in is not NULL should fail"); + CHECK(errno == ESPIPE, "fd_in is pipe and off_in is not NULL should return ESPIPE"); + + close(pipefd[0]); + close(pipefd[1]); + if (dst >= 0) close(dst); + unlink(TEST_DST); + } + + /* fd_out is pipe and off_out is not NULL -> ESPIPE */ + { + int src = reset_file(TEST_SRC, "abcdef"); + CHECK(src >= 0, "open source file"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + + off_t off_out = 0; + ssize_t n = my_splice(src, NULL, pipefd[1], &off_out, 3, 0); + CHECK(n == -1, "fd_out is pipe and off_out is not NULL should fail"); + CHECK(errno == ESPIPE, "fd_out is pipe and off_out is not NULL should return ESPIPE"); + + if (src >= 0) close(src); + close(pipefd[0]); + close(pipefd[1]); + unlink(TEST_SRC); + } + + /* unknown flags -> EINVAL */ + { + int src = reset_file(TEST_SRC, "abcdef"); + CHECK(src >= 0, "open source file"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + + ssize_t n = my_splice(src, NULL, pipefd[1], NULL, 3, 0x80000000u); + CHECK(n == -1, "unknown flags should fail"); + CHECK(errno == EINVAL, "unknown flags should return EINVAL"); + + if (src >= 0) close(src); + close(pipefd[0]); + close(pipefd[1]); + unlink(TEST_SRC); + } + + /* EOF -> 0 and off_in unchanged */ + { + int src = reset_file(TEST_SRC, "abc"); + CHECK(src >= 0, "open source file"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + + off_t off_in = 10; + ssize_t n = my_splice(src, &off_in, pipefd[1], NULL, 3, 0); + CHECK(n == 0, "splice after EOF should return 0"); + CHECK(off_in == 10, "off_in should not change after EOF"); + + if (src >= 0) close(src); + close(pipefd[0]); + close(pipefd[1]); + unlink(TEST_SRC); + } + + /* len=0 -> 0 and off_in unchanged */ + { + int src = reset_file(TEST_SRC, "abc"); + CHECK(src >= 0, "open source file"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + + off_t off_in = 1; + ssize_t n = my_splice(src, &off_in, pipefd[1], NULL, 0, 0); + CHECK(n == 0, "count zero should return 0"); + CHECK(off_in == 1, "off_in should not change when count is zero"); + + if (src >= 0) close(src); + close(pipefd[0]); + close(pipefd[1]); + unlink(TEST_SRC); + } + + { + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe"); + + ssize_t n = my_splice(-1, NULL, pipefd[1], NULL, 3, 0); + CHECK(n == -1, "bad input fd should fail"); + CHECK(errno == EBADF, "bad input fd should return EBADF"); + + close(pipefd[0]); + close(pipefd[1]); + } + + /* bad output fd -> EBADF */ + { + int src = reset_file(TEST_SRC, "abc"); + CHECK(src >= 0, "open source file"); + + ssize_t n = my_splice(src, NULL, -1, NULL, 3, 0); + CHECK(n == -1, "bad output fd should fail"); + CHECK(errno == EBADF, "bad output fd should return EBADF"); + + if (src >= 0) close(src); + unlink(TEST_SRC); + } + + { + int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC, 0644); + CHECK(dst >= 0, "open destination file for pipe direction test"); + + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe for direction test"); + + ssize_t n = my_splice(pipefd[1], NULL, dst, NULL, 3, 0); + CHECK(n == -1, "pipe write end used as input should fail"); + CHECK(errno == EBADF, "pipe write end used as input should return EBADF"); + + int src = reset_file(TEST_SRC, "abc"); + CHECK(src >= 0, "open source file for pipe direction test"); + + n = my_splice(src, NULL, pipefd[0], NULL, 3, 0); + CHECK(n == -1, "pipe read end used as output should fail"); + CHECK(errno == EBADF, "pipe read end used as output should return EBADF"); + + if (src >= 0) close(src); + if (dst >= 0) close(dst); + close(pipefd[0]); + close(pipefd[1]); + unlink(TEST_SRC); + unlink(TEST_DST); + } + /* same pipe used as both input and output -> EINVAL */ + { + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe for same-pipe test"); + + CHECK_RET(write(pipefd[1], "abc", 3), 3, "write data for same-pipe test"); + + ssize_t n = my_splice(pipefd[0], NULL, pipefd[1], NULL, 3, 0); + CHECK(n == -1, "same pipe input and output should fail"); + CHECK(errno == EINVAL, "same pipe input and output should return EINVAL"); + + close(pipefd[0]); + close(pipefd[1]); + } + /* O_APPEND output -> EINVAL */ + { + int pipefd[2]; + CHECK(pipe(pipefd) == 0, "create pipe for O_APPEND test"); + + CHECK_RET(write(pipefd[1], "abc", 3), 3, "write data for O_APPEND test"); + + int dst = open(TEST_DST, O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0644); + CHECK(dst >= 0, "open O_APPEND destination file"); + + ssize_t n = my_splice(pipefd[0], NULL, dst, NULL, 3, 0); + CHECK(n == -1, "O_APPEND output should fail"); + CHECK(errno == EINVAL, "O_APPEND output should return EINVAL"); + + close(pipefd[0]); + close(pipefd[1]); + if (dst >= 0) close(dst); + unlink(TEST_DST); + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/test_framework.h new file mode 100644 index 0000000000..b697580a68 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-splice/c/src/test_framework.h @@ -0,0 +1,85 @@ +#pragma once + +/* 必须在最前面定义,确保 pipe2/gettid 等可用 */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +/* + * StarryOS Syscall Test Framework + * + * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 + * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 + * + * 用法: + * TEST_START("测试名"); + * CHECK(call == expected, "描述"); + * CHECK_ERR(call, EBADF, "描述"); + * TEST_DONE(); + */ + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +/* ---- 核心: 带文件名+行号的检查宏 ---- */ + +/* 检查条件为真 */ +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +/* 检查 syscall 返回特定值 */ +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +/* 检查 syscall 失败且 errno 符合预期 */ +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +/* ---- 测试边界 ---- */ +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 From ed1ef612be57dc2a8ab345ccc38d7e98ecf46dc0 Mon Sep 17 00:00:00 2001 From: ya2yo <162020585+ya2yo@users.noreply.github.com> Date: Mon, 25 May 2026 12:05:02 +0800 Subject: [PATCH 08/71] =?UTF-8?q?Fix/starryos=EF=BC=9A=20=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E4=BA=86StarryOS=E7=9A=84sys=5Fmembarrier=E5=92=8Csys?= =?UTF-8?q?=5Frseq=E4=B8=A4=E4=B8=AA=E7=B3=BB=E7=BB=9F=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E3=80=82=20(#897)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(syscall): add sys_membarrier test for StarryOS * feat(starryos): Implement sys_membarrier and rseq syscall enhancements in StarryOS - Added support for sys_membarrier with various commands including global and private expedited registration. - Introduced rseq syscall with argument validation and lifecycle management for per-thread restartable sequences. - Updated Thread and ProcessData structures to maintain rseq and membarrier states. - Enhanced test suite for sys_membarrier and rseq to ensure compliance with expected behaviors. This commit improves synchronization mechanisms in StarryOS, aligning with Linux semantics. --------- Co-authored-by: Ya2yo --- .../kernel/src/syscall/sync/membarrier.rs | 44 ++++- os/StarryOS/kernel/src/syscall/sync/rseq.rs | 124 +++++++++---- os/StarryOS/kernel/src/syscall/task/execve.rs | 2 +- os/StarryOS/kernel/src/task/mod.rs | 35 ++++ .../test-membarrier/c/CMakeLists.txt | 12 ++ .../qemu-smp1/test-membarrier/c/src/main.c | 175 ++++++++++++++++++ .../test-membarrier/c/src/test_framework.h | 118 ++++++++++++ .../test-membarrier/qemu-aarch64.toml | 14 ++ .../test-membarrier/qemu-loongarch64.toml | 17 ++ .../test-membarrier/qemu-riscv64.toml | 14 ++ .../test-membarrier/qemu-x86_64.toml | 14 ++ .../qemu-smp1/test-rseq/c/CMakeLists.txt | 12 ++ .../normal/qemu-smp1/test-rseq/c/src/main.c | 130 +++++++++++++ .../test-rseq/c/src/test_framework.h | 118 ++++++++++++ .../qemu-smp1/test-rseq/qemu-x86_64.toml | 14 ++ 15 files changed, 807 insertions(+), 36 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-aarch64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-loongarch64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-riscv64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-rseq/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/test-rseq/qemu-x86_64.toml diff --git a/os/StarryOS/kernel/src/syscall/sync/membarrier.rs b/os/StarryOS/kernel/src/syscall/sync/membarrier.rs index d7fb92a47f..b0f426f298 100644 --- a/os/StarryOS/kernel/src/syscall/sync/membarrier.rs +++ b/os/StarryOS/kernel/src/syscall/sync/membarrier.rs @@ -1,6 +1,9 @@ -use core::sync::atomic::{Ordering, compiler_fence}; +use core::sync::atomic::{Ordering, fence}; use ax_errno::{AxError, AxResult}; +use ax_task::current; + +use crate::task::AsThread; /// Memory barrier commands const MEMBARRIER_CMD_QUERY: i32 = 0; @@ -10,6 +13,9 @@ const MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED: i32 = 3; const MEMBARRIER_CMD_PRIVATE_EXPEDITED: i32 = 4; const MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED: i32 = 5; +const MEMBARRIER_STATE_PRIVATE_EXPEDITED: u32 = 1 << 0; +const MEMBARRIER_STATE_GLOBAL_EXPEDITED: u32 = 1 << 1; + /// Supported command flags for query const SUPPORTED_COMMANDS: i32 = (1 << MEMBARRIER_CMD_GLOBAL) | (1 << MEMBARRIER_CMD_GLOBAL_EXPEDITED) @@ -17,17 +23,47 @@ const SUPPORTED_COMMANDS: i32 = (1 << MEMBARRIER_CMD_GLOBAL) | (1 << MEMBARRIER_CMD_PRIVATE_EXPEDITED) | (1 << MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED); +fn smp_mb() { + fence(Ordering::SeqCst); +} + pub fn sys_membarrier(cmd: i32, flags: u32, _cpu_id: i32) -> AxResult { - // 检查 flags 参数,目前应该为 0 if flags != 0 { return Err(AxError::InvalidInput); } match cmd { MEMBARRIER_CMD_QUERY => Ok(SUPPORTED_COMMANDS as isize), - _ => { - compiler_fence(Ordering::SeqCst); + MEMBARRIER_CMD_GLOBAL => { + smp_mb(); + Ok(0) + } + MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED => { + current() + .as_thread() + .proc_data + .register_membarrier_state(MEMBARRIER_STATE_GLOBAL_EXPEDITED); + Ok(0) + } + MEMBARRIER_CMD_GLOBAL_EXPEDITED => { + smp_mb(); + Ok(0) + } + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED => { + current() + .as_thread() + .proc_data + .register_membarrier_state(MEMBARRIER_STATE_PRIVATE_EXPEDITED); + Ok(0) + } + MEMBARRIER_CMD_PRIVATE_EXPEDITED => { + let proc_data = current().as_thread().proc_data.clone(); + if proc_data.membarrier_state() & MEMBARRIER_STATE_PRIVATE_EXPEDITED == 0 { + return Err(AxError::OperationNotPermitted); + } + smp_mb(); Ok(0) } + _ => Err(AxError::InvalidInput), } } diff --git a/os/StarryOS/kernel/src/syscall/sync/rseq.rs b/os/StarryOS/kernel/src/syscall/sync/rseq.rs index 7c957b1b38..5bb4bccb4c 100644 --- a/os/StarryOS/kernel/src/syscall/sync/rseq.rs +++ b/os/StarryOS/kernel/src/syscall/sync/rseq.rs @@ -1,31 +1,65 @@ -use ax_errno::AxError; +use core::mem::size_of; + +use ax_errno::{AxError, LinuxError}; use ax_task::current; -use starry_vm::VmPtr; +use starry_vm::{VmMutPtr, VmPtr}; use crate::task::AsThread; -fn validate_rseq_addr(addr: *mut u8, len: usize) -> Result, AxError> { - if addr.is_null() { - if len != 0 { - return Err(AxError::InvalidInput); - } - return Ok(None); +/// Linux rseq area layout used for ABI validation. +#[repr(C)] +#[derive(Clone, Copy)] +struct RseqArea { + cpu_id_start: u32, + cpu_id: u32, + rseq_cs: u64, + flags: u32, + padding: [u32; 3], +} + +const RSEQ_AREA_SIZE: usize = size_of::(); +const RSEQ_AREA_ALIGN: usize = 32; +const RSEQ_FLAG_UNREGISTER: u32 = 1; +const RSEQ_CPU_ID_UNINITIALIZED: u32 = u32::MAX; + +fn validate_rseq_args(addr: *mut u8, len: usize, flags: u32) -> Result { + if addr.is_null() || len != RSEQ_AREA_SIZE { + return Err(AxError::InvalidInput); + } + if flags & !RSEQ_FLAG_UNREGISTER != 0 { + return Err(AxError::InvalidInput); } - if len == 0 { + let addr = addr.addr(); + if !addr.is_multiple_of(RSEQ_AREA_ALIGN) { return Err(AxError::InvalidInput); } - Ok(Some(addr.addr())) + Ok(addr) +} + +fn ensure_rseq_area_accessible(addr: usize) -> Result<(), AxError> { + let area = addr as *mut RseqArea; + let _ = area.vm_read_uninit().map_err(|_| AxError::BadAddress)?; + area.vm_write(RseqArea { + cpu_id_start: 0, + cpu_id: RSEQ_CPU_ID_UNINITIALIZED, + rseq_cs: 0, + flags: 0, + padding: [0; 3], + }) + .map_err(|_| AxError::BadAddress)?; + Ok(()) } -/// Minimal rseq syscall handling. +/// rseq(2) — register or unregister a per-thread restartable-sequences area. /// -/// Registration is intentionally reported as unsupported until the kernel -/// maintains the full per-thread rseq state expected by libc fast paths. -/// Unregistration succeeds so cleanup code remains harmless. +/// This implements the userspace-visible registration state machine and +/// argument validation. The deeper rseq critical-section abort machinery is +/// still not implemented, so StarryOS leaves `cpu_id` uninitialized to keep +/// libc fast paths from assuming usable rseq CPU state. /// -/// C prototype (simplified): +/// C prototype: /// long rseq(void *addr, uint32_t len, int flags, uint32_t sig); pub fn sys_rseq(addr: *mut u8, len: usize, flags: u32, sig: u32) -> Result { debug!( @@ -33,45 +67,73 @@ pub fn sys_rseq(addr: *mut u8, len: usize, flags: u32, sig: u32) -> Result(), 0).unwrap_err(), + validate_rseq_args(ptr, RSEQ_AREA_SIZE, 0).unwrap_err(), AxError::InvalidInput ); } + + #[test] + fn validate_rseq_args_accepts_aligned_addr() { + let ptr = 0x1000 as *mut u8; + assert_eq!(validate_rseq_args(ptr, RSEQ_AREA_SIZE, 0).unwrap(), 0x1000); + } } diff --git a/os/StarryOS/kernel/src/syscall/task/execve.rs b/os/StarryOS/kernel/src/syscall/task/execve.rs index 73b99b2ffd..02e0a8b066 100644 --- a/os/StarryOS/kernel/src/syscall/task/execve.rs +++ b/os/StarryOS/kernel/src/syscall/task/execve.rs @@ -260,7 +260,7 @@ pub fn sys_execve( // the thread-exit path don't dereference freed user pages. thr.set_clear_child_tid(0); thr.set_robust_list_head(0); - thr.set_rseq_area(0); + thr.clear_rseq_state(); // Remove CLOEXEC fds from the table under the write guard we took // for the post-teardown snapshot — no fd can be added or have its diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index a931f3ef55..59731b0b46 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -139,6 +139,9 @@ pub struct Thread { /// sequences (`rseq(2)`). rseq_area: AtomicUsize, + /// The rseq signature recorded at registration time. + rseq_signature: AtomicU32, + /// The signal to send to this thread when its parent dies (PR_SET_PDEATHSIG). pdeathsig: AtomicU32, @@ -185,6 +188,7 @@ impl Thread { exit_event: Arc::default(), exit_request: AtomicBool::new(false), rseq_area: AtomicUsize::new(0), + rseq_signature: AtomicU32::new(0), pdeathsig: AtomicU32::new(0), no_new_privs: AtomicBool::new(false), cred: SpinNoIrq::new(cred), @@ -366,11 +370,28 @@ impl Thread { self.rseq_area.load(Ordering::SeqCst) } + /// Get the registered rseq signature. + pub fn rseq_signature(&self) -> u32 { + self.rseq_signature.load(Ordering::SeqCst) + } + /// Set the registered rseq area pointer. pub fn set_rseq_area(&self, addr: usize) { self.rseq_area.store(addr, Ordering::SeqCst); } + /// Set the registered rseq area pointer and signature. + pub fn set_rseq_state(&self, addr: usize, sig: u32) { + self.rseq_area.store(addr, Ordering::SeqCst); + self.rseq_signature.store(sig, Ordering::SeqCst); + } + + /// Clear the registered rseq state. + pub fn clear_rseq_state(&self) { + self.rseq_area.store(0, Ordering::SeqCst); + self.rseq_signature.store(0, Ordering::SeqCst); + } + /// Block the next signal check for this thread. pub fn block_next_signal_check(&self) { self.block_next_signal_check.block(); @@ -487,6 +508,9 @@ pub struct ProcessData { /// The process nice value used by getpriority/setpriority compatibility. nice: AtomicI32, + /// Process-local membarrier(2) registration state bitmask. + membarrier_state: AtomicU32, + /// PR_GET_DUMPABLE / PR_SET_DUMPABLE value (default 1 = SUID_DUMP_USER). /// Cleared to 0 (SUID_DUMP_DISABLE) whenever the effective UID/GID /// changes via setuid/setresuid/setreuid (man 2 setuid §NOTES: @@ -554,6 +578,7 @@ impl ProcessData { umask: AtomicU32::new(0o022), nice: AtomicI32::new(0), + membarrier_state: AtomicU32::new(0), dumpable: AtomicI32::new(1), children_cpu_time: SpinNoIrq::new((TimeValue::ZERO, TimeValue::ZERO)), @@ -641,6 +666,16 @@ impl ProcessData { self.nice.store(nice, Ordering::SeqCst); } + /// Get the membarrier(2) registration state bitmask. + pub fn membarrier_state(&self) -> u32 { + self.membarrier_state.load(Ordering::SeqCst) + } + + /// Add bits to the membarrier(2) registration state. + pub fn register_membarrier_state(&self, state: u32) { + self.membarrier_state.fetch_or(state, Ordering::SeqCst); + } + /// Get the dumpable flag (PR_GET_DUMPABLE). pub fn dumpable(&self) -> i32 { self.dumpable.load(Ordering::SeqCst) diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/CMakeLists.txt new file mode 100644 index 0000000000..298156df9c --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) +project(test-membarrier C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-membarrier src/main.c) +target_include_directories(test-membarrier PRIVATE src) +target_compile_options(test-membarrier PRIVATE -Wall -Wextra -Werror) + +install(TARGETS test-membarrier RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/main.c new file mode 100644 index 0000000000..111af983a0 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/main.c @@ -0,0 +1,175 @@ +#define _GNU_SOURCE + +#include "test_framework.h" +#include +#include +#include +#include +#include + +int __pass = 0; +int __fail = 0; +int __skip = 0; +int __observe = 0; + +/* membarrier command constants matching the StarryOS implementation */ +#define MEMBARRIER_CMD_QUERY 0 +#define MEMBARRIER_CMD_GLOBAL 1 +#define MEMBARRIER_CMD_GLOBAL_EXPEDITED 2 +#define MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED 3 +#define MEMBARRIER_CMD_PRIVATE_EXPEDITED 4 +#define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED 5 + +/* These are NOT supported by current StarryOS impl */ +#define MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE 32 +#define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE 64 +#define MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ 128 +#define MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ 256 + +/* + * Expected SUPPORTED_COMMANDS bitmask from the StarryOS implementation: + * (1 << GLOBAL) | (1 << GLOBAL_EXPEDITED) | + * (1 << REGISTER_GLOBAL_EXPEDITED) | (1 << PRIVATE_EXPEDITED) | + * (1 << REGISTER_PRIVATE_EXPEDITED) = 0b111110 = 62 + */ +#define EXPECTED_SUPPORTED_MASK 62 + +static int membarrier(int cmd, unsigned flags, int cpu_id) +{ + return syscall(SYS_membarrier, cmd, flags, cpu_id); +} + +static bool query_advertises(int cmd) +{ + long ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0); + if (ret < 0) + return false; + if (cmd < 0 || cmd >= (int)(8 * sizeof(long))) + return false; + return (ret & (1L << cmd)) != 0; +} + +static void part_01_query(void) +{ + long ret = membarrier(MEMBARRIER_CMD_QUERY, 0, 0); + CHECK(ret >= 0, "QUERY returns non-negative supported command mask"); + if (ret < 0) + return; + + CHECK((ret & (1 << MEMBARRIER_CMD_QUERY)) == 0, + "QUERY does not include QUERY bit itself"); + + CHECK((ret & (1 << MEMBARRIER_CMD_GLOBAL)) != 0, + "QUERY advertises GLOBAL (bit 1)"); + + CHECK((ret & (1 << MEMBARRIER_CMD_GLOBAL_EXPEDITED)) != 0, + "QUERY advertises GLOBAL_EXPEDITED (bit 2)"); + + CHECK((ret & (1 << MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED)) != 0, + "QUERY advertises REGISTER_GLOBAL_EXPEDITED (bit 3)"); + + CHECK((ret & (1 << MEMBARRIER_CMD_PRIVATE_EXPEDITED)) != 0, + "QUERY advertises PRIVATE_EXPEDITED (bit 4)"); + + CHECK((ret & (1 << MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED)) != 0, + "QUERY advertises REGISTER_PRIVATE_EXPEDITED (bit 5)"); + + CHECK(ret == EXPECTED_SUPPORTED_MASK, + "QUERY returns exact expected bitmask (62)"); + + CHECK((ret & (1UL << MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE)) == 0, + "QUERY does NOT advertise PRIVATE_EXPEDITED_SYNC_CORE (not implemented)"); + /* RSEQ=128 cannot be tested with bit shift on 64-bit long; + * the return value is a 64-bit mask and RSEQ is beyond its range anyway. */ +} + +static void part_02_flags_are_rejected(void) +{ + int commands[] = { + MEMBARRIER_CMD_QUERY, + MEMBARRIER_CMD_GLOBAL, + MEMBARRIER_CMD_GLOBAL_EXPEDITED, + MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, + MEMBARRIER_CMD_PRIVATE_EXPEDITED, + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, + 999, + }; + + for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { + char buf[128]; + snprintf(buf, sizeof(buf), "cmd=%d rejects flags=1", commands[i]); + CHECK_ERR(membarrier(commands[i], 1, 0), EINVAL, buf); + } + + CHECK_ERR(membarrier(MEMBARRIER_CMD_QUERY, 1, 0), EINVAL, + "QUERY with flags=1 returns EINVAL"); + + CHECK_ERR(membarrier(MEMBARRIER_CMD_QUERY, 0xdeadbeef, 0), EINVAL, + "QUERY with flags=0xdeadbeef returns EINVAL"); +} + +static void part_03_global_commands(void) +{ + CHECK_RET(membarrier(MEMBARRIER_CMD_GLOBAL, 0, 0), 0, + "GLOBAL with flags=0 returns 0"); + CHECK_RET(membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0, 0), 0, + "REGISTER_GLOBAL_EXPEDITED succeeds"); + CHECK_RET(membarrier(MEMBARRIER_CMD_REGISTER_GLOBAL_EXPEDITED, 0, 0), 0, + "REGISTER_GLOBAL_EXPEDITED is idempotent"); + CHECK_RET(membarrier(MEMBARRIER_CMD_GLOBAL_EXPEDITED, 0, 0), 0, + "GLOBAL_EXPEDITED succeeds after registration"); +} + +static void part_04_private_expedited_registration(void) +{ + CHECK_ERR(membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0), EPERM, + "PRIVATE_EXPEDITED before registration returns EPERM"); + CHECK_RET(membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0, 0), 0, + "REGISTER_PRIVATE_EXPEDITED succeeds"); + CHECK_RET(membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0, 0), 0, + "REGISTER_PRIVATE_EXPEDITED is idempotent"); + CHECK_RET(membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0), 0, + "PRIVATE_EXPEDITED succeeds after registration"); +} + +static void part_05_unsupported_commands(void) +{ + int unsupported[] = { + MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE, + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE, + MEMBARRIER_CMD_PRIVATE_EXPEDITED_RSEQ, + MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_RSEQ, + 999, /* bogus command */ + }; + + for (size_t i = 0; i < sizeof(unsupported) / sizeof(unsupported[0]); i++) { + char buf[128]; + snprintf(buf, sizeof(buf), "cmd=%d is not advertised by QUERY", unsupported[i]); + CHECK(!query_advertises(unsupported[i]), buf); + + snprintf(buf, sizeof(buf), "cmd=%d with flags=0 returns EINVAL", unsupported[i]); + CHECK_ERR(membarrier(unsupported[i], 0, 0), EINVAL, buf); + } +} + +static void part_06_cpu_id_is_ignored_without_cmd_flag(void) +{ + CHECK_RET(membarrier(MEMBARRIER_CMD_GLOBAL, 0, 1234), 0, + "cpu_id is ignored when flags is zero for GLOBAL"); + CHECK_RET(membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 1234), 0, + "cpu_id is ignored when flags is zero for PRIVATE_EXPEDITED"); +} + +int main(void) +{ + TEST_START("membarrier syscall"); + + part_01_query(); + part_02_flags_are_rejected(); + part_03_global_commands(); + part_04_private_expedited_registration(); + part_05_unsupported_commands(); + part_06_cpu_id_is_ignored_without_cmd_flag(); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/test_framework.h new file mode 100644 index 0000000000..e7cb3ef199 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/c/src/test_framework.h @@ -0,0 +1,118 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +extern int __pass; +extern int __fail; +extern int __skip; +extern int __observe; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_TRUE(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR_SAVED(ret, err, exp_errno, msg) do { \ + if ((ret) == -1 && (err) == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, (int)(err)); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), (long)(ret), \ + (int)(err), strerror(err)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR_OR(ret, err, e1, e2, msg) do { \ + if ((ret) == -1 && ((err) == (e1) || (err) == (e2))) { \ + printf(" PASS | %s:%d | %s (errno=%d in {%d,%d})\n", \ + __FILE__, __LINE__, msg, (int)(err), (int)(e1), (int)(e2)); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d or %d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(e1), (int)(e2), \ + (long)(ret), (int)(err), strerror(err)); \ + __fail++; \ + } \ +} while(0) + +#define TEST_SKIP(msg) do { \ + printf(" SKIP | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __skip++; \ +} while(0) + +#define TEST_OBSERVE(msg) do { \ + printf(" OBSERVE | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __observe++; \ +} while(0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail", __pass, __fail); \ + if (__skip > 0 || __observe > 0) { \ + printf(", %d skip, %d observe", __skip, __observe); \ + } \ + printf("\n"); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-aarch64.toml new file mode 100644 index 0000000000..e592e3ed3a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-aarch64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "cortex-a53", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-membarrier" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 30 diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-loongarch64.toml new file mode 100644 index 0000000000..7af6c79ffa --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-loongarch64.toml @@ -0,0 +1,17 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "128M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-membarrier" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 30 diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-riscv64.toml new file mode 100644 index 0000000000..a1f904e1bf --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-riscv64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-membarrier" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 30 diff --git a/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-x86_64.toml new file mode 100644 index 0000000000..724126d30c --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-membarrier/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-membarrier" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 30 diff --git a/test-suit/starryos/normal/qemu-smp1/test-rseq/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-rseq/c/CMakeLists.txt new file mode 100644 index 0000000000..e444fb373e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-rseq/c/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) +project(test-rseq C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-rseq src/main.c) +target_include_directories(test-rseq PRIVATE src) +target_compile_options(test-rseq PRIVATE -Wall -Wextra -Werror) + +install(TARGETS test-rseq RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/main.c new file mode 100644 index 0000000000..4a261c2c3b --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/main.c @@ -0,0 +1,130 @@ +#define _GNU_SOURCE + +#include "test_framework.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SYS_rseq +#ifdef __NR_rseq +#define SYS_rseq __NR_rseq +#elif defined(__x86_64__) +#define SYS_rseq 334 +#else +#error "SYS_rseq is not defined for this architecture" +#endif +#endif + +#define RSEQ_FLAG_UNREGISTER 1U +#define RSEQ_SIG 0x53053053U + +#define RSEQ_CPU_ID_UNINITIALIZED ((uint32_t)-1) + +int __pass = 0; +int __fail = 0; +int __skip = 0; +int __observe = 0; + +struct rseq_area { + uint32_t cpu_id_start; + uint32_t cpu_id; + uint64_t rseq_cs; + uint32_t flags; + uint32_t padding[3]; +}; + +static _Alignas(32) struct rseq_area primary_area; +static _Alignas(32) struct rseq_area second_area; +static _Alignas(32) unsigned char misaligned_storage[sizeof(struct rseq_area) + 1]; + +static long rseq_call(void *addr, uint32_t len, int flags, uint32_t sig) +{ + return syscall(SYS_rseq, addr, len, flags, sig); +} + +static void reset_area(struct rseq_area *area) +{ + memset(area, 0, sizeof(*area)); + area->cpu_id = RSEQ_CPU_ID_UNINITIALIZED; +} + +static void part_01_invalid_arguments_before_registration(void) +{ + reset_area(&primary_area); + + CHECK_ERR(rseq_call(NULL, sizeof(primary_area), 0, RSEQ_SIG), EINVAL, + "register rejects NULL addr with non-zero length"); + CHECK_ERR(rseq_call(&primary_area, 0, 0, RSEQ_SIG), EINVAL, + "register rejects non-NULL addr with zero length"); + CHECK_ERR(rseq_call(&primary_area, sizeof(primary_area), 2, RSEQ_SIG), EINVAL, + "register rejects unknown flags"); + CHECK_ERR(rseq_call(&primary_area, sizeof(primary_area) - 1, 0, RSEQ_SIG), EINVAL, + "register rejects incorrect rseq area length"); + + void *misaligned = misaligned_storage + 1; + CHECK(((uintptr_t)misaligned % 32) != 0, + "test fixture produces a deliberately misaligned rseq pointer"); + CHECK_ERR(rseq_call(misaligned, sizeof(primary_area), 0, RSEQ_SIG), EINVAL, + "register rejects misaligned rseq area pointer"); +} + +static void part_02_bad_user_memory(void) +{ + void *page = mmap(NULL, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + CHECK(page != MAP_FAILED, "mmap(PROT_NONE) test page succeeds"); + if (page == MAP_FAILED) + return; + + CHECK_ERR(rseq_call(page, sizeof(primary_area), 0, RSEQ_SIG), EFAULT, + "register rejects inaccessible rseq area with EFAULT"); + CHECK_RET(munmap(page, 4096), 0, "munmap(PROT_NONE) test page succeeds"); +} + +static void part_03_registration_lifecycle(void) +{ + reset_area(&primary_area); + reset_area(&second_area); + + CHECK_RET(rseq_call(&primary_area, sizeof(primary_area), 0, RSEQ_SIG), 0, + "register valid rseq area succeeds"); + CHECK_ERR(rseq_call(&primary_area, sizeof(primary_area), 0, RSEQ_SIG), EBUSY, + "duplicate registration in same thread fails with EBUSY"); + CHECK_ERR(rseq_call(&second_area, sizeof(second_area), RSEQ_FLAG_UNREGISTER, RSEQ_SIG), EINVAL, + "unregister rejects a different rseq area pointer"); + CHECK_ERR(rseq_call(&primary_area, sizeof(primary_area), RSEQ_FLAG_UNREGISTER, RSEQ_SIG + 1), + EINVAL, "unregister rejects mismatched signature"); + CHECK_RET(rseq_call(&primary_area, sizeof(primary_area), RSEQ_FLAG_UNREGISTER, RSEQ_SIG), 0, + "unregister matching rseq area succeeds"); + CHECK_ERR(rseq_call(&primary_area, sizeof(primary_area), RSEQ_FLAG_UNREGISTER, RSEQ_SIG), + EINVAL, "second unregister fails when no area is registered"); +} + +static void part_04_register_after_unregister(void) +{ + reset_area(&primary_area); + + CHECK_RET(rseq_call(&primary_area, sizeof(primary_area), 0, RSEQ_SIG), 0, + "register after prior unregister succeeds"); + CHECK_RET(rseq_call(&primary_area, sizeof(primary_area), RSEQ_FLAG_UNREGISTER, RSEQ_SIG), 0, + "cleanup unregister succeeds"); +} + +int main(void) +{ + TEST_START("rseq syscall"); + + CHECK(sizeof(struct rseq_area) == 32, "test rseq area size matches Linux ABI"); + CHECK(_Alignof(struct rseq_area) <= 32, "test rseq area type alignment is compatible"); + + part_01_invalid_arguments_before_registration(); + part_02_bad_user_memory(); + part_03_registration_lifecycle(); + part_04_register_after_unregister(); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/test_framework.h new file mode 100644 index 0000000000..e7cb3ef199 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-rseq/c/src/test_framework.h @@ -0,0 +1,118 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +extern int __pass; +extern int __fail; +extern int __skip; +extern int __observe; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_TRUE(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR_SAVED(ret, err, exp_errno, msg) do { \ + if ((ret) == -1 && (err) == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, (int)(err)); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), (long)(ret), \ + (int)(err), strerror(err)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR_OR(ret, err, e1, e2, msg) do { \ + if ((ret) == -1 && ((err) == (e1) || (err) == (e2))) { \ + printf(" PASS | %s:%d | %s (errno=%d in {%d,%d})\n", \ + __FILE__, __LINE__, msg, (int)(err), (int)(e1), (int)(e2)); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d or %d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(e1), (int)(e2), \ + (long)(ret), (int)(err), strerror(err)); \ + __fail++; \ + } \ +} while(0) + +#define TEST_SKIP(msg) do { \ + printf(" SKIP | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __skip++; \ +} while(0) + +#define TEST_OBSERVE(msg) do { \ + printf(" OBSERVE | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __observe++; \ +} while(0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail", __pass, __fail); \ + if (__skip > 0 || __observe > 0) { \ + printf(", %d skip, %d observe", __skip, __observe); \ + } \ + printf("\n"); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/test-rseq/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-rseq/qemu-x86_64.toml new file mode 100644 index 0000000000..d537582a14 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-rseq/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-rseq" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 30 From 20a3152e40f8c7d17aa42b86d77e8ff3a62836f9 Mon Sep 17 00:00:00 2001 From: WellDown64 <150930230+WellDown64@users.noreply.github.com> Date: Mon, 25 May 2026 12:05:59 +0800 Subject: [PATCH 09/71] test(starryos): add user test for capset syscall (#898) --- .../syscall/test-capset/c/CMakeLists.txt | 9 + .../syscall/test-capset/c/prebuild.sh | 4 + .../syscall/test-capset/c/src/main.c | 463 ++++++++++++++++++ .../test-capset/c/src/test_framework.h | 65 +++ 4 files changed, 541 insertions(+) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/CMakeLists.txt create mode 100755 test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/prebuild.sh create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/test_framework.h diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/CMakeLists.txt new file mode 100644 index 0000000000..c5a9d335ad --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-capset C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-capset src/main.c) +target_include_directories(test-capset PRIVATE src) +target_compile_options(test-capset PRIVATE -Wall -Wextra) +install(TARGETS test-capset RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/prebuild.sh b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/prebuild.sh new file mode 100755 index 0000000000..57c2153da0 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/prebuild.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +apk add linux-headers diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/main.c new file mode 100644 index 0000000000..a379a64599 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/main.c @@ -0,0 +1,463 @@ +/* + * test_capset.c — comprehensive capset(2) syscall tests + * + * capset sets the capability sets of a thread. glibc provides no wrapper; + * must be invoked via syscall(). The raw kernel ABI uses: + * syscall(SYS_capset, cap_user_header_t hdrp, const cap_user_data_t datap) + * + * Positive coverage: + * A1. Set own caps (pid=0) to same values → success (no-op) or EPERM + * A2. Clear effective set while keeping permitted → success/EPERM + * A3. V3 header with valid V3 data + * + * Negative coverage: + * B1. Invalid version → EINVAL + * B2. hdrp = NULL → EFAULT + * B3. datap = NULL → EFAULT + * B4. Invalid pid → ESRCH + * B5. Modify another thread's caps → EPERM + * B6. Add cap to effective not in permitted → EPERM + * B7. Add cap to permitted set → EPERM + * B8. Add cap to inheritable not in bounding set → EPERM + * B9. Invalid version + invalid pid → EINVAL/ESRCH + */ + +#define _GNU_SOURCE +#include "test_framework.h" +#include +#include +#include +#include + +typedef struct __user_cap_header_struct cap_header_t; +typedef struct __user_cap_data_struct cap_data_t; + +/* helper: read current caps into data[] */ +static int read_caps(cap_data_t *data, int nwords) +{ + cap_header_t hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = 0; + memset(data, 0, (size_t)nwords * sizeof(cap_data_t)); + return (syscall(SYS_capget, &hdr, data) == 0); +} + +int main(void) +{ + TEST_START("capset"); + + /* ============================================================== */ + /* A1. Positive: set own caps to current values (no-op) */ + /* ============================================================== */ + { + printf("\n--- A1. capset(pid=0, same data) — positive ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + + hdr.version = _LINUX_CAPABILITY_VERSION_3; + if (!read_caps(data, _LINUX_CAPABILITY_U32S_3)) { + printf(" FAIL | cannot read current caps\n"); + __fail++; + } else { + errno = 0; + int rc = syscall(SYS_capset, &hdr, data); + CHECK(rc == 0 || (rc == -1 && errno == EPERM), + "A1: capset(pid=0, same as current) -> 0/EPERM"); + } + } + + /* ============================================================== */ + /* A2. Positive/negative: clear effective, keep permitted */ + /* ============================================================== */ + { + printf("\n--- A2. clear effective set (positive) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + cap_data_t orig[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + + hdr.version = _LINUX_CAPABILITY_VERSION_3; + if (!read_caps(orig, _LINUX_CAPABILITY_U32S_3)) { + printf(" SKIP | cannot read current caps for effective test\n"); + } else { + /* Copy orig, clear effective bits */ + memcpy(data, orig, sizeof(data)); + for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) + data[i].effective = 0; + + errno = 0; + int rc = syscall(SYS_capset, &hdr, data); + CHECK(rc == 0 || (rc == -1 && errno == EPERM), + "A2: capset(effective=0) -> 0/EPERM"); + /* Restore original if possible */ + if (rc == 0) { + hdr.version = _LINUX_CAPABILITY_VERSION_3; + syscall(SYS_capset, &hdr, orig); + } + } + } + + /* ============================================================== */ + /* A3. Positive: V3 header + V3 data, self pid=0 */ + /* ============================================================== */ + { + printf("\n--- A3. capset(pid=0, V3) simple set — positive ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + + memset(&hdr, 0, sizeof(hdr)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + memset(data, 0, sizeof(data)); + + errno = 0; + int rc = syscall(SYS_capset, &hdr, data); + CHECK(rc == 0 || (rc == -1 && errno == EPERM), + "A3: capset(V3, all zeros) -> 0/EPERM"); + } + + /* ============================================================== */ + /* B1. Negative: invalid version → EINVAL */ + /* ============================================================== */ + { + printf("\n--- B1. invalid version (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + + /* B1a: random bad version */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = 0xDEADBEEF; + hdr.pid = 0; + CHECK_ERR(syscall(SYS_capset, &hdr, data), EINVAL, + "capset(pid=0, version=0xDEADBEEF) -> EINVAL"); + + /* B1b: version=0 */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = 0; + hdr.pid = 0; + CHECK_ERR(syscall(SYS_capset, &hdr, data), EINVAL, + "capset(pid=0, version=0) -> EINVAL"); + + /* B1c: version = V3 + 1 */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = _LINUX_CAPABILITY_VERSION_3 + 1; + hdr.pid = 0; + CHECK_ERR(syscall(SYS_capset, &hdr, data), EINVAL, + "capset(pid=0, version=V3+1) -> EINVAL"); + } + + /* ============================================================== */ + /* B2. Negative: hdrp = NULL → EFAULT */ + /* ============================================================== */ + { + printf("\n--- B2. hdrp=NULL (negative) ---\n"); + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(data, 0, sizeof(data)); + CHECK_ERR(syscall(SYS_capset, NULL, data), EFAULT, + "capset(hdrp=NULL) -> EFAULT"); + } + + /* ============================================================== */ + /* B3. Negative: datap = NULL → EFAULT */ + /* Linux returns EFAULT for capset with NULL datap because */ + /* the kernel must read capability data from userspace. */ + /* StarryOS ignores the data argument entirely, so non-root */ + /* callers may see EPERM instead (euid check fires first). */ + /* ============================================================== */ + { + printf("\n--- B3. datap=NULL (negative) ---\n"); + cap_header_t hdr; + memset(&hdr, 0, sizeof(hdr)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = 0; + errno = 0; + long r = syscall(SYS_capset, &hdr, NULL); + CHECK(r == 0 || (r == -1 && (errno == EFAULT || errno == EPERM)), + "B3: capset(datap=NULL) -> EFAULT/EPERM/0"); + } + + /* ============================================================== */ + /* B4. Negative: invalid pid → ESRCH */ + /* ============================================================== */ + { + printf("\n--- B4. invalid pid (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + + /* B4a: very large nonexistent pid */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = 0x7FFFFFFF; + CHECK_ERR(syscall(SYS_capset, &hdr, data), ESRCH, + "capset(pid=0x7FFFFFFF) -> ESRCH"); + + /* B4b: pid=-1 (on VFS kernels, this is always EPERM or ESRCH) */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = -1; + errno = 0; + long r = syscall(SYS_capset, &hdr, data); + if (r == -1 && (errno == ESRCH || errno == EPERM || errno == EINVAL)) { + printf(" PASS | capset(pid=-1) -> %s (errno=%d)\n", + errno == ESRCH ? "ESRCH" : errno == EPERM ? "EPERM" : "EINVAL", + errno); + __pass++; + } else { + printf(" FAIL | capset(pid=-1) expected ESRCH/EPERM got ret=%ld errno=%d (%s)\n", + r, errno, strerror(errno)); + __fail++; + } + + /* B4c: pid=-2 */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = -2; + errno = 0; + long r3 = syscall(SYS_capset, &hdr, data); + if (r3 == -1 && (errno == ESRCH || errno == EPERM || errno == EINVAL)) { + printf(" PASS | capset(pid=-2) -> %s (errno=%d)\n", + errno == ESRCH ? "ESRCH" : errno == EPERM ? "EPERM" : "EINVAL", + errno); + __pass++; + } else { + printf(" FAIL | capset(pid=-2) expected ESRCH/EPERM got ret=%ld errno=%d (%s)\n", + r3, errno, strerror(errno)); + __fail++; + } + + /* B4d: pid=999999 */ + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = 999999; + errno = 0; + long r4 = syscall(SYS_capset, &hdr, data); + if (r4 == -1 && (errno == ESRCH || errno == EPERM || errno == EINVAL)) { + printf(" PASS | capset(pid=999999) -> %s (errno=%d)\n", + errno == ESRCH ? "ESRCH" : errno == EPERM ? "EPERM" : "EINVAL", + errno); + __pass++; + } else { + printf(" FAIL | capset(pid=999999) expected ESRCH/EPERM got ret=%ld errno=%d (%s)\n", + r4, errno, strerror(errno)); + __fail++; + } + } + + /* ============================================================== */ + /* B5. Negative: modify another thread → EPERM (VFS kernel) */ + /* ============================================================== */ + { + printf("\n--- B5. modify another process (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + + hdr.version = _LINUX_CAPABILITY_VERSION_3; + hdr.pid = 1; /* init or another process */ + errno = 0; + long r = syscall(SYS_capset, &hdr, data); + /* On VFS kernels, capset to another pid returns EPERM. + * If pid=1 doesn't exist, ESRCH is also valid. */ + if (r == -1 && (errno == EPERM || errno == ESRCH)) { + printf(" PASS | capset(pid=1, all-zeros) -> %s (errno=%d)\n", + errno == EPERM ? "EPERM" : "ESRCH", errno); + __pass++; + } else { + printf(" FAIL | capset(pid=1, all-zeros) expected EPERM/ESRCH got ret=%ld errno=%d (%s)\n", + r, errno, strerror(errno)); + __fail++; + } + } + + /* ============================================================== */ + /* B6. Negative: add cap to effective not in permitted → EPERM */ + /* ============================================================== */ + { + printf("\n--- B6. effective not subset of permitted (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + + hdr.version = _LINUX_CAPABILITY_VERSION_3; + if (!read_caps(data, _LINUX_CAPABILITY_U32S_3)) { + printf(" SKIP | cannot read current caps for EPERM test\n"); + } else { + /* Set effective to a bit that is NOT in permitted. + * Pick CAP_DAC_OVERRIDE (bit 1) for test: or 0x2 into + * effective while ensuring it's clear in permitted. */ + data[0].effective |= 0x2; + data[0].permitted &= ~0x2; + + errno = 0; + long r = syscall(SYS_capset, &hdr, data); + if (r == -1 && errno == EPERM) { + printf(" PASS | capset(effective∉permitted) -> EPERM (privilege check)\n"); + __pass++; + } else if (r == 0) { + /* Kernel accepted it (privileged); just restore */ + printf(" PASS | capset(effective∉permitted) accepted (privileged)\n"); + __pass++; + /* Restore from original by re-reading */ + if (read_caps(data, _LINUX_CAPABILITY_U32S_3)) { + /* Undo: clear the bit we added */ + data[0].effective &= ~0x2; + data[0].permitted |= 0x2; + hdr.version = _LINUX_CAPABILITY_VERSION_3; + syscall(SYS_capset, &hdr, data); + } + } else { + printf(" FAIL | capset(effective∉permitted) unexpected ret=%ld errno=%d (%s)\n", + r, errno, strerror(errno)); + __fail++; + } + } + } + + /* ============================================================== */ + /* B7. Negative: add cap to permitted set → EPERM */ + /* ============================================================== */ + { + printf("\n--- B7. add cap to permitted set (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + + hdr.version = _LINUX_CAPABILITY_VERSION_3; + if (!read_caps(data, _LINUX_CAPABILITY_U32S_3)) { + printf(" SKIP | cannot read current caps for permitted test\n"); + } else { + /* Try to add a capability (CAP_SYS_BOOT=22, bit group 0, bit 22). + * Clear it first, then set it — the kernel should reject adding + * new bits to permitted unless we have CAP_SETPCAP. */ + __u32 bit = 1U << 22; /* CAP_SYS_BOOT */ + int was_set = (data[0].permitted & bit) != 0; + data[0].permitted &= ~bit; + /* Set the same caps to commit the removal (if possible) */ + errno = 0; + int rc0 = syscall(SYS_capset, &hdr, data); + if (rc0 == 0) { + /* Now try to add it back */ + data[0].permitted |= bit; + errno = 0; + long r = syscall(SYS_capset, &hdr, data); + if (r == -1 && errno == EPERM) { + printf(" PASS | capset(add to permitted) -> EPERM\n"); + __pass++; + } else if (r == 0) { + printf(" PASS | capset(add to permitted) accepted (privileged)\n"); + __pass++; + } else { + printf(" FAIL | capset(add to permitted) unexpected ret=%ld errno=%d (%s)\n", + r, errno, strerror(errno)); + __fail++; + } + /* Restore — re-read caps */ + cap_data_t restore[_LINUX_CAPABILITY_U32S_3]; + memset(restore, 0, sizeof(restore)); + cap_header_t rhdr; + memset(&rhdr, 0, sizeof(rhdr)); + rhdr.version = _LINUX_CAPABILITY_VERSION_3; + rhdr.pid = 0; + if (syscall(SYS_capget, &rhdr, restore) == 0) { + if (was_set) + restore[0].permitted |= bit; + else + restore[0].permitted &= ~bit; + rhdr.version = _LINUX_CAPABILITY_VERSION_3; + syscall(SYS_capset, &rhdr, restore); + } + } else if (rc0 == -1 && errno == EPERM) { + /* Can't even remove caps (no privilege) */ + printf(" SKIP | capset(remove from permitted) requires privilege\n"); + } else { + printf(" SKIP | capset(remove from permitted) unexpected ret=%d errno=%d\n", + rc0, errno); + } + } + } + + /* ============================================================== */ + /* B8. Negative: add to inheritable not in bounding → EPERM */ + /* ============================================================== */ + { + printf("\n--- B8. add to inheritable (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + + hdr.version = _LINUX_CAPABILITY_VERSION_3; + if (!read_caps(data, _LINUX_CAPABILITY_U32S_3)) { + printf(" SKIP | cannot read current caps for inheritable test\n"); + } else { + /* Add a bit to inheritable that is not currently in permitted + * or bounding. Without CAP_SETPCAP this should fail EPERM. */ + __u32 bit = 1U << 23; /* CAP_SYS_NICE */ + int was_in_permitted = (data[0].permitted & bit) != 0; + data[0].inheritable |= bit; + data[0].permitted &= ~bit; + + errno = 0; + long r = syscall(SYS_capset, &hdr, data); + if (r == -1 && errno == EPERM) { + printf(" PASS | capset(inheritable∉bounding) -> EPERM\n"); + __pass++; + } else if (r == 0) { + printf(" PASS | capset(inheritable∉bounding) accepted (privileged)\n"); + __pass++; + /* Restore */ + cap_data_t restore[_LINUX_CAPABILITY_U32S_3]; + memset(restore, 0, sizeof(restore)); + cap_header_t rhdr; + memset(&rhdr, 0, sizeof(rhdr)); + rhdr.version = _LINUX_CAPABILITY_VERSION_3; + rhdr.pid = 0; + if (syscall(SYS_capget, &rhdr, restore) == 0) { + restore[0].inheritable &= ~bit; + if (was_in_permitted) + restore[0].permitted |= bit; + rhdr.version = _LINUX_CAPABILITY_VERSION_3; + syscall(SYS_capset, &rhdr, restore); + } + } else { + printf(" FAIL | capset(inheritable∉bounding) unexpected ret=%ld errno=%d (%s)\n", + r, errno, strerror(errno)); + __fail++; + } + } + } + + /* ============================================================== */ + /* B9. Negative: bad version + bad pid → EINVAL */ + /* The version check runs before the pid check, so EINVAL */ + /* is returned regardless of the pid value. */ + /* ============================================================== */ + { + printf("\n--- B9. bad version + bad pid (negative) ---\n"); + cap_header_t hdr; + cap_data_t data[_LINUX_CAPABILITY_U32S_3]; + memset(&hdr, 0, sizeof(hdr)); + memset(data, 0, sizeof(data)); + + hdr.version = 0xDEADBEEF; + hdr.pid = 0x7FFFFFFF; + CHECK_ERR(syscall(SYS_capset, &hdr, data), EINVAL, + "B9: capset(bad version, bad pid) -> EINVAL"); + } + + if (__fail == 0) { + printf("CAPSET_ALL_PASSED\n"); + } else { + printf("CAPSET_HAS_FAILURES\n"); + } + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/test_framework.h new file mode 100644 index 0000000000..1bafd3e70e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-capset/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 From 883dc963e67e6520005413ce13bca48638e62699 Mon Sep 17 00:00:00 2001 From: Antareske <2395326872@qq.com> Date: Mon, 25 May 2026 12:08:01 +0800 Subject: [PATCH 10/71] fix(epoll,sigmask-related): align sigsetsize checks with linux abi (#900) --- os/StarryOS/kernel/src/syscall/io_mpx/poll.rs | 4 ++- os/StarryOS/kernel/src/syscall/signal.rs | 8 ++--- .../test-epoll-pwait-sigsetsize/c/src/main.c | 23 ++++++++----- .../syscall/test-epoll-pwait2/c/src/main.c | 33 +++++++++++-------- .../syscall/test-pselect-ppoll/c/src/main.c | 6 ++-- .../syscall/test-signalfd4/c/src/main.c | 6 ++-- 6 files changed, 48 insertions(+), 32 deletions(-) diff --git a/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs b/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs index 41541c5a3c..57b0725556 100644 --- a/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs +++ b/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs @@ -157,7 +157,9 @@ pub fn sys_ppoll( sigmask: UserConstPtr, sigsetsize: usize, ) -> AxResult { - check_sigset_size(sigsetsize)?; + if !sigmask.is_null() { + check_sigset_size(sigsetsize)?; + } let nfds = nfds.try_into().map_err(|_| AxError::InvalidInput)?; let mut poll_fds = read_poll_fds(fds, nfds)?; let timeout = nullable!(timeout.get_as_ref())? diff --git a/os/StarryOS/kernel/src/syscall/signal.rs b/os/StarryOS/kernel/src/syscall/signal.rs index 7663079680..32c3d0cf84 100644 --- a/os/StarryOS/kernel/src/syscall/signal.rs +++ b/os/StarryOS/kernel/src/syscall/signal.rs @@ -23,11 +23,9 @@ use crate::{ }; pub(crate) fn check_sigset_size(size: usize) -> AxResult<()> { - // Accept the kernel sigset size and any larger libc/ABI sigset size, - // since the kernel only uses the low `size_of::()` bytes - // (glibc uses 8, musl uses 16). Keep accepting 0 for callers that use - // it to mean "no mask". - if size != 0 && size < size_of::() { + // Align with Linux raw syscall semantics (for ABI param 'sigmask'): when sigsetsize is checked, + // it must exactly match the kernel SignalSet size (8 bytes). + if size != size_of::() { return Err(AxError::InvalidInput); } Ok(()) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait-sigsetsize/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait-sigsetsize/c/src/main.c index a2a76c701f..6d12b82388 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait-sigsetsize/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait-sigsetsize/c/src/main.c @@ -1,10 +1,9 @@ /* * test_epoll_pwait_sigsetsize.c * - * epoll_pwait 的 sigsetsize 参数在 musl libc 下为 16 字节 (musl _NSIG/8 = 128/8), - * glibc 为 8 字节,内核 sigset 为 8 字节。内核应接受任意 >= 内核 sigset 大小的值, - * 只读取低 8 字节。本测试通过直接发起 syscall 绕过 libc wrapper,验证不同 sigsetsize - * 的接受行为,并验证 sigmask 为 NULL 时 sigsetsize 不再参与校验(epoll_wait 语义)。 + * 验证 raw epoll_pwait 的 sigsetsize 语义: + * - sigmask == NULL 时,sigsetsize 不参与校验; + * - sigmask != NULL 时,sigsetsize 必须严格等于 8。 */ #include "test_framework.h" @@ -22,7 +21,7 @@ static long raw_epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int main(void) { - TEST_START("epoll_pwait: sigsetsize 兼容 musl/glibc"); + TEST_START("epoll_pwait: sigsetsize Linux semantics"); int epfd = epoll_create1(0); CHECK(epfd >= 0, "epoll_create1 ok"); @@ -58,15 +57,23 @@ int main(void) CHECK(r == 0, "sigmask + size=8 (glibc) accepted"); } - /* Test 4: real sigmask with musl-style sigsetsize = 16 */ + /* Test 4: real sigmask with sigsetsize = 16 should fail */ { sigset_t mask; sigemptyset(&mask); long r = raw_epoll_pwait(epfd, out, 4, 0, &mask, 16); - CHECK(r == 0, "sigmask + size=16 (musl) accepted"); + CHECK(r == -1 && errno == EINVAL, "sigmask + size=16 rejected with EINVAL"); } - /* Test 5: sigsetsize smaller than kernel sigset should fail */ + /* Test 5: non-NULL sigmask with size=0 should fail */ + { + sigset_t mask; + sigemptyset(&mask); + long r = raw_epoll_pwait(epfd, out, 4, 0, &mask, 0); + CHECK(r == -1 && errno == EINVAL, "sigmask + size=0 rejected with EINVAL"); + } + + /* Test 6: sigsetsize smaller than kernel sigset should fail */ { sigset_t mask; sigemptyset(&mask); diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait2/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait2/c/src/main.c index e5031268e8..322a2b978a 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait2/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-epoll-pwait2/c/src/main.c @@ -14,6 +14,8 @@ #include #include +#define KERNEL_SIGSET_SIZE 8 + static void on_sigusr1(int signo) { (void)signo; @@ -32,7 +34,7 @@ static void test_invalid_args(void) struct epoll_event out[4]; errno = 0; - CHECK(raw_epoll_pwait2(-1, out, 4, NULL, NULL, sizeof(sigset_t)) == -1 && errno == EBADF, + CHECK(raw_epoll_pwait2(-1, out, 4, NULL, NULL, KERNEL_SIGSET_SIZE) == -1 && errno == EBADF, "invalid epfd returns EBADF"); int epfd = epoll_create1(0); @@ -42,11 +44,11 @@ static void test_invalid_args(void) } errno = 0; - CHECK(raw_epoll_pwait2(epfd, out, 0, NULL, NULL, sizeof(sigset_t)) == -1 && errno == EINVAL, + CHECK(raw_epoll_pwait2(epfd, out, 0, NULL, NULL, KERNEL_SIGSET_SIZE) == -1 && errno == EINVAL, "maxevents == 0 returns EINVAL"); errno = 0; - CHECK(raw_epoll_pwait2(epfd, out, -1, NULL, NULL, sizeof(sigset_t)) == -1 && errno == EINVAL, + CHECK(raw_epoll_pwait2(epfd, out, -1, NULL, NULL, KERNEL_SIGSET_SIZE) == -1 && errno == EINVAL, "maxevents < 0 returns EINVAL"); close(epfd); @@ -78,11 +80,11 @@ static void test_null_timeout_and_zero_timeout(void) struct epoll_event out[4]; struct timespec ts0 = {.tv_sec = 0, .tv_nsec = 0}; - CHECK_RET(raw_epoll_pwait2(epfd, out, 4, &ts0, NULL, sizeof(sigset_t)), 0, + CHECK_RET(raw_epoll_pwait2(epfd, out, 4, &ts0, NULL, KERNEL_SIGSET_SIZE), 0, "zero timeout returns 0 when no fd is ready"); CHECK_RET(write(pipefd[1], "X", 1), 1, "write one byte to pipe"); - long n = raw_epoll_pwait2(epfd, out, 4, NULL, NULL, sizeof(sigset_t)); + long n = raw_epoll_pwait2(epfd, out, 4, NULL, NULL, KERNEL_SIGSET_SIZE); CHECK(n == 1 && (out[0].events & EPOLLIN) != 0 && out[0].data.fd == pipefd[0], "NULL timeout waits and returns ready event"); @@ -108,13 +110,18 @@ static void test_timeout_and_sigsetsize(void) CHECK_RET(raw_epoll_pwait2(epfd, out, 4, &ts, NULL, 0), 0, "NULL sigmask ignores sigsetsize and times out"); - CHECK_RET(raw_epoll_pwait2(epfd, out, 4, &ts, &mask, sizeof(sigset_t)), 0, - "sigmask + sizeof(sigset_t) accepted"); + CHECK_RET(raw_epoll_pwait2(epfd, out, 4, &ts, &mask, KERNEL_SIGSET_SIZE), 0, + "sigmask + size=8 accepted"); errno = 0; CHECK(raw_epoll_pwait2(epfd, out, 4, &ts, &mask, 4) == -1 && errno == EINVAL, "sigmask with too small sigsetsize returns EINVAL"); + errno = 0; + size_t bad_size = sizeof(sigset_t) == KERNEL_SIGSET_SIZE ? 16 : sizeof(sigset_t); + CHECK(raw_epoll_pwait2(epfd, out, 4, &ts, &mask, bad_size) == -1 && errno == EINVAL, + "sigmask with non-8 sigsetsize returns EINVAL"); + close(epfd); } @@ -143,7 +150,7 @@ static void test_efault_events_ptr(void) CHECK_RET(write(pipefd[1], "Z", 1), 1, "write one byte to make event ready"); errno = 0; - long n = raw_epoll_pwait2(epfd, (struct epoll_event *)(uintptr_t)1, 1, NULL, NULL, sizeof(sigset_t)); + long n = raw_epoll_pwait2(epfd, (struct epoll_event *)(uintptr_t)1, 1, NULL, NULL, KERNEL_SIGSET_SIZE); CHECK(n == -1 && errno == EFAULT, "invalid events pointer returns EFAULT"); close(pipefd[0]); @@ -166,7 +173,7 @@ static void test_timeout_monotonic_duration(void) struct timespec end; CHECK(clock_gettime(CLOCK_MONOTONIC, &start) == 0, "clock_gettime start ok"); - long n = raw_epoll_pwait2(epfd, out, 2, &ts, NULL, sizeof(sigset_t)); + long n = raw_epoll_pwait2(epfd, out, 2, &ts, NULL, KERNEL_SIGSET_SIZE); CHECK(n == 0, "no event with 100ms timeout returns 0"); CHECK(clock_gettime(CLOCK_MONOTONIC, &end) == 0, "clock_gettime end ok"); @@ -214,11 +221,11 @@ static void test_maxevents_boundary(void) CHECK_RET(write(p2[1], "B", 1), 1, "write pipe2"); struct epoll_event one[1]; - long n1 = raw_epoll_pwait2(epfd, one, 1, NULL, NULL, sizeof(sigset_t)); + long n1 = raw_epoll_pwait2(epfd, one, 1, NULL, NULL, KERNEL_SIGSET_SIZE); CHECK(n1 == 1, "maxevents=1 returns exactly one ready event"); struct epoll_event many[1024]; - long n2 = raw_epoll_pwait2(epfd, many, 1024, NULL, NULL, sizeof(sigset_t)); + long n2 = raw_epoll_pwait2(epfd, many, 1024, NULL, NULL, KERNEL_SIGSET_SIZE); CHECK(n2 >= 1, "large maxevents can fetch remaining ready events"); close(p1[0]); @@ -259,7 +266,7 @@ static void test_sigmask_effect_and_eintr(void) struct epoll_event out[1]; errno = 0; - long r_masked = raw_epoll_pwait2(epfd, out, 1, &ts, &block_usr1, sizeof(sigset_t)); + long r_masked = raw_epoll_pwait2(epfd, out, 1, &ts, &block_usr1, KERNEL_SIGSET_SIZE); CHECK(r_masked == 0, "masked SIGUSR1 does not interrupt epoll_pwait2 timeout"); waitpid(pid, NULL, 0); @@ -272,7 +279,7 @@ static void test_sigmask_effect_and_eintr(void) } errno = 0; - r_masked = raw_epoll_pwait2(epfd, out, 1, NULL, NULL, sizeof(sigset_t)); + r_masked = raw_epoll_pwait2(epfd, out, 1, NULL, NULL, KERNEL_SIGSET_SIZE); CHECK(r_masked == -1 && errno == EINTR, "unmasked signal interrupts with EINTR"); waitpid(pid, NULL, 0); diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-pselect-ppoll/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-pselect-ppoll/c/src/main.c index 6ef948cd6f..f320dbfe7f 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-pselect-ppoll/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-pselect-ppoll/c/src/main.c @@ -8,6 +8,8 @@ #include #include +#define KERNEL_SIGSET_SIZE 8 + // Helper to invoke raw pselect6 if available static int raw_pselect6(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timespec *timeout, const sigset_t *sigmask) { #ifdef SYS_pselect6 @@ -16,7 +18,7 @@ static int raw_pselect6(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exc struct { const sigset_t *ss; size_t ss_len; - } data = { sigmask, sizeof(sigset_t) }; + } data = { sigmask, KERNEL_SIGSET_SIZE }; return syscall(SYS_pselect6, nfds, readfds, writefds, exceptfds, timeout, &data); #else return pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask); @@ -26,7 +28,7 @@ static int raw_pselect6(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exc // Helper to invoke raw ppoll if available static int raw_ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask) { #ifdef SYS_ppoll - return syscall(SYS_ppoll, fds, nfds, tmo_p, sigmask, sizeof(sigset_t)); + return syscall(SYS_ppoll, fds, nfds, tmo_p, sigmask, KERNEL_SIGSET_SIZE); #else return ppoll(fds, nfds, tmo_p, sigmask); #endif diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-signalfd4/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-signalfd4/c/src/main.c index bbc078e58a..c37ffdd4f7 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-signalfd4/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-signalfd4/c/src/main.c @@ -3,7 +3,7 @@ * * 覆盖场景: * 1. 基本创建:各种 flag 组合,非法 flags → EINVAL - * 2. sigsetsize 校验:size < 8 → EINVAL + * 2. sigsetsize 校验:非空 mask 时 size 必须为 8 * 3. 修改已有 signalfd 的 mask(fd != -1) * 4. 修改已有 fd 时传 SFD_CLOEXEC → EINVAL * 5. 非阻塞空读 → EAGAIN @@ -130,9 +130,9 @@ static void test_sigsetsize(void) { CHECK(fd >= 0, "sigsetsize=8 succeeds"); if (fd >= 0) close(fd); - /* sigsetsize=0 is explicitly allowed ("no mask" callers) */ + /* non-NULL mask with sigsetsize=0 should fail */ fd = (int)syscall(__NR_signalfd4, -1, &mask, 0, 0); - CHECK(fd >= 0, "sigsetsize=0 succeeds"); + CHECK(fd == -1 && errno == EINVAL, "sigsetsize=0 -> EINVAL"); if (fd >= 0) close(fd); } From 7d90186eeb932bae36cb3f8a75dfeef1b564c959 Mon Sep 17 00:00:00 2001 From: WellDown64 <150930230+WellDown64@users.noreply.github.com> Date: Mon, 25 May 2026 12:10:03 +0800 Subject: [PATCH 11/71] test(starryos): add user test for syscall getrusage (#902) --- .../syscall/test-getrusage/c/CMakeLists.txt | 9 + .../syscall/test-getrusage/c/src/main.c | 378 ++++++++++++++++++ .../test-getrusage/c/src/test_framework.h | 65 +++ 3 files changed, 452 insertions(+) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/test_framework.h diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/CMakeLists.txt new file mode 100644 index 0000000000..66a1e0e328 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-getrusage C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-getrusage src/main.c) +target_include_directories(test-getrusage PRIVATE src) +target_compile_options(test-getrusage PRIVATE -Wall -Wextra) +install(TARGETS test-getrusage RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/main.c new file mode 100644 index 0000000000..166626134d --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/main.c @@ -0,0 +1,378 @@ +/* + * test_getrusage.c — comprehensive getrusage(2) syscall tests + * + * getrusage returns resource usage statistics for the calling process + * (RUSAGE_SELF=0), its terminated children (RUSAGE_CHILDREN=-1), or + * the calling thread (RUSAGE_THREAD=1). + * + * Positive coverage: + * A1. RUSAGE_SELF — basic query, validate key fields + * A2. RUSAGE_THREAD — validate fields + * A3. RUSAGE_CHILDREN — zero before children, non-zero after child reaped + * A4. Field invariants — maintained fields non-negative, unmaintained=0 + * A5. Monotonicity — utime+stime non-decreasing across successive calls + * A6. SELF vs THREAD — in single-threaded, should be close + * + * Negative coverage: + * B1. Invalid who values (-2, 2, 999, 99999) → EINVAL + * B2. EFAULT — invalid usage pointer + * B3. EFAULT — usage=NULL (must not crash) + */ + +#define _GNU_SOURCE +#include "test_framework.h" +#include +#include +#include +#include + +/* macros for timeval validation */ +#define TV_NONNEG(tv) ((tv).tv_sec >= 0 && (tv).tv_usec >= 0) +#define TV_VALID(tv) ((tv).tv_sec >= 0 && (tv).tv_usec >= 0 && (tv).tv_usec < 1000000) + +int main(void) +{ + TEST_START("getrusage"); + + /* ============================================================== */ + /* A1. Positive: RUSAGE_SELF — basic query, validate key fields */ + /* ============================================================== */ + { + printf("\n--- A1. RUSAGE_SELF (positive) ---\n"); + struct rusage u; + memset(&u, 0, sizeof(u)); + CHECK_RET(getrusage(RUSAGE_SELF, &u), 0, + "getrusage(RUSAGE_SELF) returns 0"); + CHECK(TV_VALID(u.ru_utime), "A1: ru_utime valid"); + CHECK(TV_VALID(u.ru_stime), "A1: ru_stime valid"); + printf(" info | utime=%ld.%06ld stime=%ld.%06ld maxrss=%ld " + "minflt=%ld majflt=%ld nvcsw=%ld nivcsw=%ld\n", + (long)u.ru_utime.tv_sec, (long)u.ru_utime.tv_usec, + (long)u.ru_stime.tv_sec, (long)u.ru_stime.tv_usec, + u.ru_maxrss, u.ru_minflt, u.ru_majflt, + u.ru_nvcsw, u.ru_nivcsw); + /* RUSAGE_SELF on an active process should have non-zero total time */ + CHECK(u.ru_utime.tv_sec + u.ru_utime.tv_usec + + u.ru_stime.tv_sec + u.ru_stime.tv_usec > 0, + "RUSAGE_SELF has non-zero CPU time"); + } + + /* ============================================================== */ + /* A2. Positive: RUSAGE_THREAD — validate fields */ + /* ============================================================== */ + { + printf("\n--- A2. RUSAGE_THREAD (positive) ---\n"); + struct rusage u; + memset(&u, 0, sizeof(u)); + CHECK_RET(getrusage(RUSAGE_THREAD, &u), 0, + "getrusage(RUSAGE_THREAD) returns 0"); + CHECK(TV_VALID(u.ru_utime), "A2: ru_utime valid"); + CHECK(TV_VALID(u.ru_stime), "A2: ru_stime valid"); + printf(" info | utime=%ld.%06ld stime=%ld.%06ld\n", + (long)u.ru_utime.tv_sec, (long)u.ru_utime.tv_usec, + (long)u.ru_stime.tv_sec, (long)u.ru_stime.tv_usec); + } + + /* ============================================================== */ + /* A3. Positive: RUSAGE_CHILDREN */ + /* Before any children → should be zero; after child → > 0 */ + /* ============================================================== */ + { + printf("\n--- A3. RUSAGE_CHILDREN (positive) ---\n"); + + /* A3a: before any children, RUSAGE_CHILDREN should be all zero */ + { + struct rusage u; + memset(&u, 0, sizeof(u)); + CHECK_RET(getrusage(RUSAGE_CHILDREN, &u), 0, + "getrusage(RUSAGE_CHILDREN) before children returns 0"); + /* With no children reaped, time should be zero */ + CHECK(u.ru_utime.tv_sec == 0 && u.ru_utime.tv_usec == 0 + && u.ru_stime.tv_sec == 0 && u.ru_stime.tv_usec == 0, + "RUSAGE_CHILDREN before children: utime+stime zero"); + } + + /* A3b: spawn a child process, wait for it, then check RUSAGE_CHILDREN */ + { + struct rusage u_pre; + memset(&u_pre, 0, sizeof(u_pre)); + getrusage(RUSAGE_CHILDREN, &u_pre); /* snapshot */ + + pid_t pid = fork(); + CHECK(pid >= 0, "fork for RUSAGE_CHILDREN test succeeds"); + if (pid == 0) { + /* Child: do some work to consume CPU */ + volatile int x = 0; + for (int i = 0; i < 5000000; i++) + x++; + _exit(0); + } + int status; + pid_t w; + do { + w = waitpid(pid, &status, 0); + } while (w == -1 && errno == EINTR); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "RUSAGE_CHILDREN child exited normally"); + + struct rusage u_post; + memset(&u_post, 0, sizeof(u_post)); + CHECK_RET(getrusage(RUSAGE_CHILDREN, &u_post), 0, + "getrusage(RUSAGE_CHILDREN) after reaping returns 0"); + + /* After reaping a child that did CPU work, children times + * should have increased. But on some systems RUSAGE_CHILDREN + * may remain zero if times aren't tracked. We just check + * the values are non-negative. */ + CHECK(TV_VALID(u_post.ru_utime), "A3b: children post utime valid"); + CHECK(TV_VALID(u_post.ru_stime), "A3b: children post stime valid"); + printf(" info | children utime=%ld.%06ld stime=%ld.%06ld " + "nvcsw=%ld nivcsw=%ld\n", + (long)u_post.ru_utime.tv_sec, (long)u_post.ru_utime.tv_usec, + (long)u_post.ru_stime.tv_sec, (long)u_post.ru_stime.tv_usec, + u_post.ru_nvcsw, u_post.ru_nivcsw); + } + + /* A3c: spawn multiple children, verify cumulative accounting */ + { + struct rusage u_before; + getrusage(RUSAGE_CHILDREN, &u_before); + + pid_t pid = fork(); + CHECK(pid >= 0, "fork for cumulative children test succeeds"); + if (pid == 0) { + volatile int x = 0; + for (int i = 0; i < 2000000; i++) + x++; + _exit(0); + } + int status; + pid_t w1; + do { + w1 = waitpid(pid, &status, 0); + } while (w1 == -1 && errno == EINTR); + + pid_t pid2 = fork(); + CHECK(pid2 >= 0, "fork for 2nd cumulative child succeeds"); + if (pid2 == 0) { + volatile int x = 0; + for (int i = 0; i < 2000000; i++) + x++; + _exit(0); + } + do { + w1 = waitpid(pid2, &status, 0); + } while (w1 == -1 && errno == EINTR); + CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "2nd child exited normally"); + + struct rusage u_after; + CHECK_RET(getrusage(RUSAGE_CHILDREN, &u_after), 0, + "getrusage(RUSAGE_CHILDREN) after multiple children returns 0"); + CHECK(TV_VALID(u_after.ru_utime), "A3c: multiple children utime valid"); + CHECK(TV_VALID(u_after.ru_stime), "A3c: multiple children stime valid"); + } + } + + /* ============================================================== */ + /* A4. Positive: verify every field of struct rusage */ + /* ============================================================== */ + { + printf("\n--- A4. check every struct rusage field (positive) ---\n"); + struct rusage u; + memset(&u, 0xFF, sizeof(u)); /* fill with garbage first */ + CHECK_RET(getrusage(RUSAGE_SELF, &u), 0, + "A4: getrusage(RUSAGE_SELF) returns 0"); + + /* timeval fields */ + CHECK(TV_VALID(u.ru_utime), "A4: ru_utime is valid"); + CHECK(TV_VALID(u.ru_stime), "A4: ru_stime is valid"); + printf(" info | utime=%ld.%06ld stime=%ld.%06ld\n", + (long)u.ru_utime.tv_sec, (long)u.ru_utime.tv_usec, + (long)u.ru_stime.tv_sec, (long)u.ru_stime.tv_usec); + + /* maintained counters — at least zero */ + CHECK(u.ru_minflt >= 0, "A4: ru_minflt (page reclaims) >= 0"); + CHECK(u.ru_majflt >= 0, "A4: ru_majflt (page faults) >= 0"); + CHECK(u.ru_inblock >= 0, "A4: ru_inblock (block input ops) >= 0"); + CHECK(u.ru_oublock >= 0, "A4: ru_oublock (block output ops) >= 0"); + CHECK(u.ru_nvcsw >= 0, "A4: ru_nvcsw (voluntary ctx switches) >= 0"); + CHECK(u.ru_nivcsw >= 0, "A4: ru_nivcsw (involuntary ctx switches) >= 0"); + CHECK(u.ru_maxrss >= 0, "A4: ru_maxrss (max resident set) >= 0"); + printf(" info | minflt=%ld majflt=%ld inblock=%ld oublock=%ld " + "nvcsw=%ld nivcsw=%ld maxrss=%ld\n", + u.ru_minflt, u.ru_majflt, u.ru_inblock, u.ru_oublock, + u.ru_nvcsw, u.ru_nivcsw, u.ru_maxrss); + + /* unmaintained fields — kernel sets to zero */ + CHECK(u.ru_ixrss == 0, "A4: ru_ixrss (integral shared mem) == 0"); + CHECK(u.ru_idrss == 0, "A4: ru_idrss (integral unshared data) == 0"); + CHECK(u.ru_isrss == 0, "A4: ru_isrss (integral unshared stack) == 0"); + CHECK(u.ru_nswap == 0, "A4: ru_nswap (swaps) == 0"); + CHECK(u.ru_msgsnd == 0, "A4: ru_msgsnd (IPC msgs sent) == 0"); + CHECK(u.ru_msgrcv == 0, "A4: ru_msgrcv (IPC msgs received) == 0"); + CHECK(u.ru_nsignals == 0, "A4: ru_nsignals (signals received) == 0"); + printf(" info | ixrss=%ld idrss=%ld isrss=%ld nswap=%ld " + "msgsnd=%ld msgrcv=%ld nsignals=%ld\n", + u.ru_ixrss, u.ru_idrss, u.ru_isrss, u.ru_nswap, + u.ru_msgsnd, u.ru_msgrcv, u.ru_nsignals); + } + + /* ============================================================== */ + /* A5. Positive: monotonicity — CPU time non-decreasing */ + /* ============================================================== */ + { + printf("\n--- A5. monotonicity (positive) ---\n"); + struct rusage u1, u2; + memset(&u1, 0, sizeof(u1)); + memset(&u2, 0, sizeof(u2)); + + CHECK_RET(getrusage(RUSAGE_SELF, &u1), 0, + "getrusage(RUSAGE_SELF) 1st call returns 0"); + + /* Do some work to consume CPU time */ + volatile long acc = 0; + for (volatile long i = 0; i < 10000000; i++) + acc += i; + + CHECK_RET(getrusage(RUSAGE_SELF, &u2), 0, + "getrusage(RUSAGE_SELF) 2nd call returns 0"); + + /* Total CPU time should be non-decreasing */ + long t1 = (long)u1.ru_utime.tv_sec * 1000000 + u1.ru_utime.tv_usec + + (long)u1.ru_stime.tv_sec * 1000000 + u1.ru_stime.tv_usec; + long t2 = (long)u2.ru_utime.tv_sec * 1000000 + u2.ru_utime.tv_usec + + (long)u2.ru_stime.tv_sec * 1000000 + u2.ru_stime.tv_usec; + CHECK(t2 >= t1, "A5: CPU time non-decreasing"); + printf(" info | CPU time before=%ldus after=%ldus diff=%ldus\n", + t1, t2, t2 - t1); + + CHECK(u2.ru_nvcsw >= u1.ru_nvcsw, "A5: nvcsw non-decreasing"); + printf(" info | nvcsw before=%ld after=%ld\n", + u1.ru_nvcsw, u2.ru_nvcsw); + } + + /* ============================================================== */ + /* A6. Positive: SELF vs THREAD comparison (single-threaded) */ + /* ============================================================== */ + { + printf("\n--- A6. RUSAGE_SELF vs RUSAGE_THREAD (positive) ---\n"); + struct rusage us, ut; + memset(&us, 0, sizeof(us)); + memset(&ut, 0, sizeof(ut)); + + CHECK_RET(getrusage(RUSAGE_SELF, &us), 0, + "getrusage(RUSAGE_SELF) returns 0"); + CHECK_RET(getrusage(RUSAGE_THREAD, &ut), 0, + "getrusage(RUSAGE_THREAD) returns 0"); + + /* In a single-threaded process, SELF time and THREAD time + * should be nearly identical (small differences from call + * ordering are expected). */ + long ts = (long)us.ru_utime.tv_sec * 1000000 + us.ru_utime.tv_usec + + (long)us.ru_stime.tv_sec * 1000000 + us.ru_stime.tv_usec; + long tt = (long)ut.ru_utime.tv_sec * 1000000 + ut.ru_utime.tv_usec + + (long)ut.ru_stime.tv_sec * 1000000 + ut.ru_stime.tv_usec; + long diff = ts > tt ? ts - tt : tt - ts; + CHECK(diff <= 5000, "A6: RUSAGE_SELF and RUSAGE_THREAD times close"); + printf(" info | self=%ldus thread=%ldus diff=%ldus\n", ts, tt, ts - tt); + } + + /* ============================================================== */ + /* B1. Negative: invalid who values → EINVAL */ + /* Note: RUSAGE_CHILDREN = -1 is VALID, so we test -2, 2, etc. */ + /* ============================================================== */ + { + printf("\n--- B1. invalid who (negative) ---\n"); + struct rusage u; + memset(&u, 0, sizeof(u)); + + /* B1a: who = -2 (beyond valid range) */ + CHECK_ERR(getrusage(-2, &u), EINVAL, + "getrusage(who=-2) -> EINVAL"); + /* B1b: who = 2 (beyond RUSAGE_THREAD=1) */ + CHECK_ERR(getrusage(2, &u), EINVAL, + "getrusage(who=2) -> EINVAL"); + /* B1c: who = 999 */ + CHECK_ERR(getrusage(999, &u), EINVAL, + "getrusage(who=999) -> EINVAL"); + /* B1d: who = -999 */ + CHECK_ERR(getrusage(-999, &u), EINVAL, + "getrusage(who=-999) -> EINVAL"); + /* B1e: who = 99999 */ + CHECK_ERR(getrusage(99999, &u), EINVAL, + "getrusage(who=99999) -> EINVAL"); + } + + /* ============================================================== */ + /* B2. Negative: EFAULT — invalid usage pointer */ + /* Passing a low unmapped address should trigger EFAULT. */ + /* ============================================================== */ + { + printf("\n--- B2. invalid usage pointer (negative) ---\n"); + + /* B2a: usage = NULL */ + { + errno = 0; + long r = (long)getrusage(RUSAGE_SELF, NULL); + CHECK(r == -1 && (errno == EFAULT || errno == EINVAL), + "B2a: getrusage(usage=NULL) -> EFAULT/EINVAL"); + } + + /* B2b: usage = (void*)1 (unmapped, guaranteed to fault) */ + { + errno = 0; + long r = (long)getrusage(RUSAGE_SELF, (void *)1); + CHECK(r == -1 && errno == EFAULT, + "B2b: getrusage(usage=(void*)1) -> EFAULT"); + } + + /* B2c: usage = (void*)-1 (very high, unmapped) */ + { + errno = 0; + long r = (long)getrusage(RUSAGE_SELF, (void *)(intptr_t)-1); + CHECK(r == -1 && errno == EFAULT, + "B2c: getrusage(usage=(void*)-1) -> EFAULT"); + } + + /* B2d: EFAULT with invalid who — EINVAL may take precedence */ + { + errno = 0; + long r = (long)getrusage(999, (void *)1); + CHECK(r == -1 && (errno == EFAULT || errno == EINVAL), + "B2d: getrusage(who=999, usage=(void*)1) -> EFAULT/EINVAL"); + } + } + + /* ============================================================== */ + /* B3. Edge: RUSAGE_CHILDREN vs RUSAGE_SELF content difference */ + /* ============================================================== */ + { + printf("\n--- B3. SELF vs CHILDREN independence ---\n"); + struct rusage us, uc; + memset(&us, 0, sizeof(us)); + memset(&uc, 0, sizeof(uc)); + + CHECK_RET(getrusage(RUSAGE_SELF, &us), 0, + "RUSAGE_SELF returns 0"); + CHECK_RET(getrusage(RUSAGE_CHILDREN, &uc), 0, + "RUSAGE_CHILDREN returns 0"); + + /* SELF should have non-zero time, CHILDREN may or may not. + * They track different accounting pools. */ + CHECK(TV_VALID(us.ru_utime) && TV_VALID(us.ru_stime), + "B3: RUSAGE_SELF times valid"); + CHECK(TV_VALID(uc.ru_utime) && TV_VALID(uc.ru_stime), + "B3: RUSAGE_CHILDREN times valid"); + printf(" info | self utime=%ld.%06ld, children utime=%ld.%06ld\n", + (long)us.ru_utime.tv_sec, (long)us.ru_utime.tv_usec, + (long)uc.ru_utime.tv_sec, (long)uc.ru_utime.tv_usec); + } + + if (__fail == 0) { + printf("GETRUSAGE_ALL_PASSED\n"); + } else { + printf("GETRUSAGE_HAS_FAILURES\n"); + } + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/test_framework.h new file mode 100644 index 0000000000..1bafd3e70e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-getrusage/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 From 56a0ff8f6d4592ca72f1432b8c49049c55530452 Mon Sep 17 00:00:00 2001 From: Antareske <2395326872@qq.com> Date: Mon, 25 May 2026 12:11:05 +0800 Subject: [PATCH 12/71] fix(starry-kernel): align file sync syscalls with Linux semantics (#903) * fix(starry-kernel): align file sync syscalls with Linux semantics * test(starryos): resolve syscall grouped runner conflict --- os/StarryOS/kernel/src/syscall/fs/ctl.rs | 9 +- os/StarryOS/kernel/src/syscall/fs/io.rs | 14 +-- .../syscall/test-fdatasync/c/CMakeLists.txt | 9 ++ .../syscall/test-fdatasync/c/src/main.c | 78 +++++++++++++++++ .../test-fdatasync/c/src/test_framework.h | 65 ++++++++++++++ .../syscall/test-fsync/c/CMakeLists.txt | 9 ++ .../qemu-smp1/syscall/test-fsync/c/src/main.c | 78 +++++++++++++++++ .../syscall/test-fsync/c/src/test_framework.h | 65 ++++++++++++++ .../test-sync-file-range/c/CMakeLists.txt | 9 ++ .../syscall/test-sync-file-range/c/src/main.c | 87 +++++++++++++++++++ .../c/src/test_framework.h | 65 ++++++++++++++ .../syscall/test-sync/c/CMakeLists.txt | 9 ++ .../qemu-smp1/syscall/test-sync/c/src/main.c | 51 +++++++++++ .../syscall/test-sync/c/src/test_framework.h | 65 ++++++++++++++ .../syscall/test-syncfs/c/CMakeLists.txt | 9 ++ .../syscall/test-syncfs/c/src/main.c | 63 ++++++++++++++ .../test-syncfs/c/src/test_framework.h | 65 ++++++++++++++ 17 files changed, 742 insertions(+), 8 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/test_framework.h diff --git a/os/StarryOS/kernel/src/syscall/fs/ctl.rs b/os/StarryOS/kernel/src/syscall/fs/ctl.rs index ae13e09eb4..b120ed7da4 100644 --- a/os/StarryOS/kernel/src/syscall/fs/ctl.rs +++ b/os/StarryOS/kernel/src/syscall/fs/ctl.rs @@ -912,8 +912,11 @@ pub fn sys_sync() -> AxResult { pub fn sys_syncfs(fd: c_int) -> AxResult { debug!("sys_syncfs <= fd: {fd}"); - // TODO: File::from_fd only accepts regular file fds; Linux syncfs(2) accepts any fd type. - let f = crate::file::File::from_fd(fd)?; - f.inner().location().filesystem().flush()?; + let any = get_file_like(fd)?; + if let Some(f) = any.downcast_ref::() { + f.inner().location().filesystem().flush()?; + } else if let Some(d) = any.downcast_ref::() { + d.inner().filesystem().flush()?; + } Ok(0) } diff --git a/os/StarryOS/kernel/src/syscall/fs/io.rs b/os/StarryOS/kernel/src/syscall/fs/io.rs index e48d2a0420..a07ab189cb 100644 --- a/os/StarryOS/kernel/src/syscall/fs/io.rs +++ b/os/StarryOS/kernel/src/syscall/fs/io.rs @@ -317,18 +317,22 @@ pub fn sys_fdatasync(fd: c_int) -> AxResult { Err(AxError::from(LinuxError::EINVAL)) } -pub fn sys_sync_file_range(fd: c_int, _offset: i64, _nbytes: i64, _flags: u32) -> AxResult { - debug!("sys_sync_file_range <= fd: {fd}"); +pub fn sys_sync_file_range(fd: c_int, _offset: i64, _nbytes: i64, flags: u32) -> AxResult { + debug!("sys_sync_file_range <= fd: {fd}, flags: {flags:#x}"); // sync_file_range(2) is an advisory hint to initiate writeback for a // byte range. Until range-based writeback is implemented, keep this as // a no-op after basic fd validation rather than turning it into a // stronger whole-file fdatasync-style flush (matches the advisory // nature documented in the man page). Invalid fds still surface the // underlying error (EBADF). Directory fds are accepted to match fsync. - match File::from_fd(fd) { - Ok(_) | Err(AxError::IsADirectory) => Ok(0), - Err(e) => Err(e), + let any = get_file_like(fd)?; + if flags & !0x7 != 0 { + return Err(AxError::from(LinuxError::EINVAL)); } + if any.downcast_ref::().is_none() && any.downcast_ref::().is_none() { + return Err(AxError::from(LinuxError::ESPIPE)); + } + Ok(0) } pub fn sys_fadvise64( diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/CMakeLists.txt new file mode 100644 index 0000000000..f5cf243632 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-fdatasync C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-fdatasync src/main.c) +target_include_directories(test-fdatasync PRIVATE src) +target_compile_options(test-fdatasync PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-fdatasync RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/main.c new file mode 100644 index 0000000000..24899d02ef --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/main.c @@ -0,0 +1,78 @@ +#define _GNU_SOURCE + +#include "test_framework.h" + +#include +#include +#include + +int main(void) +{ + TEST_START("fdatasync Linux 语义对齐"); + + /* 测试节点 1:普通文件写入后 fdatasync 应返回 0。 */ + { + char tmpl[] = "/tmp/test-fdatasync-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建普通文件成功"); + if (fd >= 0) { + CHECK_RET(write(fd, "datasync", 8), 8, "write 写入 8 字节"); + CHECK_RET(fdatasync(fd), 0, "普通文件 fdatasync 返回 0"); + close(fd); + unlink(tmpl); + } + } + + /* 测试节点 2:只读打开的普通文件也允许 fdatasync。 */ + { + char tmpl[] = "/tmp/test-fdatasync-ro-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建只读场景文件成功"); + if (fd >= 0) { + close(fd); + fd = open(tmpl, O_RDONLY); + CHECK(fd >= 0, "O_RDONLY 重新打开文件成功"); + if (fd >= 0) { + CHECK_RET(fdatasync(fd), 0, "只读 fd 上 fdatasync 返回 0"); + close(fd); + } + unlink(tmpl); + } + } + + /* 测试节点 3:目录 fd 在 Linux 上允许 fdatasync。 */ + { + int fd = open("/tmp", O_RDONLY | O_DIRECTORY); + CHECK(fd >= 0, "打开 /tmp 目录成功"); + if (fd >= 0) { + CHECK_RET(fdatasync(fd), 0, "目录 fd 上 fdatasync 返回 0"); + close(fd); + } + } + + /* 测试节点 4:pipe 不支持 fdatasync,应返回 EINVAL。 */ + { + int pipefd[2]; + CHECK_RET(pipe(pipefd), 0, "创建 pipe 成功"); + CHECK_ERR(fdatasync(pipefd[0]), EINVAL, "pipe 读端 fdatasync 返回 EINVAL"); + close(pipefd[0]); + close(pipefd[1]); + } + + /* 测试节点 5:非法 fd 应返回 EBADF。 */ + CHECK_ERR(fdatasync(-1), EBADF, "fd=-1 时 fdatasync 返回 EBADF"); + + /* 测试节点 6:已关闭 fd 应返回 EBADF。 */ + { + char tmpl[] = "/tmp/test-fdatasync-closed-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建关闭场景文件成功"); + if (fd >= 0) { + close(fd); + CHECK_ERR(fdatasync(fd), EBADF, "已关闭 fd 上 fdatasync 返回 EBADF"); + unlink(tmpl); + } + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/test_framework.h new file mode 100644 index 0000000000..b81037b82a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fdatasync/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/CMakeLists.txt new file mode 100644 index 0000000000..73104935b1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-fsync C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-fsync src/main.c) +target_include_directories(test-fsync PRIVATE src) +target_compile_options(test-fsync PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-fsync RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/main.c new file mode 100644 index 0000000000..30b86cb893 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/main.c @@ -0,0 +1,78 @@ +#define _GNU_SOURCE + +#include "test_framework.h" + +#include +#include +#include + +int main(void) +{ + TEST_START("fsync Linux 语义对齐"); + + /* 测试节点 1:普通文件写入后 fsync 应返回 0。 */ + { + char tmpl[] = "/tmp/test-fsync-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建普通文件成功"); + if (fd >= 0) { + CHECK_RET(write(fd, "fsync", 5), 5, "write 写入 5 字节"); + CHECK_RET(fsync(fd), 0, "普通文件 fsync 返回 0"); + close(fd); + unlink(tmpl); + } + } + + /* 测试节点 2:只读打开的普通文件也允许 fsync。 */ + { + char tmpl[] = "/tmp/test-fsync-ro-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建只读场景文件成功"); + if (fd >= 0) { + close(fd); + fd = open(tmpl, O_RDONLY); + CHECK(fd >= 0, "O_RDONLY 重新打开文件成功"); + if (fd >= 0) { + CHECK_RET(fsync(fd), 0, "只读 fd 上 fsync 返回 0"); + close(fd); + } + unlink(tmpl); + } + } + + /* 测试节点 3:目录 fd 在 Linux 上允许 fsync。 */ + { + int fd = open("/tmp", O_RDONLY | O_DIRECTORY); + CHECK(fd >= 0, "打开 /tmp 目录成功"); + if (fd >= 0) { + CHECK_RET(fsync(fd), 0, "目录 fd 上 fsync 返回 0"); + close(fd); + } + } + + /* 测试节点 4:pipe 不支持 fsync,应返回 EINVAL。 */ + { + int pipefd[2]; + CHECK_RET(pipe(pipefd), 0, "创建 pipe 成功"); + CHECK_ERR(fsync(pipefd[0]), EINVAL, "pipe 读端 fsync 返回 EINVAL"); + close(pipefd[0]); + close(pipefd[1]); + } + + /* 测试节点 5:非法 fd 应返回 EBADF。 */ + CHECK_ERR(fsync(-1), EBADF, "fd=-1 时 fsync 返回 EBADF"); + + /* 测试节点 6:已关闭 fd 应返回 EBADF。 */ + { + char tmpl[] = "/tmp/test-fsync-closed-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建关闭场景文件成功"); + if (fd >= 0) { + close(fd); + CHECK_ERR(fsync(fd), EBADF, "已关闭 fd 上 fsync 返回 EBADF"); + unlink(tmpl); + } + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/test_framework.h new file mode 100644 index 0000000000..b81037b82a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/CMakeLists.txt new file mode 100644 index 0000000000..2bee38dab3 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-sync-file-range C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-sync-file-range src/main.c) +target_include_directories(test-sync-file-range PRIVATE src) +target_compile_options(test-sync-file-range PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-sync-file-range RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/main.c new file mode 100644 index 0000000000..c76e27cdea --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/main.c @@ -0,0 +1,87 @@ +#define _GNU_SOURCE +#define _FILE_OFFSET_BITS 64 + +#include "test_framework.h" + +#include +#include +#include +#include + +static int call_sync_file_range(int fd, off_t offset, off_t nbytes, unsigned int flags) +{ + return (int)syscall(SYS_sync_file_range, fd, offset, nbytes, flags); +} + +int main(void) +{ + TEST_START("sync_file_range Linux 语义对齐"); + + /* 测试节点 1:flags=0 是合法 no-op。 */ + { + char tmpl[] = "/tmp/test-sync-file-range-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建普通文件成功"); + if (fd >= 0) { + CHECK_RET(write(fd, "0123456789", 10), 10, "write 写入测试数据"); + CHECK_RET(call_sync_file_range(fd, 0, 10, 0), 0, "flags=0 返回 0"); + CHECK_RET(call_sync_file_range(fd, 0, 10, SYNC_FILE_RANGE_WRITE), 0, + "flags=WRITE 返回 0"); + CHECK_RET(call_sync_file_range(fd, 0, 10, + SYNC_FILE_RANGE_WAIT_BEFORE | + SYNC_FILE_RANGE_WRITE | + SYNC_FILE_RANGE_WAIT_AFTER), + 0, "WAIT_BEFORE|WRITE|WAIT_AFTER 返回 0"); + CHECK_RET(call_sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE), 0, + "nbytes=0 表示到 EOF,返回 0"); + CHECK_ERR(call_sync_file_range(fd, 0, 10, 0xff), EINVAL, + "合法文件 fd + 非法 flags 返回 EINVAL"); + close(fd); + unlink(tmpl); + } + } + + /* 测试节点 2:目录 fd 在 Linux 上允许 sync_file_range。 */ + { + int fd = open("/tmp", O_RDONLY | O_DIRECTORY); + CHECK(fd >= 0, "打开 /tmp 目录成功"); + if (fd >= 0) { + CHECK_RET(call_sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE), 0, + "目录 fd 上 sync_file_range 返回 0"); + close(fd); + } + } + + /* 测试节点 3:pipe fd 返回 ESPIPE。 */ + { + int pipefd[2]; + CHECK_RET(pipe(pipefd), 0, "创建 pipe 成功"); + CHECK_ERR(call_sync_file_range(pipefd[0], 0, 0, SYNC_FILE_RANGE_WRITE), ESPIPE, + "pipe 读端 sync_file_range 返回 ESPIPE"); + CHECK_ERR(call_sync_file_range(pipefd[0], 0, 0, 0xff), EINVAL, + "pipe 读端 + 非法 flags 时优先返回 EINVAL"); + close(pipefd[0]); + close(pipefd[1]); + } + + /* 测试节点 4:非法 fd 返回 EBADF。 */ + CHECK_ERR(call_sync_file_range(-1, 0, 0, SYNC_FILE_RANGE_WRITE), EBADF, + "fd=-1 时 sync_file_range 返回 EBADF"); + CHECK_ERR(call_sync_file_range(-1, 0, 0, 0xff), EBADF, + "fd=-1 且 flags 非法时仍优先返回 EBADF"); + + /* 测试节点 5:已关闭 fd 返回 EBADF。 */ + { + char tmpl[] = "/tmp/test-sync-file-range-closed-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建关闭场景文件成功"); + if (fd >= 0) { + close(fd); + CHECK_ERR(call_sync_file_range(fd, 0, 0, SYNC_FILE_RANGE_WRITE), EBADF, + "已关闭 fd 上 sync_file_range 返回 EBADF"); + unlink(tmpl); + } + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/test_framework.h new file mode 100644 index 0000000000..b81037b82a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync-file-range/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/CMakeLists.txt new file mode 100644 index 0000000000..87caea2f41 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-sync C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-sync src/main.c) +target_include_directories(test-sync PRIVATE src) +target_compile_options(test-sync PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-sync RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/main.c new file mode 100644 index 0000000000..6aa0e9e2da --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/main.c @@ -0,0 +1,51 @@ +#define _GNU_SOURCE + +#include "test_framework.h" + +#include +#include +#include + +int main(void) +{ + TEST_START("sync Linux 语义对齐"); + + /* + * 测试节点 1:sync 没有返回值。 + * 这里验证它不会中断后续执行,并且调用后文件内容仍可正常读回。 + */ + { + char tmpl[] = "/tmp/test-sync-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建普通文件成功"); + if (fd >= 0) { + const char *msg = "sync-data"; + char buf[16] = {0}; + + CHECK_RET(write(fd, msg, strlen(msg)), (long)strlen(msg), "write 写入 sync-data"); + sync(); + CHECK(1, "sync 调用返回到用户态"); + CHECK_RET(lseek(fd, 0, SEEK_SET), 0, "lseek 回到文件头"); + CHECK_RET(read(fd, buf, strlen(msg)), (long)strlen(msg), "read 读回写入内容"); + CHECK(strcmp(buf, msg) == 0, "sync 后读回内容与写入一致"); + + close(fd); + unlink(tmpl); + } + } + + /* 测试节点 2:sync 对目录项创建路径不应造成异常。 */ + { + char path[] = "/tmp/test-sync-path-XXXXXX"; + int fd = mkstemp(path); + CHECK(fd >= 0, "mkstemp 创建路径场景文件成功"); + if (fd >= 0) { + close(fd); + sync(); + CHECK(access(path, F_OK) == 0, "sync 后刚创建的路径仍可访问"); + unlink(path); + } + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/test_framework.h new file mode 100644 index 0000000000..b81037b82a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-sync/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/CMakeLists.txt new file mode 100644 index 0000000000..43d9462775 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-syncfs C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-syncfs src/main.c) +target_include_directories(test-syncfs PRIVATE src) +target_compile_options(test-syncfs PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-syncfs RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/main.c new file mode 100644 index 0000000000..143bb1f6d2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/main.c @@ -0,0 +1,63 @@ +#define _GNU_SOURCE + +#include "test_framework.h" + +#include +#include +#include + +int main(void) +{ + TEST_START("syncfs Linux 语义对齐"); + + /* 测试节点 1:普通文件 fd 上 syncfs 返回 0。 */ + { + char tmpl[] = "/tmp/test-syncfs-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建普通文件成功"); + if (fd >= 0) { + CHECK_RET(syncfs(fd), 0, "普通文件 fd 上 syncfs 返回 0"); + close(fd); + unlink(tmpl); + } + } + + /* 测试节点 2:目录 fd 也可以作为所在文件系统的锚点。 */ + { + int fd = open("/tmp", O_RDONLY | O_DIRECTORY); + CHECK(fd >= 0, "打开 /tmp 目录成功"); + if (fd >= 0) { + CHECK_RET(syncfs(fd), 0, "目录 fd 上 syncfs 返回 0"); + close(fd); + } + } + + /* + * 测试节点 3:pipe fd 在 Linux 上同样返回 0。 + * 原因是 syncfs 只要求“合法的打开 fd”,pipe 也属于 pipefs。 + */ + { + int pipefd[2]; + CHECK_RET(pipe(pipefd), 0, "创建 pipe 成功"); + CHECK_RET(syncfs(pipefd[0]), 0, "pipe 读端 syncfs 返回 0"); + close(pipefd[0]); + close(pipefd[1]); + } + + /* 测试节点 4:非法 fd 应返回 EBADF。 */ + CHECK_ERR(syncfs(-1), EBADF, "fd=-1 时 syncfs 返回 EBADF"); + + /* 测试节点 5:已关闭 fd 应返回 EBADF。 */ + { + char tmpl[] = "/tmp/test-syncfs-closed-XXXXXX"; + int fd = mkstemp(tmpl); + CHECK(fd >= 0, "mkstemp 创建关闭场景文件成功"); + if (fd >= 0) { + close(fd); + CHECK_ERR(syncfs(fd), EBADF, "已关闭 fd 上 syncfs 返回 EBADF"); + unlink(tmpl); + } + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/test_framework.h new file mode 100644 index 0000000000..b81037b82a --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-syncfs/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 From 24f0f38cab77d6ce9eed7762d08205d0b704bcc0 Mon Sep 17 00:00:00 2001 From: Feiran Qin <161046517+jakeuibn@users.noreply.github.com> Date: Mon, 25 May 2026 12:12:04 +0800 Subject: [PATCH 13/71] fix(starry-kernel): handle COW write faults from kernel-mode user-memory writes (#909) Several syscall sites still obtain a direct `&mut` reference into user memory through get_as_mut / get_as_mut_slice and write through it outside of access_user_memory(). Examples that remain on the current upstream/dev tree include sys_select (io_mpx/select.rs), the network sendmsg / recvmsg path (net/io.rs), event-device ioctls (pseudofs/dev/event.rs) and the file-lock ioctls (fs/lock.rs). When a concurrent fork(clone_map) re-marks the underlying COW pages read-only between check_region() and the kernel-mode write, the write triggers a #PF (present + WRITE bit set) whose RIP is in kernel text and has no fixup-table entry. The previous handler returned early on !is_accessing_user_memory(), turning this into an unrecoverable panic. Extend handle_page_fault so a kernel-mode fault on a user-space address goes through the regular aspace.handle_page_fault() path (which invokes the COW backend) just like a user-mode write would. Two guards keep the extension safe: 1. Reject fault addresses outside USER_SPACE_BASE..USER_SPACE_END so genuine kernel-text / kernel-stack faults still bubble up unchanged. 2. Bail out if the current thread already holds the aspace lock, which prevents a recursive lock acquisition / deadlock when a fault occurs inside aspace.lock().handle_page_fault() itself. Co-authored-by: StarryOS Fix --- os/StarryOS/kernel/src/mm/access.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/os/StarryOS/kernel/src/mm/access.rs b/os/StarryOS/kernel/src/mm/access.rs index 140dce9646..220af50103 100644 --- a/os/StarryOS/kernel/src/mm/access.rs +++ b/os/StarryOS/kernel/src/mm/access.rs @@ -299,7 +299,26 @@ fn handle_page_fault(vaddr: VirtAddr, access_flags: MappingFlags) -> bool { }; if unlikely(!thr.is_accessing_user_memory()) { - return false; + // Still try to handle kernel-mode faults on user-space addresses. + // Several syscall sites (e.g. event.rs, net/io.rs, fs/lock.rs) obtain + // a direct `&mut` reference into user memory via get_as_mut / + // get_as_mut_slice and write through it outside of + // access_user_memory(). If a concurrent fork has re-marked the page + // read-only between check_region() and the write, the kernel write + // hits a COW #PF with no fixup-table entry and panics. Handling the + // fault here lets the standard COW path copy the page just as it + // would for a user-mode write. + let user_range = USER_SPACE_BASE..USER_SPACE_BASE + USER_SPACE_SIZE; + if !user_range.contains(&vaddr.as_usize()) { + return false; + } + // Avoid recursion / deadlock: if this thread already holds the + // aspace lock (e.g. fault inside aspace.lock().handle_page_fault()) + // we have to bail out instead of trying to lock it again. + let aspace_arc = thr.proc_data.aspace(); + if unsafe { aspace_arc.raw() }.is_owned_by_current() { + return false; + } } might_sleep(); From a80d28cf4fc3df8a725ed15a9349aabf393cbd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Mon, 25 May 2026 12:17:54 +0800 Subject: [PATCH 14/71] refactor(drivers): split shared driver stack from ArceOS (#831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(docs): 添加 rdrive + rdif 驱动框架文档,记录宿主物理设备重构目标 * Add RK3588 PCIe driver support and related components - Implemented the RK3588 PCIe host driver in `resources.rs`, handling GPIO resets, memory mapping, and configuration. - Created slot drivers for multiple PCIe slots in `slots.rs`, enabling dynamic probing of PCIe devices. - Added memory window programming logic in `windows.rs`, including configuration window detection and logging of resources. - Introduced a new `PlatformVsockDevice` in `vsock.rs` for managing Virtio socket devices, with registration and device handling. - Updated `virtio.rs` to use the new Virtio driver interface. - Modified the Clippy crates list to include new dependencies for the added components. - Adjusted build configuration to remove unnecessary Virtio net feature for the AArch64 platform. * Add DMA and MMIO support to axklib - Updated Cargo.lock and Cargo.toml to include dma-api and mmio-api dependencies. - Implemented DMA operations in axklib with a new dma.rs file, providing functionality for allocating and managing DMA pages. - Created mmio.rs to handle memory-mapped I/O operations, including ioremap and iounmap functionalities. - Refactored axruntime and various platform drivers to utilize the new DMA and MMIO abstractions, replacing previous implementations. - Removed obsolete DmaImpl struct and related code from drivers, streamlining the DMA handling process. - Ensured compatibility with existing driver interfaces by adapting to the new DMA and MMIO APIs. * Refactor static device management and remove deprecated components - Consolidated static device initialization logic into `devices.rs`. - Introduced `StaticBlockDevice` to handle block device operations. - Removed old static device modules (`block.rs`, `dma.rs`, `virtio.rs`, `virtio_block.rs`) and their associated functionality. - Updated `Cargo.toml` files to remove unused driver dependencies. - Adjusted build configurations to reflect the removal of static device drivers. - Enhanced error handling and device probing for static devices. * Refactor axdriver module structure and update dependencies - Removed the `virtio/input.rs`, `virtio/vsock.rs`, and `vsock.rs` files as they are no longer needed. - Updated `Cargo.toml` files to change dependencies from `ax-driver` to `ax-drivers` for better modularization. - Refactored device initialization in `devices.rs` to utilize the new `ax-drivers` bindings for display, input, net, and vsock devices. - Simplified PCI driver implementations by using the new `ax-drivers` functions for transport and IRQ registration. - Cleaned up the `virtio` and `pci` driver implementations to align with the new structure and removed legacy code. - Updated the `clippy_crates.csv` to include `ax-driver` and `ax-drivers` for linting checks. * Refactor and streamline driver registration and initialization - Removed static driver registration and initialization functions from `init.rs`, `registers.rs`, and `source.rs`. - Updated `ramdisk.rs`, `sdmmc.rs`, `fxmac.rs`, and other driver files to use a simplified registration approach. - Introduced `register_mmio` and `register_ecam_controller` functions for dynamic device registration. - Enhanced the `probe_pci` functions across various drivers to focus solely on PCI device registration. - Removed unnecessary static device descriptors and streamlined the initialization process for static devices. - Updated Cargo.toml files to reflect changes in dependencies and features, including the addition of `sdmmc` and `cvsd`. - Added new driver registration logic in the `riscv64-visionfive2` platform to support SD/MMC devices. * Refactor: Remove obsolete drivers and consolidate functionality - Deleted the `virtio.rs`, `virtio_pci.rs`, `display.rs`, `input.rs`, `serial/mod.rs`, `net/mod.rs`, `usb/mod.rs`, `vsock.rs`, and `pci.rs` files to streamline the driver architecture. - Moved relevant functionality to `ax_drivers` for better modularity and maintainability. - Updated `mod.rs` to reflect the removal of unused modules and adjusted the driver registration process. - Enhanced the `generic_timer.rs` by making `try_init_epoch_offset` public for broader access. - Cleaned up the `lib.rs` to expose necessary components while removing deprecated references. * Refactor: Update dependencies and remove obsolete driver modules * Refactor driver bindings and module structure - Removed the `bindings` module and integrated its contents directly into the respective driver modules (block, display, input, net, vsock). - Created new binding files for block, display, input, net, and vsock drivers to encapsulate their platform-specific device structures and functionalities. - Updated references throughout the codebase to reflect the new module structure, ensuring that all driver implementations correctly utilize the new bindings. - Improved organization and clarity of the driver code by separating concerns and enhancing modularity. * Add PCI and Virtio support across multiple platforms - Updated linker scripts to define driver registration sections for various architectures (x86, RISC-V, Loongarch). - Refactored driver initialization in axplat crates to conditionally include PCI and Virtio drivers based on feature flags. - Introduced new driver modules for RISC-V and x86 platforms to handle Virtio block devices. - Enhanced Cargo.toml files to include necessary dependencies for PCI and Virtio drivers. - Modified initialization routines to ensure proper setup of drivers during platform initialization. - Updated configuration files for QEMU boards to enable PCI and Virtio features. * Refactor driver initialization and static device registration - Removed the `registers.rs` files from various platforms, consolidating the register handling into a new `registers.rs` in the `axruntime` module. - Updated the `init.rs` files across platforms to eliminate direct driver initialization calls, instead relying on a unified `probe_all_devices` function. - Introduced static device registration for various drivers (SD/MMC, PCIe ECAM, VirtIO) using the new static device registration mechanism. - Enhanced the `StaticDeviceDesc` structure to support additional properties like `regs` and `pci_ecam`. - Updated the build scripts to include new static features for drivers. - Adjusted the `DriversIf` trait implementation to provide static device descriptions for platforms. * Enhance feature sets across various build configurations - Updated feature lists in multiple TOML files for aarch64, loongarch64, riscv64gc, and x86_64 architectures to include new hardware abstraction layer (HAL) and driver features. - Added support for PCI, VirtIO block, VirtIO net, and other drivers to improve hardware compatibility and performance. - Adjusted features in the axvisor and starryos test suites to ensure consistency and leverage new capabilities. - Ensured that all configurations now utilize the latest HAL and driver features for enhanced functionality and stability. * 为 virtio-net 驱动添加 ax-kernel-guard 依赖并重构 IRQ 处理逻辑 * 重构 PCI 设备 IRQ 处理逻辑,更新相关驱动以支持新的 IRQ 路由机制 * Refactor feature names in configuration files from "ax-drivers" to "ax-driver" - Updated multiple TOML configuration files across various test suites to change the feature prefix from "ax-drivers" to "ax-driver" for consistency and clarity. - This change affects configurations for platforms including x86_64, aarch64, loongarch64, and riscv64 in both normal and stress test scenarios. - Ensured that all relevant files reflect this naming convention to maintain uniformity across the codebase. * 更新 ax-driver 版本至 0.6.0,并相应修改 CHANGELOG * 在构建配置中添加对 "ax-hal/riscv64-qemu-virt-hv" 的支持 * 重构特性解析逻辑,添加平台特性检查并优化特性列表生成 * 优化文件系统块设备的条件编译,支持非动态平台和特定操作系统 * 移除 qemu 特性中的动态平台支持,优化特性列表 * 优化 Tty 设备的会话管理逻辑,确保终端与会话的正确关联;增加 JobControl 的会话设置错误处理;调整 QEMU 测试超时时间至 120 秒 * fix(axbuild): preserve driver features for C tests * 优化 pass_std_build_nested_features 函数以支持可变特性列表,并添加单元测试验证功能 * Update QEMU configurations and dependencies for various architectures - Added virtio-net-pci and netdev configurations to QEMU arguments for aarch64, loongarch64, riscv64, and x86_64 test cases to enable networking capabilities. - Removed unnecessary PCI and virtio driver features from build configurations for aarch64, loongarch64, riscv64, and x86_64. - Updated hello world, HTTP client, IO test, thread test, and Tokio test configurations to include virtio-blk-pci for disk access. - Adjusted Cargo.toml files to streamline dependencies by removing unused features related to multitasking and IRQ handling. * fix: 增加 QEMU 配置中的内存大小至 512M * Refactor driver-related documentation and code structure - Replaced instances of `ax-driver-base`, `axdriver_*` with new naming conventions: `rdrive`, `rd-*`, and `rdif-*` across various documentation files. - Updated dependency references in `ax-hal`, `ax-input`, `ax-net-ng`, `ax-posix-api`, `ax-runtime`, and `axplat-dyn` to reflect the new driver structure. - Removed references to deprecated driver crates in `layers.md`, `overview.md`, and `components.md`. - Adjusted the driver subsystem section in the documentation to align with the new driver architecture. - Modified the repository management script to remove specific handling for `axdriver_crates` due to structural changes. * fix(starryos): relax x86_64 qemu test timeouts * 优化块设备的读写操作,合并块读取和写入方法,提升性能 * 重构块设备读写方法,移除合并操作,简化代码逻辑 * 为 DmaPages 结构体添加 DMA 统一页面分配方法,并移除 SyncBlockOps 中的 flush 方法实现 * 为 PCI 和 USB 驱动程序添加 IRQ 解析功能,重构相关方法以提高可读性和可维护性 * 移除不再使用的 simple-sdmmc 驱动相关代码,更新文档以反映当前驱动栈状态 * 重构设备初始化逻辑,使用 cfg_if 宏简化条件编译,提升可读性 * 重构平台特性管理,简化条件编译逻辑,移除冗余代码 * 添加对 rk3588-pcie 的支持到 Orange Pi 5 Plus 的构建配置 * 为 UsbKernel 实现 DmaOp 特性,添加 prepare_read 和 confirm_write 方法 * refactor(root): simplify partition selection logic and add helper functions * refactor(driver): replace crate::register_driver! with module_driver! macro for driver registration * refactor(driver): remove unused device feature constants and simplify configuration logic * refactor(ci): rename required_repository_owner to limit_to_owner for clarity * refactor: replace ax-kspin with spin in various drivers and update dependencies * refactor: remove ax-kspin dependency and update mutex usage in USB driver * refactor: add ax-hal dependency to arceos-stack-guard-page * refactor: update UVC stream parameters and change Mutex to BlockingMutex for submitted URBs * refactor: enhance std feature normalization and add test for runtime feature propagation --- .github/workflows/ci.yml | 2 +- Cargo.lock | 490 ++++--- Cargo.toml | 37 +- .../build-aarch64-unknown-none-softfloat.toml | 18 +- .../build-aarch64-unknown-none-softfloat.toml | 11 +- .../axdriver_crates/.github/workflows/ci.yml | 56 - components/axdriver_crates/.gitignore | 4 - components/axdriver_crates/LICENSE | 201 --- components/axdriver_crates/PUBLISH_ORDER.md | 16 - components/axdriver_crates/README.md | 49 - components/axdriver_crates/README_CN.md | 49 - .../axdriver_crates/axdriver_base/Cargo.toml | 13 - .../axdriver_crates/axdriver_base/README.md | 84 -- .../axdriver_base/README_CN.md | 84 -- .../axdriver_crates/axdriver_base/src/lib.rs | 104 -- .../axdriver_block/CHANGELOG.md | 20 - .../axdriver_crates/axdriver_block/Cargo.toml | 28 - .../axdriver_crates/axdriver_block/README.md | 84 -- .../axdriver_block/README_CN.md | 84 -- .../axdriver_block/src/ahci.rs | 77 - .../axdriver_block/src/bcm2835sdhci.rs | 90 -- .../axdriver_block/src/cvsd.rs | 87 -- .../axdriver_crates/axdriver_block/src/lib.rs | 79 - .../axdriver_block/src/partition/device.rs | 71 - .../axdriver_block/src/partition/gpt.rs | 384 ----- .../axdriver_block/src/partition/mbr.rs | 273 ---- .../axdriver_block/src/partition/mod.rs | 70 - .../axdriver_block/src/ramdisk.rs | 134 -- .../axdriver_block/src/ramdisk_static.rs | 90 -- .../axdriver_block/src/sdmmc.rs | 73 - .../axdriver_display/Cargo.toml | 14 - .../axdriver_display/README.md | 84 -- .../axdriver_display/README_CN.md | 84 -- .../axdriver_display/src/lib.rs | 59 - .../axdriver_input/CHANGELOG.md | 14 - .../axdriver_crates/axdriver_input/Cargo.toml | 15 - .../axdriver_crates/axdriver_input/README.md | 84 -- .../axdriver_input/README_CN.md | 84 -- .../axdriver_crates/axdriver_input/src/lib.rs | 122 -- .../axdriver_crates/axdriver_net/CHANGELOG.md | 20 - .../axdriver_crates/axdriver_net/Cargo.toml | 29 - .../axdriver_crates/axdriver_net/README.md | 84 -- .../axdriver_crates/axdriver_net/README_CN.md | 84 -- .../axdriver_crates/axdriver_net/src/fxmac.rs | 137 -- .../axdriver_crates/axdriver_net/src/ixgbe.rs | 160 -- .../axdriver_crates/axdriver_net/src/lib.rs | 90 -- .../axdriver_net/src/net_buf.rs | 248 ---- .../axdriver_crates/axdriver_pci/Cargo.toml | 14 - .../axdriver_crates/axdriver_pci/README.md | 84 -- .../axdriver_crates/axdriver_pci/README_CN.md | 84 -- .../axdriver_crates/axdriver_pci/src/lib.rs | 53 - .../axdriver_virtio/CHANGELOG.md | 30 - .../axdriver_virtio/Cargo.toml | 29 - .../axdriver_crates/axdriver_virtio/README.md | 84 -- .../axdriver_virtio/README_CN.md | 84 -- .../axdriver_virtio/src/blk.rs | 61 - .../axdriver_virtio/src/gpu.rs | 101 -- .../axdriver_virtio/src/input.rs | 166 --- .../axdriver_virtio/src/lib.rs | 227 --- .../axdriver_virtio/src/net.rs | 1008 ------------- .../axdriver_virtio/src/socket.rs | 854 ----------- .../axdriver_crates/axdriver_vsock/Cargo.toml | 18 - .../axdriver_crates/axdriver_vsock/README.md | 84 -- .../axdriver_vsock/README_CN.md | 84 -- .../axdriver_crates/axdriver_vsock/src/lib.rs | 85 -- components/axklib/Cargo.toml | 2 + components/axklib/src/dma.rs | 262 ++++ components/axklib/src/lib.rs | 30 +- components/axklib/src/mmio.rs | 48 + components/axplat_crates/axplat/Cargo.toml | 1 + .../axplat_crates/axplat/src/drivers.rs | 19 + components/axplat_crates/axplat/src/lib.rs | 1 + .../axplat-aarch64-bsta1000b/Cargo.toml | 1 + .../axplat-aarch64-bsta1000b/src/drivers.rs | 11 + .../axplat-aarch64-bsta1000b/src/lib.rs | 1 + .../axplat-aarch64-phytium-pi/Cargo.toml | 1 + .../axplat-aarch64-phytium-pi/src/drivers.rs | 11 + .../axplat-aarch64-phytium-pi/src/lib.rs | 1 + .../axplat-aarch64-qemu-virt/Cargo.toml | 1 + .../axplat-aarch64-qemu-virt/src/drivers.rs | 28 + .../axplat-aarch64-qemu-virt/src/lib.rs | 1 + .../platforms/axplat-aarch64-raspi/Cargo.toml | 1 + .../axplat-aarch64-raspi/src/drivers.rs | 11 + .../platforms/axplat-aarch64-raspi/src/lib.rs | 1 + .../axplat-loongarch64-qemu-virt/Cargo.toml | 1 + .../src/drivers.rs | 22 + .../axplat-loongarch64-qemu-virt/src/lib.rs | 1 + .../axplat-riscv64-qemu-virt/Cargo.toml | 1 + .../axplat-riscv64-qemu-virt/src/drivers.rs | 28 + .../axplat-riscv64-qemu-virt/src/lib.rs | 1 + .../axplat-riscv64-sg2002/Cargo.toml | 1 + .../axplat-riscv64-sg2002/src/drivers.rs | 20 + .../axplat-riscv64-sg2002/src/lib.rs | 2 + .../platforms/axplat-x86-pc/Cargo.toml | 1 + .../platforms/axplat-x86-pc/src/drivers.rs | 18 + .../platforms/axplat-x86-pc/src/lib.rs | 1 + docs/docs/architecture/overview.md | 10 +- docs/docs/architecture/rdrive-rdif.md | 405 ++++++ docs/docs/components/crates/ax-api.md | 8 +- docs/docs/components/crates/ax-display.md | 2 +- docs/docs/components/crates/ax-dma.md | 8 +- docs/docs/components/crates/ax-driver-base.md | 146 -- .../docs/components/crates/ax-driver-block.md | 171 --- .../components/crates/ax-driver-display.md | 147 -- .../docs/components/crates/ax-driver-input.md | 144 -- docs/docs/components/crates/ax-driver-net.md | 172 --- docs/docs/components/crates/ax-driver-pci.md | 143 -- .../components/crates/ax-driver-virtio.md | 179 --- .../docs/components/crates/ax-driver-vsock.md | 145 -- docs/docs/components/crates/ax-driver.md | 224 +-- docs/docs/components/crates/ax-feat.md | 8 +- docs/docs/components/crates/ax-fs-ng.md | 4 +- docs/docs/components/crates/ax-hal.md | 2 +- docs/docs/components/crates/ax-input.md | 2 +- docs/docs/components/crates/ax-net-ng.md | 4 +- docs/docs/components/crates/ax-posix-api.md | 2 +- docs/docs/components/crates/ax-runtime.md | 8 +- docs/docs/components/crates/axplat-dyn.md | 11 +- docs/docs/components/crates/fxmac-rs.md | 4 +- docs/docs/components/layers.md | 40 +- docs/docs/components/overview.md | 10 +- docs/docs/development/components.md | 4 +- docs/src/pages/index.js | 16 +- .../ax-driver}/CHANGELOG.md | 8 +- drivers/ax-driver/Cargo.toml | 136 ++ drivers/ax-driver/build.rs | 76 + drivers/ax-driver/src/block/ahci.rs | 107 ++ drivers/ax-driver/src/block/bcm2835.rs | 82 ++ .../ax-driver/src/block/binding.rs | 141 +- drivers/ax-driver/src/block/cvsd.rs | 160 ++ drivers/ax-driver/src/block/mod.rs | 143 ++ .../ax-driver/src/block}/phytium_mci.rs | 16 +- drivers/ax-driver/src/block/ramdisk.rs | 14 + drivers/ax-driver/src/block/rockchip/mod.rs | 2 + .../src/block}/rockchip/sdhci_rk3568.rs | 20 +- .../ax-driver/src/block/rockchip_mmc.rs | 18 +- drivers/ax-driver/src/block/rockchip_sd.rs | 299 ++++ .../ax-driver/src/block/rockchip_sd/block.rs | 334 +++++ .../ax-driver/src/block/rockchip_sd/phase.rs | 155 ++ drivers/ax-driver/src/display/binding.rs | 57 + drivers/ax-driver/src/display/mod.rs | 3 + drivers/ax-driver/src/error.rs | 30 + drivers/ax-driver/src/input/binding.rs | 54 + drivers/ax-driver/src/input/mod.rs | 3 + drivers/ax-driver/src/lib.rs | 64 + drivers/ax-driver/src/mmio.rs | 9 + drivers/ax-driver/src/net/binding.rs | 87 ++ drivers/ax-driver/src/net/fxmac.rs | 271 ++++ .../ax-driver/src}/net/intel.rs | 21 +- drivers/ax-driver/src/net/ixgbe.rs | 312 ++++ drivers/ax-driver/src/net/mod.rs | 12 + .../ax-driver/src}/net/realtek.rs | 16 +- drivers/ax-driver/src/pci/fdt.rs | 250 ++++ drivers/ax-driver/src/pci/mod.rs | 305 ++++ drivers/ax-driver/src/pci/rk3588.rs | 11 + .../src/pci/rk3588/clocks_reset_gpio.rs | 225 +++ drivers/ax-driver/src/pci/rk3588/phy.rs | 309 ++++ drivers/ax-driver/src/pci/rk3588/resources.rs | 437 ++++++ drivers/ax-driver/src/pci/rk3588/slots.rs | 99 ++ drivers/ax-driver/src/pci/rk3588/windows.rs | 209 +++ drivers/ax-driver/src/rknpu.rs | 119 ++ drivers/ax-driver/src/serial/mod.rs | 54 + .../ax-driver/src}/soc/mod.rs | 23 +- .../ax-driver/src}/soc/rockchip/clk/mod.rs | 8 +- .../ax-driver/src}/soc/rockchip/clk/rk3568.rs | 9 +- .../ax-driver/src}/soc/rockchip/clk/rk3588.rs | 23 +- .../ax-driver/src}/soc/rockchip/mod.rs | 8 +- .../ax-driver/src}/soc/rockchip/pinctrl.rs | 83 +- .../ax-driver/src}/soc/rockchip/pm.rs | 9 +- .../ax-driver/src}/soc/scmi.rs | 5 +- .../mod.rs => drivers/ax-driver/src/time.rs | 9 +- .../ax-driver/src/usb/dwc.rs | 26 +- drivers/ax-driver/src/usb/mod.rs | 229 +++ drivers/ax-driver/src/usb/xhci_mmio.rs | 46 + .../ax-driver/src}/usb/xhci_pci.rs | 15 +- drivers/ax-driver/src/virtio/block.rs | 187 +++ drivers/ax-driver/src/virtio/display.rs | 99 ++ drivers/ax-driver/src/virtio/input.rs | 148 ++ drivers/ax-driver/src/virtio/mod.rs | 187 +++ drivers/ax-driver/src/virtio/net.rs | 352 +++++ drivers/ax-driver/src/virtio/vsock.rs | 210 +++ drivers/ax-driver/src/vsock/binding.rs | 54 + drivers/ax-driver/src/vsock/mod.rs | 3 + drivers/blk/nvme-driver/Cargo.toml | 2 +- drivers/blk/nvme-driver/src/block.rs | 2 +- drivers/blk/ramdisk/Cargo.toml | 2 +- drivers/blk/ramdisk/src/lib.rs | 2 +- drivers/blk/rd-block-volume/Cargo.toml | 12 + drivers/blk/rd-block-volume/src/error.rs | 24 + drivers/blk/rd-block-volume/src/gpt.rs | 192 +++ drivers/blk/rd-block-volume/src/lib.rs | 18 + drivers/blk/rd-block-volume/src/mbr.rs | 216 +++ drivers/blk/rd-block-volume/src/reader.rs | 33 + drivers/blk/rd-block-volume/src/scan.rs | 30 + drivers/blk/rd-block-volume/src/types.rs | 50 + drivers/blk/rd-block-volume/tests/scan.rs | 220 +++ drivers/blk/rd-block/src/lib.rs | 214 ++- drivers/firmware/arm-scmi-rs/Cargo.toml | 2 +- drivers/firmware/arm-scmi-rs/src/lib.rs | 2 +- .../firmware/arm-scmi-rs/src/protocol/mod.rs | 2 +- drivers/interface/rdif-display/Cargo.toml | 14 + drivers/interface/rdif-display/src/error.rs | 26 + .../interface/rdif-display/src/interface.rs | 38 + drivers/interface/rdif-display/src/lib.rs | 57 + drivers/interface/rdif-display/src/types.rs | 62 + drivers/interface/rdif-eth/src/lib.rs | 17 +- drivers/interface/rdif-input/Cargo.toml | 14 + drivers/interface/rdif-input/src/error.rs | 29 + drivers/interface/rdif-input/src/event.rs | 50 + drivers/interface/rdif-input/src/id.rs | 8 + drivers/interface/rdif-input/src/interface.rs | 44 + drivers/interface/rdif-input/src/lib.rs | 89 ++ drivers/interface/rdif-pcie/src/bar_alloc.rs | 105 +- drivers/interface/rdif-pcie/src/lib.rs | 4 +- drivers/interface/rdif-serial/Cargo.toml | 2 +- drivers/interface/rdif-serial/src/serial.rs | 2 +- drivers/interface/rdif-vsock/Cargo.toml | 14 + drivers/interface/rdif-vsock/src/addr.rs | 20 + drivers/interface/rdif-vsock/src/error.rs | 32 + drivers/interface/rdif-vsock/src/event.rs | 26 + drivers/interface/rdif-vsock/src/interface.rs | 33 + drivers/interface/rdif-vsock/src/lib.rs | 89 ++ drivers/net/eth-intel/src/e1000/mod.rs | 19 +- drivers/net/rd-net/src/lib.rs | 30 +- drivers/net/realtek-rtl8125/Cargo.toml | 2 +- drivers/net/realtek-rtl8125/src/hw.rs | 527 +++++++ drivers/net/realtek-rtl8125/src/lib.rs | 888 +----------- drivers/net/realtek-rtl8125/src/queue.rs | 374 +++++ drivers/rdrive/Cargo.toml | 1 - drivers/rdrive/src/error.rs | 2 + drivers/rdrive/src/lib.rs | 87 +- drivers/rdrive/src/manager.rs | 2 +- drivers/rdrive/src/probe/acpi.rs | 37 + drivers/rdrive/src/probe/fdt/mod.rs | 23 +- drivers/rdrive/src/probe/mod.rs | 6 + drivers/rdrive/src/probe/pci/mod.rs | 3 +- drivers/rdrive/src/probe/static_.rs | 232 +++ drivers/rdrive/src/register/mod.rs | 9 +- drivers/rdrive/tests/init_sources.rs | 57 + drivers/rdrive/tests/phase1.rs | 62 + drivers/soc/rockchip/rockchip-soc/src/lib.rs | 4 +- drivers/usb/usb-host/Cargo.toml | 1 - .../usb/usb-host/src/backend/kmod/xhci/cmd.rs | 3 +- .../usb-host/src/backend/kmod/xhci/device.rs | 2 +- .../src/backend/kmod/xhci/endpoint.rs | 2 +- .../usb-host/src/backend/kmod/xhci/sync.rs | 10 +- .../configs/board/licheerv-nano-sg2002.toml | 6 +- .../configs/board/orangepi-5-plus.toml | 17 +- .../configs/board/qemu-aarch64-dyn.toml | 8 +- os/StarryOS/configs/board/qemu-aarch64.toml | 7 + .../configs/board/qemu-loongarch64.toml | 11 +- os/StarryOS/configs/board/qemu-riscv64.toml | 9 +- os/StarryOS/configs/board/qemu-x86_64.toml | 11 +- os/StarryOS/kernel/Cargo.toml | 17 +- os/StarryOS/kernel/src/lib.rs | 2 - os/StarryOS/kernel/src/pseudofs/dev/card1.rs | 4 +- os/StarryOS/kernel/src/pseudofs/dev/event.rs | 48 +- os/StarryOS/kernel/src/pseudofs/dev/fb.rs | 2 - os/StarryOS/kernel/src/pseudofs/dev/loop.rs | 375 +---- .../kernel/src/pseudofs/dev/loop_block.rs | 306 ++++ os/StarryOS/kernel/src/pseudofs/dev/mod.rs | 2 + .../kernel/src/pseudofs/usbfs/manager.rs | 11 +- os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs | 4 +- os/StarryOS/kernel/src/syscall/fs/mount.rs | 4 +- os/StarryOS/starryos/Cargo.toml | 29 +- os/StarryOS/starryos/src/main.rs | 3 + os/arceos/README.md | 4 +- os/arceos/api/arceos_api/Cargo.toml | 7 +- os/arceos/api/arceos_api/src/lib.rs | 2 - os/arceos/api/axfeat/Cargo.toml | 30 +- os/arceos/api/axfeat/src/lib.rs | 8 +- os/arceos/configs/dummy.toml | 4 + os/arceos/doc/ixgbe.md | 6 +- .../examples/helloworld-myplat/Cargo.toml | 16 +- os/arceos/examples/helloworld/Cargo.toml | 2 + os/arceos/examples/httpclient/Cargo.toml | 2 + os/arceos/examples/httpserver/Cargo.toml | 2 + os/arceos/examples/shell/Cargo.toml | 2 + os/arceos/modules/axconfig/dummy.toml | 4 + .../modules/axconfig/src/driver_dyn_config.rs | 4 + os/arceos/modules/axdisplay/Cargo.toml | 2 +- os/arceos/modules/axdisplay/src/device.rs | 55 + os/arceos/modules/axdisplay/src/lib.rs | 24 +- os/arceos/modules/axdisplay/src/rdif.rs | 81 ++ os/arceos/modules/axdisplay/src/types.rs | 41 + os/arceos/modules/axdriver/Cargo.toml | 63 - os/arceos/modules/axdriver/build.rs | 109 -- os/arceos/modules/axdriver/src/bus/mmio.rs | 23 - os/arceos/modules/axdriver/src/bus/mod.rs | 4 - os/arceos/modules/axdriver/src/bus/pci.rs | 126 -- os/arceos/modules/axdriver/src/drivers.rs | 229 --- os/arceos/modules/axdriver/src/dummy.rs | 185 --- .../modules/axdriver/src/dyn_drivers/mod.rs | 28 - os/arceos/modules/axdriver/src/ixgbe.rs | 40 - os/arceos/modules/axdriver/src/lib.rs | 229 --- os/arceos/modules/axdriver/src/macros.rs | 109 -- os/arceos/modules/axdriver/src/prelude.rs | 34 - os/arceos/modules/axdriver/src/structs/dyn.rs | 51 - os/arceos/modules/axdriver/src/structs/mod.rs | 106 -- .../modules/axdriver/src/structs/static.rs | 42 - os/arceos/modules/axdriver/src/virtio.rs | 305 ---- os/arceos/modules/axfs-ng/Cargo.toml | 2 +- os/arceos/modules/axfs-ng/src/block.rs | 160 ++ .../modules/axfs-ng/src/fs/ext4/lwext4/fs.rs | 6 +- .../modules/axfs-ng/src/fs/ext4/lwext4/mod.rs | 11 +- .../modules/axfs-ng/src/fs/ext4/rsext4/fs.rs | 11 +- .../modules/axfs-ng/src/fs/ext4/rsext4/mod.rs | 9 +- os/arceos/modules/axfs-ng/src/fs/fat/disk.rs | 26 +- os/arceos/modules/axfs-ng/src/fs/fat/fs.rs | 6 +- os/arceos/modules/axfs-ng/src/fs/mod.rs | 21 +- os/arceos/modules/axfs-ng/src/lib.rs | 476 +----- os/arceos/modules/axfs-ng/src/root.rs | 544 +++++++ os/arceos/modules/axfs/Cargo.toml | 3 +- os/arceos/modules/axfs/src/dev.rs | 49 +- os/arceos/modules/axfs/src/lib.rs | 20 +- os/arceos/modules/axhal/Cargo.toml | 45 + os/arceos/modules/axhal/build.rs | 194 ++- os/arceos/modules/axhal/linker.lds.S | 4 +- os/arceos/modules/axhal/src/dummy.rs | 9 + os/arceos/modules/axhal/src/lib.rs | 37 +- os/arceos/modules/axhal/src/platform.rs | 26 + os/arceos/modules/axhal/src/time.rs | 12 + os/arceos/modules/axinput/Cargo.toml | 2 +- os/arceos/modules/axinput/src/device.rs | 101 ++ os/arceos/modules/axinput/src/event.rs | 71 + os/arceos/modules/axinput/src/id.rs | 12 + os/arceos/modules/axinput/src/lib.rs | 29 +- os/arceos/modules/axinput/src/rdif.rs | 122 ++ os/arceos/modules/axnet-ng/Cargo.toml | 5 +- .../modules/axnet-ng/src/device/driver.rs | 260 ++++ .../modules/axnet-ng/src/device/ethernet.rs | 38 +- os/arceos/modules/axnet-ng/src/device/mod.rs | 2 + .../modules/axnet-ng/src/device/vsock.rs | 53 +- os/arceos/modules/axnet-ng/src/lib.rs | 24 +- os/arceos/modules/axnet-ng/src/socket.rs | 4 +- os/arceos/modules/axnet-ng/src/vsock/mod.rs | 2 +- os/arceos/modules/axnet/Cargo.toml | 4 +- os/arceos/modules/axnet/src/lib.rs | 14 +- .../modules/axnet/src/smoltcp_impl/mod.rs | 39 +- os/arceos/modules/axruntime/Cargo.toml | 40 +- os/arceos/modules/axruntime/src/devices.rs | 293 ++++ os/arceos/modules/axruntime/src/klib.rs | 53 +- os/arceos/modules/axruntime/src/lib.rs | 104 +- os/arceos/modules/axruntime/src/registers.rs | 24 + os/arceos/scripts/make/features.mk | 14 +- os/arceos/ulib/arceos-rust/Cargo.toml | 8 - os/arceos/ulib/arceos-rust/build.rs | 71 +- os/arceos/ulib/arceos-rust/lib/Cargo.toml | 18 +- os/arceos/ulib/arceos-rust/lib/src/lib.rs | 1 + os/arceos/ulib/axlibc/Cargo.toml | 2 + os/arceos/ulib/axlibc/src/lib.rs | 1 + os/arceos/ulib/axstd/Cargo.toml | 10 - os/arceos/ulib/axstd/src/lib.rs | 8 +- os/axvisor/Cargo.toml | 17 +- os/axvisor/configs/board/orangepi-5-plus.toml | 1 - os/axvisor/configs/board/phytiumpi.toml | 1 - os/axvisor/configs/board/qemu-aarch64.toml | 3 +- .../configs/board/qemu-loongarch64.toml | 3 + os/axvisor/configs/board/qemu-riscv64.toml | 4 +- os/axvisor/configs/board/qemu-x86_64.toml | 3 + os/axvisor/configs/board/rdk-s100.toml | 2 +- os/axvisor/configs/board/roc-rk3568-pc.toml | 1 - os/axvisor/configs/board/tac-e400.toml | 2 +- os/axvisor/src/main.rs | 5 - platform/axplat-dyn/Cargo.toml | 58 +- platform/axplat-dyn/src/boot.rs | 26 +- .../src/drivers/blk/rockchip/dwmmc.rs | 765 ---------- .../src/drivers/blk/rockchip/mod.rs | 6 - platform/axplat-dyn/src/drivers/blk/virtio.rs | 173 --- .../axplat-dyn/src/drivers/blk/virtio_pci.rs | 114 -- platform/axplat-dyn/src/drivers/mod.rs | 387 +---- platform/axplat-dyn/src/drivers/net/mod.rs | 329 ----- .../axplat-dyn/src/drivers/net/virtio_pci.rs | 119 -- platform/axplat-dyn/src/drivers/pci.rs | 162 --- platform/axplat-dyn/src/drivers/pci/rk3588.rs | 1289 ----------------- platform/axplat-dyn/src/drivers/rknpu/mod.rs | 67 - platform/axplat-dyn/src/drivers/serial/mod.rs | 75 - platform/axplat-dyn/src/drivers/usb/mod.rs | 186 --- .../axplat-dyn/src/drivers/usb/xhci_mmio.rs | 54 - platform/axplat-dyn/src/drivers/virtio.rs | 42 - platform/axplat-dyn/src/generic_timer.rs | 2 +- platform/axplat-dyn/src/lib.rs | 4 +- platform/axplat-dyn/src/rknpu.rs | 54 - platform/riscv64-visionfive2/Cargo.toml | 1 + platform/riscv64-visionfive2/README.md | 2 +- platform/riscv64-visionfive2/src/drivers.rs | 13 + platform/riscv64-visionfive2/src/lib.rs | 1 + platform/somehal/link.ld | 6 +- platform/somehal/src/driver.rs | 24 +- platform/x86-qemu-q35/Cargo.toml | 1 + platform/x86-qemu-q35/src/drivers.rs | 18 + platform/x86-qemu-q35/src/lib.rs | 3 + scripts/axbuild/src/arceos/build.rs | 18 +- scripts/axbuild/src/arceos/cbuild.rs | 53 +- scripts/axbuild/src/axvisor/board.rs | 4 +- scripts/axbuild/src/axvisor/build.rs | 49 +- scripts/axbuild/src/axvisor/config.rs | 4 +- scripts/axbuild/src/build.rs | 227 ++- scripts/axbuild/src/starry/build.rs | 22 +- scripts/repo/repo.py | 16 +- scripts/repo/repos.csv | 1 - .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../httpclient/build-x86_64-unknown-none.toml | 2 +- .../rust/backtrace-raw-badfp/Cargo.toml | 2 + .../rust/backtrace-raw-basic/Cargo.toml | 2 + .../rust/backtrace-raw-normal/Cargo.toml | 2 + test-suit/arceos/rust/backtrace/Cargo.toml | 2 + test-suit/arceos/rust/display/Cargo.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../display/build-x86_64-unknown-none.toml | 2 +- test-suit/arceos/rust/exception/Cargo.toml | 2 + test-suit/arceos/rust/fs/shell/Cargo.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../fs/shell/build-x86_64-unknown-none.toml | 2 +- test-suit/arceos/rust/memtest/Cargo.toml | 2 + .../arceos/rust/net/echoserver/Cargo.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../echoserver/build-x86_64-unknown-none.toml | 2 +- .../arceos/rust/net/httpclient/Cargo.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../httpclient/build-x86_64-unknown-none.toml | 2 +- .../arceos/rust/net/httpserver/Cargo.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../httpserver/build-x86_64-unknown-none.toml | 2 +- .../arceos/rust/net/udpserver/Cargo.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../udpserver/build-x86_64-unknown-none.toml | 2 +- .../arceos/rust/task/affinity/Cargo.toml | 2 + test-suit/arceos/rust/task/ipi/Cargo.toml | 2 + test-suit/arceos/rust/task/irq/Cargo.toml | 2 + test-suit/arceos/rust/task/lockdep/Cargo.toml | 2 + .../arceos/rust/task/parallel/Cargo.toml | 2 + .../arceos/rust/task/priority/Cargo.toml | 2 + test-suit/arceos/rust/task/sleep/Cargo.toml | 2 + .../rust/task/stack_guard_page/Cargo.toml | 1 + test-suit/arceos/rust/task/tls/Cargo.toml | 2 + .../arceos/rust/task/wait_queue/Cargo.toml | 2 + test-suit/arceos/rust/task/yield/Cargo.toml | 2 + .../std/qemu-smp1/arce_agent/Cargo.toml | 2 +- .../qemu-smp1/arce_agent/qemu-aarch64.toml | 2 +- .../arce_agent/qemu-loongarch64.toml | 2 +- .../qemu-smp1/arce_agent/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/arce_agent/qemu-x86_64.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 4 +- ...ld-loongarch64-unknown-none-softfloat.toml | 4 +- .../build-riscv64gc-unknown-none-elf.toml | 4 +- .../qemu-smp1/build-x86_64-unknown-none.toml | 4 +- .../qemu-smp1/helloworld/qemu-aarch64.toml | 2 +- .../helloworld/qemu-loongarch64.toml | 2 +- .../qemu-smp1/helloworld/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/helloworld/qemu-x86_64.toml | 2 +- .../std/qemu-smp1/httpclient/Cargo.toml | 2 +- .../qemu-smp1/httpclient/qemu-aarch64.toml | 2 +- .../httpclient/qemu-loongarch64.toml | 2 +- .../qemu-smp1/httpclient/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/httpclient/qemu-x86_64.toml | 2 +- .../std/qemu-smp1/httpserver/Cargo.toml | 2 +- .../qemu-smp1/httpserver/qemu-aarch64.toml | 2 +- .../httpserver/qemu-loongarch64.toml | 2 +- .../qemu-smp1/httpserver/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/httpserver/qemu-x86_64.toml | 2 +- .../arceos/std/qemu-smp1/io_test/Cargo.toml | 2 +- .../std/qemu-smp1/io_test/qemu-aarch64.toml | 2 +- .../qemu-smp1/io_test/qemu-loongarch64.toml | 2 +- .../std/qemu-smp1/io_test/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/io_test/qemu-x86_64.toml | 2 +- .../std/qemu-smp1/thread_test/Cargo.toml | 2 +- .../qemu-smp1/thread_test/qemu-aarch64.toml | 2 +- .../thread_test/qemu-loongarch64.toml | 2 +- .../qemu-smp1/thread_test/qemu-riscv64.toml | 2 +- .../qemu-smp1/thread_test/qemu-x86_64.toml | 2 +- .../std/qemu-smp1/tokio_test/Cargo.toml | 2 +- .../qemu-smp1/tokio_test/qemu-aarch64.toml | 2 +- .../tokio_test/qemu-loongarch64.toml | 2 +- .../qemu-smp1/tokio_test/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/tokio_test/qemu-x86_64.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 1 - .../build-aarch64-unknown-none-softfloat.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 3 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 + .../build-riscv64gc-unknown-none-elf.toml | 4 +- .../qemu/build-x86_64-unknown-none.toml | 2 + .../svm/qemu/build-x86_64-unknown-none.toml | 2 + .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 17 +- .../build-aarch64-unknown-none-softfloat.toml | 14 +- .../qemu-dhcp/build-x86_64-unknown-none.toml | 11 +- .../build-aarch64-unknown-none-softfloat.toml | 13 +- ...ld-loongarch64-unknown-none-softfloat.toml | 13 +- .../build-riscv64gc-unknown-none-elf.toml | 13 +- .../build-x86_64-unknown-none.toml | 13 +- .../build-aarch64-unknown-none-softfloat.toml | 12 +- ...ld-loongarch64-unknown-none-softfloat.toml | 12 +- .../build-riscv64gc-unknown-none-elf.toml | 12 +- .../qemu-kcov/build-x86_64-unknown-none.toml | 12 +- .../qemu-smp1/apk-curl/qemu-aarch64.toml | 16 +- .../qemu-smp1/apk-curl/qemu-loongarch64.toml | 16 +- .../qemu-smp1/apk-curl/qemu-riscv64.toml | 16 +- .../qemu-smp1/apk-curl/qemu-x86_64.toml | 16 +- .../build-aarch64-unknown-none-softfloat.toml | 7 + ...ld-loongarch64-unknown-none-softfloat.toml | 11 +- .../build-riscv64gc-unknown-none-elf.toml | 9 +- .../qemu-smp1/build-x86_64-unknown-none.toml | 11 +- .../test-mmap-prot-write/qemu-x86_64.toml | 2 +- .../qemu-smp1/util-linux/qemu-riscv64.toml | 2 +- .../qemu-smp1/util-linux/qemu-x86_64.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 7 + ...ld-loongarch64-unknown-none-softfloat.toml | 11 +- .../build-riscv64gc-unknown-none-elf.toml | 9 +- .../qemu-smp4/build-x86_64-unknown-none.toml | 11 +- .../build-aarch64-unknown-none-softfloat.toml | 7 + ...ld-loongarch64-unknown-none-softfloat.toml | 11 +- .../build-riscv64gc-unknown-none-elf.toml | 9 +- .../postgresql/build-x86_64-unknown-none.toml | 11 +- .../build-aarch64-unknown-none-softfloat.toml | 7 + ...ld-loongarch64-unknown-none-softfloat.toml | 11 +- .../build-riscv64gc-unknown-none-elf.toml | 9 +- .../build-x86_64-unknown-none.toml | 11 +- 532 files changed, 14571 insertions(+), 16922 deletions(-) delete mode 100644 components/axdriver_crates/.github/workflows/ci.yml delete mode 100644 components/axdriver_crates/.gitignore delete mode 100644 components/axdriver_crates/LICENSE delete mode 100644 components/axdriver_crates/PUBLISH_ORDER.md delete mode 100644 components/axdriver_crates/README.md delete mode 100644 components/axdriver_crates/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_base/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_base/README.md delete mode 100644 components/axdriver_crates/axdriver_base/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_base/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_block/CHANGELOG.md delete mode 100644 components/axdriver_crates/axdriver_block/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_block/README.md delete mode 100644 components/axdriver_crates/axdriver_block/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_block/src/ahci.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/bcm2835sdhci.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/cvsd.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/partition/device.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/partition/gpt.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/partition/mbr.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/partition/mod.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/ramdisk.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/ramdisk_static.rs delete mode 100644 components/axdriver_crates/axdriver_block/src/sdmmc.rs delete mode 100644 components/axdriver_crates/axdriver_display/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_display/README.md delete mode 100644 components/axdriver_crates/axdriver_display/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_display/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_input/CHANGELOG.md delete mode 100644 components/axdriver_crates/axdriver_input/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_input/README.md delete mode 100644 components/axdriver_crates/axdriver_input/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_input/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_net/CHANGELOG.md delete mode 100644 components/axdriver_crates/axdriver_net/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_net/README.md delete mode 100644 components/axdriver_crates/axdriver_net/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_net/src/fxmac.rs delete mode 100644 components/axdriver_crates/axdriver_net/src/ixgbe.rs delete mode 100644 components/axdriver_crates/axdriver_net/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_net/src/net_buf.rs delete mode 100644 components/axdriver_crates/axdriver_pci/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_pci/README.md delete mode 100644 components/axdriver_crates/axdriver_pci/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_pci/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_virtio/CHANGELOG.md delete mode 100644 components/axdriver_crates/axdriver_virtio/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_virtio/README.md delete mode 100644 components/axdriver_crates/axdriver_virtio/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_virtio/src/blk.rs delete mode 100644 components/axdriver_crates/axdriver_virtio/src/gpu.rs delete mode 100644 components/axdriver_crates/axdriver_virtio/src/input.rs delete mode 100644 components/axdriver_crates/axdriver_virtio/src/lib.rs delete mode 100644 components/axdriver_crates/axdriver_virtio/src/net.rs delete mode 100644 components/axdriver_crates/axdriver_virtio/src/socket.rs delete mode 100644 components/axdriver_crates/axdriver_vsock/Cargo.toml delete mode 100644 components/axdriver_crates/axdriver_vsock/README.md delete mode 100644 components/axdriver_crates/axdriver_vsock/README_CN.md delete mode 100644 components/axdriver_crates/axdriver_vsock/src/lib.rs create mode 100644 components/axklib/src/dma.rs create mode 100644 components/axklib/src/mmio.rs create mode 100644 components/axplat_crates/axplat/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs create mode 100644 docs/docs/architecture/rdrive-rdif.md delete mode 100644 docs/docs/components/crates/ax-driver-base.md delete mode 100644 docs/docs/components/crates/ax-driver-block.md delete mode 100644 docs/docs/components/crates/ax-driver-display.md delete mode 100644 docs/docs/components/crates/ax-driver-input.md delete mode 100644 docs/docs/components/crates/ax-driver-net.md delete mode 100644 docs/docs/components/crates/ax-driver-pci.md delete mode 100644 docs/docs/components/crates/ax-driver-virtio.md delete mode 100644 docs/docs/components/crates/ax-driver-vsock.md rename {os/arceos/modules/axdriver => drivers/ax-driver}/CHANGELOG.md (73%) create mode 100644 drivers/ax-driver/Cargo.toml create mode 100644 drivers/ax-driver/build.rs create mode 100644 drivers/ax-driver/src/block/ahci.rs create mode 100644 drivers/ax-driver/src/block/bcm2835.rs rename platform/axplat-dyn/src/drivers/blk/mod.rs => drivers/ax-driver/src/block/binding.rs (76%) create mode 100644 drivers/ax-driver/src/block/cvsd.rs create mode 100644 drivers/ax-driver/src/block/mod.rs rename {platform/axplat-dyn/src/drivers/blk => drivers/ax-driver/src/block}/phytium_mci.rs (97%) create mode 100644 drivers/ax-driver/src/block/ramdisk.rs create mode 100644 drivers/ax-driver/src/block/rockchip/mod.rs rename {platform/axplat-dyn/src/drivers/blk => drivers/ax-driver/src/block}/rockchip/sdhci_rk3568.rs (97%) rename platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3588.rs => drivers/ax-driver/src/block/rockchip_mmc.rs (97%) create mode 100644 drivers/ax-driver/src/block/rockchip_sd.rs create mode 100644 drivers/ax-driver/src/block/rockchip_sd/block.rs create mode 100644 drivers/ax-driver/src/block/rockchip_sd/phase.rs create mode 100644 drivers/ax-driver/src/display/binding.rs create mode 100644 drivers/ax-driver/src/display/mod.rs create mode 100644 drivers/ax-driver/src/error.rs create mode 100644 drivers/ax-driver/src/input/binding.rs create mode 100644 drivers/ax-driver/src/input/mod.rs create mode 100644 drivers/ax-driver/src/lib.rs create mode 100644 drivers/ax-driver/src/mmio.rs create mode 100644 drivers/ax-driver/src/net/binding.rs create mode 100644 drivers/ax-driver/src/net/fxmac.rs rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/net/intel.rs (69%) create mode 100644 drivers/ax-driver/src/net/ixgbe.rs create mode 100644 drivers/ax-driver/src/net/mod.rs rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/net/realtek.rs (89%) create mode 100644 drivers/ax-driver/src/pci/fdt.rs create mode 100644 drivers/ax-driver/src/pci/mod.rs create mode 100644 drivers/ax-driver/src/pci/rk3588.rs create mode 100644 drivers/ax-driver/src/pci/rk3588/clocks_reset_gpio.rs create mode 100644 drivers/ax-driver/src/pci/rk3588/phy.rs create mode 100644 drivers/ax-driver/src/pci/rk3588/resources.rs create mode 100644 drivers/ax-driver/src/pci/rk3588/slots.rs create mode 100644 drivers/ax-driver/src/pci/rk3588/windows.rs create mode 100644 drivers/ax-driver/src/rknpu.rs create mode 100644 drivers/ax-driver/src/serial/mod.rs rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/mod.rs (78%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/rockchip/clk/mod.rs (89%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/rockchip/clk/rk3568.rs (86%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/rockchip/clk/rk3588.rs (64%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/rockchip/mod.rs (84%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/rockchip/pinctrl.rs (80%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/rockchip/pm.rs (86%) rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/soc/scmi.rs (97%) rename platform/axplat-dyn/src/drivers/time/mod.rs => drivers/ax-driver/src/time.rs (81%) rename platform/axplat-dyn/src/drivers/usb/xhci_dwc.rs => drivers/ax-driver/src/usb/dwc.rs (95%) create mode 100644 drivers/ax-driver/src/usb/mod.rs create mode 100644 drivers/ax-driver/src/usb/xhci_mmio.rs rename {platform/axplat-dyn/src/drivers => drivers/ax-driver/src}/usb/xhci_pci.rs (74%) create mode 100644 drivers/ax-driver/src/virtio/block.rs create mode 100644 drivers/ax-driver/src/virtio/display.rs create mode 100644 drivers/ax-driver/src/virtio/input.rs create mode 100644 drivers/ax-driver/src/virtio/mod.rs create mode 100644 drivers/ax-driver/src/virtio/net.rs create mode 100644 drivers/ax-driver/src/virtio/vsock.rs create mode 100644 drivers/ax-driver/src/vsock/binding.rs create mode 100644 drivers/ax-driver/src/vsock/mod.rs create mode 100644 drivers/blk/rd-block-volume/Cargo.toml create mode 100644 drivers/blk/rd-block-volume/src/error.rs create mode 100644 drivers/blk/rd-block-volume/src/gpt.rs create mode 100644 drivers/blk/rd-block-volume/src/lib.rs create mode 100644 drivers/blk/rd-block-volume/src/mbr.rs create mode 100644 drivers/blk/rd-block-volume/src/reader.rs create mode 100644 drivers/blk/rd-block-volume/src/scan.rs create mode 100644 drivers/blk/rd-block-volume/src/types.rs create mode 100644 drivers/blk/rd-block-volume/tests/scan.rs create mode 100644 drivers/interface/rdif-display/Cargo.toml create mode 100644 drivers/interface/rdif-display/src/error.rs create mode 100644 drivers/interface/rdif-display/src/interface.rs create mode 100644 drivers/interface/rdif-display/src/lib.rs create mode 100644 drivers/interface/rdif-display/src/types.rs create mode 100644 drivers/interface/rdif-input/Cargo.toml create mode 100644 drivers/interface/rdif-input/src/error.rs create mode 100644 drivers/interface/rdif-input/src/event.rs create mode 100644 drivers/interface/rdif-input/src/id.rs create mode 100644 drivers/interface/rdif-input/src/interface.rs create mode 100644 drivers/interface/rdif-input/src/lib.rs create mode 100644 drivers/interface/rdif-vsock/Cargo.toml create mode 100644 drivers/interface/rdif-vsock/src/addr.rs create mode 100644 drivers/interface/rdif-vsock/src/error.rs create mode 100644 drivers/interface/rdif-vsock/src/event.rs create mode 100644 drivers/interface/rdif-vsock/src/interface.rs create mode 100644 drivers/interface/rdif-vsock/src/lib.rs create mode 100644 drivers/net/realtek-rtl8125/src/hw.rs create mode 100644 drivers/net/realtek-rtl8125/src/queue.rs create mode 100644 drivers/rdrive/src/probe/acpi.rs create mode 100644 drivers/rdrive/src/probe/static_.rs create mode 100644 drivers/rdrive/tests/init_sources.rs create mode 100644 drivers/rdrive/tests/phase1.rs create mode 100644 os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs create mode 100644 os/arceos/modules/axdisplay/src/device.rs create mode 100644 os/arceos/modules/axdisplay/src/rdif.rs create mode 100644 os/arceos/modules/axdisplay/src/types.rs delete mode 100644 os/arceos/modules/axdriver/Cargo.toml delete mode 100644 os/arceos/modules/axdriver/build.rs delete mode 100644 os/arceos/modules/axdriver/src/bus/mmio.rs delete mode 100644 os/arceos/modules/axdriver/src/bus/mod.rs delete mode 100644 os/arceos/modules/axdriver/src/bus/pci.rs delete mode 100644 os/arceos/modules/axdriver/src/drivers.rs delete mode 100644 os/arceos/modules/axdriver/src/dummy.rs delete mode 100644 os/arceos/modules/axdriver/src/dyn_drivers/mod.rs delete mode 100644 os/arceos/modules/axdriver/src/ixgbe.rs delete mode 100644 os/arceos/modules/axdriver/src/lib.rs delete mode 100644 os/arceos/modules/axdriver/src/macros.rs delete mode 100644 os/arceos/modules/axdriver/src/prelude.rs delete mode 100644 os/arceos/modules/axdriver/src/structs/dyn.rs delete mode 100644 os/arceos/modules/axdriver/src/structs/mod.rs delete mode 100644 os/arceos/modules/axdriver/src/structs/static.rs delete mode 100644 os/arceos/modules/axdriver/src/virtio.rs create mode 100644 os/arceos/modules/axfs-ng/src/block.rs create mode 100644 os/arceos/modules/axfs-ng/src/root.rs create mode 100644 os/arceos/modules/axhal/src/platform.rs create mode 100644 os/arceos/modules/axinput/src/device.rs create mode 100644 os/arceos/modules/axinput/src/event.rs create mode 100644 os/arceos/modules/axinput/src/id.rs create mode 100644 os/arceos/modules/axinput/src/rdif.rs create mode 100644 os/arceos/modules/axnet-ng/src/device/driver.rs create mode 100644 os/arceos/modules/axruntime/src/devices.rs create mode 100644 os/arceos/modules/axruntime/src/registers.rs delete mode 100644 platform/axplat-dyn/src/drivers/blk/rockchip/dwmmc.rs delete mode 100644 platform/axplat-dyn/src/drivers/blk/rockchip/mod.rs delete mode 100644 platform/axplat-dyn/src/drivers/blk/virtio.rs delete mode 100644 platform/axplat-dyn/src/drivers/blk/virtio_pci.rs delete mode 100644 platform/axplat-dyn/src/drivers/net/mod.rs delete mode 100644 platform/axplat-dyn/src/drivers/net/virtio_pci.rs delete mode 100644 platform/axplat-dyn/src/drivers/pci.rs delete mode 100644 platform/axplat-dyn/src/drivers/pci/rk3588.rs delete mode 100644 platform/axplat-dyn/src/drivers/rknpu/mod.rs delete mode 100644 platform/axplat-dyn/src/drivers/serial/mod.rs delete mode 100644 platform/axplat-dyn/src/drivers/usb/mod.rs delete mode 100644 platform/axplat-dyn/src/drivers/usb/xhci_mmio.rs delete mode 100644 platform/axplat-dyn/src/drivers/virtio.rs delete mode 100644 platform/axplat-dyn/src/rknpu.rs create mode 100644 platform/riscv64-visionfive2/src/drivers.rs create mode 100644 platform/x86-qemu-q35/src/drivers.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a57ea56fb9..8d935830c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -510,7 +510,7 @@ jobs: command: cargo xtask starry test board --board licheerv-nano-sg2002 cache_key: "" container_image: "" - required_repository_owner: rcore-os + limit_to_owner: rcore-os main_pr_only: false uses: ./.github/workflows/reusable-command.yml with: diff --git a/Cargo.lock b/Cargo.lock index ed18e2c0ea..28751eaa9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "aarch64-cpu" @@ -243,6 +243,8 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" name = "arceos-affinity" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -250,6 +252,8 @@ dependencies = [ name = "arceos-backtrace" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "axbacktrace", ] @@ -258,6 +262,8 @@ dependencies = [ name = "arceos-backtrace-raw-badfp" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "axbacktrace", ] @@ -266,6 +272,8 @@ dependencies = [ name = "arceos-backtrace-raw-basic" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "axbacktrace", ] @@ -274,6 +282,8 @@ dependencies = [ name = "arceos-backtrace-raw-normal" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "axbacktrace", ] @@ -282,6 +292,8 @@ dependencies = [ name = "arceos-display" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "embedded-graphics", ] @@ -290,6 +302,8 @@ dependencies = [ name = "arceos-exception" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -298,8 +312,10 @@ name = "arceos-fs-shell" version = "0.3.1" dependencies = [ "ax-crate-interface", + "ax-driver", "ax-fs-ramfs", "ax-fs-vfs", + "ax-hal", "ax-std", ] @@ -307,6 +323,8 @@ dependencies = [ name = "arceos-ipi" version = "0.3.0" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -314,6 +332,8 @@ dependencies = [ name = "arceos-irq" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -321,6 +341,8 @@ dependencies = [ name = "arceos-lockdep" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-kspin", "ax-std", "axfs-ng-vfs", @@ -330,6 +352,8 @@ dependencies = [ name = "arceos-memtest" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "rand 0.8.6", ] @@ -338,6 +362,8 @@ dependencies = [ name = "arceos-net-echoserver" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -345,6 +371,8 @@ dependencies = [ name = "arceos-net-httpclient" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-io", "ax-std", ] @@ -353,6 +381,8 @@ dependencies = [ name = "arceos-net-httpserver" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -360,6 +390,8 @@ dependencies = [ name = "arceos-net-udpserver" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -367,6 +399,8 @@ dependencies = [ name = "arceos-parallel" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", "rand 0.8.6", ] @@ -375,6 +409,8 @@ dependencies = [ name = "arceos-priority" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -387,8 +423,10 @@ name = "arceos-rust-interface" version = "1.0.3" dependencies = [ "ax-api", + "ax-driver", "ax-errno", "ax-feat", + "ax-hal", "ax-io", "ax-kspin", "ax-posix-api", @@ -401,6 +439,8 @@ dependencies = [ name = "arceos-sleep" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -408,6 +448,7 @@ dependencies = [ name = "arceos-stack-guard-page" version = "0.3.1" dependencies = [ + "ax-hal", "ax-std", ] @@ -470,6 +511,8 @@ dependencies = [ name = "arceos-tls" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -477,6 +520,8 @@ dependencies = [ name = "arceos-wait-queue" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -484,6 +529,8 @@ dependencies = [ name = "arceos-yield" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -505,13 +552,13 @@ name = "arm-scmi-rs" version = "0.1.2" dependencies = [ "aarch64-cpu-ext", - "ax-kspin", "bitflags 2.11.1", "log", "mbarrier", "nb", "num-align", "smccc", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -608,9 +655,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" @@ -673,7 +720,6 @@ dependencies = [ "ax-config", "ax-display", "ax-dma", - "ax-driver", "ax-errno", "ax-feat", "ax-fs", @@ -795,10 +841,10 @@ dependencies = [ name = "ax-display" version = "0.5.14" dependencies = [ - "ax-driver", "ax-lazyinit", "ax-sync", "log", + "rdif-display", ] [[package]] @@ -817,101 +863,54 @@ dependencies = [ [[package]] name = "ax-driver" -version = "0.5.16" +version = "0.6.0" dependencies = [ + "anyhow", + "arm-scmi-rs", "ax-alloc", - "ax-config", + "ax-arm-pl031", "ax-crate-interface", - "ax-dma", - "ax-driver-base", - "ax-driver-block", - "ax-driver-display", - "ax-driver-input", - "ax-driver-net", - "ax-driver-pci", - "ax-driver-virtio", - "ax-driver-vsock", "ax-errno", - "ax-hal", - "axplat-dyn", - "cfg-if", - "log", - "smallvec", -] - -[[package]] -name = "ax-driver-base" -version = "0.3.12" - -[[package]] -name = "ax-driver-block" -version = "0.3.14" -dependencies = [ - "ax-driver-base", - "bcm2835-sdhci", - "gpt_disk_io", - "log", - "sg200x-bsp", - "simple-ahci", - "simple-sdmmc", -] - -[[package]] -name = "ax-driver-display" -version = "0.3.12" -dependencies = [ - "ax-driver-base", -] - -[[package]] -name = "ax-driver-input" -version = "0.3.13" -dependencies = [ - "ax-driver-base", - "strum 0.27.2", -] - -[[package]] -name = "ax-driver-net" -version = "0.3.13" -dependencies = [ - "ax-driver-base", + "ax-kernel-guard", "ax-kspin", - "bitflags 2.11.1", + "axklib", + "bcm2835-sdhci", + "crab-usb", + "dma-api", + "dwmmc-host", + "eth-intel", + "fdt-edit", "fxmac_rs", + "heapless 0.9.3", "ixgbe-driver", "log", -] - -[[package]] -name = "ax-driver-pci" -version = "0.3.12" -dependencies = [ - "virtio-drivers", -] - -[[package]] -name = "ax-driver-virtio" -version = "0.4.1" -dependencies = [ - "ax-driver-base", - "ax-driver-block", - "ax-driver-display", - "ax-driver-input", - "ax-driver-net", - "ax-driver-vsock", - "log", + "mmio-api", + "pcie", + "phytium-mci-host", + "ramdisk", + "rd-block", + "rd-net", + "rdif-clk", + "rdif-display", + "rdif-input", + "rdif-intc", + "rdif-pcie", + "rdif-vsock", + "rdrive", + "realtek-rtl8125", + "rk3588-pci", + "rockchip-npu", + "rockchip-pm", + "rockchip-soc", + "sdhci-host", + "sdmmc-protocol", + "sg200x-bsp", + "simple-ahci", + "some-serial", + "spin", "virtio-drivers", ] -[[package]] -name = "ax-driver-vsock" -version = "0.3.12" -dependencies = [ - "ax-driver-base", - "log", -] - [[package]] name = "ax-errno" version = "0.6.0" @@ -936,6 +935,7 @@ dependencies = [ "ax-kspin", "ax-log", "ax-net", + "ax-net-ng", "ax-runtime", "ax-sync", "ax-task", @@ -947,7 +947,6 @@ name = "ax-fs" version = "0.5.14" dependencies = [ "ax-cap-access", - "ax-driver", "ax-errno", "ax-fs-devfs", "ax-fs-ramfs", @@ -976,7 +975,6 @@ name = "ax-fs-ng" version = "0.5.15" dependencies = [ "ax-alloc", - "ax-driver", "ax-errno", "ax-hal", "ax-io", @@ -991,6 +989,7 @@ dependencies = [ "log", "lru 0.16.4", "lwext4_rust", + "rd-block-volume", "rsext4", "scope-local", "slab", @@ -1028,15 +1027,22 @@ dependencies = [ "ax-page-table-multiarch", "ax-percpu", "ax-plat", + "ax-plat-aarch64-bsta1000b", + "ax-plat-aarch64-phytium-pi", "ax-plat-aarch64-qemu-virt", + "ax-plat-aarch64-raspi", "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", + "ax-plat-riscv64-sg2002", "ax-plat-x86-pc", "axplat-dyn", + "axplat-riscv64-visionfive2", + "axplat-x86-qemu-q35", "cfg-if", "fdt-parser", "heapless 0.9.3", "log", + "rdrive", "spin", "toml 1.1.2+spec-1.1.0", ] @@ -1049,6 +1055,8 @@ version = "0.3.7" name = "ax-helloworld" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -1056,6 +1064,8 @@ dependencies = [ name = "ax-helloworld-myplat" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-plat-aarch64-bsta1000b", "ax-plat-aarch64-phytium-pi", "ax-plat-aarch64-qemu-virt", @@ -1071,6 +1081,8 @@ dependencies = [ name = "ax-httpclient" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -1078,6 +1090,8 @@ dependencies = [ name = "ax-httpserver" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -1085,10 +1099,10 @@ dependencies = [ name = "ax-input" version = "0.5.14" dependencies = [ - "ax-driver", "ax-lazyinit", "ax-sync", "log", + "rdif-input", ] [[package]] @@ -1141,8 +1155,10 @@ version = "0.4.7" name = "ax-libc" version = "0.5.15" dependencies = [ + "ax-driver", "ax-errno", "ax-feat", + "ax-hal", "ax-io", "ax-posix-api", "bindgen 0.72.1", @@ -1203,11 +1219,11 @@ dependencies = [ name = "ax-net" version = "0.5.14" dependencies = [ - "ax-driver", "ax-errno", "ax-hal", "ax-io", "ax-lazyinit", + "ax-net-ng", "ax-sync", "ax-task", "cfg-if", @@ -1223,7 +1239,6 @@ dependencies = [ "async-channel", "async-trait", "ax-config", - "ax-driver", "ax-errno", "ax-fs-ng", "ax-hal", @@ -1239,6 +1254,8 @@ dependencies = [ "event-listener", "hashbrown 0.16.1", "log", + "rd-net", + "rdif-vsock", "ringbuf", "smoltcp 0.13.1", "spin", @@ -1302,6 +1319,7 @@ dependencies = [ "ax-plat-macros", "bitflags 2.11.1", "const-str", + "rdrive", ] [[package]] @@ -1316,6 +1334,7 @@ dependencies = [ "ax-plat-aarch64-peripherals", "dw_apb_uart", "log", + "rdrive", ] [[package]] @@ -1345,6 +1364,7 @@ dependencies = [ "ax-plat", "ax-plat-aarch64-peripherals", "log", + "rdrive", ] [[package]] @@ -1357,6 +1377,7 @@ dependencies = [ "ax-plat", "ax-plat-aarch64-peripherals", "log", + "rdrive", ] [[package]] @@ -1370,6 +1391,7 @@ dependencies = [ "ax-plat", "ax-plat-aarch64-peripherals", "log", + "rdrive", ] [[package]] @@ -1385,6 +1407,7 @@ dependencies = [ "chrono", "log", "loongArch64", + "rdrive", "uart_16550 0.5.0", ] @@ -1409,6 +1432,7 @@ dependencies = [ "ax-plat", "ax-riscv-plic", "log", + "rdrive", "riscv 0.16.0", "riscv_goldfish", "sbi-rt 0.0.3", @@ -1427,6 +1451,7 @@ dependencies = [ "ax-riscv-plic", "dw_uart_rs", "log", + "rdrive", "riscv 0.16.0", "sbi-rt 0.0.3", ] @@ -1447,6 +1472,7 @@ dependencies = [ "log", "multiboot", "raw-cpuid 11.6.0", + "rdrive", "uart_16550 0.5.0", "x2apic", "x86", @@ -1493,6 +1519,7 @@ dependencies = [ "ax-ctor-bare", "ax-display", "ax-driver", + "ax-errno", "ax-fs", "ax-fs-ng", "ax-hal", @@ -1513,6 +1540,10 @@ dependencies = [ "cfg-if", "chrono", "indoc", + "rd-block", + "rd-net", + "rdrive", + "spin", ] [[package]] @@ -1526,6 +1557,8 @@ dependencies = [ name = "ax-shell" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -1751,6 +1784,8 @@ version = "0.5.9" dependencies = [ "ax-errno", "ax-memory-addr", + "dma-api", + "mmio-api", "trait-ffi", ] @@ -1763,44 +1798,18 @@ name = "axplat-dyn" version = "0.6.2" dependencies = [ "anyhow", - "arm-scmi-rs", "ax-alloc", - "ax-arm-pl031", "ax-config-macros", "ax-cpu", - "ax-driver-base", - "ax-driver-block", - "ax-driver-net", - "ax-driver-virtio", + "ax-driver", "ax-errno", - "ax-kspin", "ax-memory-addr", "ax-percpu", "ax-plat", "axklib", - "crab-usb", - "dma-api", - "dwmmc-host", - "eth-intel", - "fdt-edit", "heapless 0.9.3", "log", - "mmio-api", - "pcie", - "phytium-mci-host", - "rd-block", - "rd-net", - "rdif-clk", - "rdif-pcie", "rdrive", - "realtek-rtl8125", - "rk3588-pci", - "rockchip-npu", - "rockchip-pm", - "rockchip-soc", - "sdhci-host", - "sdmmc-protocol", - "some-serial", "somehal", "spin", ] @@ -1816,6 +1825,7 @@ dependencies = [ "ax-plat", "ax-riscv-plic", "log", + "rdrive", "riscv 0.14.0", "riscv_goldfish", "sbi-rt 0.0.3", @@ -1838,6 +1848,7 @@ dependencies = [ "log", "multiboot", "raw-cpuid 11.6.0", + "rdrive", "uart_16550 0.4.0", "x2apic", "x86", @@ -1930,6 +1941,7 @@ dependencies = [ "ax-config", "ax-cpumask", "ax-crate-interface", + "ax-driver", "ax-errno", "ax-hal", "ax-kernel-guard", @@ -2285,9 +2297,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bwbench-client" @@ -2750,7 +2762,6 @@ name = "crab-usb" version = "0.9.3" dependencies = [ "anyhow", - "ax-kspin", "bitflags 2.11.1", "crossbeam", "crossbeam-skiplist", @@ -2977,9 +2988,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -3321,7 +3332,7 @@ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid", - "crypto-common 0.2.1", + "crypto-common 0.2.2", ] [[package]] @@ -3436,9 +3447,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embedded-graphics" @@ -3560,9 +3571,9 @@ dependencies = [ [[package]] name = "enumset" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96a4a12fe60ac746ae295a1a4ecb5bb02debc20856506c8635288065f142de" +checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" dependencies = [ "enumset_derive", ] @@ -3994,9 +4005,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" @@ -4109,28 +4120,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "gpt_disk_io" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb194b955ba40423510e4342a077df49c70a599cf692cd0e30a86a32d8a9c7b1" -dependencies = [ - "bytemuck", - "gpt_disk_types", -] - -[[package]] -name = "gpt_disk_types" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3972298dc4cef533492b66bdd75747133618917f44885a11dc8abd24c4e3f1f0" -dependencies = [ - "bytemuck", - "crc", - "ucs2", - "uguid", -] - [[package]] name = "h2" version = "0.4.14" @@ -4877,9 +4866,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -5466,9 +5455,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -5581,7 +5570,6 @@ checksum = "300e4bdb6b46b592948e700ea1ef24a4296491f6a0ee722b258040abd15a3714" name = "nvme-driver" version = "0.4.2" dependencies = [ - "ax-kspin", "byte-unit", "dma-api", "fdt-edit", @@ -5589,6 +5577,7 @@ dependencies = [ "mmio-api", "pcie", "rd-block", + "spin", "tock-registers 0.10.1", ] @@ -6212,7 +6201,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6246,10 +6235,10 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" name = "ramdisk" version = "0.1.1" dependencies = [ - "ax-kspin", "dma-api", "rd-block", "rdif-block", + "spin", ] [[package]] @@ -6471,6 +6460,10 @@ dependencies = [ "spin_on", ] +[[package]] +name = "rd-block-volume" +version = "0.1.0" + [[package]] name = "rd-net" version = "0.1.1" @@ -6515,6 +6508,14 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "rdif-display" +version = "0.1.0" +dependencies = [ + "rdif-base", + "thiserror 2.0.18", +] + [[package]] name = "rdif-eth" version = "0.2.1" @@ -6524,6 +6525,14 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "rdif-input" +version = "0.1.0" +dependencies = [ + "rdif-base", + "thiserror 2.0.18", +] + [[package]] name = "rdif-intc" version = "0.14.1" @@ -6553,11 +6562,11 @@ dependencies = [ name = "rdif-serial" version = "0.7.1" dependencies = [ - "ax-kspin", "bitflags 2.11.1", "futures", "heapless 0.9.3", "rdif-base", + "spin", "thiserror 2.0.18", ] @@ -6575,11 +6584,18 @@ dependencies = [ "rdif-base", ] +[[package]] +name = "rdif-vsock" +version = "0.1.0" +dependencies = [ + "rdif-base", + "thiserror 2.0.18", +] + [[package]] name = "rdrive" version = "0.20.1" dependencies = [ - "ax-kspin", "fdt-edit", "fdt-raw", "log", @@ -6608,11 +6624,11 @@ dependencies = [ name = "realtek-rtl8125" version = "0.2.0" dependencies = [ - "ax-kspin", "dma-api", "log", "mmio-api", "rdif-eth", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7382,9 +7398,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -7601,17 +7617,6 @@ dependencies = [ "volatile 0.6.1", ] -[[package]] -name = "simple-sdmmc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f2f7127074b52811ae3b4757e33865a60a8b46e1402c028c301139e0b00d92" -dependencies = [ - "bitfield-struct", - "log", - "volatile 0.6.1", -] - [[package]] name = "siphasher" version = "1.0.3" @@ -7960,7 +7965,9 @@ name = "starryos" version = "0.5.12" dependencies = [ "anyhow", + "ax-driver", "ax-feat", + "ax-hal", "ax-plat-riscv64-sg2002", "axbuild", "axplat-dyn", @@ -8138,9 +8145,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -8955,9 +8962,6 @@ name = "uguid" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c" -dependencies = [ - "bytemuck", -] [[package]] name = "uluru" @@ -9304,9 +9308,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -9317,9 +9321,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -9327,9 +9331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9337,9 +9341,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -9350,9 +9354,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -9412,9 +9416,9 @@ checksum = "dba9c05687e501b2710833fbe2cc7ff8cc24411fb0d81967498ca44598942f88" [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -9675,7 +9679,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -9684,7 +9688,16 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", ] [[package]] @@ -9702,14 +9715,31 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -9727,48 +9757,96 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.7.15" diff --git a/Cargo.toml b/Cargo.toml index f149142c12..e5f46a6136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,6 @@ members = [ "components/x86_vlapic", "components/axconfig-gen/axconfig-gen", "components/axconfig-gen/axconfig-macros", - "components/axdriver_crates/axdriver_*", "components/axfs_crates/axfs_devfs", "components/axfs_crates/axfs_ramfs", "components/axfs_crates/axfs_vfs", @@ -90,6 +89,7 @@ members = [ "components/percpu/percpu", "components/percpu/percpu_macros", + "drivers/ax-driver", "drivers/blk/*", "drivers/examples/*", "drivers/firmware/*", @@ -125,7 +125,22 @@ members = [ "os/arceos/examples/std/io_test", "os/arceos/examples/std/thread_test", "os/arceos/examples/std/tokio_test", - "os/arceos/modules/*", + "os/arceos/modules/axalloc", + "os/arceos/modules/axconfig", + "os/arceos/modules/axdisplay", + "os/arceos/modules/axdma", + "os/arceos/modules/axfs", + "os/arceos/modules/axfs-ng", + "os/arceos/modules/axhal", + "os/arceos/modules/axinput", + "os/arceos/modules/axipi", + "os/arceos/modules/axlog", + "os/arceos/modules/axmm", + "os/arceos/modules/axnet", + "os/arceos/modules/axnet-ng", + "os/arceos/modules/axruntime", + "os/arceos/modules/axsync", + "os/arceos/modules/axtask", "os/arceos/tools/bwbench_client", "os/arceos/tools/deptool", "os/arceos/tools/raspi4/chainloader", @@ -195,15 +210,8 @@ ax-ctor-bare = { version = "0.4.8", path = "components/ctor_bare/ctor_bare" } ax-ctor-bare-macros = { version = "0.4.8", path = "components/ctor_bare/ctor_bare_macros" } ax-display = { version = "0.5.14", path = "os/arceos/modules/axdisplay" } ax-dma = { version = "0.5.15", path = "os/arceos/modules/axdma" } -ax-driver = { version = "0.5.16", path = "os/arceos/modules/axdriver" } -ax-driver-base = { version = "0.3.12", path = "components/axdriver_crates/axdriver_base" } -ax-driver-block = { version = "0.3.14", path = "components/axdriver_crates/axdriver_block" } -ax-driver-display = { version = "0.3.12", path = "components/axdriver_crates/axdriver_display" } -ax-driver-input = { version = "0.3.13", path = "components/axdriver_crates/axdriver_input" } -ax-driver-net = { version = "0.3.13", path = "components/axdriver_crates/axdriver_net" } -ax-driver-pci = { version = "0.3.12", path = "components/axdriver_crates/axdriver_pci" } -ax-driver-virtio = { version = "0.4.1", path = "components/axdriver_crates/axdriver_virtio" } -ax-driver-vsock = { version = "0.3.12", path = "components/axdriver_crates/axdriver_vsock" } +ax-driver = { version = "0.6.0", path = "drivers/ax-driver" } +virtio-drivers = { version = "0.13.0", default-features = false } ax-errno = { version = "0.6.0", path = "components/axerrno" } ax-feat = { version = "0.5.16", path = "os/arceos/api/axfeat" } ax-fs = { version = "0.5.14", path = "os/arceos/modules/axfs" } @@ -301,18 +309,22 @@ nvme-driver = { version = "0.4.2", path = "drivers/blk/nvme-driver" } pcie = { version = "0.6.1", path = "drivers/pci/pcie" } ramdisk = { version = "0.1.1", path = "drivers/blk/ramdisk" } rd-block = { version = "0.1.2", path = "drivers/blk/rd-block" } +rd-block-volume = { version = "0.1.0", path = "drivers/blk/rd-block-volume" } rd-net = { version = "0.1.1", path = "drivers/net/rd-net" } rdif-base = { version = "0.8.1", path = "drivers/interface/rdif-base" } rdif-block = { version = "0.7.1", path = "drivers/interface/rdif-block" } rdif-clk = { version = "0.5.1", path = "drivers/interface/rdif-clk" } rdif-def = { version = "0.2.3", path = "drivers/interface/rdif-def" } +rdif-display = { version = "0.1.0", path = "drivers/interface/rdif-display" } rdif-eth = { version = "0.2.1", path = "drivers/interface/rdif-eth" } +rdif-input = { version = "0.1.0", path = "drivers/interface/rdif-input" } rdif-intc = { version = "0.14.1", path = "drivers/interface/rdif-intc" } rdif-pcie = { version = "0.2.1", path = "drivers/interface/rdif-pcie" } rdif-power = { version = "0.7.1", path = "drivers/interface/rdif-power" } rdif-serial = { version = "0.7.1", path = "drivers/interface/rdif-serial" } rdif-systick = { version = "0.6.1", path = "drivers/interface/rdif-systick" } rdif-timer = { version = "0.6.1", path = "drivers/interface/rdif-timer" } +rdif-vsock = { version = "0.1.0", path = "drivers/interface/rdif-vsock" } rk3588-pci = { version = "0.2.0", path = "drivers/pci/rk3588-pci" } rockchip-npu = { version = "0.2.1", path = "drivers/npu/rockchip-npu" } realtek-rtl8125 = { version = "0.2.0", path = "drivers/net/realtek-rtl8125" } @@ -382,6 +394,9 @@ fdt-raw = "0.3" thiserror = { version = "2", default-features = false } tock-registers = "0.10" sg200x-bsp = "0.6" +simple-ahci = "0.1.1-preview.1" +bcm2835-sdhci = "0.1.1-preview.1" +ixgbe-driver = "0.1.1-preview.1" x86 = "0.52" [patch.crates-io] diff --git a/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml b/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml index d9886634e6..f9a746df72 100644 --- a/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml +++ b/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml @@ -2,16 +2,16 @@ target = "aarch64-unknown-none-softfloat" env = {} features = [ - "ax-feat/bus-mmio", - "ax-feat/bus-pci", + "ax-hal/plat-dyn", "starry-kernel/plat-dyn", - "axplat-dyn/irq", - "axplat-dyn/pci-list-devices", - "axplat-dyn/realtek-rtl8125", - "axplat-dyn/rockchip-soc", - "axplat-dyn/rockchip-dwc-xhci", - "axplat-dyn/rockchip-sdhci", - "axplat-dyn/rockchip-dwmmc", + "ax-driver/irq", + "ax-driver/pci-list-devices", + "ax-driver/rk3588-pcie", + "ax-driver/realtek-rtl8125", + "ax-driver/rockchip-soc", + "ax-driver/rockchip-dwc-xhci", + "ax-driver/rockchip-sdhci", + "ax-driver/rockchip-dwmmc", "rknpu", ] log = "Warn" diff --git a/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml b/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml index ee0d33423f..a931ef284e 100644 --- a/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml +++ b/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml @@ -2,13 +2,12 @@ target = "aarch64-unknown-none-softfloat" env = {} features = [ - "ax-feat/bus-mmio", - "ax-feat/driver-sdmmc", + "ax-hal/plat-dyn", "starry-kernel/plat-dyn", - "axplat-dyn/rockchip-soc", - "axplat-dyn/rockchip-dwc-xhci", - "axplat-dyn/rockchip-sdhci", - "axplat-dyn/rockchip-dwmmc", + "ax-driver/rockchip-soc", + "ax-driver/rockchip-dwc-xhci", + "ax-driver/rockchip-sdhci", + "ax-driver/rockchip-dwmmc", ] log = "Info" max_cpu_num = 1 diff --git a/components/axdriver_crates/.github/workflows/ci.yml b/components/axdriver_crates/.github/workflows/ci.yml deleted file mode 100644 index 31d461927a..0000000000 --- a/components/axdriver_crates/.github/workflows/ci.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: CI - -on: [push, pull_request] - -jobs: - ci: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust-toolchain: [nightly] - targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: ${{ matrix.rust-toolchain }} - components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features - - name: Unit test - if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - run: cargo test --target ${{ matrix.targets }} -- --nocapture - - doc: - runs-on: ubuntu-latest - strategy: - fail-fast: false - permissions: - contents: write - env: - default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} - RUSTDOCFLAGS: -Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - name: Build docs - run: | - for crate in axdriver_base axdriver_block axdriver_net axdriver_display axdriver_pci axdriver_virtio; - do - cargo rustdoc --all-features -p $crate - done - - name: Deploy to Github Pages - if: ${{ github.ref == env.default-branch }} - uses: JamesIves/github-pages-deploy-action@v4 - with: - single-commit: true - branch: gh-pages - folder: target/doc diff --git a/components/axdriver_crates/.gitignore b/components/axdriver_crates/.gitignore deleted file mode 100644 index ff78c42af5..0000000000 --- a/components/axdriver_crates/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/.vscode -.DS_Store -Cargo.lock diff --git a/components/axdriver_crates/LICENSE b/components/axdriver_crates/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/components/axdriver_crates/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/components/axdriver_crates/PUBLISH_ORDER.md b/components/axdriver_crates/PUBLISH_ORDER.md deleted file mode 100644 index b473383c9e..0000000000 --- a/components/axdriver_crates/PUBLISH_ORDER.md +++ /dev/null @@ -1,16 +0,0 @@ -# 發布至 crates.io 順序 - -依賴關係決定發布順序,請按下列順序執行 `cargo publish`: - -1. **ax-driver-base**(無內部依賴) -2. **ax-driver-pci**(無內部依賴) -3. **axdriver_block**、**axdriver_display**、**axdriver_net**(僅依賴 base,可任意順序) -4. **axdriver_virtio**(依賴 base、block、display、net,最後發布) - -`cargo publish --dry-run` 僅在依賴已存在於 crates.io 時會通過。因此: - -- **ax-driver-base**、**ax-driver-pci**:現在即可 `cargo publish --dry-run` 通過。 -- **axdriver_block**、**axdriver_display**、**axdriver_net**:需先發布 **ax-driver-base** 後,再執行 dry-run 才會通過。 -- **axdriver_virtio**:需先發布 base、block、display、net 後,再執行 dry-run 才會通過。 - -本地開發時,根目錄的 `[patch.crates-io]` 會將上述 crate 指到本地路徑,`cargo build --workspace` 可正常編譯。 diff --git a/components/axdriver_crates/README.md b/components/axdriver_crates/README.md deleted file mode 100644 index e29fc3007a..0000000000 --- a/components/axdriver_crates/README.md +++ /dev/null @@ -1,49 +0,0 @@ -

axdriver_crates

- -

Workspace for ArceOS driver abstraction crates

- -
- -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`axdriver_crates` is a workspace that groups related TGOSKits components under a unified layout. It helps organize closely related crates that are typically developed, versioned, and used together. - -> axdriver_crates was derived from https://github.com/arceos-org/axdriver_crates - -## Workspace Members - -- `axdriver_base` -- `axdriver_block` -- `axdriver_net` -- `axdriver_display` -- `axdriver_pci` -- `axdriver_virtio` -- `axdriver_input` -- `axdriver_vsock` - -## Quick Start - -```bash -# Enter the workspace directory -cd components/axdriver_crates - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --workspace --all-targets --all-features - -# Run tests -cargo test --workspace --all-features -``` - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/README_CN.md b/components/axdriver_crates/README_CN.md deleted file mode 100644 index 161b50c6c4..0000000000 --- a/components/axdriver_crates/README_CN.md +++ /dev/null @@ -1,49 +0,0 @@ -

axdriver_crates

- -

ArceOS 驱动抽象 crate 工作区

- -
- -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`axdriver_crates` 是一个工作区,用于将相关的 TGOSKits 组件放在统一的目录结构下,便于协同开发、版本管理与组合使用。 - -> axdriver_crates 派生自 https://github.com/arceos-org/axdriver_crates - -## 工作区成员 - -- `axdriver_base` -- `axdriver_block` -- `axdriver_net` -- `axdriver_display` -- `axdriver_pci` -- `axdriver_virtio` -- `axdriver_input` -- `axdriver_vsock` - -## 快速开始 - -```bash -# 进入工作区目录 -cd components/axdriver_crates - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --workspace --all-targets --all-features - -# 运行测试 -cargo test --workspace --all-features -``` - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_base/Cargo.toml b/components/axdriver_crates/axdriver_base/Cargo.toml deleted file mode 100644 index 4e0c892d22..0000000000 --- a/components/axdriver_crates/axdriver_base/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ax-driver-base" -edition.workspace = true -version = "0.3.12" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Common interfaces for all kinds of device drivers" -keywords = ["arceos", "driver"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[dependencies] diff --git a/components/axdriver_crates/axdriver_base/README.md b/components/axdriver_crates/axdriver_base/README.md deleted file mode 100644 index 3d75581586..0000000000 --- a/components/axdriver_crates/axdriver_base/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-base

- -

Common interfaces for all kinds of device drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-base.svg)](https://crates.io/crates/ax-driver-base) -[![Docs.rs](https://docs.rs/ax-driver-base/badge.svg)](https://docs.rs/ax-driver-base) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-base` provides Common interfaces for all kinds of device drivers. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-base was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-base = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_base - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_base as _; - -fn main() { - // Integrate `ax-driver-base` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-base](https://docs.rs/ax-driver-base) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_base/README_CN.md b/components/axdriver_crates/axdriver_base/README_CN.md deleted file mode 100644 index 4b8a109568..0000000000 --- a/components/axdriver_crates/axdriver_base/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-base

- -

Common interfaces for all kinds of device drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-base.svg)](https://crates.io/crates/ax-driver-base) -[![Docs.rs](https://docs.rs/ax-driver-base/badge.svg)](https://docs.rs/ax-driver-base) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-base` 提供了 Common interfaces for all kinds of device drivers。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-base 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-base = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_base - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_base as _; - -fn main() { - // 在这里将 `ax-driver-base` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-base](https://docs.rs/ax-driver-base) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_base/src/lib.rs b/components/axdriver_crates/axdriver_base/src/lib.rs deleted file mode 100644 index 72736927bc..0000000000 --- a/components/axdriver_crates/axdriver_base/src/lib.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! Device driver interfaces used by [ArceOS][1]. It provides common traits and -//! types for implementing a device driver. -//! -//! You have to use this crate with the following crates for corresponding -//! device types: -//! -//! - [`axdriver_block`][2]: Common traits for block storage drivers. -//! - [`axdriver_display`][3]: Common traits and types for graphics display drivers. -//! - [`axdriver_net`][4]: Common traits and types for network (NIC) drivers. -//! -//! [1]: https://github.com/arceos-org/arceos -//! [2]: ../ax-driver-block/index.html -//! [3]: ../ax-driver-display/index.html -//! [4]: ../ax-driver-net/index.html - -#![no_std] - -extern crate alloc; - -use alloc::boxed::Box; - -/// All supported device types. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum DeviceType { - /// Block storage device (e.g., disk). - Block, - /// Character device (e.g., serial port). - Char, - /// Network device (e.g., ethernet card). - Net, - /// Graphic display device (e.g., GPU) - Display, - /// Input device (e.g., keyboard, mouse). - Input, - /// Vsock device (e.g., virtio-vsock). - Vsock, -} - -/// The error type for device operation failures. -#[derive(Debug)] -pub enum DevError { - /// An entity already exists. - AlreadyExists, - /// Try again, for non-blocking APIs. - Again, - /// Bad internal state. - BadState, - /// Invalid parameter/argument. - InvalidParam, - /// Input/output error. - Io, - /// Not enough space/cannot allocate memory (DMA). - NoMemory, - /// Device or resource is busy. - ResourceBusy, - /// This operation is unsupported or unimplemented. - Unsupported, -} - -impl core::fmt::Display for DevError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - DevError::AlreadyExists => write!(f, "Entity already exists"), - DevError::Again => write!(f, "Try again"), - DevError::BadState => write!(f, "Bad state"), - DevError::InvalidParam => write!(f, "Invalid parameter"), - DevError::Io => write!(f, "Input/output error"), - DevError::NoMemory => write!(f, "Not enough memory"), - DevError::ResourceBusy => write!(f, "Resource is busy"), - DevError::Unsupported => write!(f, "Unsupported operation"), - } - } -} - -/// A specialized `Result` type for device operations. -pub type DevResult = Result; - -/// Common operations that require all device drivers to implement. -pub trait BaseDriverOps: Send + Sync { - /// The name of the device. - fn device_name(&self) -> &str; - - /// The type of the device. - fn device_type(&self) -> DeviceType; - - /// The IRQ number of the device, if applicable. - fn irq_num(&self) -> Option { - None - } -} - -impl BaseDriverOps for Box { - fn device_name(&self) -> &str { - (**self).device_name() - } - - fn device_type(&self) -> DeviceType { - (**self).device_type() - } - - fn irq_num(&self) -> Option { - (**self).irq_num() - } -} diff --git a/components/axdriver_crates/axdriver_block/CHANGELOG.md b/components/axdriver_crates/axdriver_block/CHANGELOG.md deleted file mode 100644 index f754a45bb6..0000000000 --- a/components/axdriver_crates/axdriver_block/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.14](https://github.com/rcore-os/tgoskits/compare/ax-driver-block-v0.3.13...ax-driver-block-v0.3.14) - 2026-05-22 - -### Added - -- add sg2002 USB UVC camera with ESP-compatible ioctl ([#791](https://github.com/rcore-os/tgoskits/pull/791)) - -## [0.3.13](https://github.com/rcore-os/tgoskits/compare/ax-driver-block-v0.3.12...ax-driver-block-v0.3.13) - 2026-05-16 - -### Added - -- *(sdmmc)* add reusable SD/MMC protocol and host drivers ([#538](https://github.com/rcore-os/tgoskits/pull/538)) diff --git a/components/axdriver_crates/axdriver_block/Cargo.toml b/components/axdriver_crates/axdriver_block/Cargo.toml deleted file mode 100644 index 66b4d33062..0000000000 --- a/components/axdriver_crates/axdriver_block/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "ax-driver-block" -edition.workspace = true -version = "0.3.14" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Common traits and types for block storage drivers" -keywords = ["arceos", "driver", "blk"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[features] -ramdisk = [] -ramdisk-static = [] -sdmmc = ["dep:simple-sdmmc"] -cvsd = ["dep:sg200x-bsp"] -ahci = ["dep:simple-ahci"] -bcm2835-sdhci = ["dep:bcm2835-sdhci"] - -[dependencies] -ax-driver-base = { workspace = true } -gpt_disk_io = { version = "0.16.2", default-features = false } -log = { workspace = true } -simple-sdmmc = { version = "0.1", optional = true } -simple-ahci = { version = "0.1.1-preview.1", optional = true } -bcm2835-sdhci = { version = "0.1.1-preview.1", optional = true } -sg200x-bsp = { workspace = true, optional = true } diff --git a/components/axdriver_crates/axdriver_block/README.md b/components/axdriver_crates/axdriver_block/README.md deleted file mode 100644 index f76d5d08bd..0000000000 --- a/components/axdriver_crates/axdriver_block/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-block

- -

Common traits and types for block storage drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-block.svg)](https://crates.io/crates/ax-driver-block) -[![Docs.rs](https://docs.rs/ax-driver-block/badge.svg)](https://docs.rs/ax-driver-block) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-block` provides Common traits and types for block storage drivers. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-block was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-block = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_block - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_block as _; - -fn main() { - // Integrate `ax-driver-block` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-block](https://docs.rs/ax-driver-block) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_block/README_CN.md b/components/axdriver_crates/axdriver_block/README_CN.md deleted file mode 100644 index 30c5f2463d..0000000000 --- a/components/axdriver_crates/axdriver_block/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-block

- -

Common traits and types for block storage drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-block.svg)](https://crates.io/crates/ax-driver-block) -[![Docs.rs](https://docs.rs/ax-driver-block/badge.svg)](https://docs.rs/ax-driver-block) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-block` 提供了 Common traits and types for block storage drivers。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-block 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-block = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_block - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_block as _; - -fn main() { - // 在这里将 `ax-driver-block` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-block](https://docs.rs/ax-driver-block) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_block/src/ahci.rs b/components/axdriver_crates/axdriver_block/src/ahci.rs deleted file mode 100644 index 97f8996ebe..0000000000 --- a/components/axdriver_crates/axdriver_block/src/ahci.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! AHCI driver. - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use simple_ahci::AhciDriver as SimpleAhciDriver; -pub use simple_ahci::Hal as AhciHal; - -use crate::BlockDriverOps; - -/// AHCI driver based on the `simple_ahci` crate. -pub struct AhciDriver(SimpleAhciDriver); - -impl AhciDriver { - /// Try to construct a new AHCI driver from the given MMIO base address. - /// - /// # Safety - /// - /// The caller must ensure that: - /// - `base` is a valid virtual address pointing to the AHCI controller's MMIO register block. - /// - The memory region starting at `base` is properly mapped and accessible. - /// - No other code is concurrently accessing the same AHCI controller. - /// - The AHCI controller hardware is present and functional at the given address. - pub unsafe fn try_new(base: usize) -> Option { - unsafe { SimpleAhciDriver::::try_new(base) }.map(AhciDriver) - } -} - -impl BaseDriverOps for AhciDriver { - fn device_name(&self) -> &str { - "ahci" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Block - } -} - -impl BlockDriverOps for AhciDriver { - fn block_size(&self) -> usize { - self.0.block_size() - } - - fn num_blocks(&self) -> u64 { - self.0.capacity() - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - if !buf.len().is_multiple_of(self.block_size()) { - return Err(DevError::InvalidParam); - } - if !(buf.as_ptr() as usize).is_multiple_of(4) { - return Err(DevError::InvalidParam); - } - if self.0.read(block_id, buf) { - Ok(()) - } else { - Err(DevError::Io) - } - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - if !buf.len().is_multiple_of(self.block_size()) { - return Err(DevError::InvalidParam); - } - if !(buf.as_ptr() as usize).is_multiple_of(4) { - return Err(DevError::InvalidParam); - } - if self.0.write(block_id, buf) { - Ok(()) - } else { - Err(DevError::Io) - } - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } -} diff --git a/components/axdriver_crates/axdriver_block/src/bcm2835sdhci.rs b/components/axdriver_crates/axdriver_block/src/bcm2835sdhci.rs deleted file mode 100644 index d0c5ef959e..0000000000 --- a/components/axdriver_crates/axdriver_block/src/bcm2835sdhci.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! SD card driver for raspi4 - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use bcm2835_sdhci::{ - Bcm2835SDhci::{BLOCK_SIZE, EmmcCtl}, - SDHCIError, -}; - -use crate::BlockDriverOps; - -/// BCM2835 SDHCI driver (Raspberry Pi SD card). -pub struct SDHCIDriver(EmmcCtl); - -impl SDHCIDriver { - /// Initialize the SDHCI driver, returns `Ok` if successful. - pub fn try_new() -> DevResult { - let mut ctrl = EmmcCtl::new(); - if ctrl.init() == 0 { - log::info!("BCM2835 sdhci: successfully initialized"); - Ok(SDHCIDriver(ctrl)) - } else { - log::warn!("BCM2835 sdhci: init failed"); - Err(DevError::Io) - } - } -} - -fn deal_sdhci_err(err: SDHCIError) -> DevError { - match err { - SDHCIError::Io => DevError::Io, - SDHCIError::AlreadyExists => DevError::AlreadyExists, - SDHCIError::Again => DevError::Again, - SDHCIError::BadState => DevError::BadState, - SDHCIError::InvalidParam => DevError::InvalidParam, - SDHCIError::NoMemory => DevError::NoMemory, - SDHCIError::ResourceBusy => DevError::ResourceBusy, - SDHCIError::Unsupported => DevError::Unsupported, - } -} - -impl BaseDriverOps for SDHCIDriver { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - - fn device_name(&self) -> &str { - "bcm2835_sdhci" - } -} - -impl BlockDriverOps for SDHCIDriver { - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - if buf.len() < BLOCK_SIZE { - return Err(DevError::InvalidParam); - } - let (prefix, aligned_buf, suffix) = unsafe { buf.align_to_mut::() }; - if !prefix.is_empty() || !suffix.is_empty() { - return Err(DevError::InvalidParam); - } - self.0 - .read_block(block_id as u32, 1, aligned_buf) - .map_err(deal_sdhci_err) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - if buf.len() < BLOCK_SIZE { - return Err(DevError::Io); - } - let (prefix, aligned_buf, suffix) = unsafe { buf.align_to::() }; - if !prefix.is_empty() || !suffix.is_empty() { - return Err(DevError::InvalidParam); - } - self.0 - .write_block(block_id as u32, 1, aligned_buf) - .map_err(deal_sdhci_err) - } - fn flush(&mut self) -> DevResult { - Ok(()) - } - - #[inline] - fn num_blocks(&self) -> u64 { - self.0.get_block_num() - } - - #[inline] - fn block_size(&self) -> usize { - self.0.get_block_size() - } -} diff --git a/components/axdriver_crates/axdriver_block/src/cvsd.rs b/components/axdriver_crates/axdriver_block/src/cvsd.rs deleted file mode 100644 index e0bdae9f73..0000000000 --- a/components/axdriver_crates/axdriver_block/src/cvsd.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! A SD Card driver for cv181x-sd device - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use sg200x_bsp::sdmmc::Sdmmc; - -use crate::BlockDriverOps; - -const BLOCK_SIZE: usize = 512; - -/// CVSD driver based on SG200x BSP SD/MMC. -pub struct CvsdDriver(Sdmmc); - -unsafe impl Send for CvsdDriver {} -unsafe impl Sync for CvsdDriver {} - -impl CvsdDriver { - /// Initializes SD/MMC and creates a new [`CvsdDriver`]. - pub fn new(sdmmc: usize, syscon: usize) -> DevResult { - let sdmmc = unsafe { Sdmmc::new(sdmmc, syscon) }; - sdmmc.init().map_err(|_| DevError::Io)?; - sdmmc.clk_en(true); - Ok(Self(sdmmc)) - } - - fn checked_u32_lba(block_id: u64, offset: usize) -> DevResult { - let lba = block_id - .checked_add(u64::try_from(offset).map_err(|_| DevError::InvalidParam)?) - .ok_or(DevError::InvalidParam)?; - u32::try_from(lba).map_err(|_| DevError::InvalidParam) - } -} - -impl BaseDriverOps for CvsdDriver { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - - fn device_name(&self) -> &str { - "cvsd" - } -} - -impl BlockDriverOps for CvsdDriver { - fn num_blocks(&self) -> u64 { - self.0.card_capacity_blocks() - } - - fn block_size(&self) -> usize { - BLOCK_SIZE - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - let (blocks, remainder) = buf.as_chunks_mut::<{ BLOCK_SIZE }>(); - - if !remainder.is_empty() { - return Err(DevError::InvalidParam); - } - - for (i, block) in blocks.iter_mut().enumerate() { - self.0 - .read_block(Self::checked_u32_lba(block_id, i)?, block) - .map_err(|_| DevError::Io)?; - } - - Ok(()) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - let (blocks, remainder) = buf.as_chunks::<{ BLOCK_SIZE }>(); - - if !remainder.is_empty() { - return Err(DevError::InvalidParam); - } - - for (i, block) in blocks.iter().enumerate() { - self.0 - .write_block(Self::checked_u32_lba(block_id, i)?, block) - .map_err(|_| DevError::Io)?; - } - - Ok(()) - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } -} diff --git a/components/axdriver_crates/axdriver_block/src/lib.rs b/components/axdriver_crates/axdriver_block/src/lib.rs deleted file mode 100644 index 40ed6d9d6a..0000000000 --- a/components/axdriver_crates/axdriver_block/src/lib.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Common traits and types for block storage device drivers (i.e. disk). - -#![no_std] -#![cfg_attr(doc, feature(doc_cfg))] - -extern crate alloc; - -#[cfg(test)] -extern crate std; - -use alloc::boxed::Box; - -#[cfg(feature = "bcm2835-sdhci")] -pub mod bcm2835sdhci; - -#[cfg(feature = "cvsd")] -pub mod cvsd; - -#[cfg(feature = "ramdisk")] -pub mod ramdisk; - -#[cfg(feature = "ramdisk-static")] -pub mod ramdisk_static; - -#[cfg(feature = "ahci")] -pub mod ahci; -pub mod partition; -#[cfg(feature = "sdmmc")] -pub mod sdmmc; - -#[doc(no_inline)] -pub use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; - -/// Operations that require a block storage device driver to implement. -pub trait BlockDriverOps: BaseDriverOps { - /// The number of blocks in this storage device. - /// - /// The total size of the device is `num_blocks() * block_size()`. - fn num_blocks(&self) -> u64; - /// The size of each block in bytes. - fn block_size(&self) -> usize; - - /// Reads blocked data from the given block. - /// - /// The size of the buffer may exceed the block size, in which case multiple - /// contiguous blocks will be read. - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult; - - /// Writes blocked data to the given block. - /// - /// The size of the buffer may exceed the block size, in which case multiple - /// contiguous blocks will be written. - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult; - - /// Flushes the device to write all pending data to the storage. - fn flush(&mut self) -> DevResult; -} - -impl BlockDriverOps for Box { - fn num_blocks(&self) -> u64 { - (**self).num_blocks() - } - - fn block_size(&self) -> usize { - (**self).block_size() - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - (**self).read_block(block_id, buf) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - (**self).write_block(block_id, buf) - } - - fn flush(&mut self) -> DevResult { - (**self).flush() - } -} diff --git a/components/axdriver_crates/axdriver_block/src/partition/device.rs b/components/axdriver_crates/axdriver_block/src/partition/device.rs deleted file mode 100644 index 3acc942b94..0000000000 --- a/components/axdriver_crates/axdriver_block/src/partition/device.rs +++ /dev/null @@ -1,71 +0,0 @@ -use super::PartitionRegion; -use crate::{BaseDriverOps, BlockDriverOps, DevError, DevResult, DeviceType}; - -pub struct PartitionBlockDevice { - inner: T, - region: PartitionRegion, -} - -impl PartitionBlockDevice { - pub const fn new(inner: T, region: PartitionRegion) -> Self { - Self { inner, region } - } - - pub const fn region(&self) -> PartitionRegion { - self.region - } - - fn check_io_bounds(&self, block_id: u64, buf_len: usize) -> DevResult { - let block_size = self.inner.block_size(); - if block_size == 0 || !buf_len.is_multiple_of(block_size) { - return Err(DevError::InvalidParam); - } - - let blocks = u64::try_from(buf_len / block_size).map_err(|_| DevError::BadState)?; - let end_block = block_id.checked_add(blocks).ok_or(DevError::BadState)?; - if end_block > self.num_blocks() { - return Err(DevError::InvalidParam); - } - - Ok(()) - } -} - -impl BaseDriverOps for PartitionBlockDevice { - fn device_name(&self) -> &str { - self.inner.device_name() - } - - fn device_type(&self) -> DeviceType { - self.inner.device_type() - } - - fn irq_num(&self) -> Option { - self.inner.irq_num() - } -} - -impl BlockDriverOps for PartitionBlockDevice { - fn num_blocks(&self) -> u64 { - self.region.num_blocks() - } - - fn block_size(&self) -> usize { - self.inner.block_size() - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - self.check_io_bounds(block_id, buf.len())?; - self.inner.read_block(self.region.start_lba + block_id, buf) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - self.check_io_bounds(block_id, buf.len())?; - self.inner - .write_block(self.region.start_lba + block_id, buf) - } - - fn flush(&mut self) -> DevResult { - self.inner.flush() - } -} diff --git a/components/axdriver_crates/axdriver_block/src/partition/gpt.rs b/components/axdriver_crates/axdriver_block/src/partition/gpt.rs deleted file mode 100644 index 376d42f6dc..0000000000 --- a/components/axdriver_crates/axdriver_block/src/partition/gpt.rs +++ /dev/null @@ -1,384 +0,0 @@ -use alloc::{ - format, - string::{String, ToString}, - vec, - vec::Vec, -}; - -use gpt_disk_io::{ - BlockIo, Disk, DiskError, - gpt_disk_types::{BlockSize, GptHeader, GptHeaderRevision, GptPartitionEntryArray, Lba}, -}; -use log::{debug, warn}; - -use super::{PartitionInfo, PartitionRegion, PartitionTable, PartitionTableKind}; -use crate::{BlockDriverOps, DevError, DevResult}; - -struct BlockDriverAdapter<'a, T: ?Sized>(&'a mut T); - -impl BlockIo for BlockDriverAdapter<'_, T> { - type Error = DevError; - - fn block_size(&self) -> BlockSize { - BlockSize::from_usize(self.0.block_size()).expect("validated block size") - } - - fn num_blocks(&mut self) -> Result { - Ok(self.0.num_blocks()) - } - - fn read_blocks(&mut self, start_lba: Lba, dst: &mut [u8]) -> Result<(), Self::Error> { - self.block_size().assert_valid_block_buffer(dst); - self.0.read_block(start_lba.0, dst) - } - - fn write_blocks(&mut self, start_lba: Lba, src: &[u8]) -> Result<(), Self::Error> { - self.block_size().assert_valid_block_buffer(src); - self.0.write_block(start_lba.0, src) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.0.flush() - } -} - -#[derive(Debug)] -enum GptProbeResult { - Absent, - Valid(PartitionTable), -} - -pub(super) fn scan_gpt_partitions( - inner: &mut T, -) -> DevResult> { - match probe_gpt(inner)? { - GptProbeResult::Absent => Ok(None), - GptProbeResult::Valid(table) => Ok(Some(table)), - } -} - -fn probe_gpt(inner: &mut T) -> DevResult { - let block_size = BlockSize::from_usize(inner.block_size()).ok_or(DevError::InvalidParam)?; - let block_size_usize = block_size.to_usize().ok_or(DevError::BadState)?; - let num_blocks = inner.num_blocks(); - if num_blocks < 2 { - return Ok(GptProbeResult::Absent); - } - - let mut block_buf = vec![0u8; block_size_usize]; - let mbr_ok = validate_protective_mbr(inner, &mut block_buf); - let mut disk = Disk::new(BlockDriverAdapter(inner)).map_err(map_disk_error)?; - - let primary = match try_read_valid_header(&mut disk, Lba(1), &mut block_buf, num_blocks) { - Ok(header) => header, - Err(HeaderProbeError::Absent) => None, - Err(HeaderProbeError::Invalid(err)) => return Err(err), - }; - let secondary_lba = num_blocks.checked_sub(1).ok_or(DevError::BadState)?; - let secondary = - match try_read_valid_header(&mut disk, Lba(secondary_lba), &mut block_buf, num_blocks) { - Ok(header) => header, - Err(HeaderProbeError::Absent) => None, - Err(HeaderProbeError::Invalid(err)) => return Err(err), - }; - - let header = match (primary, secondary) { - (None, None) => return Ok(GptProbeResult::Absent), - (Some(primary), Some(secondary)) => { - validate_header_pair(&primary, &secondary, num_blocks)?; - primary - } - (Some(primary), None) => { - warn!("secondary GPT header is unavailable or invalid; using primary header only"); - primary - } - (None, Some(secondary)) => { - warn!("primary GPT header is unavailable or invalid; using secondary header only"); - secondary - } - }; - - if let Err(err) = mbr_ok { - warn!("protective MBR validation failed: {err}"); - } - - let table = load_partition_table(&mut disk, &header, block_size)?; - Ok(GptProbeResult::Valid(table)) -} - -fn validate_protective_mbr( - inner: &mut T, - block_buf: &mut [u8], -) -> Result<(), String> { - inner - .read_block(0, block_buf) - .map_err(|err| format!("failed to read protective MBR: {err:?}"))?; - - let mbr_bytes = &block_buf[..512]; - let signature = &mbr_bytes[510..512]; - if signature != [0x55, 0xaa].as_slice() { - return Err("invalid protective MBR signature".into()); - } - - let partitions = &mbr_bytes[446..510]; - let mut has_protective = false; - for chunk in partitions.chunks_exact(16) { - let os_indicator = chunk[4]; - if os_indicator == 0xee { - has_protective = true; - break; - } - } - - if !has_protective { - return Err("protective MBR does not contain an 0xEE partition".into()); - } - - Ok(()) -} - -#[derive(Debug)] -enum HeaderProbeError { - Absent, - Invalid(DevError), -} - -fn try_read_valid_header( - disk: &mut Disk, - lba: Lba, - block_buf: &mut [u8], - num_blocks: u64, -) -> Result, HeaderProbeError> -where - I: BlockIo, -{ - let header = disk - .read_gpt_header(lba, block_buf) - .map_err(map_disk_error) - .map_err(HeaderProbeError::Invalid)?; - - if !header.is_signature_valid() { - return Err(HeaderProbeError::Absent); - } - - validate_header(&header, lba.0, num_blocks, block_buf.len()) - .map_err(HeaderProbeError::Invalid)?; - Ok(Some(header)) -} - -fn validate_header( - header: &GptHeader, - expected_lba: u64, - num_blocks: u64, - block_size: usize, -) -> DevResult { - if header.revision != GptHeaderRevision::VERSION_1_0 { - return Err(DevError::InvalidParam); - } - - let header_size = header.header_size.to_u32(); - if header_size < u32::try_from(core::mem::size_of::()).unwrap() - || usize::try_from(header_size).map_err(|_| DevError::BadState)? > block_size - { - return Err(DevError::InvalidParam); - } - - if header.reserved.to_u32() != 0 { - return Err(DevError::InvalidParam); - } - - if header.my_lba.to_u64() != expected_lba { - return Err(DevError::InvalidParam); - } - - let last_block = num_blocks.checked_sub(1).ok_or(DevError::BadState)?; - let alternate_lba = header.alternate_lba.to_u64(); - if alternate_lba > last_block || alternate_lba == expected_lba { - return Err(DevError::InvalidParam); - } - - if header.calculate_header_crc32() != header.header_crc32 { - return Err(DevError::InvalidParam); - } - - let layout = header - .get_partition_entry_array_layout() - .map_err(|_| DevError::InvalidParam)?; - let entry_array_bytes = layout - .num_bytes_rounded_to_block( - BlockSize::from_usize(block_size).ok_or(DevError::InvalidParam)?, - ) - .ok_or(DevError::BadState)?; - let entry_array_blocks = entry_array_bytes - .checked_div(u64::try_from(block_size).map_err(|_| DevError::BadState)?) - .ok_or(DevError::BadState)?; - let entry_array_start = layout.start_lba.to_u64(); - let entry_array_end = entry_array_start - .checked_add(entry_array_blocks) - .ok_or(DevError::BadState)?; - if entry_array_end > num_blocks { - return Err(DevError::InvalidParam); - } - - let first_usable = header.first_usable_lba.to_u64(); - let last_usable = header.last_usable_lba.to_u64(); - if first_usable > last_usable || last_usable > last_block { - return Err(DevError::InvalidParam); - } - - Ok(()) -} - -fn validate_header_pair(primary: &GptHeader, secondary: &GptHeader, num_blocks: u64) -> DevResult { - let last_block = num_blocks.checked_sub(1).ok_or(DevError::BadState)?; - let primary_disk_guid = primary.disk_guid; - let secondary_disk_guid = secondary.disk_guid; - if primary.my_lba.to_u64() != 1 - || primary.alternate_lba.to_u64() != last_block - || secondary.my_lba.to_u64() != last_block - || secondary.alternate_lba.to_u64() != 1 - { - return Err(DevError::InvalidParam); - } - - if primary.first_usable_lba != secondary.first_usable_lba - || primary.last_usable_lba != secondary.last_usable_lba - || primary_disk_guid != secondary_disk_guid - || primary.number_of_partition_entries != secondary.number_of_partition_entries - || primary.size_of_partition_entry != secondary.size_of_partition_entry - || primary.partition_entry_array_crc32 != secondary.partition_entry_array_crc32 - { - return Err(DevError::InvalidParam); - } - - Ok(()) -} - -fn load_partition_table( - disk: &mut Disk, - header: &GptHeader, - block_size: BlockSize, -) -> DevResult -where - I: BlockIo, -{ - let layout = header - .get_partition_entry_array_layout() - .map_err(|_| DevError::InvalidParam)?; - let storage_len = layout - .num_bytes_rounded_to_block_as_usize(block_size) - .ok_or(DevError::BadState)?; - let mut storage = vec![0u8; storage_len]; - let entry_array = disk - .read_gpt_partition_entry_array(layout, &mut storage) - .map_err(map_disk_error)?; - validate_partition_array(header, &entry_array)?; - - let mut partitions = Vec::new(); - for index in 0..layout.num_entries { - let entry = entry_array - .get_partition_entry(index) - .ok_or(DevError::BadState)?; - if !entry.is_used() { - continue; - } - - let range = entry.lba_range().ok_or(DevError::InvalidParam)?; - let region = PartitionRegion { - start_lba: range.start().to_u64(), - end_lba: range - .end() - .to_u64() - .checked_add(1) - .ok_or(DevError::BadState)?, - }; - - if region.start_lba < header.first_usable_lba.to_u64() - || region.end_lba.checked_sub(1).ok_or(DevError::BadState)? - > header.last_usable_lba.to_u64() - { - return Err(DevError::InvalidParam); - } - - let name = entry.name.to_string(); - let part_uuid = format_guid_as_partuuid(&entry.unique_partition_guid.to_bytes()); - debug!("validated GPT partition[{index}]: {entry}"); - partitions.push(PartitionInfo { - index: usize::try_from(index).map_err(|_| DevError::BadState)?, - table_kind: PartitionTableKind::Gpt, - region, - name: if name.is_empty() { None } else { Some(name) }, - part_uuid: Some(part_uuid), - bootable: false, - }); - } - - Ok(PartitionTable { - kind: PartitionTableKind::Gpt, - partitions, - }) -} - -fn validate_partition_array( - header: &GptHeader, - entry_array: &GptPartitionEntryArray<'_>, -) -> DevResult { - if entry_array.calculate_crc32() != header.partition_entry_array_crc32 { - return Err(DevError::InvalidParam); - } - - let mut ranges = Vec::new(); - for index in 0..entry_array.layout().num_entries { - let entry = entry_array - .get_partition_entry(index) - .ok_or(DevError::BadState)?; - if !entry.is_used() { - continue; - } - let range = entry.lba_range().ok_or(DevError::InvalidParam)?; - ranges.push((range.start().to_u64(), range.end().to_u64())); - } - - ranges.sort_unstable_by_key(|range| range.0); - for pair in ranges.windows(2) { - let (_, prev_end) = pair[0]; - let (next_start, _) = pair[1]; - if next_start <= prev_end { - return Err(DevError::InvalidParam); - } - } - - Ok(()) -} - -fn format_guid_as_partuuid(guid: &[u8; 16]) -> String { - format!( - "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:\ - 02X}{:02X}{:02X}", - guid[3], - guid[2], - guid[1], - guid[0], - guid[5], - guid[4], - guid[7], - guid[6], - guid[8], - guid[9], - guid[10], - guid[11], - guid[12], - guid[13], - guid[14], - guid[15] - ) -} - -fn map_disk_error(err: DiskError) -> DevError { - match err { - DiskError::BufferTooSmall => DevError::InvalidParam, - DiskError::Overflow => DevError::BadState, - DiskError::BlockSizeSmallerThanPartitionEntry => DevError::InvalidParam, - DiskError::Io(err) => err, - } -} diff --git a/components/axdriver_crates/axdriver_block/src/partition/mbr.rs b/components/axdriver_crates/axdriver_block/src/partition/mbr.rs deleted file mode 100644 index 55fb598364..0000000000 --- a/components/axdriver_crates/axdriver_block/src/partition/mbr.rs +++ /dev/null @@ -1,273 +0,0 @@ -use alloc::{format, vec, vec::Vec}; - -use log::{debug, warn}; - -use super::{PartitionInfo, PartitionRegion, PartitionTable, PartitionTableKind}; -use crate::{BlockDriverOps, DevError, DevResult}; - -const MBR_SIZE: usize = 512; -const MBR_DISK_SIGNATURE_OFFSET: usize = 440; -const MBR_PARTITION_TABLE_OFFSET: usize = 446; -const MBR_SIGNATURE_OFFSET: usize = 510; -const MBR_SIGNATURE: [u8; 2] = [0x55, 0xaa]; -const MBR_PARTITION_ENTRY_SIZE: usize = 16; -const MBR_PARTITION_COUNT: usize = 4; - -const PARTITION_TYPE_EMPTY: u8 = 0x00; -const PARTITION_TYPE_EXTENDED: u8 = 0x05; -const PARTITION_TYPE_EXTENDED_LBA: u8 = 0x0f; -const PARTITION_TYPE_LINUX_EXTENDED: u8 = 0x85; -const PARTITION_TYPE_GPT_PROTECTIVE: u8 = 0xee; - -pub(super) fn scan_mbr_partitions( - inner: &mut T, -) -> DevResult> { - let block_size = inner.block_size(); - if block_size < MBR_SIZE { - return Err(DevError::InvalidParam); - } - - let mut block_buf = vec![0u8; block_size]; - inner.read_block(0, &mut block_buf)?; - - let mbr = &block_buf[..MBR_SIZE]; - if mbr[MBR_SIGNATURE_OFFSET..MBR_SIGNATURE_OFFSET + MBR_SIGNATURE.len()] != MBR_SIGNATURE { - return Ok(None); - } - - let disk_signature = u32::from_le_bytes( - mbr[MBR_DISK_SIGNATURE_OFFSET..MBR_DISK_SIGNATURE_OFFSET + 4] - .try_into() - .map_err(|_| DevError::BadState)?, - ); - let total_blocks = inner.num_blocks(); - let mut partitions = Vec::new(); - - for index in 0..MBR_PARTITION_COUNT { - let entry_offset = MBR_PARTITION_TABLE_OFFSET + index * MBR_PARTITION_ENTRY_SIZE; - let entry = &mbr[entry_offset..entry_offset + MBR_PARTITION_ENTRY_SIZE]; - let boot_indicator = entry[0]; - let partition_type = entry[4]; - let start_lba = - u32::from_le_bytes(entry[8..12].try_into().map_err(|_| DevError::BadState)?) as u64; - let size_in_lba = - u32::from_le_bytes(entry[12..16].try_into().map_err(|_| DevError::BadState)?) as u64; - - if partition_type == PARTITION_TYPE_EMPTY || size_in_lba == 0 { - continue; - } - if partition_type == PARTITION_TYPE_GPT_PROTECTIVE { - debug!("skipping protective GPT MBR partition[{index}]"); - continue; - } - if matches!( - partition_type, - PARTITION_TYPE_EXTENDED | PARTITION_TYPE_EXTENDED_LBA | PARTITION_TYPE_LINUX_EXTENDED - ) { - debug!("skipping unsupported extended MBR partition[{index}]"); - continue; - } - - let Some(end_lba) = start_lba.checked_add(size_in_lba) else { - warn!("skipping overflowing MBR partition[{index}]"); - continue; - }; - if start_lba >= total_blocks || end_lba > total_blocks { - warn!( - "skipping out-of-range MBR partition[{index}] lba {start_lba}..{end_lba}, device \ - blocks {total_blocks}" - ); - continue; - } - - partitions.push(PartitionInfo { - index, - table_kind: PartitionTableKind::Mbr, - region: PartitionRegion { start_lba, end_lba }, - name: None, - part_uuid: (disk_signature != 0) - .then(|| format!("{disk_signature:08X}-{:02X}", index + 1)), - bootable: boot_indicator == 0x80, - }); - } - - Ok(Some(PartitionTable { - kind: PartitionTableKind::Mbr, - partitions, - })) -} - -#[cfg(test)] -mod tests { - use alloc::vec::Vec; - - use ax_driver_base::{BaseDriverOps, DeviceType}; - - use super::*; - use crate::partition::scan_partitions; - - struct MemBlock { - data: Vec, - block_size: usize, - } - - impl MemBlock { - fn new(num_blocks: usize) -> Self { - Self { - data: vec![0; num_blocks * MBR_SIZE], - block_size: MBR_SIZE, - } - } - - fn write_mbr_signature(&mut self) { - self.data[MBR_SIGNATURE_OFFSET..MBR_SIGNATURE_OFFSET + MBR_SIGNATURE.len()] - .copy_from_slice(&MBR_SIGNATURE); - } - - fn write_disk_signature(&mut self, signature: u32) { - self.data[MBR_DISK_SIGNATURE_OFFSET..MBR_DISK_SIGNATURE_OFFSET + 4] - .copy_from_slice(&signature.to_le_bytes()); - } - - fn write_partition( - &mut self, - index: usize, - boot_indicator: u8, - partition_type: u8, - start_lba: u32, - size_in_lba: u32, - ) { - let offset = MBR_PARTITION_TABLE_OFFSET + index * MBR_PARTITION_ENTRY_SIZE; - let entry = &mut self.data[offset..offset + MBR_PARTITION_ENTRY_SIZE]; - entry[0] = boot_indicator; - entry[4] = partition_type; - entry[8..12].copy_from_slice(&start_lba.to_le_bytes()); - entry[12..16].copy_from_slice(&size_in_lba.to_le_bytes()); - } - } - - impl BaseDriverOps for MemBlock { - fn device_name(&self) -> &str { - "memblock" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - } - - impl BlockDriverOps for MemBlock { - fn num_blocks(&self) -> u64 { - (self.data.len() / self.block_size) as u64 - } - - fn block_size(&self) -> usize { - self.block_size - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - if !buf.len().is_multiple_of(self.block_size) { - return Err(DevError::InvalidParam); - } - let offset = usize::try_from(block_id) - .map_err(|_| DevError::BadState)? - .checked_mul(self.block_size) - .ok_or(DevError::BadState)?; - let end = offset.checked_add(buf.len()).ok_or(DevError::BadState)?; - if end > self.data.len() { - return Err(DevError::Io); - } - buf.copy_from_slice(&self.data[offset..end]); - Ok(()) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - if !buf.len().is_multiple_of(self.block_size) { - return Err(DevError::InvalidParam); - } - let offset = usize::try_from(block_id) - .map_err(|_| DevError::BadState)? - .checked_mul(self.block_size) - .ok_or(DevError::BadState)?; - let end = offset.checked_add(buf.len()).ok_or(DevError::BadState)?; - if end > self.data.len() { - return Err(DevError::Io); - } - self.data[offset..end].copy_from_slice(buf); - Ok(()) - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } - } - - #[test] - fn scans_valid_primary_partitions_without_boot_requirement() { - let mut disk = MemBlock::new(128); - disk.write_mbr_signature(); - disk.write_disk_signature(0x1234_abcd); - disk.write_partition(0, 0x00, 0x83, 8, 16); - disk.write_partition(1, 0x80, 0x0c, 40, 24); - - let table = scan_partitions(&mut disk).unwrap(); - - assert_eq!(table.kind, PartitionTableKind::Mbr); - assert_eq!(table.partitions.len(), 2); - assert_eq!(table.partitions[0].index, 0); - assert!(!table.partitions[0].bootable); - assert_eq!( - table.partitions[0].region, - PartitionRegion { - start_lba: 8, - end_lba: 24, - } - ); - assert_eq!( - table.partitions[0].part_uuid.as_deref(), - Some("1234ABCD-01") - ); - assert_eq!(table.partitions[1].index, 1); - assert!(table.partitions[1].bootable); - assert_eq!( - table.partitions[1].region, - PartitionRegion { - start_lba: 40, - end_lba: 64, - } - ); - assert_eq!( - table.partitions[1].part_uuid.as_deref(), - Some("1234ABCD-02") - ); - } - - #[test] - fn skips_empty_protective_extended_and_out_of_range_entries() { - let mut disk = MemBlock::new(64); - disk.write_mbr_signature(); - disk.write_partition(0, 0x00, PARTITION_TYPE_EMPTY, 0, 0); - disk.write_partition(1, 0x00, PARTITION_TYPE_GPT_PROTECTIVE, 1, 63); - disk.write_partition(2, 0x00, PARTITION_TYPE_EXTENDED_LBA, 8, 16); - disk.write_partition(3, 0x00, 0x83, 60, 8); - - let table = scan_partitions(&mut disk).unwrap(); - - assert_eq!(table.kind, PartitionTableKind::Mbr); - assert!(table.partitions.is_empty()); - } - - #[test] - fn omits_partuuid_when_disk_signature_is_zero() { - let mut disk = MemBlock::new(64); - disk.write_mbr_signature(); - disk.write_partition(2, 0x00, 0x83, 4, 8); - - let table = scan_partitions(&mut disk).unwrap(); - - assert_eq!(table.kind, PartitionTableKind::Mbr); - assert_eq!(table.partitions.len(), 1); - assert_eq!(table.partitions[0].index, 2); - assert_eq!(table.partitions[0].part_uuid, None); - } -} diff --git a/components/axdriver_crates/axdriver_block/src/partition/mod.rs b/components/axdriver_crates/axdriver_block/src/partition/mod.rs deleted file mode 100644 index ae295a6c23..0000000000 --- a/components/axdriver_crates/axdriver_block/src/partition/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -mod device; -mod gpt; -mod mbr; - -use alloc::{string::String, vec::Vec}; - -pub use self::device::PartitionBlockDevice; -use crate::{BlockDriverOps, DevResult}; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum PartitionTableKind { - Gpt, - Mbr, - None, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct PartitionRegion { - pub start_lba: u64, - pub end_lba: u64, -} - -impl PartitionRegion { - pub const fn from_num_blocks(num_blocks: u64) -> Self { - Self { - start_lba: 0, - end_lba: num_blocks, - } - } - - pub const fn num_blocks(self) -> u64 { - self.end_lba.saturating_sub(self.start_lba) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PartitionInfo { - pub index: usize, - pub table_kind: PartitionTableKind, - pub region: PartitionRegion, - pub name: Option, - pub part_uuid: Option, - pub bootable: bool, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct PartitionTable { - pub kind: PartitionTableKind, - pub partitions: Vec, -} - -impl PartitionTable { - pub const fn empty() -> Self { - Self { - kind: PartitionTableKind::None, - partitions: Vec::new(), - } - } -} - -pub fn scan_partitions(inner: &mut T) -> DevResult { - if let Some(table) = gpt::scan_gpt_partitions(inner)? { - return Ok(table); - } - if let Some(table) = mbr::scan_mbr_partitions(inner)? { - return Ok(table); - } - - Ok(PartitionTable::empty()) -} diff --git a/components/axdriver_crates/axdriver_block/src/ramdisk.rs b/components/axdriver_crates/axdriver_block/src/ramdisk.rs deleted file mode 100644 index 4a22528ac2..0000000000 --- a/components/axdriver_crates/axdriver_block/src/ramdisk.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! A RAM disk driver backed by heap memory. - -extern crate alloc; - -use alloc::alloc::{alloc_zeroed, dealloc}; -use core::{ - alloc::Layout, - ops::{Deref, DerefMut}, - ptr::NonNull, -}; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; - -use crate::BlockDriverOps; - -const BLOCK_SIZE: usize = 512; - -/// A RAM disk backed by heap memory. -pub struct RamDisk(NonNull<[u8]>); - -unsafe impl Send for RamDisk {} -unsafe impl Sync for RamDisk {} - -impl Default for RamDisk { - fn default() -> Self { - Self(NonNull::<[u8; 0]>::dangling()) - } -} - -impl RamDisk { - /// Creates a new RAM disk with the given size hint. - /// - /// The actual size of the RAM disk will be aligned upwards to the block - /// size (512 bytes). - pub fn new(size_hint: usize) -> Self { - let size = align_up(size_hint); - let ptr = unsafe { - NonNull::new_unchecked(alloc_zeroed(Layout::from_size_align_unchecked( - size, BLOCK_SIZE, - ))) - }; - Self(NonNull::slice_from_raw_parts(ptr, size)) - } -} - -impl Drop for RamDisk { - fn drop(&mut self) { - if self.0.is_empty() { - return; - } - unsafe { - dealloc( - self.0.cast::().as_ptr(), - Layout::from_size_align_unchecked(self.0.len(), BLOCK_SIZE), - ); - } - } -} - -impl Deref for RamDisk { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - unsafe { self.0.as_ref() } - } -} - -impl DerefMut for RamDisk { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.0.as_mut() } - } -} - -impl From<&[u8]> for RamDisk { - fn from(data: &[u8]) -> Self { - let mut this = RamDisk::new(data.len()); - this[..data.len()].copy_from_slice(data); - this - } -} - -impl BaseDriverOps for RamDisk { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - - fn device_name(&self) -> &str { - "ramdisk" - } -} - -impl BlockDriverOps for RamDisk { - #[inline] - fn num_blocks(&self) -> u64 { - (self.len() / BLOCK_SIZE) as u64 - } - - #[inline] - fn block_size(&self) -> usize { - BLOCK_SIZE - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - if !buf.len().is_multiple_of(BLOCK_SIZE) { - return Err(DevError::InvalidParam); - } - let offset = block_id as usize * BLOCK_SIZE; - if offset + buf.len() > self.len() { - return Err(DevError::Io); - } - buf.copy_from_slice(&self[offset..offset + buf.len()]); - Ok(()) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - if !buf.len().is_multiple_of(BLOCK_SIZE) { - return Err(DevError::InvalidParam); - } - let offset = block_id as usize * BLOCK_SIZE; - if offset + buf.len() > self.len() { - return Err(DevError::Io); - } - self[offset..offset + buf.len()].copy_from_slice(buf); - Ok(()) - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } -} - -const fn align_up(val: usize) -> usize { - (val + BLOCK_SIZE - 1) & !(BLOCK_SIZE - 1) -} diff --git a/components/axdriver_crates/axdriver_block/src/ramdisk_static.rs b/components/axdriver_crates/axdriver_block/src/ramdisk_static.rs deleted file mode 100644 index 5487181be6..0000000000 --- a/components/axdriver_crates/axdriver_block/src/ramdisk_static.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! A RAM disk driver backed by a static slice. - -use core::ops::{Deref, DerefMut}; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; - -use crate::BlockDriverOps; - -const BLOCK_SIZE: usize = 512; - -/// A RAM disk backed by a static slice. -#[derive(Default)] -pub struct RamDisk(&'static mut [u8]); - -impl RamDisk { - /// Creates a new RAM disk from the given static buffer. - /// - /// # Panics - /// Panics if the buffer is not aligned to block size or its size is not - /// a multiple of block size. - pub fn new(buf: &'static mut [u8]) -> Self { - assert!(buf.as_ptr().addr().is_multiple_of(BLOCK_SIZE)); - assert!(buf.len().is_multiple_of(BLOCK_SIZE)); - RamDisk(buf) - } -} - -impl Deref for RamDisk { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl DerefMut for RamDisk { - fn deref_mut(&mut self) -> &mut Self::Target { - self.0 - } -} - -impl BaseDriverOps for RamDisk { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - - fn device_name(&self) -> &str { - "ramdisk" - } -} - -impl BlockDriverOps for RamDisk { - #[inline] - fn num_blocks(&self) -> u64 { - (self.len() / BLOCK_SIZE) as u64 - } - - #[inline] - fn block_size(&self) -> usize { - BLOCK_SIZE - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - if !buf.len().is_multiple_of(BLOCK_SIZE) { - return Err(DevError::InvalidParam); - } - let offset = block_id as usize * BLOCK_SIZE; - if offset + buf.len() > self.len() { - return Err(DevError::Io); - } - buf.copy_from_slice(&self[offset..offset + buf.len()]); - Ok(()) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - if !buf.len().is_multiple_of(BLOCK_SIZE) { - return Err(DevError::InvalidParam); - } - let offset = block_id as usize * BLOCK_SIZE; - if offset + buf.len() > self.len() { - return Err(DevError::Io); - } - self[offset..offset + buf.len()].copy_from_slice(buf); - Ok(()) - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } -} diff --git a/components/axdriver_crates/axdriver_block/src/sdmmc.rs b/components/axdriver_crates/axdriver_block/src/sdmmc.rs deleted file mode 100644 index ac256f53ad..0000000000 --- a/components/axdriver_crates/axdriver_block/src/sdmmc.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! SD/MMC driver based on SDIO. - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use simple_sdmmc::SdMmc; - -use crate::BlockDriverOps; - -/// A SD/MMC driver. -pub struct SdMmcDriver(SdMmc); - -impl SdMmcDriver { - /// Creates a new [`SdMmcDriver`] from the given base address. - /// - /// # Safety - /// - /// The caller must ensure that `base` is a valid pointer to the SD/MMC controller's - /// register block and that no other code is concurrently accessing the same hardware. - pub unsafe fn new(base: usize) -> Self { - Self(unsafe { SdMmc::new(base) }) - } -} - -impl BaseDriverOps for SdMmcDriver { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - - fn device_name(&self) -> &str { - "sdmmc" - } -} - -impl BlockDriverOps for SdMmcDriver { - fn num_blocks(&self) -> u64 { - self.0.num_blocks() - } - - fn block_size(&self) -> usize { - SdMmc::BLOCK_SIZE - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - let (blocks, remainder) = buf.as_chunks_mut::<{ SdMmc::BLOCK_SIZE }>(); - - if !remainder.is_empty() { - return Err(DevError::InvalidParam); - } - - for (i, block) in blocks.iter_mut().enumerate() { - self.0.read_block(block_id as u32 + i as u32, block); - } - - Ok(()) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - let (blocks, remainder) = buf.as_chunks::<{ SdMmc::BLOCK_SIZE }>(); - - if !remainder.is_empty() { - return Err(DevError::InvalidParam); - } - - for (i, block) in blocks.iter().enumerate() { - self.0.write_block(block_id as u32 + i as u32, block); - } - - Ok(()) - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } -} diff --git a/components/axdriver_crates/axdriver_display/Cargo.toml b/components/axdriver_crates/axdriver_display/Cargo.toml deleted file mode 100644 index 4934e3b1cf..0000000000 --- a/components/axdriver_crates/axdriver_display/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "ax-driver-display" -edition.workspace = true -version = "0.3.12" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Common traits and types for graphics device drivers" -keywords = ["arceos", "driver", "gpu", "framebuffer"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[dependencies] -ax-driver-base = { workspace = true } diff --git a/components/axdriver_crates/axdriver_display/README.md b/components/axdriver_crates/axdriver_display/README.md deleted file mode 100644 index 6b7ea6695c..0000000000 --- a/components/axdriver_crates/axdriver_display/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-display

- -

Common traits and types for graphics device drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-display.svg)](https://crates.io/crates/ax-driver-display) -[![Docs.rs](https://docs.rs/ax-driver-display/badge.svg)](https://docs.rs/ax-driver-display) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-display` provides Common traits and types for graphics device drivers. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-display was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-display = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_display - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_display as _; - -fn main() { - // Integrate `ax-driver-display` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-display](https://docs.rs/ax-driver-display) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_display/README_CN.md b/components/axdriver_crates/axdriver_display/README_CN.md deleted file mode 100644 index 70f32f1610..0000000000 --- a/components/axdriver_crates/axdriver_display/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-display

- -

Common traits and types for graphics device drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-display.svg)](https://crates.io/crates/ax-driver-display) -[![Docs.rs](https://docs.rs/ax-driver-display/badge.svg)](https://docs.rs/ax-driver-display) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-display` 提供了 Common traits and types for graphics device drivers。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-display 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-display = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_display - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_display as _; - -fn main() { - // 在这里将 `ax-driver-display` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-display](https://docs.rs/ax-driver-display) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_display/src/lib.rs b/components/axdriver_crates/axdriver_display/src/lib.rs deleted file mode 100644 index 2d4b933764..0000000000 --- a/components/axdriver_crates/axdriver_display/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Common traits and types for graphics display device drivers. - -#![no_std] - -#[doc(no_inline)] -pub use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; - -/// The information of the graphics device. -#[derive(Debug, Clone, Copy)] -pub struct DisplayInfo { - /// The visible width. - pub width: u32, - /// The visible height. - pub height: u32, - /// The base virtual address of the framebuffer. - pub fb_base_vaddr: usize, - /// The size of the framebuffer in bytes. - pub fb_size: usize, -} - -/// The framebuffer. -/// -/// It's a special memory buffer that mapped from the device memory. -pub struct FrameBuffer<'a> { - _raw: &'a mut [u8], -} - -impl<'a> FrameBuffer<'a> { - /// Use the given raw pointer and size as the framebuffer. - /// - /// # Safety - /// - /// Caller must insure that the given memory region is valid and accessible. - pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Self { - Self { - _raw: unsafe { core::slice::from_raw_parts_mut(ptr, len) }, - } - } - - /// Use the given slice as the framebuffer. - pub fn from_slice(slice: &'a mut [u8]) -> Self { - Self { _raw: slice } - } -} - -/// Operations that require a graphics device driver to implement. -pub trait DisplayDriverOps: BaseDriverOps { - /// Get the display information. - fn info(&self) -> DisplayInfo; - - /// Get the framebuffer. - fn fb(&self) -> FrameBuffer<'_>; - - /// Whether need to flush the framebuffer to the screen. - fn need_flush(&self) -> bool; - - /// Flush framebuffer to the screen. - fn flush(&mut self) -> DevResult; -} diff --git a/components/axdriver_crates/axdriver_input/CHANGELOG.md b/components/axdriver_crates/axdriver_input/CHANGELOG.md deleted file mode 100644 index 45d50db156..0000000000 --- a/components/axdriver_crates/axdriver_input/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.13](https://github.com/rcore-os/tgoskits/compare/ax-driver-input-v0.3.12...ax-driver-input-v0.3.13) - 2026-05-15 - -### Fixed - -- *(evdev)* expose eventN for all input devices and plumb EVIOCGPROP/EVIOCGABS ([#513](https://github.com/rcore-os/tgoskits/pull/513)) diff --git a/components/axdriver_crates/axdriver_input/Cargo.toml b/components/axdriver_crates/axdriver_input/Cargo.toml deleted file mode 100644 index 0bbee3db57..0000000000 --- a/components/axdriver_crates/axdriver_input/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "ax-driver-input" -edition.workspace = true -version = "0.3.13" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Common traits and types for input device drivers" -keywords = ["arceos", "driver", "input"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[dependencies] -ax-driver-base = { workspace = true } -strum = { version = "0.27.2", default-features = false, features = ["derive"] } diff --git a/components/axdriver_crates/axdriver_input/README.md b/components/axdriver_crates/axdriver_input/README.md deleted file mode 100644 index a49f32adde..0000000000 --- a/components/axdriver_crates/axdriver_input/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-input

- -

Common traits and types for input device drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-input.svg)](https://crates.io/crates/ax-driver-input) -[![Docs.rs](https://docs.rs/ax-driver-input/badge.svg)](https://docs.rs/ax-driver-input) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-input` provides Common traits and types for input device drivers. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-input was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-input = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_input - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_input as _; - -fn main() { - // Integrate `ax-driver-input` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-input](https://docs.rs/ax-driver-input) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_input/README_CN.md b/components/axdriver_crates/axdriver_input/README_CN.md deleted file mode 100644 index a7a8a76b86..0000000000 --- a/components/axdriver_crates/axdriver_input/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-input

- -

Common traits and types for input device drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-input.svg)](https://crates.io/crates/ax-driver-input) -[![Docs.rs](https://docs.rs/ax-driver-input/badge.svg)](https://docs.rs/ax-driver-input) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-input` 提供了 Common traits and types for input device drivers。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-input 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-input = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_input - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_input as _; - -fn main() { - // 在这里将 `ax-driver-input` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-input](https://docs.rs/ax-driver-input) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_input/src/lib.rs b/components/axdriver_crates/axdriver_input/src/lib.rs deleted file mode 100644 index f8df76fc5d..0000000000 --- a/components/axdriver_crates/axdriver_input/src/lib.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! Common traits and types for graphics display device drivers. - -#![no_std] - -#[doc(no_inline)] -pub use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use strum::FromRepr; - -#[repr(u8)] -#[derive(Debug, Clone, Copy, Eq, PartialEq, FromRepr)] -pub enum EventType { - Synchronization = 0x00, - Key = 0x01, - Relative = 0x02, - Absolute = 0x03, - Misc = 0x04, - Switch = 0x05, - Led = 0x11, - Sound = 0x12, - ForceFeedback = 0x15, -} - -impl EventType { - pub const MAX: u8 = 0x1f; - pub const COUNT: u8 = Self::MAX + 1; - - pub const fn bits_count(&self) -> usize { - match self { - EventType::Synchronization => 0x10, - EventType::Key => 0x300, - EventType::Relative => 0x10, - EventType::Absolute => 0x40, - EventType::Misc => 0x08, - EventType::Switch => 0x12, - EventType::Led => 0x10, - EventType::Sound => 0x08, - EventType::ForceFeedback => 0x80, - } - } -} - -/// An input event, as defined by the Linux input subsystem. -#[repr(C)] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct Event { - pub event_type: u16, - pub code: u16, - pub value: u32, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct InputDeviceId { - /// The bustype identifier. - pub bus_type: u16, - /// The vendor identifier. - pub vendor: u16, - /// The product identifier. - pub product: u16, - /// The version identifier. - pub version: u16, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct AbsInfo { - /// The minimum value for the axis. - pub min: u32, - /// The maximum value for the axis. - pub max: u32, - /// The fuzz value used to filter noise from the event stream. - pub fuzz: u32, - /// The size of the dead zone; values less than this will be reported as 0. - pub flat: u32, - /// The resolution for values reported for the axis. - pub res: u32, -} - -/// Operations that require a graphics device driver to implement. -pub trait InputDriverOps: BaseDriverOps { - /// Returns the device ID of the input device. - fn device_id(&self) -> InputDeviceId; - - /// Returns the physical location of the input device. - fn physical_location(&self) -> &str; - - /// Returns a unique ID of the input device. - fn unique_id(&self) -> &str; - - /// Fetches the bitmap of supported event codes for the specified event - /// type. - /// - /// Returns true if the event type is supported and the bitmap is written to - /// `out`. - fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> DevResult; - - /// Reads an input event from the device. - /// - /// If no events are available, `Err(DevError::Again)` is returned. - fn read_event(&mut self) -> DevResult; - - /// Fetches the input property bitmap (`EVIOCGPROP` equivalent). - /// - /// libinput reads this to distinguish pointers (`INPUT_PROP_POINTER`) - /// from touchscreens (`INPUT_PROP_DIRECT`) and graphics tablets. Writes - /// at most `out.len()` bytes; returns the number of bytes populated. - /// Default returns 0 (no props advertised) for drivers that don't yet - /// expose this information. - fn get_prop_bits(&mut self, _out: &mut [u8]) -> DevResult { - Ok(0) - } - - /// Fetches the [`AbsInfo`] for an absolute axis (`EVIOCGABS` equivalent). - /// - /// libinput needs `min`/`max`/`res` to normalize raw axis values into - /// compositor coordinates. Default returns - /// `Err(DevError::Unsupported)`; drivers that advertise `EV_ABS` must - /// override. - fn get_abs_info(&mut self, _axis: u8) -> DevResult { - Err(DevError::Unsupported) - } -} diff --git a/components/axdriver_crates/axdriver_net/CHANGELOG.md b/components/axdriver_crates/axdriver_net/CHANGELOG.md deleted file mode 100644 index 2718a8d7ee..0000000000 --- a/components/axdriver_crates/axdriver_net/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.13](https://github.com/rcore-os/tgoskits/compare/ax-driver-net-v0.3.12...ax-driver-net-v0.3.13) - 2026-05-19 - -### Other - -- updated the following local packages: fxmac_rs - -## [0.3.12](https://github.com/rcore-os/tgoskits/compare/ax-driver-net-v0.3.11...ax-driver-net-v0.3.12) - 2026-05-15 - -### Other - -- updated the following local packages: fxmac_rs diff --git a/components/axdriver_crates/axdriver_net/Cargo.toml b/components/axdriver_crates/axdriver_net/Cargo.toml deleted file mode 100644 index 3a0298aca3..0000000000 --- a/components/axdriver_crates/axdriver_net/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "ax-driver-net" -edition.workspace = true -version = "0.3.13" -repository = "https://github.com/rcore-os/tgoskits" -authors = [ - "Yuekai Jia ", - "ChengXiang Qi ", - "朝倉水希 ", - "Yu Chen " -] -description = "Common traits and types for network device (NIC) drivers" -keywords = ["arceos", "driver", "nic"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[features] -default = [] -fxmac = ["dep:fxmac_rs"] -ixgbe = ["dep:ixgbe-driver"] - -[dependencies] -ax-driver-base = { workspace = true } -ax-kspin.workspace = true -bitflags = "2.9" -fxmac_rs = { workspace = true, optional = true } -ixgbe-driver = { version = "0.1.1-preview.1", optional = true } -log = { workspace = true } diff --git a/components/axdriver_crates/axdriver_net/README.md b/components/axdriver_crates/axdriver_net/README.md deleted file mode 100644 index c667bcf6af..0000000000 --- a/components/axdriver_crates/axdriver_net/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-net

- -

Common traits and types for network device (NIC) drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-net.svg)](https://crates.io/crates/ax-driver-net) -[![Docs.rs](https://docs.rs/ax-driver-net/badge.svg)](https://docs.rs/ax-driver-net) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-net` provides Common traits and types for network device (NIC) drivers. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-net was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-net = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_net - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_net as _; - -fn main() { - // Integrate `ax-driver-net` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-net](https://docs.rs/ax-driver-net) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_net/README_CN.md b/components/axdriver_crates/axdriver_net/README_CN.md deleted file mode 100644 index 3aff086092..0000000000 --- a/components/axdriver_crates/axdriver_net/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-net

- -

Common traits and types for network device (NIC) drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-net.svg)](https://crates.io/crates/ax-driver-net) -[![Docs.rs](https://docs.rs/ax-driver-net/badge.svg)](https://docs.rs/ax-driver-net) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-net` 提供了 Common traits and types for network device (NIC) drivers。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-net 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-net = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_net - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_net as _; - -fn main() { - // 在这里将 `ax-driver-net` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-net](https://docs.rs/ax-driver-net) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_net/src/fxmac.rs b/components/axdriver_crates/axdriver_net/src/fxmac.rs deleted file mode 100644 index dbddf38f21..0000000000 --- a/components/axdriver_crates/axdriver_net/src/fxmac.rs +++ /dev/null @@ -1,137 +0,0 @@ -use alloc::{boxed::Box, collections::VecDeque, vec}; -use core::ptr::NonNull; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -pub use fxmac_rs::KernelFunc; -use fxmac_rs::{self, FXmac, FXmacGetMacAddress, FXmacLwipPortTx, FXmacRecvHandler, xmac_init}; -use log::*; - -use crate::{EthernetAddress, NetBufPtr, NetDriverOps}; - -const QS: usize = 64; - -/// fxmac driver device -pub struct FXmacNic { - inner: &'static mut FXmac, - hwaddr: [u8; 6], - rx_buffer_queue: VecDeque, -} - -unsafe impl Sync for FXmacNic {} -unsafe impl Send for FXmacNic {} - -impl FXmacNic { - /// initialize fxmac driver - pub fn init(mapped_regs: usize) -> DevResult { - info!("FXmacNic init @ {mapped_regs:#x}"); - let rx_buffer_queue = VecDeque::with_capacity(QS); - - let mut hwaddr: [u8; 6] = [0; 6]; - FXmacGetMacAddress(&mut hwaddr, 0); - info!("Got FXmac HW address: {hwaddr:x?}"); - - let inner = xmac_init(&hwaddr); - let dev = Self { - inner, - hwaddr, - rx_buffer_queue, - }; - Ok(dev) - } -} - -impl BaseDriverOps for FXmacNic { - fn device_name(&self) -> &str { - "cdns,phytium-gem-1.0" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Net - } -} - -impl NetDriverOps for FXmacNic { - fn mac_address(&self) -> EthernetAddress { - EthernetAddress(self.hwaddr) - } - - fn rx_queue_size(&self) -> usize { - QS - } - - fn tx_queue_size(&self) -> usize { - QS - } - - fn can_receive(&self) -> bool { - !self.rx_buffer_queue.is_empty() - } - - fn can_transmit(&self) -> bool { - true - } - - fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { - unsafe { - drop(Box::from_raw(rx_buf.raw_ptr::())); - } - Ok(()) - } - - fn recycle_tx_buffers(&mut self) -> DevResult { - // drop tx_buf - Ok(()) - } - - fn receive(&mut self) -> DevResult { - if !self.rx_buffer_queue.is_empty() { - // RX buffer have received packets. - Ok(self.rx_buffer_queue.pop_front().unwrap()) - } else { - match FXmacRecvHandler(self.inner) { - None => Err(DevError::Again), - Some(packets) => { - for packet in packets { - debug!("received packet length {}", packet.len()); - let mut buf = Box::new(packet); - let buf_ptr = buf.as_mut_ptr(); - let buf_len = buf.len(); - let rx_buf = NetBufPtr::new( - NonNull::new(Box::into_raw(buf) as *mut u8).unwrap(), - NonNull::new(buf_ptr).unwrap(), - buf_len, - ); - - self.rx_buffer_queue.push_back(rx_buf); - } - - Ok(self.rx_buffer_queue.pop_front().unwrap()) - } - } - } - } - - fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { - let tx_vec = vec![tx_buf.packet().to_vec()]; - let ret = FXmacLwipPortTx(self.inner, tx_vec); - unsafe { - drop(Box::from_raw(tx_buf.raw_ptr::())); - } - if ret < 0 { - Err(DevError::Again) - } else { - Ok(()) - } - } - - fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { - let mut tx_buf = Box::new(alloc::vec![0; size]); - let tx_buf_ptr = tx_buf.as_mut_ptr(); - - Ok(NetBufPtr::new( - NonNull::new(Box::into_raw(tx_buf) as *mut u8).unwrap(), - NonNull::new(tx_buf_ptr).unwrap(), - size, - )) - } -} diff --git a/components/axdriver_crates/axdriver_net/src/ixgbe.rs b/components/axdriver_crates/axdriver_net/src/ixgbe.rs deleted file mode 100644 index b24aeb5c50..0000000000 --- a/components/axdriver_crates/axdriver_net/src/ixgbe.rs +++ /dev/null @@ -1,160 +0,0 @@ -use alloc::{collections::VecDeque, sync::Arc}; -use core::{convert::From, mem::ManuallyDrop, ptr::NonNull}; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -pub use ixgbe_driver::{INTEL_82599, INTEL_VEND, IxgbeHal, PhysAddr}; -use ixgbe_driver::{IxgbeDevice, IxgbeError, IxgbeNetBuf, MemPool, NicDevice}; -use log::*; - -use crate::{EthernetAddress, NetBufPtr, NetDriverOps}; - -const RECV_BATCH_SIZE: usize = 64; -const RX_BUFFER_SIZE: usize = 1024; -const MEM_POOL: usize = 4096; -const MEM_POOL_ENTRY_SIZE: usize = 2048; - -/// The ixgbe NIC device driver. -/// -/// `QS` is the ixgbe queue size, `QN` is the ixgbe queue num. -pub struct IxgbeNic { - inner: IxgbeDevice, - mem_pool: Arc, - rx_buffer_queue: VecDeque, -} - -unsafe impl Sync for IxgbeNic {} -unsafe impl Send for IxgbeNic {} - -impl IxgbeNic { - /// Creates a net ixgbe NIC instance and initialize, or returns a error if - /// any step fails. - pub fn init(base: usize, len: usize) -> DevResult { - let mem_pool = MemPool::allocate::(MEM_POOL, MEM_POOL_ENTRY_SIZE) - .map_err(|_| DevError::NoMemory)?; - let inner = IxgbeDevice::::init(base, len, QN, QN, &mem_pool).map_err(|err| { - error!("Failed to initialize ixgbe device: {err:?}"); - DevError::BadState - })?; - - let rx_buffer_queue = VecDeque::with_capacity(RX_BUFFER_SIZE); - Ok(Self { - inner, - mem_pool, - rx_buffer_queue, - }) - } -} - -impl BaseDriverOps for IxgbeNic { - fn device_name(&self) -> &str { - self.inner.get_driver_name() - } - - fn device_type(&self) -> DeviceType { - DeviceType::Net - } -} - -impl NetDriverOps for IxgbeNic { - fn mac_address(&self) -> EthernetAddress { - EthernetAddress(self.inner.get_mac_addr()) - } - - fn rx_queue_size(&self) -> usize { - QS - } - - fn tx_queue_size(&self) -> usize { - QS - } - - fn can_receive(&self) -> bool { - !self.rx_buffer_queue.is_empty() || self.inner.can_receive(0).unwrap() - } - - fn can_transmit(&self) -> bool { - // Default implementation is return true forever. - self.inner.can_send(0).unwrap() - } - - fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { - let rx_buf = ixgbe_ptr_to_buf(rx_buf, &self.mem_pool)?; - drop(rx_buf); - Ok(()) - } - - fn recycle_tx_buffers(&mut self) -> DevResult { - self.inner - .recycle_tx_buffers(0) - .map_err(|_| DevError::BadState)?; - Ok(()) - } - - fn receive(&mut self) -> DevResult { - if !self.can_receive() { - return Err(DevError::Again); - } - if !self.rx_buffer_queue.is_empty() { - // RX buffer have received packets. - Ok(self.rx_buffer_queue.pop_front().unwrap()) - } else { - let f = |rx_buf| { - let rx_buf = NetBufPtr::from(rx_buf); - self.rx_buffer_queue.push_back(rx_buf); - }; - - // RX queue is empty, receive from ixgbe NIC. - match self.inner.receive_packets(0, RECV_BATCH_SIZE, f) { - Ok(recv_nums) => { - if recv_nums == 0 { - // No packet is received, it is impossible things. - panic!("Error: No receive packets.") - } else { - Ok(self.rx_buffer_queue.pop_front().unwrap()) - } - } - Err(e) => match e { - IxgbeError::NotReady => Err(DevError::Again), - _ => Err(DevError::BadState), - }, - } - } - } - - fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { - let tx_buf = ixgbe_ptr_to_buf(tx_buf, &self.mem_pool)?; - match self.inner.send(0, tx_buf) { - Ok(_) => Ok(()), - Err(err) => match err { - IxgbeError::QueueFull => Err(DevError::Again), - _ => panic!("Unexpected err: {:?}", err), - }, - } - } - - fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { - let tx_buf = IxgbeNetBuf::alloc(&self.mem_pool, size).map_err(|_| DevError::NoMemory)?; - Ok(NetBufPtr::from(tx_buf)) - } -} - -impl From for NetBufPtr { - fn from(buf: IxgbeNetBuf) -> Self { - // Use `ManuallyDrop` to avoid drop `tx_buf`. - let mut buf = ManuallyDrop::new(buf); - // In ixgbe, `raw_ptr` is the pool entry, `buf_ptr` is the packet ptr, `len` is packet len - // to avoid too many dynamic memory allocation. - let buf_ptr = buf.packet_mut().as_mut_ptr(); - Self::new( - NonNull::new(buf.pool_entry() as *mut u8).unwrap(), - NonNull::new(buf_ptr).unwrap(), - buf.packet_len(), - ) - } -} - -// Converts a `NetBufPtr` to `IxgbeNetBuf`. -fn ixgbe_ptr_to_buf(ptr: NetBufPtr, pool: &Arc) -> DevResult { - IxgbeNetBuf::construct(ptr.raw_ptr::<()>().addr(), pool, ptr.packet_len()) - .map_err(|_| DevError::BadState) -} diff --git a/components/axdriver_crates/axdriver_net/src/lib.rs b/components/axdriver_crates/axdriver_net/src/lib.rs deleted file mode 100644 index 7e04accdf1..0000000000 --- a/components/axdriver_crates/axdriver_net/src/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Common traits and types for network device (NIC) drivers. - -#![no_std] -#![cfg_attr(doc, feature(doc_cfg))] - -extern crate alloc; - -use bitflags::bitflags; - -#[cfg(feature = "fxmac")] -/// fxmac driver for PhytiumPi -pub mod fxmac; -#[cfg(feature = "ixgbe")] -/// ixgbe NIC device driver. -pub mod ixgbe; - -#[doc(no_inline)] -pub use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; - -mod net_buf; -pub use self::net_buf::{NetBuf, NetBufBox, NetBufPool, NetBufPtr}; - -/// The ethernet address of the NIC (MAC address). -pub struct EthernetAddress(pub [u8; 6]); - -bitflags! { - /// Network IRQ events reported by NIC drivers. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct NetIrqEvent: u32 { - /// The device has receive data ready. - const RX_READY = 1 << 0; - /// The device completed one or more transmit operations. - const TX_DONE = 1 << 1; - /// The device reported a receive-side error. - const RX_ERROR = 1 << 2; - /// The interrupt was not relevant to this NIC. - const SPURIOUS = 1 << 31; - } -} - -/// Operations that require a network device (NIC) driver to implement. -pub trait NetDriverOps: BaseDriverOps { - /// The ethernet address of the NIC. - fn mac_address(&self) -> EthernetAddress; - - /// Whether can transmit packets. - fn can_transmit(&self) -> bool; - - /// Whether can receive packets. - fn can_receive(&self) -> bool; - - /// Size of the receive queue. - fn rx_queue_size(&self) -> usize; - - /// Size of the transmit queue. - fn tx_queue_size(&self) -> usize; - - /// Gives back the `rx_buf` to the receive queue for later receiving. - /// - /// `rx_buf` should be the same as the one returned by - /// [`NetDriverOps::receive`]. - fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult; - - /// Poll the transmit queue and gives back the buffers for previous transmiting. - /// returns [`DevResult`]. - fn recycle_tx_buffers(&mut self) -> DevResult; - - /// Transmits a packet in the buffer to the network, without blocking, - /// returns [`DevResult`]. - fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult; - - /// Receives a packet from the network and store it in the [`NetBuf`], - /// returns the buffer. - /// - /// Before receiving, the driver should have already populated some buffers - /// in the receive queue by [`NetDriverOps::recycle_rx_buffer`]. - /// - /// If currently no incomming packets, returns an error with type - /// [`DevError::Again`]. - fn receive(&mut self) -> DevResult; - - /// Allocate a memory buffer of a specified size for network transmission, - /// returns [`DevResult`] - fn alloc_tx_buffer(&mut self, size: usize) -> DevResult; - - /// Handles a NIC interrupt and returns the device events it observed. - fn handle_irq(&mut self) -> NetIrqEvent { - NetIrqEvent::SPURIOUS - } -} diff --git a/components/axdriver_crates/axdriver_net/src/net_buf.rs b/components/axdriver_crates/axdriver_net/src/net_buf.rs deleted file mode 100644 index 6251b6e857..0000000000 --- a/components/axdriver_crates/axdriver_net/src/net_buf.rs +++ /dev/null @@ -1,248 +0,0 @@ -use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; -use core::ptr::NonNull; - -use ax_kspin::SpinNoIrq as Mutex; - -use crate::{DevError, DevResult}; - -/// A raw buffer struct for network device. -pub struct NetBufPtr { - // The raw pointer of the original object. - raw_ptr: NonNull, - // The pointer to the net buffer. - buf_ptr: NonNull, - len: usize, -} - -impl NetBufPtr { - /// Create a new [`NetBufPtr`]. - pub fn new(raw_ptr: NonNull, buf_ptr: NonNull, len: usize) -> Self { - Self { - raw_ptr, - buf_ptr, - len, - } - } - - /// Return raw pointer of the original object. - pub fn raw_ptr(&self) -> *mut T { - self.raw_ptr.as_ptr() as *mut T - } - - /// Return [`NetBufPtr`] buffer len. - pub fn packet_len(&self) -> usize { - self.len - } - - /// Return [`NetBufPtr`] buffer as &[u8]. - pub fn packet(&self) -> &[u8] { - unsafe { core::slice::from_raw_parts(self.buf_ptr.as_ptr() as *const u8, self.len) } - } - - /// Return [`NetBufPtr`] buffer as &mut [u8]. - pub fn packet_mut(&mut self) -> &mut [u8] { - unsafe { core::slice::from_raw_parts_mut(self.buf_ptr.as_ptr(), self.len) } - } -} - -const MIN_BUFFER_LEN: usize = 1526; -const MAX_BUFFER_LEN: usize = 65535; - -/// A RAII network buffer wrapped in a [`Box`]. -pub type NetBufBox = Box; - -/// A RAII network buffer. -/// -/// It should be allocated from the [`NetBufPool`], and it will be -/// deallocated into the pool automatically when dropped. -/// -/// The layout of the buffer is: -/// -/// ```text -/// ______________________ capacity ______________________ -/// / \ -/// +------------------+------------------+------------------+ -/// | Header | Packet | Unused | -/// +------------------+------------------+------------------+ -/// |\__ header_len __/ \__ packet_len __/ -/// | -/// buf_ptr -/// ``` -pub struct NetBuf { - header_len: usize, - packet_len: usize, - capacity: usize, - buf_ptr: NonNull, - pool_offset: usize, - pool: Arc, -} - -unsafe impl Send for NetBuf {} -unsafe impl Sync for NetBuf {} - -impl NetBuf { - const unsafe fn get_slice(&self, start: usize, len: usize) -> &[u8] { - unsafe { core::slice::from_raw_parts(self.buf_ptr.as_ptr().add(start), len) } - } - - const unsafe fn get_slice_mut(&mut self, start: usize, len: usize) -> &mut [u8] { - unsafe { core::slice::from_raw_parts_mut(self.buf_ptr.as_ptr().add(start), len) } - } - - /// Returns the capacity of the buffer. - pub const fn capacity(&self) -> usize { - self.capacity - } - - /// Returns the length of the header part. - pub const fn header_len(&self) -> usize { - self.header_len - } - - /// Returns the header part of the buffer. - pub const fn header(&self) -> &[u8] { - unsafe { self.get_slice(0, self.header_len) } - } - - /// Returns the packet part of the buffer. - pub const fn packet(&self) -> &[u8] { - unsafe { self.get_slice(self.header_len, self.packet_len) } - } - - /// Returns the mutable reference to the packet part. - pub const fn packet_mut(&mut self) -> &mut [u8] { - unsafe { self.get_slice_mut(self.header_len, self.packet_len) } - } - - /// Returns both the header and the packet parts, as a contiguous slice. - pub const fn packet_with_header(&self) -> &[u8] { - unsafe { self.get_slice(0, self.header_len + self.packet_len) } - } - - /// Returns the entire buffer. - pub const fn raw_buf(&self) -> &[u8] { - unsafe { self.get_slice(0, self.capacity) } - } - - /// Returns the mutable reference to the entire buffer. - pub const fn raw_buf_mut(&mut self) -> &mut [u8] { - unsafe { self.get_slice_mut(0, self.capacity) } - } - - /// Set the length of the header part. - pub fn set_header_len(&mut self, header_len: usize) { - debug_assert!(header_len + self.packet_len <= self.capacity); - self.header_len = header_len; - } - - /// Set the length of the packet part. - pub fn set_packet_len(&mut self, packet_len: usize) { - debug_assert!(self.header_len + packet_len <= self.capacity); - self.packet_len = packet_len; - } - - /// Converts the buffer into a [`NetBufPtr`]. - pub fn into_buf_ptr(mut self: Box) -> NetBufPtr { - let buf_ptr = self.packet_mut().as_mut_ptr(); - let len = self.packet_len; - NetBufPtr::new( - NonNull::new(Box::into_raw(self) as *mut u8).unwrap(), - NonNull::new(buf_ptr).unwrap(), - len, - ) - } - - /// Restore [`NetBuf`] struct from a raw pointer. - /// - /// # Safety - /// - /// This function is unsafe because it may cause some memory issues, - /// so we must ensure that it is called after calling `into_buf_ptr`. - pub unsafe fn from_buf_ptr(ptr: NetBufPtr) -> Box { - unsafe { Box::from_raw(ptr.raw_ptr::()) } - } -} - -impl Drop for NetBuf { - /// Deallocates the buffer into the [`NetBufPool`]. - fn drop(&mut self) { - self.pool.dealloc(self.pool_offset); - } -} - -/// A pool of [`NetBuf`]s to speed up buffer allocation. -/// -/// It divides a large memory into several equal parts for each buffer. -pub struct NetBufPool { - capacity: usize, - buf_len: usize, - pool: Vec, - free_list: Mutex>, -} - -impl NetBufPool { - /// Creates a new pool with the given `capacity`, and all buffer lengths are - /// set to `buf_len`. - pub fn new(capacity: usize, buf_len: usize) -> DevResult> { - if capacity == 0 { - return Err(DevError::InvalidParam); - } - if !(MIN_BUFFER_LEN..=MAX_BUFFER_LEN).contains(&buf_len) { - return Err(DevError::InvalidParam); - } - - let pool = vec![0; capacity * buf_len]; - let mut free_list = Vec::with_capacity(capacity); - for i in 0..capacity { - free_list.push(i * buf_len); - } - Ok(Arc::new(Self { - capacity, - buf_len, - pool, - free_list: Mutex::new(free_list), - })) - } - - /// Returns the capacity of the pool. - pub const fn capacity(&self) -> usize { - self.capacity - } - - /// Returns the length of each buffer. - pub const fn buffer_len(&self) -> usize { - self.buf_len - } - - /// Allocates a buffer from the pool. - /// - /// Returns `None` if no buffer is available. - pub fn alloc(self: &Arc) -> Option { - let pool_offset = self.free_list.lock().pop()?; - let buf_ptr = - unsafe { NonNull::new(self.pool.as_ptr().add(pool_offset) as *mut u8).unwrap() }; - Some(NetBuf { - header_len: 0, - packet_len: 0, - capacity: self.buf_len, - buf_ptr, - pool_offset, - pool: Arc::clone(self), - }) - } - - /// Allocates a buffer wrapped in a [`Box`] from the pool. - /// - /// Returns `None` if no buffer is available. - pub fn alloc_boxed(self: &Arc) -> Option { - Some(Box::new(self.alloc()?)) - } - - /// Deallocates a buffer at the given offset. - /// - /// `pool_offset` must be a multiple of `buf_len`. - fn dealloc(&self, pool_offset: usize) { - debug_assert_eq!(pool_offset % self.buf_len, 0); - self.free_list.lock().push(pool_offset); - } -} diff --git a/components/axdriver_crates/axdriver_pci/Cargo.toml b/components/axdriver_crates/axdriver_pci/Cargo.toml deleted file mode 100644 index aeb5de931d..0000000000 --- a/components/axdriver_crates/axdriver_pci/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "ax-driver-pci" -edition.workspace = true -version = "0.3.12" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Structures and functions for PCI bus operations" -keywords = ["arceos", "driver", "pci"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[dependencies] -virtio-drivers = { version = "0.13.0", default-features = false } diff --git a/components/axdriver_crates/axdriver_pci/README.md b/components/axdriver_crates/axdriver_pci/README.md deleted file mode 100644 index 059620c0c5..0000000000 --- a/components/axdriver_crates/axdriver_pci/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-pci

- -

Structures and functions for PCI bus operations

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-pci.svg)](https://crates.io/crates/ax-driver-pci) -[![Docs.rs](https://docs.rs/ax-driver-pci/badge.svg)](https://docs.rs/ax-driver-pci) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-pci` provides Structures and functions for PCI bus operations. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-pci was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-pci = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_pci - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_pci as _; - -fn main() { - // Integrate `ax-driver-pci` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-pci](https://docs.rs/ax-driver-pci) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_pci/README_CN.md b/components/axdriver_crates/axdriver_pci/README_CN.md deleted file mode 100644 index 7af98e409c..0000000000 --- a/components/axdriver_crates/axdriver_pci/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-pci

- -

Structures and functions for PCI bus operations

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-pci.svg)](https://crates.io/crates/ax-driver-pci) -[![Docs.rs](https://docs.rs/ax-driver-pci/badge.svg)](https://docs.rs/ax-driver-pci) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-pci` 提供了 Structures and functions for PCI bus operations。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-pci 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-pci = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_pci - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_pci as _; - -fn main() { - // 在这里将 `ax-driver-pci` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-pci](https://docs.rs/ax-driver-pci) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_pci/src/lib.rs b/components/axdriver_crates/axdriver_pci/src/lib.rs deleted file mode 100644 index bdfeb58b0a..0000000000 --- a/components/axdriver_crates/axdriver_pci/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Structures and functions for PCI bus operations. -//! -//! Currently, it just re-exports structures from the crate [virtio-drivers][1] -//! and its module [`virtio_drivers::transport::pci::bus`][2]. -//! -//! [1]: https://docs.rs/virtio-drivers/latest/virtio_drivers/ -//! [2]: https://docs.rs/virtio-drivers/latest/virtio_drivers/transport/pci/bus/index.html - -#![no_std] - -pub use virtio_drivers::transport::pci::bus::{ - BarInfo, Cam, CapabilityInfo, Command, ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, - HeaderType, MemoryBarType, MmioCam, PciError, PciRoot, Status, -}; - -/// Used to allocate MMIO regions for PCI BARs. -pub struct PciRangeAllocator { - _start: u64, - end: u64, - current: u64, -} - -impl PciRangeAllocator { - /// Creates a new allocator from a memory range. - pub const fn new(base: u64, size: u64) -> Self { - Self { - _start: base, - end: base + size, - current: base, - } - } - - /// Allocates a memory region with the given size. - /// - /// The `size` should be a power of 2, and the returned value is also a - /// multiple of `size`. - pub fn alloc(&mut self, size: u64) -> Option { - if !size.is_power_of_two() { - return None; - } - let ret = align_up(self.current, size); - if ret + size > self.end { - return None; - } - - self.current = ret + size; - Some(ret) - } -} - -const fn align_up(addr: u64, align: u64) -> u64 { - (addr + align - 1) & !(align - 1) -} diff --git a/components/axdriver_crates/axdriver_virtio/CHANGELOG.md b/components/axdriver_crates/axdriver_virtio/CHANGELOG.md deleted file mode 100644 index a2d85b8a4f..0000000000 --- a/components/axdriver_crates/axdriver_virtio/CHANGELOG.md +++ /dev/null @@ -1,30 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.4.1](https://github.com/rcore-os/tgoskits/compare/ax-driver-virtio-v0.4.0...ax-driver-virtio-v0.4.1) - 2026-05-22 - -### Other - -- updated the following local packages: ax-driver-block - -## [0.4.0](https://github.com/rcore-os/tgoskits/compare/ax-driver-virtio-v0.3.12...ax-driver-virtio-v0.4.0) - 2026-05-19 - -### Fixed - -- *(starry)* weston bringup fixes + IRQ wakers + AF_UNIX cmsg byte marks ([#509](https://github.com/rcore-os/tgoskits/pull/509)) - -## [0.3.12](https://github.com/rcore-os/tgoskits/compare/ax-driver-virtio-v0.3.11...ax-driver-virtio-v0.3.12) - 2026-05-15 - -### Fixed - -- *(evdev)* expose eventN for all input devices and plumb EVIOCGPROP/EVIOCGABS ([#513](https://github.com/rcore-os/tgoskits/pull/513)) - -### Other - -- 增强 ArceOS 中 VirtIO Net、Vsock 及通用探测路径 ([#376](https://github.com/rcore-os/tgoskits/pull/376)) diff --git a/components/axdriver_crates/axdriver_virtio/Cargo.toml b/components/axdriver_crates/axdriver_virtio/Cargo.toml deleted file mode 100644 index 014375b210..0000000000 --- a/components/axdriver_crates/axdriver_virtio/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "ax-driver-virtio" -edition.workspace = true -version = "0.4.1" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `ax-driver-base` series crates" -keywords = ["arceos", "driver", "virtio"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[features] -alloc = ["virtio-drivers/alloc"] -block = ["ax-driver-block"] -gpu = ["alloc", "ax-driver-display"] -input = ["alloc", "ax-driver-input"] -net = ["alloc", "ax-driver-net"] -socket = ["alloc", "ax-driver-vsock"] - -[dependencies] -ax-driver-base = { workspace = true } -ax-driver-block = { workspace = true, optional = true } -ax-driver-display = { workspace = true, optional = true } -ax-driver-input = { workspace = true, optional = true } -ax-driver-net = { workspace = true, optional = true } -ax-driver-vsock = { workspace = true, optional = true } -log = { workspace = true } -virtio-drivers = { version = "0.13.0", default-features = false } diff --git a/components/axdriver_crates/axdriver_virtio/README.md b/components/axdriver_crates/axdriver_virtio/README.md deleted file mode 100644 index 20013d6e76..0000000000 --- a/components/axdriver_crates/axdriver_virtio/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-virtio

- -

Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `ax-driver-base` series crates

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-virtio.svg)](https://crates.io/crates/ax-driver-virtio) -[![Docs.rs](https://docs.rs/ax-driver-virtio/badge.svg)](https://docs.rs/ax-driver-virtio) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-virtio` provides Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `ax-driver-base` series crates. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-virtio was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-virtio = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_virtio - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_virtio as _; - -fn main() { - // Integrate `ax-driver-virtio` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-virtio](https://docs.rs/ax-driver-virtio) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_virtio/README_CN.md b/components/axdriver_crates/axdriver_virtio/README_CN.md deleted file mode 100644 index ff8f217550..0000000000 --- a/components/axdriver_crates/axdriver_virtio/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-virtio

- -

Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `ax-driver-base` series crates

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-virtio.svg)](https://crates.io/crates/ax-driver-virtio) -[![Docs.rs](https://docs.rs/ax-driver-virtio/badge.svg)](https://docs.rs/ax-driver-virtio) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-virtio` 提供了 Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `ax-driver-base` series crates。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-virtio 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-virtio = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_virtio - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_virtio as _; - -fn main() { - // 在这里将 `ax-driver-virtio` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-virtio](https://docs.rs/ax-driver-virtio) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_virtio/src/blk.rs b/components/axdriver_crates/axdriver_virtio/src/blk.rs deleted file mode 100644 index 00e79fcab9..0000000000 --- a/components/axdriver_crates/axdriver_virtio/src/blk.rs +++ /dev/null @@ -1,61 +0,0 @@ -use ax_driver_base::{BaseDriverOps, DevResult, DeviceType}; -use ax_driver_block::BlockDriverOps; -use virtio_drivers::{Hal, device::blk::VirtIOBlk as InnerDev, transport::Transport}; - -use crate::as_dev_err; - -/// The VirtIO block device driver. -pub struct VirtIoBlkDev { - inner: InnerDev, -} - -unsafe impl Send for VirtIoBlkDev {} -unsafe impl Sync for VirtIoBlkDev {} - -impl VirtIoBlkDev { - /// Creates a new driver instance and initializes the device, or returns - /// an error if any step fails. - pub fn try_new(transport: T) -> DevResult { - let mut inner = InnerDev::new(transport).map_err(as_dev_err)?; - inner.disable_interrupts(); - Ok(Self { inner }) - } -} - -impl BaseDriverOps for VirtIoBlkDev { - fn device_name(&self) -> &str { - "virtio-blk" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Block - } -} - -impl BlockDriverOps for VirtIoBlkDev { - #[inline] - fn num_blocks(&self) -> u64 { - self.inner.capacity() - } - - #[inline] - fn block_size(&self) -> usize { - virtio_drivers::device::blk::SECTOR_SIZE - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { - self.inner - .read_blocks(block_id as _, buf) - .map_err(as_dev_err) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { - self.inner - .write_blocks(block_id as _, buf) - .map_err(as_dev_err) - } - - fn flush(&mut self) -> DevResult { - Ok(()) - } -} diff --git a/components/axdriver_crates/axdriver_virtio/src/gpu.rs b/components/axdriver_crates/axdriver_virtio/src/gpu.rs deleted file mode 100644 index 0ac79aa664..0000000000 --- a/components/axdriver_crates/axdriver_virtio/src/gpu.rs +++ /dev/null @@ -1,101 +0,0 @@ -use ax_driver_base::{BaseDriverOps, DevResult, DeviceType}; -use ax_driver_display::{DisplayDriverOps, DisplayInfo, FrameBuffer}; -use virtio_drivers::{Hal, device::gpu::VirtIOGpu as InnerDev, transport::Transport}; - -use crate::as_dev_err; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct FrameBufferState { - base_vaddr: usize, - size: usize, -} - -impl FrameBufferState { - const fn new(base_vaddr: usize, size: usize) -> Self { - Self { base_vaddr, size } - } - - const fn base_vaddr(self) -> usize { - self.base_vaddr - } - - const fn size(self) -> usize { - self.size - } -} - -/// The VirtIO GPU device driver. -pub struct VirtIoGpuDev { - inner: InnerDev, - info: DisplayInfo, -} - -unsafe impl Send for VirtIoGpuDev {} -unsafe impl Sync for VirtIoGpuDev {} - -impl VirtIoGpuDev { - fn setup_framebuffer_state(virtio: &mut InnerDev) -> DevResult { - let framebuffer = virtio.setup_framebuffer().map_err(as_dev_err)?; - Ok(FrameBufferState::new( - framebuffer.as_mut_ptr() as usize, - framebuffer.len(), - )) - } - - fn read_resolution(virtio: &mut InnerDev) -> DevResult<(u32, u32)> { - virtio.resolution().map_err(as_dev_err) - } - - fn build_display_info(framebuffer: FrameBufferState, width: u32, height: u32) -> DisplayInfo { - DisplayInfo { - width, - height, - fb_base_vaddr: framebuffer.base_vaddr(), - fb_size: framebuffer.size(), - } - } - - /// Creates a new driver instance and initializes the device, or returns - /// an error if any step fails. - pub fn try_new(transport: T) -> DevResult { - let mut virtio = InnerDev::new(transport).map_err(as_dev_err)?; - let framebuffer = Self::setup_framebuffer_state(&mut virtio)?; - let (width, height) = Self::read_resolution(&mut virtio)?; - let info = Self::build_display_info(framebuffer, width, height); - - Ok(Self { - inner: virtio, - info, - }) - } -} - -impl BaseDriverOps for VirtIoGpuDev { - fn device_name(&self) -> &str { - "virtio-gpu" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Display - } -} - -impl DisplayDriverOps for VirtIoGpuDev { - fn info(&self) -> DisplayInfo { - self.info - } - - fn fb(&self) -> FrameBuffer<'_> { - unsafe { - FrameBuffer::from_raw_parts_mut(self.info.fb_base_vaddr as *mut u8, self.info.fb_size) - } - } - - fn need_flush(&self) -> bool { - true - } - - fn flush(&mut self) -> DevResult { - self.inner.flush().map_err(as_dev_err) - } -} diff --git a/components/axdriver_crates/axdriver_virtio/src/input.rs b/components/axdriver_crates/axdriver_virtio/src/input.rs deleted file mode 100644 index 01c2d18b6f..0000000000 --- a/components/axdriver_crates/axdriver_virtio/src/input.rs +++ /dev/null @@ -1,166 +0,0 @@ -use alloc::{borrow::ToOwned, format, string::String}; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use ax_driver_input::{AbsInfo, Event, EventType, InputDeviceId, InputDriverOps}; -use virtio_drivers::{ - Hal, - device::input::{InputConfigSelect, VirtIOInput as InnerDev}, - transport::Transport, -}; - -use crate::as_dev_err; - -/// The VirtIO Input device driver. -pub struct VirtIoInputDev { - inner: InnerDev, - device_id: InputDeviceId, - name: String, - physical_location: String, - unique_id: String, - /// IRQ line probed for this device (PCI INTx vector or MMIO IRQ - /// number). Consumers read it back through - /// [`BaseDriverOps::irq_num`] to wire IRQ-driven wakers. - irq: Option, -} - -unsafe impl Send for VirtIoInputDev {} -unsafe impl Sync for VirtIoInputDev {} - -impl VirtIoInputDev { - fn normalize_name(name: String) -> String { - if name.is_empty() { - "".to_owned() - } else { - name - } - } - - fn read_device_name(virtio: &mut InnerDev) -> String { - let name = virtio.name().unwrap_or_else(|_| "".to_owned()); - Self::normalize_name(name) - } - - fn read_device_id(virtio: &mut InnerDev) -> DevResult { - let device_id = virtio.ids().map_err(as_dev_err)?; - Ok(InputDeviceId { - bus_type: device_id.bustype, - vendor: device_id.vendor, - product: device_id.product, - version: device_id.version, - }) - } - - fn build_physical_location(device_id: InputDeviceId) -> String { - format!( - "virtio-input/{:04x}:{:04x}:{:04x}:{:04x}", - device_id.bus_type, device_id.vendor, device_id.product, device_id.version - ) - } - - fn build_unique_id(device_id: InputDeviceId, name: &str) -> String { - format!( - "virtio-{:04x}-{:04x}-{:04x}-{:04x}-{}", - device_id.bus_type, device_id.vendor, device_id.product, device_id.version, name - ) - } - - fn query_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> DevResult { - self.inner - .query_config_select(InputConfigSelect::EvBits, ty as u8, out) - .map(|read| read as usize) - .map_err(as_dev_err) - } - - fn map_pending_event(event: virtio_drivers::device::input::InputEvent) -> Event { - Event { - event_type: event.event_type, - code: event.code, - value: event.value, - } - } - - fn pop_pending_event(&mut self) -> DevResult { - self.inner - .pop_pending_event() - .map(Self::map_pending_event) - .ok_or(DevError::Again) - } - - /// Creates a new driver instance and initializes the device, or - /// returns an error if any step fails. The `irq` argument carries - /// the IRQ number probed for this device (PCI INTx vector or MMIO - /// IRQ line). Consumers (e.g. `EventDev`) read it back through - /// [`BaseDriverOps::irq_num`] to register IRQ-driven wakers. - pub fn try_new(transport: T, irq: Option) -> DevResult { - let mut virtio = InnerDev::new(transport).map_err(as_dev_err)?; - let name = Self::read_device_name(&mut virtio); - let device_id = Self::read_device_id(&mut virtio)?; - let physical_location = Self::build_physical_location(device_id); - let unique_id = Self::build_unique_id(device_id, &name); - - Ok(Self { - inner: virtio, - device_id, - name, - physical_location, - unique_id, - irq, - }) - } -} - -impl BaseDriverOps for VirtIoInputDev { - fn device_name(&self) -> &str { - &self.name - } - - fn device_type(&self) -> DeviceType { - DeviceType::Input - } - - fn irq_num(&self) -> Option { - self.irq - } -} - -impl InputDriverOps for VirtIoInputDev { - fn device_id(&self) -> InputDeviceId { - self.device_id - } - - fn physical_location(&self) -> &str { - &self.physical_location - } - - fn unique_id(&self) -> &str { - &self.unique_id - } - - fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> DevResult { - let read = self.query_event_bits(ty, out)?; - Ok(read != 0) - } - - fn read_event(&mut self) -> DevResult { - self.inner.ack_interrupt(); - self.pop_pending_event() - } - - fn get_prop_bits(&mut self, out: &mut [u8]) -> DevResult { - let bits = self.inner.prop_bits().map_err(as_dev_err)?; - let len = bits.len().min(out.len()); - out[..len].copy_from_slice(&bits[..len]); - Ok(len) - } - - fn get_abs_info(&mut self, axis: u8) -> DevResult { - let info = self.inner.abs_info(axis).map_err(as_dev_err)?; - Ok(AbsInfo { - min: info.min, - max: info.max, - fuzz: info.fuzz, - flat: info.flat, - res: info.res, - }) - } -} diff --git a/components/axdriver_crates/axdriver_virtio/src/lib.rs b/components/axdriver_crates/axdriver_virtio/src/lib.rs deleted file mode 100644 index c36abfad24..0000000000 --- a/components/axdriver_crates/axdriver_virtio/src/lib.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! Wrappers of some devices in the [`virtio-drivers`][1] crate, that implement -//! traits in the [`ax-driver-base`][2] series crates. -//! -//! Like the [`virtio-drivers`][1] crate, you must implement the [`VirtIoHal`] -//! trait (alias of [`virtio-drivers::Hal`][3]), to allocate DMA regions and -//! translate between physical addresses (as seen by devices) and virtual -//! addresses (as seen by your program). -//! -//! [1]: https://docs.rs/virtio-drivers/latest/virtio_drivers/ -//! [2]: https://github.com/arceos-org/axdriver_crates/tree/main/axdriver_base -//! [3]: https://docs.rs/virtio-drivers/latest/virtio_drivers/trait.Hal.html - -#![no_std] -#![cfg_attr(doc, feature(doc_cfg))] - -#[cfg(feature = "alloc")] -extern crate alloc; - -#[cfg(feature = "block")] -mod blk; -#[cfg(feature = "block")] -pub use self::blk::VirtIoBlkDev; - -#[cfg(feature = "gpu")] -mod gpu; -#[cfg(feature = "gpu")] -pub use self::gpu::VirtIoGpuDev; - -#[cfg(feature = "input")] -mod input; -#[cfg(feature = "input")] -pub use self::input::VirtIoInputDev; - -#[cfg(feature = "net")] -mod net; -#[cfg(feature = "net")] -pub use self::net::VirtIoNetDev; - -#[cfg(feature = "socket")] -mod socket; -use ax_driver_base::{DevError, DeviceType}; -use virtio_drivers::transport::DeviceType as VirtIoDevType; -pub use virtio_drivers::{ - BufferDirection, Hal as VirtIoHal, PhysAddr, - transport::{ - Transport, - pci::{PciTransport, bus as pci}, - }, -}; -pub type MmioTransport = virtio_drivers::transport::mmio::MmioTransport<'static>; - -use self::pci::{ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, PciRoot}; -#[cfg(feature = "socket")] -pub use self::socket::VirtIoSocketDev; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct MmioProbeRegion { - base: *mut u8, - size: usize, -} - -impl MmioProbeRegion { - fn try_new(base: *mut u8, size: usize) -> Option { - if base.is_null() || size == 0 { - return None; - } - Some(Self { base, size }) - } - - fn header(self) -> Option> { - use core::ptr::NonNull; - - use virtio_drivers::transport::mmio::VirtIOHeader; - - NonNull::new(self.base as *mut VirtIOHeader) - } - - fn try_transport(self) -> Option { - let header = self.header()?; - unsafe { MmioTransport::new(header, self.size) }.ok() - } -} - -const fn as_socket_dev_err(e: virtio_drivers::device::socket::SocketError) -> DevError { - use virtio_drivers::device::socket::SocketError::*; - - match e { - ConnectionExists => DevError::AlreadyExists, - NotConnected => DevError::BadState, - InvalidOperation | InvalidNumber | UnknownOperation(_) => DevError::InvalidParam, - OutputBufferTooShort(_) | BufferTooShort | BufferTooLong(..) => DevError::InvalidParam, - UnexpectedDataInPacket | PeerSocketShutdown => DevError::Io, - InsufficientBufferSpaceInPeer => DevError::Again, - RecycledWrongBuffer => DevError::BadState, - } -} - -/// Try to probe a VirtIO MMIO device from the given memory region. -/// -/// If the device is recognized, returns the device type and a transport object -/// for later operations. Otherwise, returns [`None`]. -pub fn probe_mmio_device( - reg_base: *mut u8, - reg_size: usize, -) -> Option<(DeviceType, MmioTransport)> { - let region = MmioProbeRegion::try_new(reg_base, reg_size)?; - let transport = region.try_transport()?; - let dev_type = as_dev_type(transport.device_type())?; - Some((dev_type, transport)) -} - -/// Try to probe a VirtIO PCI device from the given PCI address. -/// -/// If the device is recognized, returns the device type and a transport object -/// for later operations. Otherwise, returns [`None`]. -pub fn probe_pci_device( - root: &mut PciRoot, - bdf: DeviceFunction, - dev_info: &DeviceFunctionInfo, -) -> Option<(DeviceType, PciTransport)> { - use virtio_drivers::transport::pci::virtio_device_type; - - let dev_type = virtio_device_type(dev_info).and_then(as_dev_type)?; - let transport = PciTransport::new::(root, bdf).ok()?; - Some((dev_type, transport)) -} - -const fn as_dev_type(t: VirtIoDevType) -> Option { - use VirtIoDevType::*; - match t { - Block => Some(DeviceType::Block), - Network => Some(DeviceType::Net), - GPU => Some(DeviceType::Display), - Input => Some(DeviceType::Input), - Socket => Some(DeviceType::Vsock), - _ => None, - } -} - -#[allow(dead_code)] -const fn as_dev_err(e: virtio_drivers::Error) -> DevError { - use virtio_drivers::Error::*; - match e { - QueueFull => DevError::BadState, - NotReady => DevError::Again, - WrongToken => DevError::BadState, - AlreadyUsed => DevError::AlreadyExists, - InvalidParam => DevError::InvalidParam, - DmaError => DevError::NoMemory, - IoError => DevError::Io, - Unsupported => DevError::Unsupported, - ConfigSpaceTooSmall => DevError::BadState, - ConfigSpaceMissing => DevError::BadState, - SocketDeviceError(e) => as_socket_dev_err(e), - } -} - -#[cfg(test)] -mod tests { - use ax_driver_base::{DevError, DeviceType}; - use virtio_drivers::{ - Error, device::socket::SocketError, transport::DeviceType as VirtIoDevType, - }; - - use super::{as_dev_err, as_dev_type, probe_mmio_device}; - - #[test] - fn as_dev_type_maps_supported_devices() { - assert_eq!(as_dev_type(VirtIoDevType::Block), Some(DeviceType::Block)); - assert_eq!(as_dev_type(VirtIoDevType::Network), Some(DeviceType::Net)); - assert_eq!(as_dev_type(VirtIoDevType::GPU), Some(DeviceType::Display)); - assert_eq!(as_dev_type(VirtIoDevType::Input), Some(DeviceType::Input)); - assert_eq!(as_dev_type(VirtIoDevType::Socket), Some(DeviceType::Vsock)); - } - - #[test] - fn as_dev_type_rejects_unsupported_devices() { - assert_eq!(as_dev_type(VirtIoDevType::Console), None); - assert_eq!(as_dev_type(VirtIoDevType::EntropySource), None); - } - - #[test] - fn probe_mmio_device_returns_none_for_null_base() { - assert!(probe_mmio_device(core::ptr::null_mut(), 0x1000).is_none()); - } - - #[test] - fn as_dev_err_maps_common_errors() { - assert!(matches!(as_dev_err(Error::QueueFull), DevError::BadState)); - assert!(matches!( - as_dev_err(Error::InvalidParam), - DevError::InvalidParam - )); - assert!(matches!(as_dev_err(Error::DmaError), DevError::NoMemory)); - assert!(matches!(as_dev_err(Error::IoError), DevError::Io)); - assert!(matches!( - as_dev_err(Error::Unsupported), - DevError::Unsupported - )); - } - - #[test] - fn as_dev_err_maps_socket_errors() { - assert!(matches!( - as_dev_err(Error::SocketDeviceError(SocketError::ConnectionExists)), - DevError::AlreadyExists - )); - assert!(matches!( - as_dev_err(Error::SocketDeviceError(SocketError::NotConnected)), - DevError::BadState - )); - assert!(matches!( - as_dev_err(Error::SocketDeviceError(SocketError::InvalidNumber)), - DevError::InvalidParam - )); - assert!(matches!( - as_dev_err(Error::SocketDeviceError( - SocketError::InsufficientBufferSpaceInPeer - )), - DevError::Again - )); - assert!(matches!( - as_dev_err(Error::SocketDeviceError(SocketError::PeerSocketShutdown)), - DevError::Io - )); - } -} diff --git a/components/axdriver_crates/axdriver_virtio/src/net.rs b/components/axdriver_crates/axdriver_virtio/src/net.rs deleted file mode 100644 index f4609f31df..0000000000 --- a/components/axdriver_crates/axdriver_virtio/src/net.rs +++ /dev/null @@ -1,1008 +0,0 @@ -use alloc::{sync::Arc, vec::Vec}; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use ax_driver_net::{ - EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr, NetDriverOps, NetIrqEvent, -}; -use virtio_drivers::{Hal, device::net::VirtIONetRaw as InnerDev, transport::Transport}; - -use crate::as_dev_err; - -const NET_BUF_LEN: usize = 1526; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct QueueOccupancy { - occupied: usize, - capacity: usize, -} - -impl QueueOccupancy { - const fn new(occupied: usize, capacity: usize) -> Self { - Self { occupied, capacity } - } - - const fn vacant(self) -> usize { - self.capacity - self.occupied - } - - const fn has_capacity_for(self, needed: usize) -> bool { - self.vacant() >= needed - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct RuntimeStateSnapshot { - rx: QueueOccupancy, - tx_in_flight: QueueOccupancy, - free_rx_buffers: usize, - free_tx_buffers: usize, - checked_out_rx_buffers: usize, - checked_out_tx_buffers: usize, -} - -impl RuntimeStateSnapshot { - const fn queue_size(self) -> usize { - self.rx.capacity - } - - const fn provisioned_tx_buffers(self) -> usize { - self.tx_in_flight.occupied + self.free_tx_buffers + self.checked_out_tx_buffers - } - - const fn all_rx_buffers_accounted_for(self) -> bool { - self.rx.occupied + self.free_rx_buffers + self.checked_out_rx_buffers == self.rx.capacity - } - - const fn all_tx_buffers_accounted_for(self) -> bool { - self.provisioned_tx_buffers() == self.tx_in_flight.capacity - } - - const fn can_allocate_tx(self) -> bool { - self.free_tx_buffers != 0 - } - - const fn can_provision_tx(self, needed: usize) -> bool { - self.queue_size() - self.provisioned_tx_buffers() >= needed - } -} - -fn validate_queue_token(token: u16, expected_token: usize, queue_size: usize) -> DevResult { - let token = token as usize; - if token >= queue_size { - return Err(DevError::BadState); - } - if token != expected_token { - return Err(DevError::BadState); - } - Ok(()) -} - -fn slot_mut(slots: &mut [Option], token: usize) -> DevResult<&mut Option> { - slots.get_mut(token).ok_or(DevError::BadState) -} - -#[cfg(test)] -fn insert_buffer(slots: &mut [Option], token: usize, buf: T) -> DevResult { - let slot = slot_mut(slots, token)?; - if slot.is_some() { - return Err(DevError::BadState); - } - *slot = Some(buf); - Ok(()) -} - -fn insert_buffer_or_return(slots: &mut [Option], token: usize, buf: T) -> Result<(), T> { - let Some(slot) = slots.get_mut(token) else { - return Err(buf); - }; - if slot.is_some() { - return Err(buf); - } - *slot = Some(buf); - Ok(()) -} - -fn take_buffer(slots: &mut [Option], token: u16) -> DevResult { - slot_mut(slots, token as usize)? - .take() - .ok_or(DevError::BadState) -} - -fn count_populated_slots(slots: &[Option]) -> usize { - slots.iter().filter(|slot| slot.is_some()).count() -} - -fn validate_slot_accounting(slots: &[Option], queue_size: usize) -> DevResult { - let occupied = count_populated_slots(slots); - if occupied > queue_size { - return Err(DevError::BadState); - } - Ok(occupied) -} - -fn validate_tx_accounting( - slots: &[Option], - free_buffers: usize, - checked_out_buffers: usize, - queue_size: usize, -) -> DevResult { - let in_flight = validate_slot_accounting(slots, queue_size)?; - if in_flight + free_buffers + checked_out_buffers != queue_size { - return Err(DevError::BadState); - } - Ok(()) -} - -fn rollback_checked_out_tx_buffer( - free_buffers: &mut Vec, - checked_out_buffers: &mut usize, - tx_buf: T, -) -> DevResult { - if *checked_out_buffers == 0 { - return Err(DevError::BadState); - } - *checked_out_buffers -= 1; - free_buffers.push(tx_buf); - Ok(()) -} - -fn rollback_checked_out_rx_buffer( - free_buffers: &mut Vec, - checked_out_buffers: &mut usize, - rx_buf: T, -) -> DevResult { - if *checked_out_buffers == 0 { - return Err(DevError::BadState); - } - *checked_out_buffers -= 1; - free_buffers.push(rx_buf); - Ok(()) -} - -fn validate_packet_layout(header_len: usize, packet_len: usize, capacity: usize) -> DevResult { - if header_len > capacity { - return Err(DevError::BadState); - } - if header_len + packet_len > capacity { - return Err(DevError::InvalidParam); - } - Ok(()) -} - -fn validate_received_packet_layout( - header_len: usize, - packet_len: usize, - capacity: usize, -) -> DevResult { - validate_packet_layout(header_len, packet_len, capacity) -} - -fn queue_occupancy(slots: &[Option], queue_size: usize) -> DevResult { - let occupied = validate_slot_accounting(slots, queue_size)?; - Ok(QueueOccupancy::new(occupied, queue_size)) -} - -fn validate_runtime_snapshot(snapshot: RuntimeStateSnapshot) -> DevResult { - if snapshot.rx.capacity != snapshot.tx_in_flight.capacity { - return Err(DevError::BadState); - } - if !snapshot.all_rx_buffers_accounted_for() { - return Err(DevError::BadState); - } - if !snapshot.all_tx_buffers_accounted_for() { - return Err(DevError::BadState); - } - Ok(()) -} - -/// The VirtIO network device driver. -/// -/// `QS` is the VirtIO queue size. -pub struct VirtIoNetDev { - rx_buffers: Vec>, - tx_buffers: Vec>, - free_rx_bufs: Vec, - free_tx_bufs: Vec, - checked_out_rx_buffers: usize, - checked_out_tx_buffers: usize, - poisoned: bool, - buf_pool: Arc, - inner: InnerDev, - irq: Option, -} - -unsafe impl Send for VirtIoNetDev {} -unsafe impl Sync for VirtIoNetDev {} - -impl VirtIoNetDev { - fn ensure_not_poisoned(&self) -> DevResult { - if self.poisoned { - return Err(DevError::BadState); - } - Ok(()) - } - - fn queue_capacity(&self) -> usize { - QS - } - - fn queued_rx_buffers(&self) -> usize { - count_populated_slots(&self.rx_buffers) - } - - fn in_flight_tx_buffers(&self) -> usize { - count_populated_slots(&self.tx_buffers) - } - - fn free_tx_buffer_count(&self) -> usize { - self.free_tx_bufs.len() - } - - fn free_rx_buffer_count(&self) -> usize { - self.free_rx_bufs.len() - } - - fn checked_out_rx_buffer_count(&self) -> usize { - self.checked_out_rx_buffers - } - - fn checked_out_tx_buffer_count(&self) -> usize { - self.checked_out_tx_buffers - } - - fn has_queued_rx_buffer(&self) -> bool { - self.runtime_state_snapshot() - .map(|snapshot| snapshot.rx.occupied != 0) - .unwrap_or(false) - } - - fn has_in_flight_tx_buffer(&self) -> bool { - self.runtime_state_snapshot() - .map(|snapshot| snapshot.tx_in_flight.occupied != 0) - .unwrap_or(false) - } - - fn has_free_tx_buffer(&self) -> bool { - self.runtime_state_snapshot() - .map(|snapshot| snapshot.can_allocate_tx()) - .unwrap_or(false) - } - - fn checkout_rx_buffer(&mut self) -> DevResult { - let checked_out = self.checked_out_rx_buffer_count(); - if checked_out >= self.queue_capacity() { - return Err(DevError::BadState); - } - self.checked_out_rx_buffers += 1; - Ok(()) - } - - fn recycle_checked_out_rx_buffer(&mut self) -> DevResult { - if self.checked_out_rx_buffers == 0 { - return Err(DevError::BadState); - } - self.checked_out_rx_buffers -= 1; - Ok(()) - } - - fn checkout_tx_buffer(&mut self) -> DevResult { - let checked_out = self.checked_out_tx_buffer_count(); - if checked_out >= self.queue_capacity() { - return Err(DevError::BadState); - } - self.checked_out_tx_buffers += 1; - Ok(()) - } - - fn submit_checked_out_tx_buffer(&mut self) -> DevResult { - if self.checked_out_tx_buffers == 0 { - return Err(DevError::BadState); - } - self.checked_out_tx_buffers -= 1; - Ok(()) - } - - fn recycle_tx_buffer_box(&mut self, tx_buf: NetBufBox) { - self.free_tx_bufs.push(tx_buf); - } - - fn alloc_tx_buffer_box(&mut self) -> DevResult { - self.free_tx_bufs.pop().ok_or(DevError::NoMemory) - } - - fn prepare_tx_buffer(&self, tx_buf: &mut NetBuf, packet_len: usize) -> DevResult { - let header_len = tx_buf.header_len(); - validate_packet_layout(header_len, packet_len, tx_buf.capacity())?; - tx_buf.set_packet_len(packet_len); - Ok(()) - } - - fn runtime_state_snapshot(&self) -> DevResult { - self.ensure_not_poisoned()?; - let rx = queue_occupancy(&self.rx_buffers, self.queue_capacity())?; - let tx_in_flight = queue_occupancy(&self.tx_buffers, self.queue_capacity())?; - Ok(RuntimeStateSnapshot { - rx, - tx_in_flight, - free_rx_buffers: self.free_rx_buffer_count(), - free_tx_buffers: self.free_tx_buffer_count(), - checked_out_rx_buffers: self.checked_out_rx_buffer_count(), - checked_out_tx_buffers: self.checked_out_tx_buffer_count(), - }) - } - - fn validate_tx_capacity(&self, needed: usize) -> DevResult { - let snapshot = self.runtime_state_snapshot()?; - if snapshot.free_tx_buffers < needed { - return Err(DevError::NoMemory); - } - Ok(()) - } - - fn validate_tx_provision_capacity(&self, needed: usize) -> DevResult { - let snapshot = self.runtime_state_snapshot()?; - if !snapshot.can_provision_tx(needed) { - return Err(DevError::BadState); - } - Ok(()) - } - - fn validate_rx_capacity(&self, needed: usize) -> DevResult { - let snapshot = self.runtime_state_snapshot()?; - if !snapshot.rx.has_capacity_for(needed) { - return Err(DevError::BadState); - } - Ok(()) - } - - fn validate_tx_completion_capacity(&self) -> DevResult { - let snapshot = self.runtime_state_snapshot()?; - if snapshot.tx_in_flight.occupied > snapshot.queue_size() { - return Err(DevError::BadState); - } - Ok(()) - } - - fn validate_before_rx_recycle(&self) -> DevResult { - let snapshot = self.runtime_state_snapshot()?; - if !snapshot.rx.has_capacity_for(1) { - return Err(DevError::BadState); - } - if snapshot.checked_out_rx_buffers == 0 { - return Err(DevError::BadState); - } - Ok(()) - } - - fn validate_before_tx_recycle(&self) -> DevResult { - if !self.has_in_flight_tx_buffer() { - return Ok(()); - } - self.validate_tx_completion_capacity() - } - - fn validate_before_transmit(&self) -> DevResult { - if self.checked_out_tx_buffer_count() == 0 { - return Err(DevError::BadState); - } - Ok(()) - } - - fn validate_before_receive(&self) -> DevResult { - if !self.has_queued_rx_buffer() { - return Err(DevError::BadState); - } - Ok(()) - } - - fn allocate_pool_buffer(&self) -> DevResult { - self.buf_pool.alloc_boxed().ok_or(DevError::NoMemory) - } - - fn poison(&mut self) { - self.poisoned = true; - } - - fn poison_submitted_rx_buffer(&mut self, rx_buf: NetBufBox) -> DevError { - let _ = self.recycle_checked_out_rx_buffer(); - self.poison(); - core::mem::forget(rx_buf); - DevError::BadState - } - - fn rollback_unsubmitted_rx_buffer(&mut self, rx_buf: NetBufBox, err: DevError) -> DevError { - if rollback_checked_out_rx_buffer( - &mut self.free_rx_bufs, - &mut self.checked_out_rx_buffers, - rx_buf, - ) - .is_err() - { - self.poison(); - return DevError::BadState; - } - err - } - - fn poison_submitted_tx_buffer(&mut self, tx_buf: NetBufBox) -> DevError { - let _ = self.submit_checked_out_tx_buffer(); - self.poison(); - core::mem::forget(tx_buf); - DevError::BadState - } - - fn rollback_unsubmitted_tx_buffer(&mut self, tx_buf: NetBufBox, err: DevError) -> DevError { - if rollback_checked_out_tx_buffer( - &mut self.free_tx_bufs, - &mut self.checked_out_tx_buffers, - tx_buf, - ) - .is_err() - { - self.poison(); - return DevError::BadState; - } - err - } - - fn prime_rx_buffer(&mut self, expected_token: usize) -> DevResult { - self.validate_rx_capacity(1)?; - let mut rx_buf = self.allocate_pool_buffer()?; - let token = unsafe { - self.inner - .receive_begin(rx_buf.raw_buf_mut()) - .map_err(as_dev_err)? - }; - if self.validate_rx_token(token, expected_token).is_err() { - self.poison(); - core::mem::forget(rx_buf); - return Err(DevError::BadState); - } - if let Err(rx_buf) = self.insert_rx_buffer_or_return(token as usize, rx_buf) { - self.poison(); - core::mem::forget(rx_buf); - return Err(DevError::BadState); - } - Ok(()) - } - - fn fill_rx_buffers(&mut self) -> DevResult { - for expected_token in 0..QS { - self.prime_rx_buffer(expected_token)?; - } - Ok(()) - } - - fn prepare_free_tx_buffer(&mut self) -> DevResult { - self.validate_tx_provision_capacity(1)?; - let mut tx_buf = self.allocate_pool_buffer()?; - let hdr_len = self - .inner - .fill_buffer_header(tx_buf.raw_buf_mut()) - .map_err(as_dev_err)?; - tx_buf.set_header_len(hdr_len); - self.recycle_tx_buffer_box(tx_buf); - Ok(()) - } - - fn fill_tx_buffers(&mut self) -> DevResult { - for _ in 0..QS { - self.prepare_free_tx_buffer()?; - } - Ok(()) - } - - fn validate_rx_token(&self, token: u16, expected_token: usize) -> DevResult { - validate_queue_token(token, expected_token, QS) - } - - fn insert_rx_buffer_or_return( - &mut self, - token: usize, - rx_buf: NetBufBox, - ) -> Result<(), NetBufBox> { - insert_buffer_or_return(&mut self.rx_buffers, token, rx_buf) - } - - fn take_rx_buffer(&mut self, token: u16) -> DevResult { - take_buffer(&mut self.rx_buffers, token) - } - - fn insert_tx_buffer_or_return( - &mut self, - token: u16, - tx_buf: NetBufBox, - ) -> Result<(), NetBufBox> { - insert_buffer_or_return(&mut self.tx_buffers, token as usize, tx_buf) - } - - fn take_tx_buffer(&mut self, token: u16) -> DevResult { - take_buffer(&mut self.tx_buffers, token) - } - - fn begin_recycled_rx_buffer(&mut self, rx_buf: &mut NetBuf) -> DevResult { - unsafe { - self.inner - .receive_begin(rx_buf.raw_buf_mut()) - .map_err(as_dev_err) - } - } - - fn replenish_free_rx_buffers(&mut self) -> DevResult { - while let Some(mut rx_buf) = self.free_rx_bufs.pop() { - match self.begin_recycled_rx_buffer(&mut rx_buf) { - Ok(token) => { - if let Err(rx_buf) = self.insert_rx_buffer_or_return(token as usize, rx_buf) { - return Err(self.poison_submitted_rx_buffer(rx_buf)); - } - } - Err(err) => { - self.free_rx_bufs.push(rx_buf); - return Err(err); - } - } - } - Ok(()) - } - - fn submit_recycled_rx_buffer(&mut self, rx_buf: NetBufBox) -> DevResult { - self.validate_rx_capacity(1)?; - let mut rx_buf = rx_buf; - let token = match self.begin_recycled_rx_buffer(&mut rx_buf) { - Ok(token) => token, - Err(err) => return Err(self.rollback_unsubmitted_rx_buffer(rx_buf, err)), - }; - if let Err(rx_buf) = self.insert_rx_buffer_or_return(token as usize, rx_buf) { - return Err(self.poison_submitted_rx_buffer(rx_buf)); - } - self.recycle_checked_out_rx_buffer() - } - - fn poll_completed_tx_token(&mut self) -> Option { - self.inner.poll_transmit() - } - - fn complete_tx_buffer(&mut self, token: u16) -> DevResult { - self.validate_tx_completion_capacity()?; - let tx_buf = self.take_tx_buffer(token)?; - unsafe { - self.inner - .transmit_complete(token, tx_buf.packet_with_header()) - .map_err(as_dev_err)?; - } - Ok(tx_buf) - } - - fn recycle_completed_transmissions(&mut self) -> DevResult { - let mut recycled = 0; - while let Some(token) = self.poll_completed_tx_token() { - let tx_buf = self.complete_tx_buffer(token)?; - self.recycle_tx_buffer_box(tx_buf); - recycled += 1; - } - Ok(recycled) - } - - fn submit_tx_buffer(&mut self, tx_buf: NetBufBox) -> DevResult { - let token = match unsafe { self.inner.transmit_begin(tx_buf.packet_with_header()) } { - Ok(token) => token, - Err(err) => return Err(self.rollback_unsubmitted_tx_buffer(tx_buf, as_dev_err(err))), - }; - if let Err(tx_buf) = self.insert_tx_buffer_or_return(token, tx_buf) { - return Err(self.poison_submitted_tx_buffer(tx_buf)); - } - self.submit_checked_out_tx_buffer() - } - - fn poll_received_token(&mut self) -> Option { - self.inner.poll_receive() - } - - fn complete_received_buffer(&mut self, token: u16) -> DevResult { - let mut rx_buf = self.take_rx_buffer(token)?; - let (hdr_len, pkt_len) = unsafe { - self.inner - .receive_complete(token, rx_buf.raw_buf_mut()) - .map_err(as_dev_err)? - }; - if let Err(err) = validate_received_packet_layout(hdr_len, pkt_len, rx_buf.capacity()) { - self.free_rx_bufs.push(rx_buf); - let _ = self.replenish_free_rx_buffers(); - return Err(err); - } - rx_buf.set_header_len(hdr_len); - rx_buf.set_packet_len(pkt_len); - Ok(rx_buf) - } - - fn validate_runtime_state(&self) -> DevResult { - let snapshot = self.runtime_state_snapshot()?; - if self.queued_rx_buffers() != snapshot.rx.occupied { - return Err(DevError::BadState); - } - if self.in_flight_tx_buffers() != snapshot.tx_in_flight.occupied { - return Err(DevError::BadState); - } - validate_tx_accounting( - &self.tx_buffers, - self.free_tx_buffer_count(), - self.checked_out_tx_buffer_count(), - QS, - )?; - validate_runtime_snapshot(snapshot)?; - Ok(()) - } - - /// Creates a new driver instance and initializes the device, or returns - /// an error if any step fails. - pub fn try_new(transport: T, irq: Option) -> DevResult { - // Keep queue bookkeeping on the heap to avoid very large debug stack frames. - let inner = InnerDev::new(transport).map_err(as_dev_err)?; - let mut rx_buffers = Vec::with_capacity(QS); - rx_buffers.resize_with(QS, || None); - let mut tx_buffers = Vec::with_capacity(QS); - tx_buffers.resize_with(QS, || None); - let buf_pool = NetBufPool::new(2 * QS, NET_BUF_LEN)?; - let free_rx_bufs = Vec::with_capacity(QS); - let free_tx_bufs = Vec::with_capacity(QS); - - let mut dev = Self { - rx_buffers, - inner, - tx_buffers, - free_rx_bufs, - free_tx_bufs, - checked_out_rx_buffers: 0, - checked_out_tx_buffers: 0, - poisoned: false, - buf_pool, - irq, - }; - - // 1. Fill all rx buffers. - dev.fill_rx_buffers()?; - - // 2. Allocate all tx buffers. - dev.fill_tx_buffers()?; - - // 3. Validate queue bookkeeping before exposing the device. - dev.validate_runtime_state()?; - - if irq.is_some() { - dev.inner.enable_interrupts(); - } - - // 4. Return the driver instance. - Ok(dev) - } -} - -impl BaseDriverOps for VirtIoNetDev { - fn device_name(&self) -> &str { - "virtio-net" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Net - } - - fn irq_num(&self) -> Option { - self.irq - } -} - -impl NetDriverOps for VirtIoNetDev { - #[inline] - fn mac_address(&self) -> EthernetAddress { - EthernetAddress(self.inner.mac_address()) - } - - #[inline] - fn can_transmit(&self) -> bool { - !self.poisoned && self.has_free_tx_buffer() && self.inner.can_send() - } - - #[inline] - fn can_receive(&self) -> bool { - !self.poisoned && self.inner.poll_receive().is_some() - } - - #[inline] - fn rx_queue_size(&self) -> usize { - QS - } - - #[inline] - fn tx_queue_size(&self) -> usize { - QS - } - - fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { - self.validate_runtime_state()?; - self.validate_before_rx_recycle()?; - let rx_buf = unsafe { NetBuf::from_buf_ptr(rx_buf) }; - self.submit_recycled_rx_buffer(rx_buf)?; - let _ = self.replenish_free_rx_buffers(); - self.validate_runtime_state() - } - - fn recycle_tx_buffers(&mut self) -> DevResult { - self.validate_runtime_state()?; - self.validate_before_tx_recycle()?; - let _ = self.recycle_completed_transmissions()?; - self.validate_runtime_state() - } - - fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { - self.validate_runtime_state()?; - self.validate_before_transmit()?; - let tx_buf = unsafe { NetBuf::from_buf_ptr(tx_buf) }; - self.submit_tx_buffer(tx_buf)?; - self.validate_runtime_state() - } - - fn receive(&mut self) -> DevResult { - self.validate_runtime_state()?; - self.validate_before_receive()?; - if let Some(token) = self.poll_received_token() { - let rx_buf = self.complete_received_buffer(token)?; - self.checkout_rx_buffer()?; - self.validate_runtime_state()?; - Ok(rx_buf.into_buf_ptr()) - } else { - Err(DevError::Again) - } - } - - fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { - self.validate_runtime_state()?; - self.validate_tx_capacity(1)?; - // 0. Allocate a buffer from the queue. - let mut net_buf = self.alloc_tx_buffer_box()?; - self.checkout_tx_buffer()?; - - // 1. Check if the buffer is large enough. - if let Err(err) = self.prepare_tx_buffer(&mut net_buf, size) { - self.recycle_tx_buffer_box(net_buf); - self.submit_checked_out_tx_buffer()?; - return Err(err); - } - - // 2. Return the buffer. - self.validate_runtime_state()?; - Ok(net_buf.into_buf_ptr()) - } - - fn handle_irq(&mut self) -> NetIrqEvent { - self.inner.ack_interrupt(); - - let mut events = NetIrqEvent::empty(); - if self.inner.poll_receive().is_some() { - events |= NetIrqEvent::RX_READY; - } - - if self - .recycle_completed_transmissions() - .ok() - .is_some_and(|count| count != 0) - { - events |= NetIrqEvent::TX_DONE; - } - - if events.is_empty() { - NetIrqEvent::SPURIOUS - } else { - events - } - } -} - -#[cfg(test)] -mod tests { - use alloc::vec; - - use ax_driver_base::DevError; - - use super::{ - QueueOccupancy, RuntimeStateSnapshot, count_populated_slots, insert_buffer, - rollback_checked_out_rx_buffer, rollback_checked_out_tx_buffer, take_buffer, - validate_packet_layout, validate_queue_token, validate_received_packet_layout, - validate_runtime_snapshot, validate_slot_accounting, validate_tx_accounting, - }; - - #[test] - fn validate_queue_token_accepts_expected_token() { - assert!(validate_queue_token(2, 2, 4).is_ok()); - } - - #[test] - fn validate_queue_token_rejects_out_of_range_token() { - assert!(matches!( - validate_queue_token(4, 4, 4), - Err(DevError::BadState) - )); - } - - #[test] - fn validate_queue_token_rejects_unexpected_token() { - assert!(matches!( - validate_queue_token(1, 2, 4), - Err(DevError::BadState) - )); - } - - #[test] - fn insert_buffer_rejects_duplicate_slot() { - let mut slots = vec![Some(1u8), None]; - assert!(matches!( - insert_buffer(&mut slots, 0, 2u8), - Err(DevError::BadState) - )); - } - - #[test] - fn take_buffer_rejects_empty_slot() { - let mut slots = vec![None::, Some(2u8)]; - assert!(matches!( - take_buffer(&mut slots, 0), - Err(DevError::BadState) - )); - } - - #[test] - fn insert_and_take_buffer_round_trip() { - let mut slots = vec![None::, None]; - insert_buffer(&mut slots, 1, 7u8).unwrap(); - let value = take_buffer(&mut slots, 1).unwrap(); - assert_eq!(value, 7); - assert!(slots[1].is_none()); - } - - #[test] - fn validate_packet_layout_accepts_valid_lengths() { - assert!(validate_packet_layout(14, 128, 1526).is_ok()); - } - - #[test] - fn validate_packet_layout_rejects_oversized_packet() { - assert!(matches!( - validate_packet_layout(64, 1500, 1526), - Err(DevError::InvalidParam) - )); - } - - #[test] - fn validate_packet_layout_rejects_invalid_header_len() { - assert!(matches!( - validate_packet_layout(1600, 8, 1526), - Err(DevError::BadState) - )); - } - - #[test] - fn validate_received_packet_layout_accepts_capacity_boundary() { - assert!(validate_received_packet_layout(10, 1516, 1526).is_ok()); - } - - #[test] - fn validate_received_packet_layout_rejects_oversized_total_len() { - assert!(matches!( - validate_received_packet_layout(14, 1513, 1526), - Err(DevError::InvalidParam) - )); - } - - #[test] - fn count_populated_slots_counts_present_entries() { - let slots = vec![Some(1u8), None, Some(3u8)]; - assert_eq!(count_populated_slots(&slots), 2); - } - - #[test] - fn validate_slot_accounting_accepts_valid_occupancy() { - let slots = vec![Some(1u8), None, Some(3u8)]; - assert_eq!(validate_slot_accounting(&slots, 3).unwrap(), 2); - } - - #[test] - fn validate_tx_accounting_accepts_balanced_state() { - let slots = vec![Some(1u8), None, Some(3u8), None]; - assert!(validate_tx_accounting(&slots, 2, 0, 4).is_ok()); - } - - #[test] - fn validate_tx_accounting_rejects_unbalanced_state() { - let slots = vec![Some(1u8), None, Some(3u8), None]; - assert!(matches!( - validate_tx_accounting(&slots, 1, 0, 4), - Err(DevError::BadState) - )); - } - - #[test] - fn rollback_checked_out_tx_buffer_restores_free_list_and_count() { - let mut free_buffers = vec![1u8]; - let mut checked_out_buffers = 1usize; - rollback_checked_out_tx_buffer(&mut free_buffers, &mut checked_out_buffers, 2u8).unwrap(); - assert_eq!(checked_out_buffers, 0); - assert_eq!(free_buffers, vec![1u8, 2u8]); - } - - #[test] - fn rollback_checked_out_tx_buffer_rejects_missing_checked_out_state() { - let mut free_buffers = vec![1u8]; - let mut checked_out_buffers = 0usize; - assert!(matches!( - rollback_checked_out_tx_buffer(&mut free_buffers, &mut checked_out_buffers, 2u8), - Err(DevError::BadState) - )); - assert_eq!(checked_out_buffers, 0); - assert_eq!(free_buffers, vec![1u8]); - } - - #[test] - fn rollback_checked_out_rx_buffer_restores_free_list_and_count() { - let mut free_buffers = vec![1u8]; - let mut checked_out_buffers = 1usize; - rollback_checked_out_rx_buffer(&mut free_buffers, &mut checked_out_buffers, 2u8).unwrap(); - assert_eq!(checked_out_buffers, 0); - assert_eq!(free_buffers, vec![1u8, 2u8]); - } - - #[test] - fn rollback_checked_out_rx_buffer_rejects_missing_checked_out_state() { - let mut free_buffers = vec![1u8]; - let mut checked_out_buffers = 0usize; - assert!(matches!( - rollback_checked_out_rx_buffer(&mut free_buffers, &mut checked_out_buffers, 2u8), - Err(DevError::BadState) - )); - assert_eq!(checked_out_buffers, 0); - assert_eq!(free_buffers, vec![1u8]); - } - - #[test] - fn runtime_snapshot_accepts_tx_provisioning_before_free_list_is_filled() { - let snapshot = RuntimeStateSnapshot { - rx: QueueOccupancy::new(4, 4), - tx_in_flight: QueueOccupancy::new(0, 4), - free_rx_buffers: 0, - free_tx_buffers: 0, - checked_out_rx_buffers: 0, - checked_out_tx_buffers: 0, - }; - assert!(snapshot.can_provision_tx(1)); - assert!(!snapshot.can_allocate_tx()); - } - - #[test] - fn validate_runtime_snapshot_accepts_reclaimed_rx_buffer() { - let snapshot = RuntimeStateSnapshot { - rx: QueueOccupancy::new(3, 4), - tx_in_flight: QueueOccupancy::new(0, 4), - free_rx_buffers: 1, - free_tx_buffers: 4, - checked_out_rx_buffers: 0, - checked_out_tx_buffers: 0, - }; - - assert!(validate_runtime_snapshot(snapshot).is_ok()); - } - - #[test] - fn validate_runtime_snapshot_rejects_lost_rx_buffer() { - let snapshot = RuntimeStateSnapshot { - rx: QueueOccupancy::new(3, 4), - tx_in_flight: QueueOccupancy::new(0, 4), - free_rx_buffers: 0, - free_tx_buffers: 4, - checked_out_rx_buffers: 0, - checked_out_tx_buffers: 0, - }; - - assert!(matches!( - validate_runtime_snapshot(snapshot), - Err(DevError::BadState) - )); - } -} diff --git a/components/axdriver_crates/axdriver_virtio/src/socket.rs b/components/axdriver_crates/axdriver_virtio/src/socket.rs deleted file mode 100644 index ca64e8add7..0000000000 --- a/components/axdriver_crates/axdriver_virtio/src/socket.rs +++ /dev/null @@ -1,854 +0,0 @@ -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use ax_driver_vsock::{VsockConnId, VsockDriverEvent, VsockDriverOps}; -use virtio_drivers::{ - Hal, - device::socket::{ - DisconnectReason, VirtIOSocket, VsockAddr, VsockConnectionManager as InnerDev, VsockEvent, - VsockEventType, - }, - transport::Transport, -}; - -use crate::as_dev_err; - -const DEFAULT_RX_BUFFER_CAPACITY: u32 = 32 * 1024; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct MappedConnId { - peer_addr: VsockAddr, - local_port: u32, -} - -impl MappedConnId { - const fn new(peer_addr: VsockAddr, local_port: u32) -> Self { - Self { - peer_addr, - local_port, - } - } - - const fn peer_addr(self) -> VsockAddr { - self.peer_addr - } - - const fn local_port(self) -> u32 { - self.local_port - } - - fn into_driver_conn_id(self) -> VsockConnId { - VsockConnId { - peer_addr: ax_driver_vsock::VsockAddr { - cid: self.peer_addr.cid, - port: self.peer_addr.port, - }, - local_port: self.local_port, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum ConnectionOperation { - Connect, - Send, - Receive, - ReceiveAvailable, - Disconnect, - Abort, -} - -impl ConnectionOperation { - const fn requires_non_empty_buffer(self) -> bool { - matches!(self, Self::Send | Self::Receive) - } - - const fn refreshes_credit_after_completion(self) -> bool { - matches!(self, Self::Receive | Self::ReceiveAvailable) - } - - const fn name(self) -> &'static str { - match self { - Self::Connect => "connect", - Self::Send => "send", - Self::Receive => "receive", - Self::ReceiveAvailable => "receive_available", - Self::Disconnect => "disconnect", - Self::Abort => "abort", - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct OperationRequest { - conn: MappedConnId, - operation: ConnectionOperation, -} - -impl OperationRequest { - const fn new(conn: MappedConnId, operation: ConnectionOperation) -> Self { - Self { conn, operation } - } - - const fn conn(self) -> MappedConnId { - self.conn - } - - const fn operation(self) -> ConnectionOperation { - self.operation - } -} - -fn normalize_operation_request(request: OperationRequest) -> DevResult { - let conn = validate_mapped_conn(request.conn(), request.operation())?; - Ok(OperationRequest::new(conn, request.operation())) -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct ListenRequest { - src_port: u32, -} - -impl ListenRequest { - const fn new(src_port: u32) -> Self { - Self { src_port } - } - - const fn src_port(self) -> u32 { - self.src_port - } - - const fn is_valid(self) -> bool { - self.src_port != 0 - } -} - -fn normalize_listen_request(request: ListenRequest) -> Option { - validate_port(request.src_port()).ok().map(|_| request) -} - -fn should_listen_on_port(request: ListenRequest) -> bool { - request.is_valid() -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum TranslatedEventKind { - ConnectionRequest, - Connected, - Received(usize), - Disconnected, - CreditUpdate, - Unknown, -} - -impl TranslatedEventKind { - const fn is_connection_event(self) -> bool { - matches!( - self, - Self::ConnectionRequest | Self::Connected | Self::Disconnected - ) - } - - const fn is_data_event(self) -> bool { - matches!(self, Self::Received(_) | Self::CreditUpdate) - } - - const fn should_surface_to_driver(self) -> bool { - !matches!(self, Self::Unknown) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct TranslatedEvent { - conn_id: VsockConnId, - kind: TranslatedEventKind, -} - -impl TranslatedEvent { - const fn new(conn_id: VsockConnId, kind: TranslatedEventKind) -> Self { - Self { conn_id, kind } - } - - const fn kind(self) -> TranslatedEventKind { - self.kind - } - - const fn conn_id(self) -> VsockConnId { - self.conn_id - } - - const fn should_surface_to_driver(self) -> bool { - self.kind.should_surface_to_driver() - } -} - -#[derive(Debug)] -enum PollOutcome { - NoEvent, - DriverEvent(VsockDriverEvent), -} - -impl PollOutcome { - const fn into_option(self) -> Option { - match self { - Self::NoEvent => None, - Self::DriverEvent(event) => Some(event), - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum CreditRefreshPolicy { - Never, - AfterReceive, - AfterQuery, -} - -impl CreditRefreshPolicy { - const fn should_refresh_after_recv(self, bytes_read: usize) -> bool { - matches!(self, Self::AfterReceive) && bytes_read != 0 - } - - const fn should_refresh_after_query(self) -> bool { - matches!(self, Self::AfterQuery) - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct EventRoutingDecision { - translated: Option, - refresh_credit: bool, -} - -impl EventRoutingDecision { - const fn ignore() -> Self { - Self { - translated: None, - refresh_credit: false, - } - } - - const fn surface(translated: TranslatedEvent) -> Self { - Self { - translated: Some(translated), - refresh_credit: false, - } - } - - const fn translated(self) -> Option { - self.translated - } - - const fn should_refresh_credit(self) -> bool { - self.refresh_credit - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct EventEndpoints { - source: ax_driver_vsock::VsockAddr, - destination: ax_driver_vsock::VsockAddr, -} - -impl EventEndpoints { - fn from_event(event: &VsockEvent) -> DevResult { - let source = map_socket_addr_to_driver(event.source); - let destination = map_socket_addr_to_driver(event.destination); - validate_event_endpoint(&source)?; - validate_port(destination.port)?; - Ok(Self { - source, - destination, - }) - } - - const fn source(self) -> ax_driver_vsock::VsockAddr { - self.source - } - - const fn destination(self) -> ax_driver_vsock::VsockAddr { - self.destination - } - - fn into_conn_id(self) -> VsockConnId { - VsockConnId { - peer_addr: self.source(), - local_port: self.destination().port, - } - } -} - -fn validate_port(port: u32) -> DevResult<()> { - if port == 0 { - return Err(DevError::InvalidParam); - } - Ok(()) -} - -fn validate_buffer_len(buf_len: usize, operation: ConnectionOperation) -> DevResult<()> { - if operation.requires_non_empty_buffer() && buf_len == 0 { - return Err(DevError::InvalidParam); - } - Ok(()) -} - -const fn short_circuit_empty_io(buf_len: usize) -> Option { - if buf_len == 0 { Some(0) } else { None } -} - -fn validate_peer_addr_for_operation( - addr: &ax_driver_vsock::VsockAddr, - _operation: ConnectionOperation, -) -> DevResult<()> { - validate_port(addr.port) -} - -fn validate_conn_id(cid: VsockConnId, operation: ConnectionOperation) -> DevResult { - validate_peer_addr_for_operation(&cid.peer_addr, operation)?; - validate_port(cid.local_port)?; - Ok(cid) -} - -fn validate_event_endpoint(addr: &ax_driver_vsock::VsockAddr) -> DevResult<()> { - validate_port(addr.port) -} - -fn validate_received_length_for_event(length: usize) -> DevResult { - if length == 0 { - return Err(DevError::InvalidParam); - } - Ok(length) -} - -fn map_socket_addr_to_driver(addr: VsockAddr) -> ax_driver_vsock::VsockAddr { - ax_driver_vsock::VsockAddr { - cid: addr.cid, - port: addr.port, - } -} - -fn map_peer_addr(addr: ax_driver_vsock::VsockAddr) -> VsockAddr { - VsockAddr { - cid: addr.cid, - port: addr.port, - } -} - -fn validate_conn_id_for_operation( - cid: VsockConnId, - operation: ConnectionOperation, -) -> DevResult { - let cid = validate_conn_id(cid, operation)?; - match operation { - ConnectionOperation::Connect - | ConnectionOperation::Send - | ConnectionOperation::Receive - | ConnectionOperation::ReceiveAvailable - | ConnectionOperation::Disconnect - | ConnectionOperation::Abort => Ok(cid), - } -} - -fn map_conn_id_checked( - cid: VsockConnId, - operation: ConnectionOperation, -) -> DevResult { - let cid = validate_conn_id_for_operation(cid, operation)?; - Ok(MappedConnId::new( - map_peer_addr(cid.peer_addr), - cid.local_port, - )) -} - -fn validate_send_request(cid: VsockConnId, buf_len: usize) -> DevResult { - validate_buffer_len(buf_len, ConnectionOperation::Send)?; - map_conn_id_checked(cid, ConnectionOperation::Send) -} - -fn validate_recv_request(cid: VsockConnId, buf_len: usize) -> DevResult { - validate_buffer_len(buf_len, ConnectionOperation::Receive)?; - map_conn_id_checked(cid, ConnectionOperation::Receive) -} - -fn validate_credit_query(cid: VsockConnId) -> DevResult { - map_conn_id_checked(cid, ConnectionOperation::ReceiveAvailable) -} - -fn validate_disconnect_request(cid: VsockConnId) -> DevResult { - map_conn_id_checked(cid, ConnectionOperation::Disconnect) -} - -fn validate_abort_request(cid: VsockConnId) -> DevResult { - map_conn_id_checked(cid, ConnectionOperation::Abort) -} - -fn validate_connect_request(cid: VsockConnId) -> DevResult { - map_conn_id_checked(cid, ConnectionOperation::Connect) -} - -fn build_operation_request( - cid: VsockConnId, - operation: ConnectionOperation, -) -> DevResult { - let conn = match operation { - ConnectionOperation::Connect => validate_connect_request(cid)?, - ConnectionOperation::ReceiveAvailable => validate_credit_query(cid)?, - ConnectionOperation::Disconnect => validate_disconnect_request(cid)?, - ConnectionOperation::Abort => validate_abort_request(cid)?, - ConnectionOperation::Send | ConnectionOperation::Receive => { - return Err(DevError::InvalidParam); - } - }; - Ok(OperationRequest::new(conn, operation)) -} - -fn build_buffered_operation_request( - cid: VsockConnId, - operation: ConnectionOperation, - buf_len: usize, -) -> DevResult { - let conn = match operation { - ConnectionOperation::Send => validate_send_request(cid, buf_len)?, - ConnectionOperation::Receive => validate_recv_request(cid, buf_len)?, - ConnectionOperation::Connect - | ConnectionOperation::ReceiveAvailable - | ConnectionOperation::Disconnect - | ConnectionOperation::Abort => return Err(DevError::InvalidParam), - }; - Ok(OperationRequest::new(conn, operation)) -} - -fn prepare_listen_request(src_port: u32) -> Option { - validate_port(src_port) - .ok() - .map(|_| ListenRequest::new(src_port)) -} - -fn credit_refresh_policy_for_operation(operation: ConnectionOperation) -> CreditRefreshPolicy { - match operation { - ConnectionOperation::Receive => CreditRefreshPolicy::AfterReceive, - ConnectionOperation::ReceiveAvailable => CreditRefreshPolicy::AfterQuery, - ConnectionOperation::Connect - | ConnectionOperation::Send - | ConnectionOperation::Disconnect - | ConnectionOperation::Abort => CreditRefreshPolicy::Never, - } -} - -fn map_disconnect_reason(_reason: DisconnectReason) -> TranslatedEventKind { - TranslatedEventKind::Disconnected -} - -fn map_received_length(length: usize) -> TranslatedEventKind { - TranslatedEventKind::Received(validate_received_length_for_event(length).unwrap_or(length)) -} - -fn translate_event_kind(event_type: VsockEventType) -> TranslatedEventKind { - match event_type { - VsockEventType::ConnectionRequest => TranslatedEventKind::ConnectionRequest, - VsockEventType::Connected => TranslatedEventKind::Connected, - VsockEventType::Received { length } => map_received_length(length), - VsockEventType::Disconnected { reason } => map_disconnect_reason(reason), - VsockEventType::CreditUpdate => TranslatedEventKind::CreditUpdate, - _ => TranslatedEventKind::Unknown, - } -} - -/// The VirtIO socket device driver. -pub struct VirtIoSocketDev { - inner: InnerDev, -} - -unsafe impl Send for VirtIoSocketDev {} -unsafe impl Sync for VirtIoSocketDev {} - -impl VirtIoSocketDev { - /// Creates a new driver instance and initializes the device, or returns - /// an error if any step fails. - pub fn try_new(transport: T) -> DevResult { - let virtio_socket = VirtIOSocket::::new(transport).map_err(as_dev_err)?; - Ok(Self { - inner: InnerDev::new_with_capacity(virtio_socket, DEFAULT_RX_BUFFER_CAPACITY), - }) - } - - fn connect_mapped(&mut self, request: OperationRequest) -> DevResult<()> { - let conn = normalize_operation_request(request)?.conn(); - self.inner - .connect(conn.peer_addr(), conn.local_port()) - .map_err(as_dev_err) - } - - fn send_on_mapped(&mut self, request: OperationRequest, buf: &[u8]) -> DevResult { - let conn = normalize_operation_request(request)?.conn(); - match self.inner.send(conn.peer_addr(), conn.local_port(), buf) { - Ok(()) => Ok(buf.len()), - Err(e) => Err(as_dev_err(e)), - } - } - - fn recv_on_mapped(&mut self, request: OperationRequest, buf: &mut [u8]) -> DevResult { - let conn = normalize_operation_request(request)?.conn(); - let res = self - .inner - .recv(conn.peer_addr(), conn.local_port(), buf) - .map_err(as_dev_err); - self.refresh_peer_credit_after_recv(conn, &res); - res - } - - fn recv_available_on_mapped(&mut self, request: OperationRequest) -> DevResult { - let conn = normalize_operation_request(request)?.conn(); - self.inner - .recv_buffer_available_bytes(conn.peer_addr(), conn.local_port()) - .map_err(as_dev_err) - } - - fn shutdown_mapped(&mut self, request: OperationRequest) -> DevResult<()> { - let conn = normalize_operation_request(request)?.conn(); - self.inner - .shutdown(conn.peer_addr(), conn.local_port()) - .map_err(as_dev_err) - } - - fn abort_mapped(&mut self, request: OperationRequest) -> DevResult<()> { - let conn = normalize_operation_request(request)?.conn(); - self.inner - .force_close(conn.peer_addr(), conn.local_port()) - .map_err(as_dev_err) - } - - fn update_peer_credit(&mut self, conn: MappedConnId) { - let _ = self - .inner - .update_credit(conn.peer_addr(), conn.local_port()); - } - - fn refresh_peer_credit_after_recv( - &mut self, - conn: MappedConnId, - recv_result: &DevResult, - ) { - let policy = credit_refresh_policy_for_operation(ConnectionOperation::Receive); - if matches!(recv_result, Ok(bytes_read) if policy.should_refresh_after_recv(*bytes_read)) { - self.update_peer_credit(conn); - } - } - - fn refresh_peer_credit_for_query(&mut self, conn: MappedConnId) { - let policy = credit_refresh_policy_for_operation(ConnectionOperation::ReceiveAvailable); - if policy.should_refresh_after_query() { - self.update_peer_credit(conn); - } - } - - fn poll_raw_event(&mut self) -> DevResult> { - self.inner.poll().map_err(as_dev_err) - } - - fn listen_on_port(&mut self, request: ListenRequest) { - self.inner.listen(request.src_port()) - } - - fn poll_driver_event_once(&mut self) -> DevResult { - match self.poll_raw_event()? { - None => Ok(PollOutcome::NoEvent), - Some(event) => Ok(PollOutcome::DriverEvent(convert_vsock_event(event)?)), - } - } -} - -impl BaseDriverOps for VirtIoSocketDev { - fn device_name(&self) -> &str { - "virtio-socket" - } - - fn device_type(&self) -> DeviceType { - DeviceType::Vsock - } -} - -#[cfg(test)] -fn map_conn_id(cid: VsockConnId) -> (VsockAddr, u32) { - let mapped = map_conn_id_checked(cid, ConnectionOperation::Connect) - .expect("vsock connection id should be valid"); - (mapped.peer_addr, mapped.local_port) -} - -fn map_event_cid(event: &VsockEvent) -> VsockConnId { - EventEndpoints::from_event(event) - .map(EventEndpoints::into_conn_id) - .unwrap_or_default() -} - -fn map_driver_event(event: TranslatedEvent) -> VsockDriverEvent { - match event.kind { - TranslatedEventKind::ConnectionRequest => { - VsockDriverEvent::ConnectionRequest(event.conn_id) - } - TranslatedEventKind::Connected => VsockDriverEvent::Connected(event.conn_id), - TranslatedEventKind::Received(length) => VsockDriverEvent::Received(event.conn_id, length), - TranslatedEventKind::Disconnected => VsockDriverEvent::Disconnected(event.conn_id), - TranslatedEventKind::CreditUpdate => VsockDriverEvent::CreditUpdate(event.conn_id), - TranslatedEventKind::Unknown => VsockDriverEvent::Unknown, - } -} - -fn translate_vsock_event(event: VsockEvent) -> TranslatedEvent { - let conn_id = map_event_cid(&event); - let kind = translate_event_kind(event.event_type); - TranslatedEvent::new(conn_id, kind) -} - -fn validate_translated_event(translated: TranslatedEvent) -> DevResult { - validate_conn_id(translated.conn_id(), ConnectionOperation::Connect)?; - if let TranslatedEventKind::Received(length) = translated.kind() { - validate_received_length_for_event(length)?; - } - Ok(translated) -} - -fn should_surface_translated_event(translated: TranslatedEvent) -> bool { - if translated.kind().is_connection_event() { - return true; - } - if translated.kind().is_data_event() { - return true; - } - translated.should_surface_to_driver() -} - -fn validate_mapped_conn( - conn: MappedConnId, - operation: ConnectionOperation, -) -> DevResult { - let driver_conn = conn.into_driver_conn_id(); - validate_conn_id(driver_conn, operation)?; - if operation.refreshes_credit_after_completion() { - validate_peer_addr_for_operation(&driver_conn.peer_addr, operation)?; - } - let _ = operation.name(); - Ok(conn) -} - -fn route_translated_event(translated: TranslatedEvent) -> EventRoutingDecision { - if should_surface_translated_event(translated) { - return EventRoutingDecision::surface(translated); - } - EventRoutingDecision::ignore() -} - -fn normalize_polled_event(event: VsockEvent) -> DevResult { - let translated = validate_translated_event(translate_vsock_event(event))?; - Ok(route_translated_event(translated)) -} - -impl VsockDriverOps for VirtIoSocketDev { - fn guest_cid(&self) -> u64 { - self.inner.guest_cid() - } - - fn listen(&mut self, src_port: u32) { - if let Some(request) = prepare_listen_request(src_port) - && should_listen_on_port(request) - && let Some(request) = normalize_listen_request(request) - { - self.listen_on_port(request); - } - } - - fn connect(&mut self, cid: VsockConnId) -> DevResult<()> { - let request = build_operation_request(cid, ConnectionOperation::Connect)?; - self.connect_mapped(request) - } - - fn send(&mut self, cid: VsockConnId, buf: &[u8]) -> DevResult { - if let Some(result) = short_circuit_empty_io(buf.len()) { - return Ok(result); - } - let request = build_buffered_operation_request(cid, ConnectionOperation::Send, buf.len())?; - self.send_on_mapped(request, buf) - } - - fn recv(&mut self, cid: VsockConnId, buf: &mut [u8]) -> DevResult { - if let Some(result) = short_circuit_empty_io(buf.len()) { - return Ok(result); - } - let request = - build_buffered_operation_request(cid, ConnectionOperation::Receive, buf.len())?; - self.recv_on_mapped(request, buf) - } - - fn recv_avail(&mut self, cid: VsockConnId) -> DevResult { - let request = build_operation_request(cid, ConnectionOperation::ReceiveAvailable)?; - let conn = request.conn(); - let available = self.recv_available_on_mapped(request)?; - self.refresh_peer_credit_for_query(conn); - Ok(available) - } - - fn disconnect(&mut self, cid: VsockConnId) -> DevResult<()> { - let request = build_operation_request(cid, ConnectionOperation::Disconnect)?; - self.shutdown_mapped(request) - } - - fn abort(&mut self, cid: VsockConnId) -> DevResult<()> { - let request = build_operation_request(cid, ConnectionOperation::Abort)?; - self.abort_mapped(request) - } - - fn poll_event(&mut self) -> DevResult> { - Ok(self.poll_driver_event_once()?.into_option()) - } -} - -fn convert_vsock_event(event: VsockEvent) -> DevResult { - let decision = normalize_polled_event(event)?; - let _ = decision.should_refresh_credit(); - if let Some(translated) = decision.translated() { - return Ok(map_driver_event(translated)); - } - Ok(VsockDriverEvent::Unknown) -} - -#[cfg(test)] -mod tests { - use ax_driver_vsock::{VsockAddr as DriverVsockAddr, VsockConnId, VsockDriverEvent}; - use virtio_drivers::device::socket::{DisconnectReason, VsockAddr, VsockEvent, VsockEventType}; - - use super::{ - ConnectionOperation, convert_vsock_event, map_conn_id, map_event_cid, - short_circuit_empty_io, validate_conn_id_for_operation, validate_event_endpoint, - }; - - fn sample_conn_id() -> VsockConnId { - VsockConnId { - peer_addr: DriverVsockAddr { - cid: 52, - port: 2048, - }, - local_port: 4096, - } - } - - fn sample_event(event_type: VsockEventType) -> VsockEvent { - let mut event: VsockEvent = unsafe { core::mem::zeroed() }; - event.source = VsockAddr { - cid: 33, - port: 1025, - }; - event.destination = VsockAddr { - cid: 44, - port: 2049, - }; - event.event_type = event_type; - event - } - - #[test] - fn map_conn_id_preserves_peer_and_local_port() { - let conn_id = sample_conn_id(); - let (peer_addr, local_port) = map_conn_id(conn_id); - assert_eq!(peer_addr.cid, conn_id.peer_addr.cid as _); - assert_eq!(peer_addr.port, conn_id.peer_addr.port); - assert_eq!(local_port, conn_id.local_port); - } - - #[test] - fn map_event_cid_uses_event_endpoints() { - let event = sample_event(VsockEventType::Connected); - let conn_id = map_event_cid(&event); - assert_eq!(conn_id.peer_addr.cid, event.source.cid as _); - assert_eq!(conn_id.peer_addr.port, event.source.port); - assert_eq!(conn_id.local_port, event.destination.port); - } - - #[test] - fn convert_vsock_event_maps_connection_request() { - let event = sample_event(VsockEventType::ConnectionRequest); - let mapped = convert_vsock_event(event).unwrap(); - assert!(matches!(mapped, VsockDriverEvent::ConnectionRequest(_))); - } - - #[test] - fn convert_vsock_event_maps_connected() { - let event = sample_event(VsockEventType::Connected); - let mapped = convert_vsock_event(event).unwrap(); - assert!(matches!(mapped, VsockDriverEvent::Connected(_))); - } - - #[test] - fn convert_vsock_event_maps_received_length() { - let event = sample_event(VsockEventType::Received { length: 128 }); - let mapped = convert_vsock_event(event).unwrap(); - assert!(matches!(mapped, VsockDriverEvent::Received(_, 128))); - } - - #[test] - fn convert_vsock_event_maps_disconnected() { - let event = sample_event(VsockEventType::Disconnected { - reason: DisconnectReason::Shutdown, - }); - let mapped = convert_vsock_event(event).unwrap(); - assert!(matches!(mapped, VsockDriverEvent::Disconnected(_))); - } - - #[test] - fn convert_vsock_event_maps_credit_update() { - let event = sample_event(VsockEventType::CreditUpdate); - let mapped = convert_vsock_event(event).unwrap(); - assert!(matches!(mapped, VsockDriverEvent::CreditUpdate(_))); - } - - #[test] - fn convert_vsock_event_maps_credit_request_to_unknown() { - let event = sample_event(VsockEventType::CreditRequest); - let mapped = convert_vsock_event(event).unwrap(); - assert!(matches!(mapped, VsockDriverEvent::Unknown)); - } - - #[test] - fn short_circuit_empty_io_returns_zero_for_empty_buffer() { - assert_eq!(short_circuit_empty_io(0), Some(0)); - } - - #[test] - fn short_circuit_empty_io_skips_non_empty_buffer() { - assert_eq!(short_circuit_empty_io(8), None); - } - - #[test] - fn connect_validation_allows_hypervisor_cid() { - let conn = VsockConnId { - peer_addr: DriverVsockAddr { cid: 0, port: 1025 }, - local_port: 2048, - }; - - assert!(validate_conn_id_for_operation(conn, ConnectionOperation::Connect).is_ok()); - } - - #[test] - fn send_validation_allows_hypervisor_cid() { - let conn = VsockConnId { - peer_addr: DriverVsockAddr { cid: 0, port: 1025 }, - local_port: 2048, - }; - - assert!(validate_conn_id_for_operation(conn, ConnectionOperation::Send).is_ok()); - } - - #[test] - fn event_endpoint_allows_hypervisor_cid() { - let endpoint = DriverVsockAddr { cid: 0, port: 1025 }; - - assert!(validate_event_endpoint(&endpoint).is_ok()); - } - - #[test] - fn peer_port_zero_remains_invalid() { - let conn = VsockConnId { - peer_addr: DriverVsockAddr { cid: 0, port: 0 }, - local_port: 2048, - }; - - assert!(validate_conn_id_for_operation(conn, ConnectionOperation::Connect).is_err()); - } -} diff --git a/components/axdriver_crates/axdriver_vsock/Cargo.toml b/components/axdriver_crates/axdriver_vsock/Cargo.toml deleted file mode 100644 index cb288a3151..0000000000 --- a/components/axdriver_crates/axdriver_vsock/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "ax-driver-vsock" -edition.workspace = true -version = "0.3.12" -repository = "https://github.com/rcore-os/tgoskits" -authors = ["Yuekai Jia ", "朝倉水希 ", "Yu Chen "] -description = "Common traits and types for vsock drivers" -keywords = ["arceos", "driver", "vsock"] -categories = ["os", "no-std", "hardware-support"] -readme = "README.md" -license = "Apache-2.0" - -[features] -default = [] - -[dependencies] -ax-driver-base = { workspace = true } -log = "0.4" diff --git a/components/axdriver_crates/axdriver_vsock/README.md b/components/axdriver_crates/axdriver_vsock/README.md deleted file mode 100644 index d5aed48d0d..0000000000 --- a/components/axdriver_crates/axdriver_vsock/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-vsock

- -

Common traits and types for vsock drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-vsock.svg)](https://crates.io/crates/ax-driver-vsock) -[![Docs.rs](https://docs.rs/ax-driver-vsock/badge.svg)](https://docs.rs/ax-driver-vsock) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-driver-vsock` provides Common traits and types for vsock drivers. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-driver-vsock was derived from https://github.com/arceos-org/axdriver_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-driver-vsock = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axdriver_crates/axdriver_vsock - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_driver_vsock as _; - -fn main() { - // Integrate `ax-driver-vsock` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-driver-vsock](https://docs.rs/ax-driver-vsock) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axdriver_crates/axdriver_vsock/README_CN.md b/components/axdriver_crates/axdriver_vsock/README_CN.md deleted file mode 100644 index 4508618853..0000000000 --- a/components/axdriver_crates/axdriver_vsock/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-driver-vsock

- -

Common traits and types for vsock drivers

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-driver-vsock.svg)](https://crates.io/crates/ax-driver-vsock) -[![Docs.rs](https://docs.rs/ax-driver-vsock/badge.svg)](https://docs.rs/ax-driver-vsock) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-driver-vsock` 提供了 Common traits and types for vsock drivers。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-driver-vsock 派生自 https://github.com/arceos-org/axdriver_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-driver-vsock = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axdriver_crates/axdriver_vsock - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_driver_vsock as _; - -fn main() { - // 在这里将 `ax-driver-vsock` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-driver-vsock](https://docs.rs/ax-driver-vsock) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axdriver_crates/axdriver_vsock/src/lib.rs b/components/axdriver_crates/axdriver_vsock/src/lib.rs deleted file mode 100644 index 414c236bd5..0000000000 --- a/components/axdriver_crates/axdriver_vsock/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Common traits and types for socket communite device drivers (i.e. disk). - -#![no_std] -#![cfg_attr(doc, feature(doc_cfg))] - -#[doc(no_inline)] -pub use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; - -/// Vsock address. -#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct VsockAddr { - /// Context Identifier. - pub cid: u64, - /// Port number. - pub port: u32, -} - -/// Vsock connection id. -#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct VsockConnId { - /// Peer address. - pub peer_addr: VsockAddr, - /// Local port. - pub local_port: u32, -} - -impl VsockConnId { - /// Create a new [`VsockConnId`] for listening socket - pub fn listening(local_port: u32) -> Self { - Self { - peer_addr: VsockAddr { cid: 0, port: 0 }, - local_port, - } - } -} - -/// VsockDriverEvent -#[derive(Debug)] -pub enum VsockDriverEvent { - /// ConnectionRequest - ConnectionRequest(VsockConnId), - /// Connected - Connected(VsockConnId), - /// Received - Received(VsockConnId, usize), - /// Disconnected - Disconnected(VsockConnId), - /// Credit Update - CreditUpdate(VsockConnId), - /// unknown event - Unknown, -} - -/// Operations that require a block storage device driver to implement. -pub trait VsockDriverOps: BaseDriverOps { - /// guest cid - fn guest_cid(&self) -> u64; - - /// Listen on a specific port. - fn listen(&mut self, src_port: u32); - - /// Connect to a peer socket. - fn connect(&mut self, cid: VsockConnId) -> DevResult<()>; - - /// Send data to the connected peer socket. need addr for DGRAM mode - fn send(&mut self, cid: VsockConnId, buf: &[u8]) -> DevResult; - - /// Receive data from the connected peer socket. - fn recv(&mut self, cid: VsockConnId, buf: &mut [u8]) -> DevResult; - - /// Returns the number of bytes in the receive buffer available to be read by recv. - fn recv_avail(&mut self, cid: VsockConnId) -> DevResult; - - /// Disconnect from the connected peer socket. - /// - /// Requests to shut down the connection cleanly, telling the peer that we won't send or receive - /// any more data. - fn disconnect(&mut self, cid: VsockConnId) -> DevResult<()>; - - /// Forcibly closes the connection without waiting for the peer. - fn abort(&mut self, cid: VsockConnId) -> DevResult<()>; - - /// poll event from driver - fn poll_event(&mut self) -> DevResult>; -} diff --git a/components/axklib/Cargo.toml b/components/axklib/Cargo.toml index 4ba56d861e..1f5698ab86 100644 --- a/components/axklib/Cargo.toml +++ b/components/axklib/Cargo.toml @@ -14,4 +14,6 @@ license = "Apache-2.0" [dependencies] ax-errno = { workspace = true } ax-memory-addr = { workspace = true } +dma-api = { workspace = true } +mmio-api = { workspace = true } trait-ffi = "0.2" diff --git a/components/axklib/src/dma.rs b/components/axklib/src/dma.rs new file mode 100644 index 0000000000..5d6ad5df15 --- /dev/null +++ b/components/axklib/src/dma.rs @@ -0,0 +1,262 @@ +use core::{alloc::Layout, num::NonZeroUsize, ptr::NonNull}; + +use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; +use dma_api::{DeviceDma, DmaDirection, DmaError, DmaHandle, DmaMapHandle, DmaOp}; + +pub struct KlibDma; + +static DMA: KlibDma = KlibDma; + +pub fn op() -> &'static KlibDma { + &DMA +} + +pub fn device(dma_mask: u64) -> DeviceDma { + DeviceDma::new(dma_mask, op()) +} + +struct DmaPages { + cpu_addr: NonNull, + dma_addr: u64, + num_pages: usize, +} + +impl DmaPages { + fn layout_pages(layout: Layout) -> usize { + layout.size().div_ceil(PAGE_SIZE_4K) + } + + fn layout_align(layout: Layout) -> usize { + layout.align().max(PAGE_SIZE_4K) + } + + /// Allocates DMA-coherent pages using the kernel DMA allocator. + /// + /// `dma_alloc_pages` is expected to honor `dma_mask` and the requested + /// alignment. The checks below are defensive validation so a bad platform + /// allocator fails before the buffer is handed to a device. + unsafe fn alloc_for_layout(dma_mask: u64, layout: Layout) -> Result { + if layout.size() == 0 { + return Ok(Self { + cpu_addr: NonNull::dangling(), + dma_addr: 0, + num_pages: 0, + }); + } + + let num_pages = Self::layout_pages(layout); + let align = Self::layout_align(layout); + let cpu_vaddr = crate::klib::dma_alloc_pages(dma_mask, num_pages, align) + .map_err(|_| DmaError::NoMemory)?; + let cpu_addr = NonNull::new(cpu_vaddr.as_mut_ptr()).ok_or(DmaError::NoMemory)?; + let dma_addr = dma_addr_from_vaddr(cpu_vaddr); + + if !dma_range_fits_mask(dma_addr, layout.size(), dma_mask) { + unsafe { Self::dealloc_pages(cpu_addr, num_pages) }; + return Err(DmaError::DmaMaskNotMatch { + addr: dma_addr.into(), + mask: dma_mask, + }); + } + if !dma_addr_is_aligned(dma_addr, layout.align()) { + unsafe { Self::dealloc_pages(cpu_addr, num_pages) }; + return Err(DmaError::AlignMismatch { + required: layout.align(), + address: dma_addr.into(), + }); + } + + Ok(Self { + cpu_addr, + dma_addr, + num_pages, + }) + } + + unsafe fn dealloc_pages(cpu_addr: NonNull, num_pages: usize) { + if num_pages == 0 { + return; + } + crate::klib::dma_dealloc_pages(VirtAddr::from_usize(cpu_addr.as_ptr() as usize), num_pages); + } +} + +struct CoherentDmaPolicy; + +impl CoherentDmaPolicy { + fn make_uncached(pages: &DmaPages, layout: Layout) -> Result<(), DmaError> { + if pages.num_pages == 0 { + return Ok(()); + } + + let range_size = pages.num_pages * PAGE_SIZE_4K; + let start = VirtAddr::from_usize(pages.cpu_addr.as_ptr() as usize).align_down_4k(); + crate::klib::mem_make_dma_coherent_uncached(start, range_size) + .map_err(|_| DmaError::NoMemory)?; + unsafe { + pages.cpu_addr.as_ptr().write_bytes(0, layout.size()); + } + Ok(()) + } + + fn restore_cached(pages: NonNull, num_pages: usize) -> Result<(), DmaError> { + if num_pages == 0 { + return Ok(()); + } + + let start = VirtAddr::from_usize(pages.as_ptr() as usize).align_down_4k(); + crate::klib::mem_restore_dma_cached(start, num_pages * PAGE_SIZE_4K) + .map_err(|_| DmaError::NoMemory) + } +} + +impl DmaOp for KlibDma { + fn page_size(&self) -> usize { + PAGE_SIZE_4K + } + + unsafe fn map_single( + &self, + dma_mask: u64, + addr: NonNull, + size: NonZeroUsize, + align: usize, + direction: DmaDirection, + ) -> Result { + let align = align.max(1); + let layout = Layout::from_size_align(size.get(), align)?; + let dma_addr = dma_addr_from_ptr(addr); + + if dma_range_fits_mask(dma_addr, size.get(), dma_mask) + && dma_addr_is_aligned(dma_addr, align) + { + return Ok(unsafe { DmaMapHandle::new(addr, dma_addr.into(), layout, None) }); + } + + let map_pages = unsafe { DmaPages::alloc_for_layout(dma_mask, layout)? }; + let map_virt = map_pages.cpu_addr; + + if matches!( + direction, + DmaDirection::ToDevice | DmaDirection::Bidirectional + ) { + unsafe { + map_virt + .as_ptr() + .copy_from_nonoverlapping(addr.as_ptr(), size.get()); + } + } + + Ok(unsafe { DmaMapHandle::new(addr, map_pages.dma_addr.into(), layout, Some(map_virt)) }) + } + + unsafe fn unmap_single(&self, handle: DmaMapHandle) { + if let Some(map_virt) = handle.alloc_virt() { + let num_pages = DmaPages::layout_pages(handle.layout()); + unsafe { DmaPages::dealloc_pages(map_virt, num_pages) }; + } + } + + fn prepare_read( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + if !matches!( + direction, + DmaDirection::FromDevice | DmaDirection::Bidirectional + ) { + return; + } + + let target = unsafe { handle.as_ptr().add(offset) }; + if let Some(map_virt) = handle.alloc_virt() + && map_virt != handle.as_ptr() + { + let source = unsafe { map_virt.add(offset) }; + self.invalidate(source, size); + unsafe { + target + .as_ptr() + .copy_from_nonoverlapping(source.as_ptr(), size); + } + return; + } + + self.invalidate(target, size); + } + + fn confirm_write( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + if !matches!( + direction, + DmaDirection::ToDevice | DmaDirection::Bidirectional + ) { + return; + } + + let source = unsafe { handle.as_ptr().add(offset) }; + if let Some(map_virt) = handle.alloc_virt() + && map_virt != handle.as_ptr() + { + let target = unsafe { map_virt.add(offset) }; + unsafe { + target + .as_ptr() + .copy_from_nonoverlapping(source.as_ptr(), size); + } + self.flush(target, size); + return; + } + + self.flush(source, size); + } + + unsafe fn alloc_coherent(&self, dma_mask: u64, layout: Layout) -> Option { + let pages = unsafe { DmaPages::alloc_for_layout(dma_mask, layout).ok()? }; + if CoherentDmaPolicy::make_uncached(&pages, layout).is_err() { + unsafe { DmaPages::dealloc_pages(pages.cpu_addr, pages.num_pages) }; + return None; + } + + Some(unsafe { DmaHandle::new(pages.cpu_addr, pages.dma_addr.into(), layout) }) + } + + unsafe fn dealloc_coherent(&self, handle: DmaHandle) { + let num_pages = DmaPages::layout_pages(handle.layout()); + if CoherentDmaPolicy::restore_cached(handle.as_ptr(), num_pages).is_err() { + return; + } + unsafe { DmaPages::dealloc_pages(handle.as_ptr(), num_pages) }; + } +} + +fn dma_addr_from_ptr(ptr: NonNull) -> u64 { + dma_addr_from_vaddr(VirtAddr::from_usize(ptr.as_ptr() as usize)) +} + +fn dma_addr_from_vaddr(vaddr: VirtAddr) -> u64 { + crate::klib::mem_virt_to_phys(vaddr).as_usize() as u64 +} + +fn dma_range_fits_mask(dma_addr: u64, size: usize, dma_mask: u64) -> bool { + if size == 0 { + dma_addr <= dma_mask + } else { + dma_addr + .checked_add(size.saturating_sub(1) as u64) + .map(|end| end <= dma_mask) + .unwrap_or(false) + } +} + +fn dma_addr_is_aligned(dma_addr: u64, align: usize) -> bool { + dma_addr.is_multiple_of(align.max(1) as u64) +} diff --git a/components/axklib/src/lib.rs b/components/axklib/src/lib.rs index a5e78c1d71..647d75587e 100644 --- a/components/axklib/src/lib.rs +++ b/components/axklib/src/lib.rs @@ -42,10 +42,13 @@ use core::time::Duration; -pub use ax_errno::AxResult; +pub use ax_errno::{AxError, AxResult}; pub use ax_memory_addr::{PhysAddr, VirtAddr}; use trait_ffi::*; +pub mod dma; +pub mod mmio; + /// A simple IRQ handler function pointer type. /// /// The handler receives the IRQ number that triggered it. @@ -73,6 +76,9 @@ pub trait Klib { /// is later cleaned up if the platform/ABI requires it. fn mem_iomap(addr: PhysAddr, size: usize) -> AxResult; + /// Translates a kernel virtual address to the corresponding physical address. + fn mem_virt_to_phys(addr: VirtAddr) -> PhysAddr; + /// Converts newly allocated DMA-coherent pages to an uncached kernel mapping. /// /// This is not a general-purpose memory attribute switching API. Callers @@ -92,6 +98,15 @@ pub trait Klib { /// allocator. fn mem_restore_dma_cached(addr: VirtAddr, size: usize) -> AxResult; + /// Allocates contiguous DMA pages. + /// + /// `dma_mask` is the device-visible address mask. Implementations should + /// use a DMA32-capable allocator when the mask requires it. + fn dma_alloc_pages(dma_mask: u64, num_pages: usize, align: usize) -> AxResult; + + /// Releases pages previously allocated by [`Klib::dma_alloc_pages`]. + fn dma_dealloc_pages(addr: VirtAddr, num_pages: usize); + /// Busy-wait the current execution context for the provided duration. /// /// This is intended for short delays where sleeping or timer-based @@ -101,6 +116,12 @@ pub trait Klib { /// are platform-dependent. fn time_busy_wait(dur: Duration); + /// Returns monotonic time in nanoseconds. + fn time_monotonic_nanos() -> u64; + + /// Initializes the wall-clock epoch offset from an absolute epoch time. + fn time_try_init_epoch_offset(epoch_time_nanos: u64) -> bool; + /// Enable or disable the edge/level for a platform IRQ. /// /// `irq` is a platform IRQ number. `enabled` selects whether the IRQ @@ -120,13 +141,16 @@ pub trait Klib { pub mod mem { pub use super::klib::{ mem_iomap as iomap, mem_make_dma_coherent_uncached as make_dma_coherent_uncached, - mem_restore_dma_cached as restore_dma_cached, + mem_restore_dma_cached as restore_dma_cached, mem_virt_to_phys as virt_to_phys, }; } /// Convenience re-export for busy-wait timing. pub mod time { - pub use super::klib::time_busy_wait as busy_wait; + pub use super::klib::{ + time_busy_wait as busy_wait, time_monotonic_nanos as monotonic_nanos, + time_try_init_epoch_offset as try_init_epoch_offset, + }; } /// Convenience re-exports for IRQ operations. diff --git a/components/axklib/src/mmio.rs b/components/axklib/src/mmio.rs new file mode 100644 index 0000000000..67afe56b17 --- /dev/null +++ b/components/axklib/src/mmio.rs @@ -0,0 +1,48 @@ +use core::ptr::NonNull; + +use mmio_api::{MapError, Mmio, MmioAddr, MmioOp, MmioRaw}; + +use crate::AxError; + +pub struct KlibMmio; + +static MMIO: KlibMmio = KlibMmio; + +pub fn op() -> &'static KlibMmio { + &MMIO +} + +pub fn init_global() { + mmio_api::init(op()); +} + +pub fn ioremap(addr: MmioAddr, size: usize) -> Result { + init_global(); + mmio_api::ioremap(addr, size) +} + +pub fn ioremap_raw(addr: MmioAddr, size: usize) -> Result { + op().ioremap(addr, size) +} + +impl MmioOp for KlibMmio { + fn ioremap(&self, addr: MmioAddr, size: usize) -> Result { + if size == 0 { + return Err(MapError::Invalid); + } + + let virt = crate::klib::mem_iomap(addr.as_usize().into(), size).map_err(map_error)?; + let virt = NonNull::new(virt.as_mut_ptr()).ok_or(MapError::Invalid)?; + Ok(unsafe { MmioRaw::new(addr, virt, size) }) + } + + fn iounmap(&self, _mmio: &MmioRaw) {} +} + +fn map_error(err: AxError) -> MapError { + match err { + AxError::NoMemory => MapError::NoMemory, + AxError::AlreadyExists | AxError::ResourceBusy => MapError::Busy, + _ => MapError::Invalid, + } +} diff --git a/components/axplat_crates/axplat/Cargo.toml b/components/axplat_crates/axplat/Cargo.toml index 68179c58f7..33e3af8295 100644 --- a/components/axplat_crates/axplat/Cargo.toml +++ b/components/axplat_crates/axplat/Cargo.toml @@ -22,6 +22,7 @@ const-str = "1.0" ax-plat-macros = { workspace = true } ax-kspin = { workspace = true } ax-percpu.workspace = true +rdrive.workspace = true [package.metadata.docs.rs] all-features = true diff --git a/components/axplat_crates/axplat/src/drivers.rs b/components/axplat_crates/axplat/src/drivers.rs new file mode 100644 index 0000000000..1f2e62b451 --- /dev/null +++ b/components/axplat_crates/axplat/src/drivers.rs @@ -0,0 +1,19 @@ +//! Static platform driver resources. + +use rdrive::probe::static_::StaticDeviceDesc; + +/// Static platform driver resource interface. +/// +/// Static platforms describe probe inputs here. Driver implementations are +/// registered through `.driver.register*` and consume these resources through +/// `ProbeKind::Static`. +#[def_plat_interface] +pub trait DriversIf { + /// Returns the static device descriptors exposed by the platform. + fn static_devices_fn() -> &'static [StaticDeviceDesc]; +} + +/// Returns the static device descriptors exposed by the platform. +pub fn static_devices() -> &'static [StaticDeviceDesc] { + crate::__priv::call_interface!(DriversIf::static_devices_fn) +} diff --git a/components/axplat_crates/axplat/src/lib.rs b/components/axplat_crates/axplat/src/lib.rs index f67d7322f7..118b68cc8c 100644 --- a/components/axplat_crates/axplat/src/lib.rs +++ b/components/axplat_crates/axplat/src/lib.rs @@ -6,6 +6,7 @@ extern crate ax_plat_macros; pub mod console; +pub mod drivers; pub mod init; #[cfg(feature = "irq")] pub mod irq; diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml index 2c30017efb..671960715b 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml @@ -25,6 +25,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs new file mode 100644 index 0000000000..03b8fc9449 --- /dev/null +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs @@ -0,0 +1,11 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::StaticDeviceDesc; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + &[] + } +} diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs index 47b27bbf71..f5884a76db 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs @@ -22,6 +22,7 @@ pub mod config { } mod boot; +mod drivers; mod dw_apb_uart; mod init; mod mem; diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml index ec22899bf5..91eef558fe 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml @@ -22,6 +22,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs new file mode 100644 index 0000000000..03b8fc9449 --- /dev/null +++ b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs @@ -0,0 +1,11 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::StaticDeviceDesc; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + &[] + } +} diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/lib.rs b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/lib.rs index 0b9801b090..a49b825613 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/lib.rs @@ -22,6 +22,7 @@ pub mod config { } mod boot; +mod drivers; mod init; mod mem; mod power; diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml index 3272d6467a..5f33f2192c 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml @@ -30,6 +30,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs new file mode 100644 index 0000000000..3004c26739 --- /dev/null +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs @@ -0,0 +1,28 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; + +use crate::config::devices; + +const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; +const PCI_LEGACY_IRQS: &[usize] = &[35, 36, 37, 38]; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[ + StaticDeviceDesc::new("virtio-mmio") + .with_regs(devices::VIRTIO_MMIO_RANGES) + .with_probe_each_reg(), + StaticDeviceDesc::new("pci-ecam") + .with_irqs(PCI_LEGACY_IRQS) + .with_pci_ecam( + StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE) + .with_ranges(devices::PCI_RANGES), + ), +]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs index 35c66df351..e06c0596fb 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs @@ -4,6 +4,7 @@ extern crate ax_plat; mod boot; +mod drivers; mod init; mod mem; mod power; diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml index fd1626eefa..ab0aa5dc77 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml @@ -23,6 +23,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs new file mode 100644 index 0000000000..03b8fc9449 --- /dev/null +++ b/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs @@ -0,0 +1,11 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::StaticDeviceDesc; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + &[] + } +} diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/lib.rs b/components/axplat_crates/platforms/axplat-aarch64-raspi/src/lib.rs index bfa65c3d5f..38d378dd3d 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-raspi/src/lib.rs @@ -4,6 +4,7 @@ extern crate ax_plat; mod boot; +mod drivers; mod init; mod mem; mod power; diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml index 0963cb1b75..a52768466d 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml @@ -27,6 +27,7 @@ uart_16550 = "0.5" ax-config-macros = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [package.metadata.docs.rs] targets = ["loongarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs new file mode 100644 index 0000000000..dcf69c738c --- /dev/null +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs @@ -0,0 +1,22 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; + +use crate::config::devices; + +const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; +const PCI_LEGACY_IRQS: &[usize] = &[16, 17, 18, 19]; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("pci-ecam") + .with_irqs(PCI_LEGACY_IRQS) + .with_pci_ecam( + StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE).with_ranges(devices::PCI_RANGES), + )]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs index ccc722691d..6bec199b1f 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs @@ -23,6 +23,7 @@ pub mod config { mod boot; mod console; +mod drivers; mod init; #[cfg(feature = "irq")] mod irq; diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml index d26c83c33c..19e657b26d 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml @@ -29,6 +29,7 @@ some-serial.workspace = true ax-config-macros = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [package.metadata.docs.rs] targets = ["riscv64gc-unknown-none-elf"] diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs new file mode 100644 index 0000000000..70d11659db --- /dev/null +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs @@ -0,0 +1,28 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; + +use crate::config::devices; + +const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; +const PCI_LEGACY_IRQS: &[usize] = &[32, 33, 34, 35]; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[ + StaticDeviceDesc::new("virtio-mmio") + .with_regs(devices::VIRTIO_MMIO_RANGES) + .with_probe_each_reg(), + StaticDeviceDesc::new("pci-ecam") + .with_irqs(PCI_LEGACY_IRQS) + .with_pci_ecam( + StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE) + .with_ranges(devices::PCI_RANGES), + ), +]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs index 251f01398a..6a8d835445 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs @@ -13,6 +13,7 @@ pub mod irq; mod boot; mod console; +mod drivers; mod init; mod mem; mod power; diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml b/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml index d4c6e0c6f8..e1a6ad3639 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml @@ -28,6 +28,7 @@ ax-cpu = { workspace = true, features = ["xuantie-c9xx"] } ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] ax-riscv-plic = { workspace = true, optional = true } diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs new file mode 100644 index 0000000000..9919c1f3cd --- /dev/null +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs @@ -0,0 +1,20 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::StaticDeviceDesc; + +use crate::config::devices; + +static CVSD_REGS: &[(usize, usize)] = &[ + (devices::CVSD_PADDR, 0x1000), + (devices::SYSCON_PADDR, 0x8000), +]; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("cvsd").with_regs(CVSD_REGS)]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs index 3750491421..e0e2d7b2eb 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs @@ -12,6 +12,8 @@ mod boot; #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod console; #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +mod drivers; +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod init; #[cfg(all(feature = "irq", any(target_arch = "riscv32", target_arch = "riscv64")))] mod irq; diff --git a/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml b/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml index 24276189c0..d23da85083 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml @@ -27,6 +27,7 @@ heapless = "0.9" ax-config-macros = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true x86 = "0.52" x86_64 = "0.15.2" diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs b/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs new file mode 100644 index 0000000000..19df95039d --- /dev/null +++ b/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs @@ -0,0 +1,18 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; + +use crate::config::devices; + +const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("pci-ecam") + .with_pci_ecam(StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE))]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs b/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs index 04794104ed..f0de88d544 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs @@ -8,6 +8,7 @@ extern crate ax_plat; mod apic; mod boot; mod console; +mod drivers; mod init; mod mem; mod power; diff --git a/docs/docs/architecture/overview.md b/docs/docs/architecture/overview.md index 0dc6f1abb1..b4dc6e6fa0 100644 --- a/docs/docs/architecture/overview.md +++ b/docs/docs/architecture/overview.md @@ -13,7 +13,7 @@ flowchart TD direction LR C1["架构组件
aarch64_sysreg arm_vcpu ..."] C2["内存组件
axaddrspace axmm_crates ..."] - C3["设备组件
axdriver_crates axdevice ..."] + C3["设备组件
drivers/ axdevice ..."] C4["文件系统组件
axfs_crates axfs-ng-vfs rsext4"] C5["平台组件
axplat_crates percpu ..."] C6["Starry 组件
starry-process starry-signal starry-vm"] @@ -94,6 +94,14 @@ Axvisor 是基于 ArceOS 的统一组件化 Type-I Hypervisor,建立在 ArceOS → 详细架构见 [Axvisor 架构](./axvisor) +## 驱动框架重构方向 + +宿主物理设备路径正在收敛到 `rdrive + rdif`。这个方向将静态平台、FDT 动态平台和未来 ACPI 平台放进同一套 `rdrive` 注册与 probe 主线,并让文件系统、网络、显示、输入、vsock、StarryOS 和 Axvisor 直接消费 `rdif-*` / `rd-*` 设备。 + +本轮重构不迁移 `axdevice` / `axdevice_base`。它们继续作为 Axvisor / axvm 的 guest emulated device model,与宿主物理设备路径保持边界。 + +→ 详细设计见 [rdrive + rdif 驱动框架](./rdrive-rdif) + ## 核心层次 TGOSKits 按职责将 crate 组织为六个核心层次和一个辅助层,每一层都面向明确的职责边界。上层依赖下层,但下层不感知上层——这一原则使得同一套组件可以同时服务于多个系统。 diff --git a/docs/docs/architecture/rdrive-rdif.md b/docs/docs/architecture/rdrive-rdif.md new file mode 100644 index 0000000000..2669eab648 --- /dev/null +++ b/docs/docs/architecture/rdrive-rdif.md @@ -0,0 +1,405 @@ +--- +sidebar_position: 5 +sidebar_label: "rdrive + rdif 驱动框架" +--- + +# rdrive + rdif 驱动框架 + +本文记录 #606 的宿主物理设备重构目标。新的设备路径硬切到 `rdrive + rdif`:`rdrive` 负责发现、probe、注册和查询,`rdif-*` / `rd-*` 负责能力接口和运行时封装,上层系统按领域能力消费设备。旧驱动接口包组已移除,宿主物理设备初始化与交付主线不再经过 legacy driver crates。 + +`axdevice` 与 `axdevice_base` 不纳入本轮迁移。它们继续作为 Axvisor / axvm 的 guest emulated device model,不参与宿主物理设备 probe,不作为 FS、NET、display、input、vsock 的设备来源。 + +## 非目标与硬约束 + +本轮只处理宿主侧物理设备,包括 ArceOS、StarryOS、Axvisor 在真实平台或 QEMU 平台上使用的块设备、网卡、中断控制器、时钟、显示、输入、vsock、PCIe、USB host 等设备。 + +架构硬约束如下: + +- 不新增长期存在的 `rdrive <-> ax-driver` 双向适配层。 +- 不新增 `RDriveDeviceContainer`、`AllRDriveDevices` 这类换名后的 `AllDevices` 大容器。 +- 不用一个 `KernelHal`、`PlatformSystem` 或其它大结构体包办 Static、FDT、ACPI、PCI、MMIO、DMA、IRQ、runtime。 +- 不把 FDT 当作唯一或默认平台抽象;Static、FDT、ACPI 是并列平台来源。 +- 不在 portable driver core 中调用 `iomap`、`ioremap`、`axklib`、`somehal`、任务调度或 IRQ 注册。 +- 不用字符串拼接或 ad-hoc 匹配替代 FDT compatible、ACPI HID/CID、PCI vendor/device 的结构化匹配。 +- 不在文档或代码中保留“以后补”的占位路径;ACPI 第一版必须返回明确 unsupported error。 +- 除测试外,新增或重构后的单个 `.rs` 文件不超过 600 行。 + +`ax-driver` 现在作为共享驱动聚合 crate 接入 `rdrive + rdif`,不再依赖旧驱动接口包组。迁移目标模块完成后,不应再引入 `AllDevices`、`AxDeviceContainer`、`AxBlockDevice`、`AxNetDevice` 这类旧全局容器模型。 + +## 总体结构 + +新的宿主设备路径分为五层:平台发现来源、`rdrive` backend 分发、具体驱动、`rdif` 能力边界、领域 service 与上层消费方。 + +```mermaid +flowchart TB + subgraph Sources["平台发现来源"] + Static["Static: 平台常量 / ax_config"] + Fdt["FDT: device tree"] + Acpi["ACPI: reserved API / unsupported first"] + end + + subgraph Rdrive["rdrive"] + Manager["Manager: register + typed registry only"] + Register["DriverRegister"] + Kind["ProbeKind: Static / FDT / ACPI / PCI"] + Backends["probe::{static_,fdt,acpi,pci}"] + end + + subgraph DriverCore["Driver Core"] + HwState["registers / descriptors / state machines"] + Queues["queues / requests / events"] + end + + subgraph Capability["Capability Boundary"] + RdifBlock["rdif-block + rd-block"] + RdifEth["rdif-eth + rd-net"] + RdifDisplay["rdif-display"] + RdifInput["rdif-input"] + RdifVsock["rdif-vsock"] + RdifPlatform["rdif-intc / pcie / clk / timer / serial"] + end + + subgraph Services["领域 service"] + BlockVolume["block volume / partition"] + NetService["net interface service"] + UiService["display / input service"] + VsockService["vsock service"] + end + + subgraph Consumers["消费方"] + Runtime["ax-runtime"] + Fs["ax-fs / ax-fs-ng"] + Net["ax-net / ax-net-ng"] + Starry["starry-kernel"] + Axvisor["axvisor"] + end + + Sources --> Backends + Backends --> Register + Register --> Kind + Kind --> DriverCore + DriverCore --> Capability + Capability --> Manager + Runtime --> Backends + Manager --> Services + Services --> Fs + Services --> Net + Services --> Starry + Services --> Axvisor +``` + +`rdrive::Manager` 只保存 `DriverRegister` 和类型化设备 registry。Static、FDT、ACPI、PCI 各自拥有独立 `probe::*::{System, Info, FnOnProbe}`,不把平台状态合并成一个大 `System`。 + +## rdrive Backend 模型 + +`rdrive` 公共 API 的目标形态是多来源初始化和按 `ProbeKind` 分发: + +```rust +pub enum PlatformSource { + Static(&'static [StaticDeviceDesc]), + Fdt(core::ptr::NonNull), + Acpi(AcpiRoot), +} + +pub enum ProbeKind { + Static { on_probe: static_::FnOnProbe }, + Fdt { compatibles: &'static [&'static str], on_probe: fdt::FnOnProbe }, + Acpi { ids: &'static [AcpiId], on_probe: acpi::FnOnProbe }, + Pci { on_probe: pci::FnOnProbe }, +} +``` + +各 backend 的职责如下: + +| backend | 独立状态 | 匹配输入 | probe 输入 | 第一版行为 | +| --- | --- | --- | --- | --- | +| `probe::static_` | `System { devices, probed }` | `StaticDeviceDesc` | `StaticInfo` + `PlatformDevice` | 支持静态平台常量注册 | +| `probe::fdt` | `System { fdt, phandle_map, probed }` | compatible + node status | `FdtInfo` + `PlatformDevice` | 保留当前 FDT 能力 | +| `probe::acpi` | `System { root, probed }` | HID/CID + ACPI device | `AcpiInfo` + `PlatformDevice` | API 存在,返回 unsupported | +| `probe::pci` | PCIe controller enumerator | vendor/device/class | endpoint + `PlatformDevice` | 保留当前 PCIe 二阶段 probe | + +`probe_pre_kernel()` 只运行 `ProbeLevel::PreKernel`,并通过 backend 分发器执行 Static、FDT、ACPI 中的早期 probe。PCI endpoint 枚举依赖已注册的 PCIe controller,因此仍在普通 probe 阶段触发。 + +`probe_all(stop_if_fail)` 运行普通设备 probe,再执行 PCI endpoint 枚举。它不能被塞进 early init,也不能变成 FDT 专用流程。 + +## 初始化时序 + +初始化顺序固定为: + +1. `ax_hal::init_early(cpu_id, arg)` 只记录 boot arg / DTB,初始化 early trap、console、time 等最低层能力,不 probe 宿主设备。 +2. allocator 和 paging 初始化完成后,`ax_hal::init_later(cpu_id, arg)` 或平台 post-paging 阶段执行 `rdrive::init(...)`、`rdrive::register_append(...)`、`rdrive::probe_pre_kernel()`。 +3. `probe_pre_kernel()` 只初始化后续平台依赖,例如 interrupt controller、clock、timer、systick、pinmux、PCIe root complex。 +4. 平台 later init 完成后,`ax-runtime` 调用 `rdrive::probe_all(false)`。 +5. FS、NET、display、input、vsock、StarryOS、Axvisor 通过领域 service 或 `rdif-*` 能力接口消费设备。 + +```mermaid +sequenceDiagram + participant Runtime as ax-runtime + participant Hal as ax-hal / platform + participant Rdrive as rdrive + participant Driver as driver probe + participant Registry as typed registry + participant Service as domain service + participant Upper as upper modules + + Runtime->>Hal: init_early(cpu_id, arg) + Runtime->>Runtime: alloc + paging + Runtime->>Hal: init_later(cpu_id, arg) + Hal->>Rdrive: init(PlatformSource...) + Hal->>Rdrive: register_append(linker section) + Hal->>Rdrive: probe_pre_kernel() + Rdrive->>Driver: PreKernel Static/FDT/ACPI probe + Driver->>Registry: register rdif platform devices + Runtime->>Rdrive: probe_all(false) + Rdrive->>Driver: Static/FDT/ACPI/Pci probe + Driver->>Registry: register rdif data devices + Upper->>Service: init from domain service + Service->>Registry: query typed rdif/rd devices +``` + +`ax-runtime` 不再拆 `AllDevices.block/net/display/input/vsock` 后逐个传给模块。它只触发 probe 和领域 service 初始化。 + +## Capability Boundary + +`rdif-*` 是能力边界,只定义某类设备向上暴露什么能力,不负责设备发现、iomap、IRQ 注册、任务调度或系统启动顺序。`rd-*` 是 runtime wrapper,负责 waker、poll、blocking API、buffer pool 等运行时行为。 + +| 能力 | interface crate | runtime crate | 上层消费 | +| --- | --- | --- | --- | +| 块设备 | `rdif-block` | `rd-block` | block volume service、FS | +| 网络设备 | `rdif-eth` | `rd-net` | net interface service、NET/NET-NG | +| 显示 | `rdif-display` | `rd-display` | display service、Starry fb | +| 输入 | `rdif-input` | `rd-input` | input service、Starry input | +| vsock | `rdif-vsock` | `rd-vsock` | vsock service | +| 平台设备 | `rdif-intc`、`rdif-pcie`、`rdif-clk`、`rdif-timer`、`rdif-systick`、`rdif-serial` | 按需 | HAL、Axvisor backend、平台 glue | + +新增接口按多文件拆分: + +- `rdif-display/src/lib.rs` 只 re-export;`types.rs` 定义 `DisplayInfo`、`PixelFormat`、`FrameBuffer<'_>`;`error.rs` 定义 `DisplayError`;`interface.rs` 定义 `Interface` 和 `Event`。 +- `rdif-input/src/lib.rs` 只 re-export;`event.rs` 定义 `EventType`、`InputEvent`、`AbsInfo`;`id.rs` 定义 `InputDeviceId`;`error.rs` 定义 `InputError`;`interface.rs` 定义 `Interface` 和 `Event`。 +- `rdif-vsock/src/lib.rs` 只 re-export;`addr.rs` 定义 `VsockAddr`、`VsockConnId`;`event.rs` 定义 `VsockEvent`;`error.rs` 定义 `VsockError`;`interface.rs` 定义 `Interface` 和 `Event`。 + +接口目标形态: + +```rust +pub trait DisplayInterface: rdif_base::DriverGeneric { + fn info(&self) -> DisplayInfo; + fn framebuffer(&mut self) -> Result, DisplayError>; + fn need_flush(&self) -> bool; + fn flush(&mut self) -> Result<(), DisplayError>; + fn handle_irq(&mut self) -> DisplayEvent; +} +``` + +```rust +pub trait InputInterface: rdif_base::DriverGeneric { + fn device_id(&self) -> InputDeviceId; + fn physical_location(&self) -> &str; + fn unique_id(&self) -> &str; + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> Result; + fn read_event(&mut self) -> Result; + fn get_prop_bits(&mut self, out: &mut [u8]) -> Result; + fn get_abs_info(&mut self, axis: u8) -> Result; + fn handle_irq(&mut self) -> InputEventState; +} +``` + +```rust +pub trait VsockInterface: rdif_base::DriverGeneric { + fn guest_cid(&self) -> u64; + fn listen(&mut self, port: u32) -> Result<(), VsockError>; + fn connect(&mut self, id: VsockConnId) -> Result<(), VsockError>; + fn send(&mut self, id: VsockConnId, buf: &[u8]) -> Result; + fn recv(&mut self, id: VsockConnId, buf: &mut [u8]) -> Result; + fn recv_avail(&mut self, id: VsockConnId) -> Result; + fn disconnect(&mut self, id: VsockConnId) -> Result<(), VsockError>; + fn abort(&mut self, id: VsockConnId) -> Result<(), VsockError>; + fn poll_event(&mut self) -> Result, VsockError>; + fn handle_irq(&mut self) -> VsockIrqEvent; +} +``` + +IRQ 路径只返回稳定事件和唤醒等待方;不能在 IRQ handler 中执行阻塞 I/O、长流程状态推进或广域锁持有。 + +## Driver Core / OS Glue / Runtime + +驱动按四层拆分: + +| 层 | 位置 | 允许依赖 | 不允许 | +| --- | --- | --- | --- | +| Driver Core | `drivers//` | `no_std`、寄存器/队列/描述符、`mmio-api`、`dma-api` 小边界 | `ax-driver`、`ax-hal`、`axplat-dyn`、`rdrive::PlatformDevice` | +| Capability Boundary | `drivers/interface/rdif-*` | `rdif-base`、小型错误和事件类型 | 平台、runtime、任务调度 | +| OS Glue | `platform/axplat-dyn/src/drivers/*` 或平台 crate | `rdrive::module_driver!`、FDT/PCI/Static probe、iomap、IRQ 注册、DMA op | 上层 FS/NET 策略 | +| Runtime | `drivers/*/rd-*` | `rdif-*`、waker、poll/blocking wrapper、buffer pool | probe、设备树、ACPI、平台选择 | + +Driver Core 只推进硬件状态机。OS Glue 将硬件实例包装成 `rdif-*::Interface` 后通过 `PlatformDevice::register(...)` 注册。Runtime wrapper 从 `rdif-*::Interface` 构建领域运行时对象,供服务层和上层模块使用。 + +## 领域 Service 与上层消费 + +上层业务模块不直接处理 `AllDevices`,也不直接把 `rdrive` 当作全局设备篮子。每个领域有自己的 service,service 可以从 `rdrive` 查询 typed device 并整理成上层需要的能力集合。 + +| 领域 | 新 service 职责 | 上层边界 | +| --- | --- | --- | +| block | 枚举 disk,扫描 partition,生成 `BlockVolume`,根据 bootargs 选择 root candidate | FS 只拿 volume / FS block trait | +| net | 枚举 `rd-net`,建立 interface,处理 DHCP/static IP policy | NET/NET-NG 只拿 net interface | +| display | 枚举 `rdif-display` / `rd-display`,选择 primary display | display 模块和 Starry fb 只拿 display handle | +| input | 枚举 `rdif-input` / `rd-input`,建立 event stream | input 模块和 Starry input 只拿 event source | +| vsock | 枚举 `rdif-vsock` / `rd-vsock`,维护 connection/event API | vsock socket 层只拿 vsock device | + +直接使用 `rdrive::get_*` 只允许出现在设备管理型或低层 HAL 型代码中,例如 Starry USBFS host 管理和 Axvisor AArch64 GIC backend。普通 FS、NET、display、input、vsock 上层模块不得裸查 `rdrive`。 + +## Block Volume 与分区扫描 + +分区扫描抽成唯一实现,位于独立 block volume 层,而不是 FS 层或旧块驱动接口路径。 + +目标数据模型: + +```rust +pub struct BlockVolume { + pub disk_id: DiskId, + pub partition_id: Option, + pub region: BlockRegion, + pub table_kind: PartitionTableKind, + pub partuuid: Option, + pub partlabel: Option, +} +``` + +block volume 层负责: + +- 从 `rd-block` / `rdif-block` 枚举 physical disk。 +- 支持 GPT、MBR、raw disk。 +- 产出稳定 volume metadata。 +- 提供裁剪到 `BlockRegion` 的 block reader。 + +FS 负责: + +- 根据 `root=/dev/sdXn`、`root=/dev/mmcblkXpY`、`PARTUUID=`、`PARTLABEL=` 选择 root volume。 +- 检测 ext4、FAT 等 filesystem magic。 +- 挂载选定 volume。 + +FS 不再 import `ax_driver::{AxBlockDevice, AxDeviceContainer, PartitionInfo, PartitionRegion, PartitionTableKind}`,也不调用 `ax_driver::scan_partitions`。 + +## Feature 映射 + +Feature 的职责从“选择 `ax-driver` 子模块和单个 Ax*Device 类型”调整为“选择要链接的 rdrive probe module、driver core、rdif 能力和 runtime wrapper”。 + +| 旧 feature 语义 | 新 feature 语义 | +| --- | --- | +| `ax-driver` | 启用宿主设备 probe 主线,即 `rdrive` | +| `virtio-blk` / `virtio-net` | 链接 VirtIO probe 和对应 `rdif-block` / `rdif-eth` 注册 | +| `virtio-gpu` | 链接 `rdif-display` / `rd-display` probe | +| `virtio-input` | 链接 `rdif-input` / `rd-input` probe | +| `virtio-socket` | 链接 `rdif-vsock` / `rd-vsock` probe | +| `driver-*` | 链接具体硬件 driver core 和 OS glue probe | +| `bus-*` | 链接总线枚举或控制器 probe,例如 PCIe | +| `plat-dyn` | 选择 FDT 动态平台来源,不走 `ax_driver_*Ops` 回灌 | + +MMIO 与 PCI feature 要分开表达,例如 `virtio-gpu-mmio` 与 `virtio-gpu-pci` 都是 probe module,而不是上层设备类型选择。 + +## 文件拆分规则 + +新增 crate 默认遵循以下布局: + +```text +src/ + lib.rs # re-export only + error.rs # error type and conversions + types.rs # public data types + interface.rs # trait and event contract + device.rs # runtime device wrapper, if this is rd-* crate + irq.rs # irq event handling, if needed + queue.rs # queue/request/event stream, if needed +``` + +已有大文件在迁移触及时必须拆分: + +| 文件 | 当前问题 | 拆分方向 | +| --- | --- | --- | +| `platform/axplat-dyn/src/drivers/pci/rk3588.rs` | 单文件超过 600 行 | RC init、ATU/window、MSI/IRQ、config space、FDT glue | +| `platform/axplat-dyn/src/drivers/blk/rockchip_sd.rs` | 单文件超过 600 行 | probe/FDT、clock/tuning、card init、rd-block adapter | +| `platform/axplat-dyn/src/drivers/blk/mod.rs` | 容器、adapter、IRQ、FDT decode 混杂 | registry、adapter、irq、probe | +| `platform/axplat-dyn/src/drivers/mod.rs` | 设备收集、iomap、DMA 混杂 | device collection、iomap、dma | + +`lib.rs` 只做模块声明和 re-export,不承载核心实现。 + +## 分阶段硬切实施 + +Phase 1: `rdrive` backend 分发 + +- 增加 `PlatformSource::{Static,Fdt,Acpi}` 和 `ProbeKind::{Static,Fdt,Acpi,Pci}`。 +- 新增 `probe::static_` 与 `probe::acpi` 模块;ACPI 第一版返回 unsupported error。 +- `probe_pre_kernel()` 和 `probe_all()` 改为 backend 分发,保留当前 FDT 与 PCI 能力。 +- `Manager` 保持只管理 register 和 typed device registry。 + +Phase 2: 补齐 `rdif-display/input/vsock` + +- 新增三个 interface crate 并接入 workspace。 +- 每个 crate 按 `error/types/interface` 或 `addr/event/interface` 拆文件。 +- 不依赖 `ax-driver`、`ax-runtime`、`ax-hal` 或平台 crate。 + +Phase 3: block volume service + +- 抽出唯一分区扫描实现,支持 GPT、MBR、raw disk。 +- 产出 `BlockVolume` 和裁剪后的 block reader。 +- `ax-fs` / `ax-fs-ng` 只消费 volume 和 FS block trait。 + +Phase 4: NET / NET-NG 硬切 + +- `ax-net` / `ax-net-ng` 从 `AxNetDevice` 切到 `rd-net` 或 net service。 +- DHCP/static IP policy 留在 net service 或 NET/NET-NG,不回到 platform glue。 + +Phase 5: display / input / vsock 硬切 + +- 新增 runtime wrapper `rd-display`、`rd-input`、`rd-vsock`。 +- 上层 display/input/vsock 模块消费领域 service,不接收 `AxDeviceContainer`。 + +Phase 6: `ax-runtime` 切主线 + +- 删除宿主初始化主线中的 `ax-driver::init_drivers()` 和 `AllDevices` 拆包。 +- 平台 later init 后调用 `rdrive::probe_all(false)`。 +- 调用领域 service 初始化 FS、NET、display、input、vsock。 + +Phase 7: feature 映射切换 + +- `ax-feat` 中旧 `ax-driver/virtio-*`、`driver-*`、`bus-*` 映射到 rdrive probe feature。 +- legacy `ax-driver` feature 只保留给未迁移代码,不作为新宿主路径入口。 + +## 验收标准 + +文档验收: + +```bash +git diff --check +cd docs +yarn build +``` + +本地 `docs` 未安装依赖时,先执行 `corepack enable` 与 `yarn install --frozen-lockfile`,再运行 `yarn build`。 + +代码验收: + +```bash +cargo xtask clippy --package rdrive +cargo xtask clippy --package ax-runtime +cargo xtask clippy --package ax-fs-ng +cargo xtask clippy --package ax-net-ng +cargo xtask clippy --package starry-kernel +cargo xtask clippy --package axvisor +``` + +搜索验收: + +```bash +rg "AllDevices|AxDeviceContainer|AxBlockDevice|AxNetDevice|ax_driver::scan_partitions" os/arceos/modules os/StarryOS/kernel os/axvisor +rg "rdrive::get_|rdrive::get_one|rdrive::get_list" os/arceos/modules os/StarryOS/kernel os/axvisor +``` + +第二条搜索只允许 Starry USBFS 设备管理路径和 Axvisor HAL/GIC backend 出现裸 `rdrive::get_*`。 + +系统回归重点: + +- StarryOS QEMU smoke。 +- ext4 rootfs 启动与读写。 +- `net-ng` / DHCP。 +- `qemu-aarch64-plat-dyn` 动态平台。 +- Axvisor QEMU / GIC / `rdif-intc` 路径。 diff --git a/docs/docs/components/crates/ax-api.md b/docs/docs/components/crates/ax-api.md index aead4e1c04..34080ba3b2 100644 --- a/docs/docs/components/crates/ax-api.md +++ b/docs/docs/components/crates/ax-api.md @@ -67,10 +67,10 @@ graph LR - `ax-config-gen` - `ax-config-macros` - `ax-cpu` -- `ax-driver-base` -- `axdriver_block` -- `axdriver_display` -- `axdriver_input` +- `rdrive` +- `rd-block` +- `rdif-display` +- `rdif-input` - 另外还有 `48` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-display.md b/docs/docs/components/crates/ax-display.md index ef049fdf90..a8fa0253ab 100644 --- a/docs/docs/components/crates/ax-display.md +++ b/docs/docs/components/crates/ax-display.md @@ -57,7 +57,7 @@ graph LR - `ax-config-macros` - `ax-cpu` - `ax-dma` -- `ax-driver-base` +- `rdif-display` - 另外还有 `41` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-dma.md b/docs/docs/components/crates/ax-dma.md index e569284527..331a31337c 100644 --- a/docs/docs/components/crates/ax-dma.md +++ b/docs/docs/components/crates/ax-dma.md @@ -4,7 +4,7 @@ > 类型:库 crate > 分层:ArceOS 层 / DMA 内存服务层 > 版本:`0.3.0-preview.3` -> 文档依据:`Cargo.toml`、`src/lib.rs`、`src/dma.rs`、`os/arceos/modules/axdriver/src/ixgbe.rs`、`os/arceos/modules/axdriver/src/drivers.rs`、`os/arceos/api/ax-api/src/imp/mem.rs`、`platform/axplat-dyn/src/drivers/mod.rs`、`os/axvisor/src/driver/blk/mod.rs` +> 文档依据:`Cargo.toml`、`src/lib.rs`、`src/dma.rs`、`drivers/ax-driver/src/ixgbe.rs`、`drivers/ax-driver/src/drivers.rs`、`os/arceos/api/ax-api/src/imp/mem.rs`、`platform/axplat-dyn/src/drivers/mod.rs`、`os/axvisor/src/driver/blk/mod.rs` `ax-dma` 不是驱动聚合层,也不是某类设备驱动。它的真实职责是为 ArceOS 内核提供一套全局一致的 DMA 一致性内存分配服务:从页分配器拿到内存、把页表属性改成 `UNCACHED`、给设备返回可用的总线地址,并在释放时尽可能恢复映射属性。它位于 `ax-alloc` / `ax-mm` / `ax-hal` 等内存基础设施之上,位于需要软件管理 DMA 缓冲的驱动之下。 @@ -66,7 +66,7 @@ 这说明当前 `ax-dma` 假设平台满足一种简单的线性总线地址模型,而不是依赖 IOMMU 做复杂映射。 ### 1.6 与驱动栈的真实接线关系 -当前仓库里,`ax-dma` 的最明确直接使用者是 `os/arceos/modules/axdriver/src/ixgbe.rs`: +当前仓库里,`ax-dma` 的最明确直接使用者是 `drivers/ax-driver/src/ixgbe.rs`: - `IxgbeHalImpl::dma_alloc()` 调用 `ax_dma::alloc_coherent()`; - `dma_dealloc()` 调用 `ax_dma::dealloc_coherent()`。 @@ -74,7 +74,7 @@ 但要特别注意一个实现事实: - `ax-driver/Cargo.toml` 中 `fxmac` feature 也声明了 `dep:ax-dma`; -- 可是 `os/arceos/modules/axdriver/src/drivers.rs` 里的 `FXmacDriver` glue 实际使用的是 `ax-alloc::global_allocator().alloc_pages(..., UsageKind::Dma)`,并没有直接调用 `ax-dma` 的 coherent allocator。 +- 可是 `drivers/ax-driver/src/drivers.rs` 里的 `FXmacDriver` glue 实际使用的是 `ax-alloc::global_allocator().alloc_pages(..., UsageKind::Dma)`,并没有直接调用 `ax-dma` 的 coherent allocator。 这说明在当前代码树里,**`ax-dma` 不是所有 DMA 驱动的唯一后端**,而是其中一条已被明确使用的 DMA 服务路径。 @@ -127,7 +127,7 @@ | `log` | 错误与调试日志 | ### 主要消费者 -- `os/arceos/modules/axdriver/src/ixgbe.rs` +- `drivers/ax-driver/src/ixgbe.rs` - `os/arceos/api/ax-api/src/imp/mem.rs` ### 3.3 分层关系总结 diff --git a/docs/docs/components/crates/ax-driver-base.md b/docs/docs/components/crates/ax-driver-base.md deleted file mode 100644 index 9e2becd7cf..0000000000 --- a/docs/docs/components/crates/ax-driver-base.md +++ /dev/null @@ -1,146 +0,0 @@ -# `ax-driver-base` - -> 路径:`components/axdriver_crates/axdriver_base` -> 类型:库 crate -> 分层:组件层 / 驱动共性契约层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`os/arceos/modules/axdriver/src/prelude.rs` - -`ax-driver-base` 是整个 `axdriver_crates` 体系的最小公共基座。它不负责探测设备、枚举总线、管理 DMA,也不组织 `AllDevices` 这样的设备聚合对象;它只把所有驱动都必须共享的设备分类、错误模型和基础元信息接口集中定义出来,供 `axdriver_block`、`axdriver_net`、`axdriver_display`、`axdriver_input`、`axdriver_vsock` 以及上层 `ax-driver` 聚合层复用。 - -## 架构设计 -### 设计定位 -`ax-driver-base` 的职责非常克制,核心只有三件事: - -- 用 `DeviceType` 给驱动实例贴上统一类别标签。 -- 用 `DevError` / `DevResult` 统一设备操作失败语义。 -- 用 `BaseDriverOps` 规定所有设备都必须暴露的最小信息面。 - -这意味着它在驱动栈中的位置很明确: - -- 它不是 `ax-driver` 那样的驱动聚合层。 -- 它不是 `ax-driver-pci` 或 `ax-driver-virtio` 那样的总线/传输层。 -- 它也不是 `axdriver_block`、`axdriver_net` 这类具体设备类别层。 - -它解决的问题只有一个:**让不同类别驱动至少拥有一致的“名字、类别、错误类型和可选 IRQ”表达方式。** - -### 1.2 关键对象 -| 符号 | 作用 | 在体系中的位置 | -| --- | --- | --- | -| `DeviceType` | 统一设备类别枚举 | 被 `ax-driver::AxDeviceEnum`、`AllDevices` 和日志输出使用 | -| `DevError` | 统一设备错误枚举 | 被所有 `*DriverOps` 实现共享 | -| `DevResult` | 设备操作统一返回类型 | `Result` 的别名 | -| `BaseDriverOps` | 所有设备的最小公共 trait | 是各类别 `*DriverOps` 的共同父接口 | - -`DeviceType` 当前覆盖 `Block`、`Char`、`Net`、`Display`、`Input`、`Vsock` 六类。其中 `Char` 在当前 `ax-driver` 聚合层里还没有对应容器字段,这也说明 `ax-driver-base` 的枚举范围略大于当前 ArceOS 运行时实际接入面。 - -### 1.3 接口约束 -`BaseDriverOps` 的接口极小: - -- `device_name()`:提供用于日志、调试和上层识别的设备名。 -- `device_type()`:把实例绑定到统一类别。 -- `irq_num()`:默认为 `None`,只有需要向上暴露 IRQ 号的驱动才覆写。 - -这种设计有两个直接后果: - -1. 各类别 crate 可以在自己的 trait 中追加能力,而不必重复定义名称和类别接口。 -2. 上层聚合层和日志系统可以只依赖 `BaseDriverOps` 做通用处理,而不用知道具体是块设备、网卡还是输入设备。 - -### 1.4 与相邻层的边界 -| 层次 | 负责内容 | 不负责内容 | -| --- | --- | --- | -| `ax-driver-base` | 设备类别、错误模型、最小元信息接口 | 设备探测、总线枚举、DMA、具体读写协议 | -| `axdriver_block`/`net`/`display`/`input`/`vsock` | 各设备类别专属 trait 与少量实现 | 全局设备聚合、系统初始化时序 | -| `ax-driver-pci` / `ax-driver-virtio` | 总线访问、传输探测和设备包装 | 跨类别统一错误模型定义 | -| `ax-driver` | 设备探测、分类、聚合、向上交付 `AllDevices` | 重新定义基础错误与元信息接口 | - -这里最关键的边界澄清是:**`ax-driver-base` 只是公共契约层,不是驱动管理器。** - -## 核心功能 -### 功能概览 -- 为所有驱动提供统一的 `DeviceType`。 -- 为所有驱动提供统一的 `DevError` / `DevResult`。 -- 为各类别 trait 提供共同父接口 `BaseDriverOps`。 -- 让上层 `ax-driver::prelude` 可以用一套统一名字重导出基础类型。 - -### 2.2 典型使用方式 -在各类别 crate 中,常见模式是: - -1. 先实现 `BaseDriverOps`。 -2. 再实现本类别的专属 trait,例如 `BlockDriverOps` 或 `NetDriverOps`。 -3. 最终由 `ax-driver` 聚合层把实例包装进 `AxDeviceEnum` 或 `Ax*Device`。 - -也就是说,`ax-driver-base` 只定义“底座接口”,并不规定设备如何初始化、如何被发现、如何被消费。 - -### 2.3 当前实现特征 -- 该 crate 没有 Cargo feature,也没有内部子模块拆分,说明它被刻意维持在最稳定、最少变化的一层。 -- `DevError` 主要覆盖驱动常见失败语义,例如 `Again`、`NoMemory`、`Unsupported`、`BadState` 等,适合 no_std 驱动环境的通用错误归一。 -- `core::fmt::Display` 只为 `DevError` 提供人类可读文本,不承担额外错误上下文聚合。 - -## 依赖关系 -### 直接依赖 -`ax-driver-base` 当前没有本地或外部 Rust 依赖;`Cargo.toml` 的 `[dependencies]` 为空。这进一步说明它只承担语言层面的共性抽象。 - -### 主要消费者 -- `axdriver_block` -- `axdriver_display` -- `axdriver_input` -- `axdriver_net` -- `axdriver_vsock` -- `ax-driver-virtio` -- `os/arceos/modules/axdriver` -- `platform/axplat-dyn` 的动态块设备适配路径 - -### 3.3 关系总结 -可以把依赖方向概括为: - -- 向下:无本地依赖。 -- 向上:被所有类别层、总线适配层和聚合层共享。 -- 向旁:通过 `ax-driver::prelude` 间接进入 `ax-display`、`ax-input`、`ax-net`、`ax-fs` 等上层模块。 - -## 开发指南 -### 4.1 何时应该修改本 crate -只有在以下场景才应改动 `ax-driver-base`: - -- 需要新增一种全新的设备类别。 -- 需要为所有驱动统一引入新的基础元信息。 -- 需要补充跨类别通用的错误语义。 - -如果只是新增块设备、网卡、显示设备或输入设备能力,通常应改对应的类别 crate,而不是这里。 - -### 4.2 修改时必须同步检查的地方 -1. 若新增 `DeviceType` 成员,需要同步检查 `ax-driver::AxDeviceEnum`、`AllDevices`、`prelude.rs` 和上层消费者是否都能识别该类别。 -2. 若调整 `DevError`,需要确认 `ax_driver_virtio::as_dev_err()`、`axdriver_block`、`axdriver_net` 等映射逻辑仍然一致。 -3. 若给 `BaseDriverOps` 增加新方法,会影响所有驱动实现,是高风险接口变更。 - -### 4.3 常见误区 -- 不要把类别专属能力塞进 `BaseDriverOps`。例如 `read_block()`、`flush()`、`read_event()` 都应留在各自类别 trait 中。 -- 不要把设备探测接口放进这里;探测属于 `ax-driver` 或总线/传输适配层的职责。 -- 不要把“设备名可打印”误解为“设备可被系统自动管理”;管理逻辑在上层。 - -## 测试 -### 5.1 当前验证形态 -该 crate 没有单独的 `tests/` 目录。它的正确性主要通过编译期接口一致性和上层集成使用来验证。 - -### 单元测试 -- `DeviceType` 的基础匹配逻辑。 -- `DevError` 的 `Display` 文本。 -- `BaseDriverOps` 默认 `irq_num()` 返回 `None` 的契约。 - -### 集成测试 -- 各类别驱动能否以同一套 `BaseDriverOps` 被 `ax-driver::prelude` 和 `AllDevices` 使用。 -- `DevError` 是否能被 `ax-driver-virtio`、`axdriver_net`、`axdriver_block` 等映射成一致行为。 - -### 5.4 风险点 -- 新增设备类别时,最容易漏掉上层聚合容器和日志路径。 -- 改动 `BaseDriverOps` 是典型的“低层小改动、全栈大影响”风险点。 - -## 跨项目定位 -### ArceOS -这是当前仓库中最主要的直接消费方。ArceOS 通过 `ax-driver` 聚合层把它作为整个驱动体系的公共接口底座。 - -### StarryOS -StarryOS 不是直接围绕 `ax-driver-base` 写业务逻辑,而是通过共享的 `ax-driver`、`ax-display`、`ax-input` 等模块间接复用这套基础契约。 - -### Axvisor -当前仓库里没有看到 Axvisor 直接把 `ax-driver-base` 当作其核心设备管理接口。即便未来在某些宿主兼容路径中复用它,它也只会扮演“共享驱动契约层”,而不是虚拟机设备分发中心。 diff --git a/docs/docs/components/crates/ax-driver-block.md b/docs/docs/components/crates/ax-driver-block.md deleted file mode 100644 index e2b79cc6b0..0000000000 --- a/docs/docs/components/crates/ax-driver-block.md +++ /dev/null @@ -1,171 +0,0 @@ -# `axdriver_block` - -> 路径:`components/axdriver_crates/axdriver_block` -> 类型:库 crate -> 分层:组件层 / 块设备类别接口层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`src/ramdisk.rs`、`src/ramdisk_static.rs`、`src/sdmmc.rs`、`src/bcm2835sdhci.rs`、`os/arceos/modules/axdriver/src/drivers.rs`、`platform/axplat-dyn/src/drivers/blk/mod.rs` - -`axdriver_block` 不是文件系统,也不是块缓存层。它的真实定位是 ArceOS 驱动栈里的块设备类别接口 crate:一方面定义统一的 `BlockDriverOps`,另一方面在 feature 打开时提供少量叶子块设备实现,例如 `ramdisk`、`sdmmc`、`bcm2835-sdhci` 和 `ahci`。上层 `ax-driver` 负责探测与聚合,`ax-fs`/`ax-fs-ng` 才是消费块设备并组织文件系统语义的地方。 - -## 架构设计 -### 设计定位 -这个 crate 同时承担两类职责: - -- **类别接口层**:通过 `BlockDriverOps` 统一块设备的容量、块大小、读写和刷盘语义。 -- **可选叶子实现层**:通过 feature 暴露若干具体块设备实现。 - -因此它不是纯粹的“trait-only crate”,但也不是系统级块设备管理器。当前真实模块如下: - -| 模块 | 启用条件 | 作用 | -| --- | --- | --- | -| `ramdisk` | `ramdisk` | 基于堆内存的 RAM 盘 | -| `ramdisk_static` | `ramdisk-static` | 基于静态切片的 RAM 盘 | -| `sdmmc` | `sdmmc` | 基于 `simple_sdmmc::SdMmc` 的 SD/MMC 驱动 | -| `bcm2835sdhci` | `bcm2835-sdhci` | Raspberry Pi 侧的 BCM2835 SDHCI 驱动 | -| `ahci` | `ahci` | AHCI 控制器驱动实现入口 | - -### 1.2 核心接口 -`BlockDriverOps` 继承 `BaseDriverOps`,定义了五个关键方法: - -- `num_blocks()`:返回逻辑块总数。 -- `block_size()`:返回单块大小。 -- `read_block()`:从给定块号开始读取,可跨多块。 -- `write_block()`:从给定块号开始写入,可跨多块。 -- `flush()`:把待刷写数据提交到底层介质。 - -这里最重要的设计点是:**读写接口以“逻辑块设备”视角工作,而不是以文件、分区或页缓存视角工作。** - -### 1.3 具体实现的行为差异 -#### `ramdisk` -`ramdisk::RamDisk` 用 512 字节对齐的堆内存作为后端: - -- `new(size_hint)` 会向上按 512 字节对齐。 -- `read_block()` / `write_block()` 要求缓冲区长度是块大小整数倍。 -- 超界访问返回 `DevError::Io`。 -- `flush()` 为空操作,因为数据本来就在内存中。 - -#### `sdmmc` -`sdmmc::SdMmcDriver` 是对 `simple_sdmmc::SdMmc` 的薄封装: - -- 构造函数是 `unsafe fn new(base: usize)`。 -- 多块读写通过 `as_chunks()` / `as_chunks_mut()` 分块循环完成。 -- 若缓冲区长度不是块大小整数倍,返回 `DevError::InvalidParam`。 - -#### `bcm2835sdhci` -`bcm2835sdhci::SDHCIDriver` 通过 `try_new()` 初始化控制器: - -- 初始化失败直接映射为 `DevError::Io`。 -- 读写要求缓冲区至少覆盖一个块,且需满足 `u32` 对齐要求。 -- 将外部 `SDHCIError` 映射回统一的 `DevError`。 - -### 1.4 与 `ax-driver` 聚合层的接线关系 -在当前仓库中,真正把这些实现接进系统初始化流程的是 `os/arceos/modules/axdriver/src/drivers.rs`: - -- `ramdisk` 通过 `RamDiskDriver::probe_global()` 创建固定大小 16 MiB RAM 盘。 -- `sdmmc` 通过 `SdMmcDriver::new()` 使用 `ax_config::devices::SDMMC_PADDR` 对应寄存器基址。 -- `bcm2835-sdhci` 通过 `SDHCIDriver::try_new()` 接入。 - -需要特别注意一个实现事实:**`axdriver_block` 虽然有 `ahci` feature 和 `ahci` 模块,但当前 `ax-driver::drivers.rs` 并没有把 AHCI 探测逻辑注册进去。** 也就是说,打开 feature 并不等于当前 ArceOS 探测路径就会自动发现 AHCI 设备。 - -### 1.5 与动态平台路径的关系 -在 `platform/axplat-dyn/src/drivers/blk/mod.rs` 中,`rd_block::Block` 会被包装成实现 `BlockDriverOps` 的 `Block`。这说明: - -- `axdriver_block` 的核心价值首先是 trait 契约。 -- 具体实现不一定非要写在本 crate 内,也可以由其它 crate 适配后满足 `BlockDriverOps`。 - -## 核心功能 -### 功能概览 -- 提供统一的块设备 trait `BlockDriverOps`。 -- 为 RAM 盘、SD/MMC、BCM2835 SDHCI、AHCI 等设备提供可选实现入口。 -- 复用 `ax-driver-base` 的名称、类别和错误模型。 -- 作为 `ax_driver_virtio::VirtIoBlkDev` 与 `platform/axplat-dyn` 动态块设备包装的共同契约。 - -### 调用链路 -当前仓库里最典型的使用主线是: - -1. `ax_runtime::init_drivers()` 调用 `ax-driver::init_drivers()`。 -2. `ax-driver` 根据 feature 选择 `ramdisk`、`sdmmc`、`bcm2835-sdhci` 或 `virtio-blk` 路径。 -3. 设备实例被包装成 `AxBlockDevice` 放入 `AllDevices.block`。 -4. `ax-fs` 或 `ax-fs-ng` 再接手这些块设备。 - -也就是说,本 crate 输出的是“可供上层消费的块设备实例”,而不是最终文件系统能力。 - -### 2.3 当前实现限制 -- `ahci` 在本 crate 中存在,但当前 ArceOS 静态探测主线尚未接入。 -- `flush()` 在多个实现里目前为空操作或恒成功,语义上更接近“同步点占位接口”。 -- 各实现对缓冲区对齐、长度和块大小的要求不同,调用方不能假设所有块驱动都能接受任意字节数。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `ax-driver-base` | 统一设备元信息和错误类型 | -| `simple-sdmmc` | `sdmmc` 模块的底层控制器实现 | -| `simple-ahci` | `ahci` 模块的底层控制器实现 | -| `bcm2835-sdhci` | `bcm2835sdhci` 模块的底层控制器实现 | -| `log` | 初始化与错误日志 | - -### 主要消费者 -- `os/arceos/modules/axdriver` -- `components/axdriver_crates/axdriver_virtio` -- `platform/axplat-dyn` -- `os/arceos/modules/axfs` -- `os/arceos/modules/axfs-ng` - -### 3.3 分层关系总结 -- 向下依赖具体控制器库或内存后端。 -- 向上输出统一的 `BlockDriverOps` 语义。 -- 由 `ax-driver` 聚合层决定哪些设备真正进入系统。 - -## 开发指南 -### 4.1 何时应在这里扩展 -适合放进 `axdriver_block` 的内容有两类: - -- 一个能代表“块设备通用语义”的 trait 或辅助类型。 -- 一个已经被 ArceOS 驱动栈广泛使用、适合以 feature 方式内建的叶子块设备实现。 - -如果某个块设备只在单一平台实验使用,也可以不放进这里,而是直接在外部 crate 实现 `BlockDriverOps`。 - -### 4.2 新增实现时必须同步检查的地方 -1. `Cargo.toml` 的 feature 和可选依赖。 -2. `os/arceos/modules/axdriver/src/drivers.rs` 是否真的注册了 probe 路径。 -3. `ax-feat` 顶层 feature 是否需要把该驱动能力暴露给整机配置。 -4. 读写接口对块大小、缓冲区长度和对齐的约束是否写清楚。 - -### 4.3 常见坑 -- 不要把本 crate 写成“块设备子系统”;队列调度、页缓存、文件系统挂载都不在这里。 -- 仅在本 crate 中加入一个模块,并不会自动进入 `ax-driver` 探测流程。 -- `flush()` 语义在不同实现里强弱不同,不能想当然地把它当作硬件缓存落盘保证。 - -## 测试 -### 测试覆盖 -该 crate 没有独立的 `tests/` 目录,验证主要依赖: - -- `ramdisk` 的内存读写行为。 -- `ax-driver` 初始化阶段是否能真正注册块设备。 -- `ax-fs` / `ax-fs-ng` 是否能基于这些设备完成挂载和读写。 - -### 单元测试 -- `ramdisk` 的块对齐、越界与多块读写。 -- `sdmmc` 与 `bcm2835sdhci` 的参数检查和错误映射。 -- `BlockDriverOps` 约定下 `num_blocks() * block_size()` 的边界一致性。 - -### 集成测试 -- `virtio-blk`、`ramdisk`、`sdmmc` 至少各保留一条整机启动路径。 -- 文件系统挂载和基础 I/O 回归。 -- 若补齐 `ahci` 接线,应新增对应的总线探测和 BAR 配置验证。 - -### 5.4 风险点 -- Feature 已声明但探测主线未接线,是当前最容易引入误判的地方。 -- 块大小和缓冲区约束一旦处理错误,问题通常会在更上层文件系统中以数据损坏形式出现。 - -## 跨项目定位 -### ArceOS -这是当前仓库里的主消费方。ArceOS 通过 `ax-driver` 和文件系统模块把它作为块设备类别契约和部分内建驱动实现使用。 - -### StarryOS -StarryOS 在当前仓库中没有把 `axdriver_block` 当成独立块子系统直接使用;它更多是通过共享的 ArceOS 底层模块栈间接受益。 - -### Axvisor -当前 Axvisor 代码主线更偏向 `rd_block` 与自身驱动体系,而不是直接依赖 `axdriver_block`。因此不应把本 crate 写成 Axvisor 的通用块设备框架。 diff --git a/docs/docs/components/crates/ax-driver-display.md b/docs/docs/components/crates/ax-driver-display.md deleted file mode 100644 index f8d7a56745..0000000000 --- a/docs/docs/components/crates/ax-driver-display.md +++ /dev/null @@ -1,147 +0,0 @@ -# `axdriver_display` - -> 路径:`components/axdriver_crates/axdriver_display` -> 类型:库 crate -> 分层:组件层 / 显示设备类别接口层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`components/axdriver_crates/axdriver_virtio/src/gpu.rs`、`os/arceos/modules/axdisplay/src/lib.rs`、`os/StarryOS/kernel/src/pseudofs/dev/fb.rs` - -`axdriver_display` 的职责是为显示设备定义统一的驱动接口。它不负责探测设备,也不负责把帧缓冲暴露成 `/dev/fb0`,更不提供窗口系统或图形合成能力;它只定义显示驱动最小需要暴露的信息和操作,让 `virtio-gpu` 之类的具体实现可以被 `ax-driver` 聚合层和 `ax-display` 上层模块统一消费。 - -## 架构设计 -### 设计定位 -这个 crate 只处理“显示设备驱动应该向上暴露什么”: - -- `DisplayInfo` 描述显示分辨率和帧缓冲位置。 -- `FrameBuffer` 封装设备映射出来的帧缓冲内存。 -- `DisplayDriverOps` 定义查询信息、获取帧缓冲、判断是否需要刷新以及执行刷新这四类操作。 - -它在分层上位于: - -- `ax-driver-base` 之上:继承统一的 `BaseDriverOps`。 -- 具体显示驱动之下:例如 `ax_driver_virtio::VirtIoGpuDev` 会实现它。 -- `ax-display` 之下:上层模块通过 `AxDisplayDevice` 使用它,而不是直接接触具体 GPU 驱动类型。 - -### 1.2 关键类型 -| 符号 | 作用 | 备注 | -| --- | --- | --- | -| `DisplayInfo` | 描述宽、高、帧缓冲虚拟地址和大小 | 是上层显示能力的元信息入口 | -| `FrameBuffer<'a>` | 对帧缓冲内存的薄包装 | 不拥有显存,只包装已映射内存 | -| `DisplayDriverOps` | 显示设备统一 trait | 继承 `BaseDriverOps` | - -### 1.3 帧缓冲对象模型 -`FrameBuffer` 的设计非常克制: - -- `from_raw_parts_mut()` 允许驱动把一段已经映射好的设备内存包装成帧缓冲。 -- `from_slice()` 允许直接用切片构造。 -- 它本身不做像素格式协商、双缓冲管理或显存映射建立。 - -因此,`FrameBuffer` 只是“对一段可写图像内存的访问句柄”,不是图形 API。 - -### 1.4 与上下层的接线关系 -在当前仓库中,最典型的实现来自 `ax_driver_virtio::VirtIoGpuDev`: - -- `try_new()` 内部调用 `virtio_drivers::device::gpu::VirtIOGpu`。 -- 通过 `setup_framebuffer()` 建立帧缓冲。 -- 用 `resolution()` 生成 `DisplayInfo`。 -- `flush()` 最终转发给底层 VirtIO GPU 刷新。 - -再往上一层: - -- `ax-driver` 把具体实例包装为 `AxDisplayDevice` 放入 `AllDevices.display`。 -- `ax-runtime` 调用 `ax_display::init_display(all_devices.display)`。 -- `ax-display` 提供 `framebuffer_info()` / `framebuffer_flush()`。 -- StarryOS 的 `pseudofs/dev/fb.rs` 再把这组能力包装成伪文件系统的 framebuffer 设备。 - -### 1.5 边界澄清 -最重要的边界是:**`axdriver_display` 只定义“显示驱动该如何向上暴露帧缓冲能力”,它不是图形子系统,也不是用户可见显示服务本身。** - -## 核心功能 -### 功能概览 -- 为显示设备统一定义 `DisplayDriverOps`。 -- 用 `DisplayInfo` 传递最关键的屏幕和帧缓冲信息。 -- 用 `FrameBuffer` 表达设备映射显存的可写视图。 -- 让不同显示驱动能被 `ax-driver` 和 `ax-display` 统一消费。 - -### 2.2 关键 API 与语义 -- `info()`:返回显示元信息。 -- `fb()`:返回帧缓冲视图。 -- `need_flush()`:说明写入显存后是否还需要显式提交。 -- `flush()`:把帧缓冲内容真正推到设备侧。 - -`need_flush()` 的存在很关键,因为它把“直写显存即可见”和“还需要显式提交”这两类设备统一在一套接口里。 - -### 2.3 当前实现特征 -- 该 crate 自身没有 Cargo feature,也没有内建具体 GPU 驱动。 -- `FrameBuffer::from_raw_parts_mut()` 是 `unsafe`,调用者必须保证地址区间有效且可访问。 -- 像素格式、模式切换、硬件加速等更复杂语义并未纳入当前接口模型。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `ax-driver-base` | 提供 `BaseDriverOps`、`DeviceType`、`DevError` | - -### 主要消费者 -- `components/axdriver_crates/axdriver_virtio` -- `os/arceos/modules/axdriver` -- `os/arceos/modules/axdisplay` -- `os/StarryOS/kernel/src/pseudofs/dev/fb.rs` - -### 3.3 分层关系总结 -- 向下没有具体总线或设备依赖。 -- 向上为显示驱动统一暴露帧缓冲语义。 -- 最终由 `ax-display` 把能力整理成更接近上层使用的形式。 - -## 开发指南 -### 4.1 什么时候应修改这里 -适合修改 `axdriver_display` 的情况是: - -- 需要给所有显示驱动新增共通元信息。 -- 需要为显示驱动补充真正“类别共通”的操作。 -- 需要修正帧缓冲生命周期或刷新语义的契约。 - -如果只是给某个具体显示驱动补充模式设置逻辑,通常应改对应实现 crate,而不是这里。 - -### 4.2 实现新驱动时的建议 -1. 先保证 `DisplayInfo` 中的宽、高、`fb_base_vaddr`、`fb_size` 都准确可用。 -2. 明确 `fb()` 返回的内存是否长期有效,以及是否与 `flush()` 配套。 -3. 若设备支持直接显存映射但无需刷新,应让 `need_flush()` 返回 `false`。 -4. 若显存地址来源于 MMIO 或 DMA 映射,应在驱动实现层完成映射建立,不要把映射职责推给本 crate。 - -### 4.3 常见坑 -- 不要把 `FrameBuffer` 当成图形渲染 API;它只是显存切片。 -- 不要把 `DisplayInfo` 当成模式枚举器;当前只表达当前工作模式。 -- 不要在这里引入用户态或伪文件系统语义;那属于 `ax-display` 或更上层。 - -## 测试 -### 测试覆盖 -该 crate 没有独立测试目录,当前验证主要依赖: - -- `virtio-gpu` 驱动能否正确实现 `DisplayDriverOps`。 -- `ax_display::framebuffer_info()` 和 `framebuffer_flush()` 是否能正常工作。 -- StarryOS 的 `/dev/fb` 是否能据此提供可用帧缓冲设备。 - -### 单元测试 -- `FrameBuffer::from_slice()` 和 `from_raw_parts_mut()` 的基本包装行为。 -- `need_flush()` / `flush()` 契约在 mock 设备上的一致性。 -- `DisplayInfo` 元信息正确传递到上层。 - -### 集成测试 -- QEMU `virtio-gpu` 启动和 framebuffer 写屏验证。 -- `ax-display` 单全局设备路径。 -- StarryOS `fb` 设备的 `ioctl`、`mmap` 和刷新路径。 - -### 5.4 风险点 -- 帧缓冲地址和大小一旦错误,上层看到的不是普通错误,而是直接的显存越界风险。 -- 若驱动对 `need_flush()` 语义实现不一致,表现往往是“写屏成功但屏幕不更新”。 - -## 跨项目定位 -### ArceOS -ArceOS 通过 `ax-driver` 和 `ax-display` 直接消费本 crate 的接口,是当前最明确的主线使用者。 - -### StarryOS -StarryOS 不直接实现新的显示驱动 trait,而是通过 `ax-display` 提供的能力在 `pseudofs/dev/fb.rs` 中构造 framebuffer 设备,因此它更偏上层消费者。 - -### Axvisor -当前仓库中没有看到 Axvisor 直接把 `axdriver_display` 作为核心图形抽象使用。它不是 Axvisor 的虚拟显示设备框架。 diff --git a/docs/docs/components/crates/ax-driver-input.md b/docs/docs/components/crates/ax-driver-input.md deleted file mode 100644 index a523f092c4..0000000000 --- a/docs/docs/components/crates/ax-driver-input.md +++ /dev/null @@ -1,144 +0,0 @@ -# `axdriver_input` - -> 路径:`components/axdriver_crates/axdriver_input` -> 类型:库 crate -> 分层:组件层 / 输入设备类别接口层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`components/axdriver_crates/axdriver_virtio/src/input.rs`、`os/arceos/modules/axinput/src/lib.rs`、`os/StarryOS/kernel/src/pseudofs/dev/event.rs` - -`axdriver_input` 用来定义输入设备驱动的统一契约。它不是输入事件分发系统,也不是 `/dev/input/event*` 这样的用户可见设备层,而是把“输入设备如何报告支持的事件位图、如何吐出事件、如何暴露设备 ID”这一层接口固定下来,供 `virtio-input` 等具体驱动和 `ax-input` / StarryOS evdev 适配层共同使用。 - -## 架构设计 -### 设计定位 -这个 crate 的设计明显借鉴了 Linux input 子系统的数据模型: - -- `EventType` 定义同步、按键、相对坐标、绝对坐标、开关、LED、声音等事件类别。 -- `Event` 使用 `(event_type, code, value)` 三元组描述单条输入事件。 -- `InputDeviceId` 和 `AbsInfo` 分别表达设备身份和绝对坐标轴信息。 -- `InputDriverOps` 给输入设备统一定义查询能力和取事件的方法。 - -需要特别指出:源码顶部和 trait 注释里还留有 copy-paste 造成的 “graphics device” 文案,但真实接口语义完全是输入设备,而不是显示设备。 - -### 1.2 关键对象 -| 符号 | 作用 | 备注 | -| --- | --- | --- | -| `EventType` | 输入事件类别枚举 | 通过 `strum::FromRepr` 支持数值转枚举 | -| `Event` | 单条输入事件 | 与 Linux input 事件三元组一致 | -| `InputDeviceId` | 设备身份信息 | 包含 bus/vendor/product/version | -| `AbsInfo` | 绝对轴属性 | 当前更多是契约预留 | -| `InputDriverOps` | 输入设备统一 trait | 是上层读取事件的入口 | - -### 1.3 能力模型 -`InputDriverOps` 定义的核心方法有: - -- `device_id()`:返回设备 ID。 -- `physical_location()` / `unique_id()`:提供设备位置和唯一标识字符串。 -- `get_event_bits()`:查询某类事件支持的 code 位图。 -- `read_event()`:取出一条待处理事件;无事件时返回 `DevError::Again`。 - -这种接口设计说明它关注的是“驱动如何以轮询方式向上暴露事件”,而不是“系统如何把事件广播给多个消费者”。 - -### 1.4 与上下层的关系 -当前仓库里的典型接线链路如下: - -1. `ax_driver_virtio::VirtIoInputDev` 实现 `InputDriverOps`。 -2. `ax-driver` 把它包装成 `AxInputDevice` 放进 `AllDevices.input`。 -3. `ax-runtime` 调用 `ax_input::init_input(all_devices.input)`。 -4. StarryOS 的 `pseudofs/dev/event.rs` 再用 `ax_input::take_inputs()` 取走这些设备,构造 evdev 风格的字符设备。 - -因此,本 crate 位于“驱动契约”和“系统输入服务”之间,而不是输入服务本身。 - -### 1.5 边界澄清 -最重要的边界是:**`axdriver_input` 只定义输入设备驱动契约,不负责事件队列复用、焦点管理、终端输入分发或 `/dev/input` 节点。** - -## 核心功能 -### 功能概览 -- 统一输入事件和事件类型表示。 -- 统一输入设备 ID 和能力位图查询接口。 -- 统一“无事件时返回 `DevError::Again`”的轮询约定。 -- 让不同输入设备能被 `ax-driver`、`ax-input` 和 StarryOS evdev 层共同消费。 - -### 2.2 当前 VirtIO 路径的实现方式 -`ax_driver_virtio::VirtIoInputDev` 是当前仓库里的主要实现: - -- `try_new()` 从底层设备读取名称和 `ids()`。 -- `get_event_bits()` 通过 `query_config_select(InputConfigSelect::EvBits, ...)` 读取能力位图。 -- `read_event()` 调用 `pop_pending_event()`,并在无事件时返回 `DevError::Again`。 - -这条实现主线也印证了本 crate 的设计:它要求驱动暴露事件能力和取事件接口,但不内建共享缓冲或广播语义。 - -### 2.3 当前实现特征 -- crate 本身没有 feature 开关,保持纯接口层。 -- `EventType::bits_count()` 为不同事件类别预设了位图长度上限,供上层按类别申请输出缓冲区。 -- `AbsInfo` 已定义但当前仓库主要消费点仍集中在 `Event` 和 `get_event_bits()`。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `ax-driver-base` | 基础设备信息和错误模型 | -| `strum` | 为 `EventType` 提供 `FromRepr` | - -### 主要消费者 -- `components/axdriver_crates/axdriver_virtio` -- `os/arceos/modules/axdriver` -- `os/arceos/modules/axinput` -- `os/StarryOS/kernel/src/pseudofs/dev/event.rs` - -### 3.3 分层关系总结 -- 向下不依赖任何总线或设备实现。 -- 向上为输入驱动暴露统一事件契约。 -- 更上层的 `ax-input` 与 StarryOS evdev 适配负责把这些事件变成系统可消费能力。 - -## 开发指南 -### 4.1 何时修改这里 -应在以下场景修改 `axdriver_input`: - -- 输入设备类别需要新增真正通用的元信息或操作。 -- 事件模型需要与上层 evdev 风格接口保持一致演进。 -- `EventType` 或位图长度契约需要扩展。 - -如果只是新增某个具体输入设备驱动,通常应在外部实现 `InputDriverOps`,而不是改这里。 - -### 4.2 实现新驱动时的建议 -1. `read_event()` 无事件时应返回 `DevError::Again`,不要把“暂时没数据”误写成 `Io`。 -2. `get_event_bits()` 应清晰区分“不支持该事件类型”和“查询失败”。 -3. `device_id()`、`physical_location()`、`unique_id()` 最好保持稳定,便于上层做设备识别。 -4. 若驱动支持绝对轴,后续应考虑把 `AbsInfo` 相关能力补齐,而不是另起私有接口。 - -### 4.3 常见坑 -- 不要把输入事件缓冲、poll/wakeup 机制塞进本 crate;那属于更上层。 -- 不要把 `get_event_bits()` 的 `false` 和 `Err(...)` 混为一谈。 -- 不要假设上层一定会持续持有设备;当前 `ax-input` 采用“收集后一次性交付”的模型。 - -## 测试 -### 测试覆盖 -该 crate 没有独立测试目录,当前主要通过以下整机路径验证: - -- `virtio-input` 驱动是否能正确返回事件位图和事件流。 -- `ax-input` 是否能收集设备。 -- StarryOS 的 evdev 伪设备是否能基于这些接口工作。 - -### 单元测试 -- `EventType::bits_count()` 的边界值。 -- `FromRepr` 与枚举值映射。 -- mock 输入设备上 `get_event_bits()` / `read_event()` 的契约测试。 - -### 集成测试 -- QEMU `virtio-input` 键盘/鼠标事件通路。 -- StarryOS `event.rs` 的 `ioctl`、`read_at()` 和 `poll()` 路径。 -- 多输入设备并存时的能力位图和设备名回传。 - -### 5.4 风险点 -- 事件位图长度或编码一旦不一致,会在上层表现为“设备存在但功能位识别错误”。 -- 若把无事件错误映射错,会让轮询或伪文件系统阻塞语义出现异常。 - -## 跨项目定位 -### ArceOS -ArceOS 通过 `ax-driver` 和 `ax-input` 直接消费它,是当前仓库中的主线使用场景。 - -### StarryOS -StarryOS 在 `pseudofs/dev/event.rs` 中直接使用 `InputDriverOps`、`EventType`、`InputDeviceId` 等符号,把它变成 evdev 风格输入设备,因此是更贴近上层能力的一侧消费者。 - -### Axvisor -当前仓库里没有看到 Axvisor 直接以 `axdriver_input` 为核心接口。它不是虚拟化输入分发框架,也不是通用的宿主输入抽象层。 diff --git a/docs/docs/components/crates/ax-driver-net.md b/docs/docs/components/crates/ax-driver-net.md deleted file mode 100644 index 09512988f1..0000000000 --- a/docs/docs/components/crates/ax-driver-net.md +++ /dev/null @@ -1,172 +0,0 @@ -# `axdriver_net` - -> 路径:`components/axdriver_crates/axdriver_net` -> 类型:库 crate -> 分层:组件层 / 网络设备类别接口层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`src/net_buf.rs`、`src/fxmac.rs`、`src/ixgbe.rs`、`components/axdriver_crates/axdriver_virtio/src/net.rs`、`os/arceos/modules/axdriver/src/drivers.rs` - -`axdriver_net` 的定位是 NIC 驱动类别层,而不是网络栈。它一方面定义网卡驱动必须实现的 `NetDriverOps`,另一方面内建 `fxmac` 和 `ixgbe` 两个具体实现模块,并提供一套在当前 ArceOS 网络驱动栈里非常关键的缓冲区抽象 `NetBuf` / `NetBufPool` / `NetBufPtr`。上层 `ax-net`、`ax-net-ng` 依赖它消费网卡,下层具体设备和总线探测则由 `ax-driver`、`ax-driver-virtio` 等承担。 - -## 架构设计 -### 设计定位 -这个 crate 同样是“类别契约 + 部分叶子实现”的混合结构: - -- `NetDriverOps` 统一定义 NIC 的收发、队列和缓冲区契约。 -- `net_buf.rs` 提供通用网络缓冲区模型。 -- `fxmac.rs` 和 `ixgbe.rs` 在 feature 打开时提供具体网卡实现。 - -因此它既不是纯接口层,也不是完整网络子系统。真正的 TCP/IP、socket、协议栈和接口管理在 `ax-net` / `ax-net-ng` 中。 - -### 1.2 关键接口与对象 -| 符号 | 作用 | 备注 | -| --- | --- | --- | -| `EthernetAddress` | MAC 地址类型 | 设备级身份信息 | -| `NetDriverOps` | NIC 统一 trait | 定义收发与队列接口 | -| `NetBufPtr` | 对已分配网络缓冲的轻量句柄 | 通过原始指针连接底层缓冲 | -| `NetBuf` | RAII 网络缓冲区 | Drop 时自动归还池 | -| `NetBufPool` | 固定长度缓冲池 | 提升收发缓冲分配效率 | - -### 1.3 缓冲区模型 -`NetBuf` 系列是本 crate 的一大实现重点: - -- `NetBufPool` 把一大块内存切成定长槽位,池容量和每块长度在创建时固定。 -- `NetBuf` 区分 header 和 packet 两个逻辑区域。 -- `NetBuf::into_buf_ptr()` 把 RAII 对象转成 `NetBufPtr`,便于驱动把所有权交给底层队列。 -- `NetBuf::from_buf_ptr()` 再把裸指针还原回来。 - -这套设计说明 `axdriver_net` 关注的不只是 trait 形状,还关注驱动与上层之间的缓冲所有权转移模型。 - -### 1.4 `NetDriverOps` 的核心语义 -`NetDriverOps` 的方法大致可分为四组: - -- 元信息:`mac_address()`。 -- 队列状态:`can_transmit()`、`can_receive()`、`rx_queue_size()`、`tx_queue_size()`。 -- 缓冲回收:`recycle_rx_buffer()`、`recycle_tx_buffers()`。 -- 数据通路:`transmit()`、`receive()`、`alloc_tx_buffer()`。 - -其中最容易被误解的是 `receive()` / `recycle_rx_buffer()` 配对关系:驱动把收到的 `NetBufPtr` 交给上层后,上层必须在处理完成后归还给驱动,否则收包队列会耗尽。 - -### 1.5 当前内建实现 -#### `fxmac` -`FXmacNic` 基于 `fxmac_rs`,主要特点是: - -- 通过 `KernelFunc` 接口向外索取 `virt_to_phys`、`phys_to_virt`、DMA 分配和 IRQ 申请等平台能力。 -- `receive()` 会调用 `FXmacRecvHandler()` 拉取批量报文,再封成 `NetBufPtr`。 -- `transmit()` 直接转发给 `FXmacLwipPortTx()`。 - -#### `ixgbe` -`IxgbeNic` 基于 `ixgbe-driver`,主要特点是: - -- 用 `MemPool` 管理 DMA 友好的网卡缓冲。 -- `init(base, len)` 完成设备初始化。 -- `receive_packets()` 支持批量接收并转成 `NetBufPtr`。 -- `IxgbeHal` 由 `os/arceos/modules/axdriver/src/ixgbe.rs` 对接 `ax-dma`。 - -### 1.6 与 `ax-driver` 和 `ax-driver-virtio` 的接线关系 -当前仓库中的三条主要接线路径是: - -- `ax_driver_virtio::VirtIoNetDev`:用 `NetBufPool` 组织 VirtIO 队列收发。 -- `ax-driver::drivers::IxgbeDriver`:在 PCI 探测路径中构造 `IxgbeNic`。 -- `ax-driver::drivers::FXmacDriver`:在全局 probe 路径中构造 `FXmacNic`,并通过 `crate_interface` 实现 `KernelFunc`。 - -所以 `axdriver_net` 负责“网卡应该如何工作”,而不是“系统去哪里找到网卡”。 - -### 1.7 边界澄清 -最重要的边界是:**`axdriver_net` 是 NIC 驱动类别层,不是网络栈、socket 层,也不是接口管理层。** - -## 核心功能 -### 功能概览 -- 定义统一的 NIC 驱动 trait `NetDriverOps`。 -- 提供网络缓冲区池和所有权转换模型。 -- 通过 feature 内建 `fxmac` 和 `ixgbe` 网卡实现。 -- 作为 `virtio-net`、`ixgbe`、`fxmac` 等不同设备路径的共同上层接口。 - -### 2.2 当前实现特征 -- `NetBufPool::new(capacity, buf_len)` 要求 `buf_len` 落在 `1526..=65535` 范围内。 -- `VirtIoNetDev::try_new()` 会预填 RX 缓冲并预分配 TX 缓冲,说明当前网络接口假设底层驱动能长期持有这些缓冲。 -- `ixgbe` 与 `fxmac` 的缓冲来源完全不同,但都收敛到 `NetBufPtr` 接口,这正是本 crate 的统一价值。 - -### 2.3 对上层的意义 -对 `ax-net` / `ax-net-ng` 来说,它们并不关心底层设备是 VirtIO、Intel 82599 还是 Phytium FXmac,只需要按 `NetDriverOps` 拿到: - -- 一个 MAC 地址; -- 一个可收发的网卡; -- 一组可回收的收发缓冲。 - -这正是类别层和网络栈之间的清晰分界。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `ax-driver-base` | 提供共性设备接口与错误类型 | -| `spin` | 保护 `NetBufPool` 空闲链表 | -| `fxmac_rs` | `fxmac` 实现的底层库 | -| `ixgbe-driver` | `ixgbe` 实现的底层库 | -| `log` | 初始化和错误日志 | - -### 主要消费者 -- `components/axdriver_crates/axdriver_virtio` -- `os/arceos/modules/axdriver` -- `os/arceos/modules/ax-net` -- `os/arceos/modules/axnet-ng` - -### 3.3 分层关系总结 -- 向下可接不同 NIC 实现。 -- 向上统一暴露网卡语义。 -- 由 `ax-driver` 决定设备探测与聚合,由 `ax-net`/`ax-net-ng` 决定协议栈语义。 - -## 开发指南 -### 4.1 何时应该改这里 -适合修改 `axdriver_net` 的场景包括: - -- 需要扩展所有 NIC 驱动都共有的接口契约。 -- 需要调整通用网络缓冲区模型。 -- 需要增加一个应被整个 ArceOS 生态共享的网卡实现模块。 - -如果只是处理某个网络协议或接口配置,不应改这里。 - -### 4.2 新增驱动时的建议 -1. 先决定是实现 `NetDriverOps` 还是直接在本 crate 内新增 feature 模块。 -2. 明确 `receive()` 返回后缓冲区如何归还。 -3. 如果驱动依赖 DMA,需要同时明确 DMA 分配和地址转换由哪一层提供。 -4. 若接入 `ax-driver` 探测主线,还需同步修改 `os/arceos/modules/axdriver/src/drivers.rs`。 - -### 4.3 常见坑 -- `receive()` 返回的 `NetBufPtr` 不是普通切片,背后有明确的所有权和回收语义。 -- `can_transmit()` / `can_receive()` 只是即时状态,不代表永远可用。 -- 不要把本 crate 写成“网络子系统”;路由、socket、poll 接口都不在这里。 - -## 测试 -### 测试覆盖 -该 crate 没有独立测试目录,当前有效验证主要来自: - -- `virtio-net`、`ixgbe`、`fxmac` 的整机 bring-up。 -- `ax-net` / `ax-net-ng` 的网络收发路径。 -- `NetBufPool` 在不同驱动上的缓冲回收行为。 - -### 单元测试 -- `NetBufPool` 的容量、长度边界和回收行为。 -- `NetBuf` 的 header / packet 区域长度管理。 -- `NetBufPtr` 与 `NetBuf` 的往返恢复。 - -### 集成测试 -- QEMU `virtio-net` 网络冒烟。 -- ixgbe PCI 探测和 DMA 路径。 -- fxmac 平台初始化和报文收发。 -- 长时间收发下的缓冲回收稳定性。 - -### 5.4 风险点 -- 缓冲回收协议一旦写错,问题往往表现为“跑一段时间后 RX/TX 队列耗尽”。 -- DMA 和物理地址转换错误通常不会只影响一个包,而会直接让整个 NIC 不稳定。 - -## 跨项目定位 -### ArceOS -ArceOS 是当前仓库里最主要的直接消费者:`ax-driver` 负责把设备接进来,`ax-net` / `ax-net-ng` 负责把它们变成网络能力。 - -### StarryOS -StarryOS 当前并没有把 `axdriver_net` 当成独立网络栈;若复用网络能力,也主要是通过共享的 ArceOS 底层模块链路间接使用。 - -### Axvisor -当前仓库未显示 Axvisor 直接以 `axdriver_net` 作为其主线网卡框架。它不是虚拟化设备网络平面的核心抽象。 diff --git a/docs/docs/components/crates/ax-driver-pci.md b/docs/docs/components/crates/ax-driver-pci.md deleted file mode 100644 index b13f6ba821..0000000000 --- a/docs/docs/components/crates/ax-driver-pci.md +++ /dev/null @@ -1,143 +0,0 @@ -# `ax-driver-pci` - -> 路径:`components/axdriver_crates/axdriver_pci` -> 类型:库 crate -> 分层:组件层 / PCI 总线访问层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`os/arceos/modules/axdriver/src/bus/pci.rs`、`os/arceos/modules/axdriver/src/virtio.rs` - -`ax-driver-pci` 的定位非常清晰:它是 `ax-driver` 体系里的 PCI 总线访问辅助层。它不负责把设备按类别聚合,也不负责实现具体网卡、块设备或显示设备驱动;它提供的是 PCI 配置空间相关类型的统一来源,以及一个很小但很关键的 `PciRangeAllocator`,供上层为未分配地址的 PCI BAR 安排 MMIO 窗口。 - -## 架构设计 -### 设计定位 -这个 crate 的源码很小,但边界十分明确: - -- 绝大多数 PCI 相关类型直接从 `virtio_drivers::transport::pci::bus` 再导出。 -- 本 crate 自己真正新增的核心对象只有 `PciRangeAllocator`。 -- 真正的 PCI 枚举、BAR 配置和驱动派发逻辑在 `os/arceos/modules/axdriver/src/bus/pci.rs`。 - -因此它更像是 ArceOS 驱动栈里的“PCI 公共类型与分配辅助层”,而不是独立 PCI 子系统。 - -### 1.2 关键对象 -| 符号 | 作用 | 来源 | -| --- | --- | --- | -| `PciRoot`、`DeviceFunction`、`BarInfo` 等 | 访问 PCI 配置空间和设备信息 | 由 `virtio-drivers` 再导出 | -| `PciRangeAllocator` | 为 BAR 分配 MMIO 地址空间 | 本 crate 自定义 | -| `PciError`、`Command`、`Status` | PCI 操作辅助类型 | 由 `virtio-drivers` 再导出 | - -### 1.3 `PciRangeAllocator` 的作用 -`PciRangeAllocator` 只做一件事:从一段 MMIO 地址窗口中分配满足对齐要求的连续区域。 - -其特点是: - -- `new(base, size)` 以一段物理窗口初始化分配器。 -- `alloc(size)` 要求 `size` 必须是 2 的幂。 -- 返回地址会自动按 `size` 对齐。 -- 若空间不足或参数不合法,返回 `None`。 - -它本质上是一个顺序递增分配器,适合 BAR 窗口这种“启动期一次性分配、运行期不回收”的场景。 - -### 1.4 在 `ax-driver` 中的真实调用方式 -在 `os/arceos/modules/axdriver/src/bus/pci.rs` 里,PCI 探测主线如下: - -1. 用 `PciRoot::new(..., Cam::Ecam)` 打开 ECAM。 -2. 从 `ax_config::devices::PCI_RANGES` 取出 32 位 MMIO 窗口,创建 `PciRangeAllocator`。 -3. 枚举每个 bus 上的设备。 -4. 对 BAR 逐个调用 `config_pci_device()`: - - 若 BAR 未分配地址,调用 `PciRangeAllocator::alloc()` 分配。 - - 再把 `IO_SPACE`、`MEMORY_SPACE`、`BUS_MASTER` 打开。 -5. 最后再把设备交给 `Driver::probe_pci()` 做类别识别和具体驱动创建。 - -这说明: - -- `ax-driver-pci` 负责的是“访问和分配”。 -- `ax-driver` 才负责“枚举和派发”。 - -### 1.5 与 VirtIO 路径的关系 -`ax_driver_virtio::probe_pci_device()` 和 `os/arceos/modules/axdriver/src/virtio.rs` 会继续基于 `PciRoot`、`DeviceFunction`、`DeviceFunctionInfo` 做 VirtIO PCI 设备识别。因此 `ax-driver-pci` 也是 VirtIO PCI 探测路径的底层依赖。 - -### 1.6 边界澄清 -最关键的边界是:**`ax-driver-pci` 是 PCI 总线访问辅助层,不是通用驱动聚合层,也不是完整的 PCI 设备管理子系统。** - -## 核心功能 -### 功能概览 -- 统一提供 PCI 配置空间访问相关类型。 -- 为 BAR 资源分配提供最小分配器 `PciRangeAllocator`。 -- 为上层 `ax-driver` 的 PCI 枚举和 VirtIO PCI 探测提供公共底座。 - -### 2.2 当前实现特征 -- crate 自身没有 Cargo feature,也没有内部子模块拆分。 -- 本地逻辑极少,绝大部分能力直接复用 `virtio-drivers`。 -- 设计上故意不引入设备类别概念,从而保持总线层的中立性。 - -### 2.3 不负责的事情 -- 不负责扫描 bus 范围和设备树。 -- 不负责 IRQ 路由。 -- 不负责设备类别识别。 -- 不负责 DMA、IOMMU 或驱动生命周期管理。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `virtio-drivers` | 提供 PCI bus 访问类型和实现 | - -### 主要消费者 -- `os/arceos/modules/axdriver/src/bus/pci.rs` -- `os/arceos/modules/axdriver/src/virtio.rs` - -### 3.3 分层关系总结 -- 向下:依赖 `virtio-drivers` 的 PCI bus 实现。 -- 向上:服务 `ax-driver` 的 PCI 探测与 VirtIO PCI 路径。 -- 横向:保持对设备类别中立,不直接依赖 `axdriver_block`、`axdriver_net` 等类别 crate。 - -## 开发指南 -### 4.1 何时应改这里 -适合修改 `ax-driver-pci` 的场景包括: - -- 需要给所有 PCI 设备路径新增通用辅助逻辑。 -- 需要补充 BAR 分配或 PCI 配置访问相关能力。 -- 需要统一仓库内对 `virtio-drivers` PCI bus API 的适配点。 - -如果只是新增某类 PCI 设备驱动,通常应修改 `ax-driver` 或对应设备 crate,而不是这里。 - -### 4.2 修改时要同步检查的地方 -1. `os/arceos/modules/axdriver/src/bus/pci.rs` 的枚举和 BAR 配置逻辑。 -2. `ax_config::devices::PCI_ECAM_BASE`、`PCI_RANGES`、`PCI_BUS_END` 等平台配置。 -3. `ax-driver-virtio` 的 PCI 探测路径是否仍与类型再导出保持一致。 - -### 4.3 常见坑 -- 不要把 `PciRangeAllocator` 当成通用内存分配器;它只适合启动期 BAR 窗口。 -- 不要在这里引入按设备类别分支的逻辑;那会污染总线层边界。 -- 升级 `virtio-drivers` 时,要注意其 PCI 类型 API 变更可能直接影响本 crate 的对外接口。 - -## 测试 -### 测试覆盖 -该 crate 没有独立测试目录,当前有效验证主要来自: - -- QEMU/真实平台上的 PCI 枚举。 -- BAR 地址自动分配。 -- VirtIO PCI 和 ixgbe PCI 设备是否能被后续驱动正确接管。 - -### 单元测试 -- `PciRangeAllocator::alloc()` 的对齐、越界和非法参数处理。 -- 不同 BAR 大小组合下的顺序分配结果。 - -### 集成测试 -- `PCI_ECAM_BASE` 有效性与总线枚举。 -- 未初始化 BAR 的自动分配与命令寄存器启用。 -- `virtio-net`、`virtio-blk`、`ixgbe` 等不同 PCI 设备的后续探测。 - -### 5.4 风险点 -- BAR 分配错误会直接导致后续驱动映射错误,通常很难在更上层快速定位。 -- 若把设备类别识别逻辑下沉到这里,会破坏整个驱动栈的层次边界。 - -## 跨项目定位 -### ArceOS -ArceOS 是当前仓库里唯一明确的直接主线消费者。`ax-driver` 的 PCI 探测路径完全建立在本 crate 暴露的类型和分配器之上。 - -### StarryOS -StarryOS 若复用 ArceOS 底层驱动栈,会间接依赖本 crate;但当前仓库中没有看到它作为 StarryOS 独立 PCI 子系统直接存在。 - -### Axvisor -当前仓库里没有看到 Axvisor 直接依赖 `ax-driver-pci`。它不是 Axvisor 的宿主 PCI 管理核心,也不是虚拟 PCI 设备框架。 diff --git a/docs/docs/components/crates/ax-driver-virtio.md b/docs/docs/components/crates/ax-driver-virtio.md deleted file mode 100644 index 43615483a4..0000000000 --- a/docs/docs/components/crates/ax-driver-virtio.md +++ /dev/null @@ -1,179 +0,0 @@ -# `ax-driver-virtio` - -> 路径:`components/axdriver_crates/axdriver_virtio` -> 类型:库 crate -> 分层:组件层 / VirtIO 传输与设备适配层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`src/blk.rs`、`src/net.rs`、`src/gpu.rs`、`src/input.rs`、`src/socket.rs`、`os/arceos/modules/axdriver/src/virtio.rs`、`platform/axplat-dyn/src/drivers/blk/mod.rs` - -`ax-driver-virtio` 负责把 `virtio-drivers` crate 中的具体设备包装成 `axdriver_*` 系列接口。它既不是全局驱动聚合层,也不是总线枚举层,而是位于两者之间的 “VirtIO 设备适配层”:上接 `axdriver_block` / `axdriver_net` / `axdriver_display` / `axdriver_input` / `axdriver_vsock` 的类别 trait,下接 `virtio-drivers` 的 MMIO/PCI transport 与设备对象。 - -## 架构设计 -### 设计定位 -本 crate 的职责可以概括为两部分: - -- **设备包装**:把 VirtIO block/net/gpu/input/socket 设备包装成 `*DriverOps` 实现。 -- **传输探测辅助**:提供 `probe_mmio_device()` 和 `probe_pci_device()`,识别 VirtIO 设备类型并构造 transport 对象。 - -它在分层中的位置很明确: - -- 不是 `ax-driver`:不负责枚举所有设备,也不维护 `AllDevices`。 -- 不是 `ax-driver-pci`:不负责扫描总线或分配 BAR。 -- 也不是 `virtio-drivers` 本身:它提供的是 ArceOS 风格的包装接口,而非原始 VirtIO API。 - -### 模块结构 -| 模块 | feature | 作用 | -| --- | --- | --- | -| `blk` | `block` | `VirtIoBlkDev`,实现 `BlockDriverOps` | -| `net` | `net` | `VirtIoNetDev`,实现 `NetDriverOps` | -| `gpu` | `gpu` | `VirtIoGpuDev`,实现 `DisplayDriverOps` | -| `input` | `input` | `VirtIoInputDev`,实现 `InputDriverOps` | -| `socket` | `socket` | `VirtIoSocketDev`,实现 `VsockDriverOps` | -| `lib.rs` | 始终存在 | 探测辅助、错误映射、transport 与 HAL 类型导出 | - -### 1.3 关键对象 -| 符号 | 作用 | -| --- | --- | -| `VirtIoHal` | `virtio_drivers::Hal` 的别名,要求平台提供 DMA 与地址转换 | -| `MmioTransport` / `PciTransport` | VirtIO 设备的两条 transport 路径 | -| `probe_mmio_device()` | 识别一段 MMIO 区间是否为支持的 VirtIO 设备 | -| `probe_pci_device()` | 识别 PCI 设备是否为支持的 VirtIO 设备,并计算 IRQ | -| `as_dev_type()` | 把 VirtIO 设备类型映射到 `ax_driver_base::DeviceType` | - -### 1.4 设备包装方式 -每个具体设备模块都做两件事: - -1. 保存底层 `virtio-drivers` 设备对象。 -2. 把底层接口翻译成 `axdriver_*` 对应的 trait。 - -例如: - -- `VirtIoBlkDev` 把 `read_blocks()` / `write_blocks()` 翻译成 `BlockDriverOps`。 -- `VirtIoNetDev` 用 `NetBufPool` 预分配 RX/TX 缓冲,并实现 `receive()` / `transmit()` / 回收逻辑。 -- `VirtIoGpuDev` 用 `setup_framebuffer()` 建立帧缓冲并对接 `flush()`。 -- `VirtIoInputDev` 通过 `query_config_select()` 暴露事件位图,通过 `pop_pending_event()` 取事件。 -- `VirtIoSocketDev` 把底层 vsock 事件翻译成 `VsockDriverEvent`。 - -### 1.5 与 `ax-driver` 的配合方式 -在 `os/arceos/modules/axdriver/src/virtio.rs` 中,ArceOS 进一步定义了: - -- `VirtIoDevMeta`:为每种 VirtIO 设备绑定 `DeviceType`、具体 `Device` 类型和 `try_new()`。 -- `VirtIoDriver`:实现 `DriverProbe`,把 MMIO/PCI 识别结果转成 `AxDeviceEnum`。 -- `VirtIoHalImpl`:对接 `ax-alloc`、`ax-hal`,为 `virtio-drivers` 提供 DMA 和地址转换。 - -因此本 crate 只做到“把一个已经识别出来的 VirtIO 设备变成类别驱动”;真正把它纳入系统初始化流程的是 `ax-driver`。 - -### 1.6 与动态平台路径的关系 -`platform/axplat-dyn` 的块设备动态探测会直接使用本 crate: - -- 先探测 `virtio,mmio` 设备; -- 再构造 `VirtIoBlkDev`; -- 最终把它包装成实现 `BlockDriverOps` 的动态块设备。 - -这说明 `ax-driver-virtio` 不是只服务 `os/arceos/modules/axdriver` 一家,它也可以被其它平台 glue 层直接拿来做设备包装。 - -### 1.7 当前实现中的现实细节 -- `probe_pci_device()` 会按架构计算一个 `PCI_IRQ_BASE + (bdf.device & 3)` 的 IRQ 号,这是一套仓库内约定,而不是 VirtIO 规范本身。 -- `gpu.rs` 和 `input.rs` 内部有若干 `unwrap()`,说明初始化失败在某些分支上会比 `DevError` 更早暴露为 panic。 -- `socket.rs` 当前使用 `VsockConnectionManager` 并为缓冲区固定分配 32 KiB 容量。 - -### 1.8 边界澄清 -最关键的边界是:**`ax-driver-virtio` 负责“把 VirtIO 设备包装成 ArceOS 驱动接口”,但它不负责全局设备探测编排,也不负责 PCI/MMIO 总线扫描。** - -## 核心功能 -### 功能概览 -- 识别支持的 VirtIO MMIO/PCI 设备类型。 -- 为 block/net/gpu/input/socket 提供统一包装。 -- 向外导出 `VirtIoHal`、`Transport` 等关键类型,便于平台提供 HAL glue。 -- 把底层 `virtio-drivers::Error` 映射到统一的 `DevError`。 - -### 2.2 feature 矩阵 -| Feature | 作用 | -| --- | --- | -| `alloc` | 打开 `virtio-drivers/alloc` 支持 | -| `block` | 编译 `VirtIoBlkDev`,依赖 `axdriver_block` | -| `net` | 编译 `VirtIoNetDev`,依赖 `axdriver_net` | -| `gpu` | 编译 `VirtIoGpuDev`,依赖 `axdriver_display` | -| `input` | 编译 `VirtIoInputDev`,依赖 `axdriver_input` | -| `socket` | 编译 `VirtIoSocketDev`,依赖 `axdriver_vsock` | - -### 2.3 当前支持的设备映射 -`as_dev_type()` 当前只把以下 VirtIO 设备映射进 ArceOS 驱动类别: - -- `Block` -> `DeviceType::Block` -- `Network` -> `DeviceType::Net` -- `GPU` -> `DeviceType::Display` -- `Input` -> `DeviceType::Input` -- `Socket` -> `DeviceType::Vsock` - -这也意味着本 crate 不是“所有 VirtIO 设备的统一外壳”,而是只覆盖当前仓库已接入的那几个类别。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `virtio-drivers` | 提供底层 transport 和设备实现 | -| `ax-driver-base` | 提供统一设备类型和错误模型 | -| `axdriver_block` / `display` / `input` / `net` / `vsock` | 提供各类别 trait | -| `log` | 初始化和错误日志 | - -### 主要消费者 -- `os/arceos/modules/axdriver` -- `platform/axplat-dyn` - -### 3.3 分层关系总结 -- 向下连接 `virtio-drivers`。 -- 向上输出 `axdriver_*` 兼容设备对象。 -- 由 `ax-driver` 决定这些对象何时、如何进入 `AllDevices`。 - -## 开发指南 -### 4.1 新增一种 VirtIO 设备支持时要改哪些地方 -1. 在本 crate 中新增对应模块,实现目标 `*DriverOps`。 -2. 在 `lib.rs` 中加 feature、导出和 `as_dev_type()` 映射。 -3. 在 `os/arceos/modules/axdriver/src/virtio.rs` 中补 `VirtIoDevMeta`。 -4. 在 `os/arceos/modules/axdriver/src/drivers.rs` 中注册对应类别驱动。 -5. 若需要顶层 feature,还要同步 `ax-driver/Cargo.toml` 和 `ax-feat/Cargo.toml`。 - -### 4.2 HAL 接入注意事项 -- `VirtIoHal` 要正确实现 DMA 分配、回收、MMIO 地址转换、share/unshare。 -- ArceOS 当前 `VirtIoHalImpl` 主要基于 `ax-alloc::global_allocator()` 和 `ax-hal::mem::{phys_to_virt, virt_to_phys}`。 -- 不同平台若总线地址不等于物理地址,必须在 HAL 层处理,不要把这个问题推给设备包装层。 - -### 4.3 常见坑 -- 仅仅识别到设备类型并不代表驱动初始化一定成功;具体 `try_new()` 仍可能失败。 -- `probe_pci_device()` 计算 IRQ 的方式带有平台/架构假设,迁移时需要重新检查。 -- `gpu.rs` / `input.rs` 的 `unwrap()` 表明某些错误分支还未完全软化为 `DevError`。 - -## 测试 -### 测试覆盖 -该 crate 没有独立测试目录,当前主要依赖: - -- QEMU/平台上的 VirtIO MMIO 或 PCI 启动。 -- `ax-driver` 对 `virtio-*` 设备的探测与初始化。 -- `ax-display`、`ax-input`、`ax-net`、`ax-fs`、`ax-net-ng` 对包装后设备的实际消费。 - -### 单元测试 -- `as_dev_type()` 和 `as_dev_err()` 的映射。 -- `probe_mmio_device()` / `probe_pci_device()` 对不同设备类型的识别。 -- `VirtIoNetDev` 的缓冲回收流程。 - -### 集成测试 -- `virtio-blk` 文件系统挂载。 -- `virtio-net` 网络收发。 -- `virtio-gpu` framebuffer 刷新。 -- `virtio-input` 事件读取。 -- `virtio-vsock` 连接、收发与事件轮询。 - -### 5.4 风险点 -- HAL 地址转换或 DMA 错误会影响所有 VirtIO 设备。 -- 设备初始化中的 `unwrap()` 会让某些异常以 panic 形式暴露,而不是优雅错误返回。 - -## 跨项目定位 -### ArceOS -ArceOS 是当前最主要的主线消费者:`ax-driver` 通过它把 VirtIO 设备接入块、网、显、输入和 vsock 各类别路径。 - -### StarryOS -StarryOS 若通过共享 ArceOS 驱动栈获得显示、输入或存储能力,会间接使用本 crate;但它并不把本 crate 当作独立的 VirtIO 管理框架。 - -### Axvisor -当前仓库里没有看到 Axvisor 直接把 `ax-driver-virtio` 作为其虚拟设备框架使用。这里处理的是宿主侧/内核侧 VirtIO 设备包装,而不是 VMM 侧 VirtIO 仿真。 diff --git a/docs/docs/components/crates/ax-driver-vsock.md b/docs/docs/components/crates/ax-driver-vsock.md deleted file mode 100644 index bc4ec80a02..0000000000 --- a/docs/docs/components/crates/ax-driver-vsock.md +++ /dev/null @@ -1,145 +0,0 @@ -# `axdriver_vsock` - -> 路径:`components/axdriver_crates/axdriver_vsock` -> 类型:库 crate -> 分层:组件层 / vsock 设备类别接口层 -> 版本:`0.1.4-preview.3` -> 文档依据:`Cargo.toml`、`README.md`、`src/lib.rs`、`components/axdriver_crates/axdriver_virtio/src/socket.rs`、`os/arceos/modules/axruntime/src/lib.rs` - -`axdriver_vsock` 用来定义 vsock 驱动的统一接口。它既不是通用 socket API,也不是网络协议栈,而是把“面向 host/guest 通道的设备驱动”抽象成一组统一操作,让 `virtio-vsock` 之类的具体实现能被 `ax-driver` 聚合层和 `ax-net-ng` 上层消费。 - -## 架构设计 -### 设计定位 -这个 crate 的职责集中在三个方面: - -- 定义 `VsockAddr` 和 `VsockConnId` 这两个连接标识类型。 -- 定义 `VsockDriverEvent`,把底层设备事件统一成驱动层事件模型。 -- 定义 `VsockDriverOps`,为监听、连接、收发、断开和事件轮询提供统一接口。 - -它的层次位置是: - -- 向下承接具体 vsock 设备驱动,例如 `VirtIoSocketDev`。 -- 向上被 `ax-driver::prelude` 和 `ax-net-ng` 使用。 -- 不承担 BSD socket、poll 语义、地址解析等更高层网络职责。 - -### 1.2 关键对象 -| 符号 | 作用 | -| --- | --- | -| `VsockAddr` | `(cid, port)` 形式的对端地址 | -| `VsockConnId` | 以 `peer_addr + local_port` 标识一条连接 | -| `VsockDriverEvent` | 驱动上报的连接/接收/断开/credit 事件 | -| `VsockDriverOps` | 统一定义监听、连接、收发和轮询接口 | - -### 1.3 事件与连接模型 -`VsockDriverOps` 暴露的操作分为几组: - -- 基本信息:`guest_cid()`。 -- 连接管理:`listen()`、`connect()`、`disconnect()`、`abort()`。 -- 数据通路:`send()`、`recv()`、`recv_avail()`。 -- 事件获取:`poll_event()`。 - -`VsockConnId::listening(local_port)` 还提供了一个特殊构造,用于表达“仅监听某个本地端口”的连接标识。 - -### 1.4 当前主要实现路径 -当前仓库里,`ax_driver_virtio::VirtIoSocketDev` 是主要实现: - -- 它内部使用 `virtio_drivers::device::socket::VsockConnectionManager`。 -- `connect()` / `send()` / `recv()` 等操作都被翻译到底层 VirtIO socket 管理器。 -- `poll_event()` 会把底层 `VsockEvent` 转成 `VsockDriverEvent`。 -- `recv()` 后还会显式 `update_credit()`,说明 credit 流控是当前实现的重要一环。 - -再往上一层: - -- `ax-driver` 把 vsock 设备放入 `AllDevices.vsock`。 -- `ax-runtime` 仅在 `net-ng` + `vsock` feature 组合下调用 `ax-net_ng::init_vsock(all_devices.vsock)`。 - -这说明该 crate 不是独立的网络主线,而是 `ax-net-ng` 可选能力的一部分。 - -### 1.5 源码中的现实细节 -`src/lib.rs` 里还残留了明显的文案复制错误,例如把顶层注释写成 “device drivers (i.e. disk)” 、把 trait 注释写成 “block storage device”。这些注释并不代表真实定位,真实接口完全围绕 vsock 连接和事件设计。 - -### 1.6 边界澄清 -最关键的边界是:**`axdriver_vsock` 定义的是 vsock 设备驱动契约,不是用户可见的 socket API,也不是通用网络栈。** - -## 核心功能 -### 功能概览 -- 统一表达 vsock 地址、连接和驱动事件。 -- 为设备驱动提供一套面向连接管理和事件轮询的 trait。 -- 让 `virtio-vsock` 这样的实现可以被 `ax-driver` 聚合和 `ax-net-ng` 消费。 - -### 2.2 当前接口特征 -- 连接标识以 `peer_addr + local_port` 为主,而不是文件描述符或句柄。 -- `poll_event()` 返回 `Option`,支持“暂时无事件”的轮询模式。 -- `recv_avail()` 把“当前可读字节数”单独抽成接口,说明上层可能需要先探测再读。 - -### 2.3 当前实现范围 -本 crate 目前只定义契约,不内建任何具体设备实现。当前仓库里的实际实现来自 `ax_driver_virtio::VirtIoSocketDev`,也就是说,它本身是纯类别层。 - -## 依赖关系 -### 直接依赖 -| 依赖 | 作用 | -| --- | --- | -| `ax-driver-base` | 提供统一设备元信息和错误类型 | -| `log` | 为实现 crate 预留日志依赖环境 | - -### 主要消费者 -- `components/axdriver_crates/axdriver_virtio` -- `os/arceos/modules/axdriver` -- `os/arceos/modules/axnet-ng` - -### 3.3 分层关系总结 -- 向下不耦合任何具体总线。 -- 向上作为 `ax-net-ng` 的一个设备能力来源。 -- 真正的设备探测和 transport 建立仍由 `ax-driver` 与 `ax-driver-virtio` 负责。 - -## 开发指南 -### 4.1 何时修改这里 -适合修改 `axdriver_vsock` 的情况包括: - -- 需要为所有 vsock 设备增加共同元信息或事件类型。 -- 需要调整连接标识或流控相关的公共接口。 -- 需要让不同实现共享统一事件模型。 - -如果只是新增具体 VirtIO vsock 功能,应优先改实现 crate。 - -### 4.2 实现新驱动时的建议 -1. 明确 `listen()`、`connect()`、`disconnect()`、`abort()` 的状态机语义。 -2. 保持 `poll_event()` 返回事件和 `recv()` / `recv_avail()` 的状态一致。 -3. 若底层协议存在 credit/窗口控制,最好在驱动实现里显式维护,而不要隐藏成无状态接口。 - -### 4.3 常见坑 -- 不要把它和 TCP/UDP socket API 混为一谈;这里处理的是设备级 vsock 通道。 -- 不要把连接 ID 当成持久化资源句柄;它只是驱动层连接标识。 -- 不要在这里引入系统调用或文件描述符语义。 - -## 测试 -### 测试覆盖 -该 crate 没有独立测试目录。当前有效验证主要依赖: - -- `virtio-vsock` 设备初始化。 -- `ax_net_ng::init_vsock()` 是否能接管驱动。 -- host/guest 之间的实际 vsock 通信。 - -### 单元测试 -- `VsockConnId::listening()` 构造行为。 -- 事件枚举与连接标识的基本映射。 -- mock 驱动上 `poll_event()`、`recv_avail()` 的契约测试。 - -### 集成测试 -- 建立连接、发送、接收、断开和强制关闭。 -- `CreditUpdate` 与读写窗口变化。 -- 无事件轮询与异常断开分支。 - -### 5.4 风险点 -- 事件模型和连接状态机一旦不一致,上层通常很难区分是驱动 bug 还是协议对端问题。 -- 当前主线只看到 VirtIO 实现,若将来接入其它 vsock 设备,必须重新检查事件语义是否兼容。 - -## 跨项目定位 -### ArceOS -ArceOS 通过 `ax-driver` 和 `ax-net-ng` 使用它,是当前仓库中唯一明确的主线落点。 - -### StarryOS -当前仓库中没有看到 StarryOS 直接消费 `axdriver_vsock` 的证据,因此不应把它描述成 StarryOS 的常规网络接口层。 - -### Axvisor -当前仓库中也没有看到 Axvisor 把 `axdriver_vsock` 当作虚拟化通信主接口使用。它不是 hypervisor 侧的 VM 通信控制层。 diff --git a/docs/docs/components/crates/ax-driver.md b/docs/docs/components/crates/ax-driver.md index 6e5cebf036..ad31157a6a 100644 --- a/docs/docs/components/crates/ax-driver.md +++ b/docs/docs/components/crates/ax-driver.md @@ -1,206 +1,58 @@ # `ax-driver` -> 路径:`os/arceos/modules/axdriver` +> 路径:`drivers/ax-driver` > 类型:库 crate -> 分层:ArceOS 层 / ArceOS 内核模块 -> 版本:`0.3.0-preview.3` -> 文档依据:`Cargo.toml`、`build.rs`、`src/lib.rs`、`src/macros.rs`、`src/drivers.rs`、`src/bus/pci.rs`、`src/bus/mmio.rs`、`src/virtio.rs`、`src/structs/*`、`src/dyn_drivers/mod.rs` - -`ax-driver` 是 ArceOS 的设备驱动聚合与探测入口。它并不试图把所有设备协议都做成统一对象模型,而是先按设备类别完成“探测 -> 分类 -> 聚合”,再把结果交给文件系统、网络栈、显示和输入等上层子系统消费。 - -## 架构设计 -### 设计定位 -`ax-driver` 的核心设计目标是把“底层设备驱动”整理成“上层子系统可消费的设备容器”: - -- 向下,它负责根据总线类型、feature 组合和平台能力探测可用设备。 -- 向上,它通过 `AllDevices` 把设备按 `block`、`net`、`display`、`input`、`vsock` 分类聚合。 -- 在实现策略上,它同时支持 **静态设备模型** 和 **动态设备模型**: - - 静态模型:每个类别在编译期选中一个具体设备类型,避免动态分发,性能更好。 - - 动态模型:每个类别使用 trait object,可支持多实例,但需要额外的动态分发和 `axplat-dyn` 路径。 - -因此,`ax-driver` 的中心价值不是单个驱动协议实现,而是“驱动装配层”和“总线探测层”的边界设计。 - -### 模块结构 -- `src/lib.rs`:crate 入口。定义 `AllDevices`、`init_drivers()`、`AllDevices::probe()` 和 `add_device()`。 -- `src/macros.rs`:生成静态模型下的设备类型别名和 `for_each_drivers!` 宏展开逻辑。 -- `src/drivers.rs`:定义 `DriverProbe` trait,并按 feature 注册 VirtIO、ramdisk、sdmmc、bcm2835、ixgbe、fxmac 等驱动探测入口。 -- `src/bus/pci.rs`:静态模型下的 PCI 总线枚举、BAR 配置和按驱动逐个 probe。 -- `src/bus/mmio.rs`:静态模型下的 MMIO 设备扫描,主要服务 VirtIO MMIO 路径。 -- `src/virtio.rs`:VirtIO 通用探测与 `VirtIoHalImpl`,连接 `ax-alloc`、`ax-hal` 和 `ax-driver-virtio`。 -- `src/ixgbe.rs`:ixgbe 的平台 HAL glue,连接 `ax-dma` 和网卡驱动。 -- `src/structs/mod.rs`、`src/structs/static.rs`、`src/structs/dyn.rs`:定义 `AxDeviceEnum`、`AxDeviceContainer` 以及静态/动态两种 `Ax*Device` 类型模型。 -- `src/dummy.rs`:在某一设备类别启用但未选中具体驱动时,提供 `dummy` 占位类型。 -- `src/dyn_drivers/mod.rs`:`dyn` 路径的设备探测入口,当前主要从 `axplat-dyn` 接入块设备。 -- `build.rs`:根据 feature 生成 `cfg(bus = "...")` 和 `cfg(net_dev = "...")` 等条件编译开关。 - -### 1.3 关键数据结构与对象 -- `AllDevices`:按设备类别聚合所有已探测设备,是 `ax-driver` 的核心输出对象。 -- `AxDeviceContainer`:内部使用 `SmallVec<[D; 1]>` 保存同类设备,兼顾“通常只有一个设备”和“支持少量多实例”的场景。 -- `AxDeviceEnum`:设备探测阶段的统一枚举,负责在加入 `AllDevices` 时按类别分发。 -- `DriverProbe`:所有驱动探测逻辑的统一 trait,定义 `probe_global()`、`probe_mmio()` 和 `probe_pci()`。 -- `VirtIoDriver` / `VirtIoHalImpl`:VirtIO 探测与 DMA/MMIO glue 的关键对象。 - -### 1.4 设备探测与初始化主线 -`ax-driver` 的主入口是 `init_drivers()`,其核心调用链如下: +> 分层:共享驱动聚合层 / ArceOS glue -```mermaid -flowchart TD - A["ax_runtime::init_drivers"] --> B["ax-driver::init_drivers()"] - B --> C["AllDevices::probe()"] - C --> D{"feature = dyn ?"} - D -- yes --> E["dyn_drivers::probe_all_devices()"] - D -- no --> F["for_each_drivers! -> probe_global()"] - F --> G["probe_bus_devices()"] - G --> H{"bus = pci / mmio"} - H -- pci --> I["枚举 PCI 设备并 probe_pci()"] - H -- mmio --> J["遍历 MMIO ranges 并 probe_mmio()"] - E --> K["add_device() 分类聚合"] - I --> K - J --> K - K --> L["返回 AllDevices 给上层子系统"] -``` - -静态模型下的主线分两步: - -1. 先对每个已编译进来的驱动调用 `probe_global()`,处理平台固定地址或全局一次性设备。 -2. 再根据 `build.rs` 生成的 `cfg(bus = "pci" | "mmio")` 选择 PCI 或 MMIO 总线扫描。 - -动态模型下的主线则明显不同: +`ax-driver` 是当前仓库的共享驱动聚合入口。重构后,旧驱动接口包组已移除,驱动实现、总线探测和 RDIF/rdrive adapter 集中在 `drivers/ax-driver` 以及 `drivers/*` 下的可复用 crate 中。 -- 不再编译 `bus` 子模块。 -- 改由 `dyn_drivers::probe_all_devices()` 调用 `axplat-dyn` 提供的平台级探测。 -- 当前实现主要把块设备收集进来,说明 `dyn` 路径仍然偏向特定类别而不是完整覆盖所有设备类型。 +## 架构定位 -### 1.5 静态模型与动态模型差异 -静态模型: +`ax-driver` 负责把平台发现结果转成上层可消费的设备能力: -- `AxNetDevice`、`AxBlockDevice` 等在编译期就绑定为具体类型。 -- `for_each_drivers!` 宏会针对选中的驱动类型展开。 -- 性能最好,但每类通常只支持一个被选中的具体设备模型。 +- 向下连接 FDT、PCI、VirtIO、SoC/板级驱动和 MMIO/DMA 能力。 +- 向上通过 `rdrive` 注册表和 `rd-block`、`rd-net`、`rdif-display`、`rdif-input`、`rdif-vsock` 暴露设备。 +- 在 ArceOS、StarryOS、Axvisor 之间复用驱动 core,同时把 OS glue 限定在 probe、iomap、IRQ 注册和运行时适配层。 -动态模型: +## 主要模块 -- `Ax*Device` 变为 trait object 包装。 -- 探测路径改走 `axplat-dyn`。 -- 更灵活,但引入动态分发,且当前实现覆盖面并不完全对称。 +- `block/*`、`net/*`、`display/*`、`input/*`、`vsock/*`:按设备类别组织驱动绑定和 RDIF adapter。 +- `virtio/*`:封装 VirtIO transport 和块设备、网卡、显示、输入、vsock adapter。 +- `pci/*`:处理 PCI/FDT 探测、BAR/window 资源和平台相关 PCIe glue。 +- `soc/*`、`usb/*`、`serial/*`、`time.rs`:承载 SoC、USB、串口和 RTC 等平台设备 glue。 -### 1.6 Feature 矩阵的决定性作用 -- `bus-pci` / `bus-mmio`:决定总线探测分支,默认是 `bus-pci`。 -- `block` / `net` / `display` / `input` / `vsock`:决定是否编译对应类别的设备容器与上游依赖。 -- `virtio-*`:打开对应 VirtIO 设备类型。 -- `ixgbe`、`fxmac`、`ramdisk`、`sdmmc`、`bcm2835-sdhci` 等:决定每类设备可能选择的具体驱动。 -- `dyn`:决定是否进入动态设备模型。 - -需要特别注意两点: +## 依赖关系 -1. 在静态模型下,`build.rs` 会对每类设备按 feature 列表顺序选择第一个命中的具体驱动,因此“同时打开多个同类驱动 feature”并不意味着都会生效。 -2. 当前 `Cargo.toml` 声明了 `ahci` feature,但 `build.rs` 和驱动注册宏体系并未完整接入该 feature,这属于现有实现与配置之间的未完全对齐点。 +```mermaid +graph LR + platform["FDT / PCI / SoC"] --> ax_driver["ax-driver"] + dma["dma-api / mmio-api / axklib"] --> ax_driver + concrete["virtio-drivers / pcie / board driver crates"] --> ax_driver + ax_driver --> rdrive["rdrive"] + rdrive --> rdif["rd-block / rd-net / rdif-*"] + rdif --> upper["ax-fs / ax-net / ax-display / ax-input / starry-kernel"] +``` -## 核心功能 -### 功能概览 -- 探测系统中可用的块设备、网卡、显示设备、输入设备和 vsock 设备。 -- 按类别聚合所有设备为 `AllDevices`。 -- 为上层子系统提供统一的设备容器类型和 trait 边界。 -- 封装 VirtIO、PCI、MMIO 等总线路径的探测 glue。 +### 直接依赖 -### 使用场景 -- `init_drivers()`:由 `ax-runtime` 调用,是系统驱动 bring-up 的总入口。 -- `AllDevices`:上层子系统从中取出自己关心的设备。 -- `AxDeviceContainer::take_one()`:很多上层模块以“只取第一台设备”的方式消费设备。 -- `DriverProbe`:新增驱动或扩展总线匹配逻辑时的统一入口。 +- `rdrive`、`rd-block`、`rd-net`、`rdif-*`:设备注册、查询和领域能力接口。 +- `dma-api`、`mmio-api`、`axklib`:DMA、MMIO 和内核映射能力边界。 +- `virtio-drivers`、`pcie`、SoC/板级驱动 crate:具体硬件协议或平台 glue。 +- `ax-alloc`、`ax-errno`、`ax-kspin` 等:分配、错误映射和同步路径。 -### 使用方式 -对上层模块来说,典型用法不是自己枚举总线,而是消费 `ax-driver` 已经聚合好的设备: +## 开发约束 -```rust -let mut all = ax-driver::init_drivers(); -let blk = all.block.take_one(); -let net = all.net.take_one(); -``` +- 新驱动应保持 Driver Core / Capability Boundary / OS Glue 分层,portable core 不直接调用 `iomap`、IRQ 注册或任务调度 API。 +- 上层模块应通过领域接口或 `rdrive` 查询设备,不重新引入 `AllDevices` 式全局容器。 +- 新增设备能力时,同步检查 feature、probe 路径、RDIF adapter 和对应 ArceOS/StarryOS/Axvisor 消费方。 -这条模式在 `ax-runtime` 初始化文件系统、网络栈、显示与输入子系统时非常关键。 +## 验证 -## 依赖关系 -```mermaid -graph LR - axconfig["ax-config"] --> ax-driver["ax-driver"] - ax-hal["ax-hal"] --> ax-driver - ax-alloc["ax-alloc"] --> ax-driver - ax_dma["ax-dma"] --> ax-driver - ax_driver_base["ax-driver-base / axdriver_*"] --> ax-driver - axplat_dyn["axplat-dyn (dyn 模式)"] --> ax-driver - - ax-driver --> ax-runtime["ax-runtime"] - ax-driver --> ax-fs["ax-fs / ax-fs-ng"] - ax-driver --> ax-net["ax-net / ax-net-ng"] - ax-driver --> ax-display["ax-display"] - ax-driver --> ax-input["ax-input"] - ax-driver --> starry_kernel["starry-kernel"] -``` +修改 `ax-driver` 或平台 glue 后,至少运行: -### 直接依赖 -- `ax-driver-base` 与各 `axdriver_*` crate:提供设备 trait 与具体驱动实现。 -- `axconfig`:提供 PCI ECAM、MMIO ranges、SDMMC 基址等平台配置。 -- `ax-hal`:提供地址转换、总线相关底层能力。 -- `ax-alloc`、`ax-dma`:服务 VirtIO 和网卡 DMA 路径。 -- `axplat-dyn`:动态模型下的平台设备探测入口。 - -### 主要消费者 -- `ax-runtime`:初始化阶段调用 `init_drivers()`,并把设备分发到文件系统、网络、显示、输入等子系统。 -- `ax-fs` / `ax-fs-ng`:消费块设备。 -- `ax-net` / `ax-net-ng`:消费网络设备和可选 vsock。 -- `ax-display`、`ax-input`:消费显示和输入设备。 -- `starry-kernel`:复用驱动接口层和若干设备抽象。 - -### 3.3 间接消费者 -- ArceOS 示例、测试与基于 `ax-std` 的应用。 -- Axvisor 中与块设备兼容层相关的 glue 路径。 -- 依赖 `axplat-dyn` 的动态平台运行环境。 - -## 开发指南 -### 接入方式 -```toml -[dependencies] -ax-driver = { workspace = true, features = ["block", "net", "virtio-blk", "virtio-net"] } +```bash +cargo xtask clippy --package ax-driver +cargo xtask clippy --package axplat-dyn ``` -### 4.2 新驱动接入原则 -1. 若新增的是静态模型驱动,需要同时修改 `Cargo.toml`、`build.rs`、`drivers.rs` 和 `macros.rs`,确保 feature、`cfg(*_dev = "...")` 和探测宏三者一致。 -2. 若新增的是动态模型路径,需要确认 `dyn_drivers` 或 `axplat-dyn` 是否真的支持该类别,而不只是编译通过。 -3. 若新增的是 PCI 驱动,应同时考虑 `probe_pci()`、BAR 配置和设备匹配规则。 -4. 若新增的是 MMIO 驱动,应明确它依赖的平台地址范围来自哪里,以及是否复用 `VIRTIO_MMIO_RANGES`。 - -### 4.3 开发建议与常见坑 -- 静态模型下不要期望同一类别的多个驱动 feature 同时生效;`build.rs` 只会选中一个。 -- `AllDevices` 的消费方很多都只 `take_one()`,因此“多实例驱动”是否真的被上层利用,需要从消费者角度一起评估。 -- `ahci` 当前属于 feature 声明与探测宏体系未完全对齐的状态,修改该路径时应先补齐接线,再谈集成测试。 -- 动态模型与静态模型的行为并不完全对称,提交变更时最好分别验证。 - -## 测试 -### 测试覆盖 -`ax-driver` 自身的验证重点主要是系统级集成测试,而不是 crate 内单元测试。 - -### 单元测试 -- `build.rs` 生成的 `cfg(bus = "...")` 与 `cfg(*_dev = "...")` 是否符合预期。 -- `AxDeviceContainer` 的分类聚合与 `take_one()` 行为。 -- 设备匹配逻辑,例如 VirtIO PCI 设备类型识别。 - -### 集成测试 -- `bus-pci` 与 `bus-mmio` 两条路径都至少需要一条真实 QEMU 启动用例。 -- 静态模型与动态模型都应验证设备是否真的被探测并注入上层子系统。 -- 至少覆盖块设备、网卡和一条显示/输入或 vsock 路径,避免只验证单一类别。 - -### 覆盖率 -- 对 `ax-driver`,比“函数覆盖率”更关键的是“设备矩阵覆盖率”。 -- 至少要覆盖总线选择、驱动选择、设备分类和上层消费四个层面。 -- 涉及 `build.rs`、探测宏、BAR/MMIO 配置或 DMA glue 的改动,都应视为高风险改动。 - -## 跨项目定位 -### ArceOS -`ax-driver` 是 ArceOS 运行时中的驱动装配中心。它本身不实现完整文件系统或网络语义,但负责把设备探测结果注入这些子系统,因此是 ArceOS bring-up 中非常关键的一层。 - -### StarryOS -StarryOS 并不完全沿用 ArceOS 的“`AllDevices` 初始化后直接交给上层”模型,但仍然复用 `ax-driver` 提供的驱动 trait 和设备抽象。因此它在 StarryOS 中更偏“共享驱动接口层”和“基础设备 glue 层”。 - -### Axvisor -Axvisor 并不把 `ax-driver` 当作虚拟化控制核心,但会在宿主侧块设备等兼容路径中借用 ArceOS 生态的设备 trait 与对象模型。因此 `ax-driver` 在 Axvisor 中扮演的是“宿主设备兼容层的基础设施”,而不是“VMM 策略层”。 +涉及 ArceOS 运行路径时,继续跑对应 `cargo xtask arceos test qemu ...` 用例。 diff --git a/docs/docs/components/crates/ax-feat.md b/docs/docs/components/crates/ax-feat.md index 3c7d44211c..2d0f5cfa98 100644 --- a/docs/docs/components/crates/ax-feat.md +++ b/docs/docs/components/crates/ax-feat.md @@ -72,10 +72,10 @@ graph LR - `ax-config-macros` - `ax-cpu` - `ax-dma` -- `ax-driver-base` -- `axdriver_block` -- `axdriver_display` -- `axdriver_input` +- `rdrive` +- `rd-block` +- `rdif-display` +- `rdif-input` - 另外还有 `48` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-fs-ng.md b/docs/docs/components/crates/ax-fs-ng.md index dc5b2bf50a..abe707a3eb 100644 --- a/docs/docs/components/crates/ax-fs-ng.md +++ b/docs/docs/components/crates/ax-fs-ng.md @@ -69,8 +69,8 @@ graph LR - `ax-config-macros` - `ax-cpu` - `ax-dma` -- `ax-driver-base` -- `axdriver_block` +- `rdrive` +- `rd-block` - 另外还有 `37` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-hal.md b/docs/docs/components/crates/ax-hal.md index d702960553..708885cf8d 100644 --- a/docs/docs/components/crates/ax-hal.md +++ b/docs/docs/components/crates/ax-hal.md @@ -137,7 +137,7 @@ graph LR - `ax-alloc`:在页表/虚拟化路径下承担帧或内存块来源。 ### 间接依赖 -- 各类驱动基础组件,如 `ax-driver-base`、`ax-driver-virtio` 等,会通过 `axplat` 与上层模块间接参与平台 bring-up。 +- 各类驱动能力接口,如 `rdrive`、`rd-block` 和 `rdif-*`,会通过平台与上层模块间接参与 bring-up。 - `ax-percpu`、`kernel_guard`、`memory_addr` 等基础组件通过 `ax-cpu`、`axplat` 或 `paging` 路径提供底层支持。 ### 3.3 关键直接消费者 diff --git a/docs/docs/components/crates/ax-input.md b/docs/docs/components/crates/ax-input.md index 886dca04a9..4bd4c7c6d3 100644 --- a/docs/docs/components/crates/ax-input.md +++ b/docs/docs/components/crates/ax-input.md @@ -56,7 +56,7 @@ graph LR - `ax-config-macros` - `ax-cpu` - `ax-dma` -- `ax-driver-base` +- `rdif-input` - 另外还有 `41` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-net-ng.md b/docs/docs/components/crates/ax-net-ng.md index c4cd524c76..a87348f1f8 100644 --- a/docs/docs/components/crates/ax-net-ng.md +++ b/docs/docs/components/crates/ax-net-ng.md @@ -75,8 +75,8 @@ graph LR - `ax-config-macros` - `ax-cpu` - `ax-dma` -- `ax-driver-base` -- `axdriver_block` +- `rdrive` +- `rd-net` - 另外还有 `38` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-posix-api.md b/docs/docs/components/crates/ax-posix-api.md index 1af6e971d8..a031bd55b2 100644 --- a/docs/docs/components/crates/ax-posix-api.md +++ b/docs/docs/components/crates/ax-posix-api.md @@ -71,7 +71,7 @@ graph LR - `ax-display` - `ax-dma` - `ax-driver` -- `ax-driver-base` +- `rdrive` - 另外还有 `52` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/ax-runtime.md b/docs/docs/components/crates/ax-runtime.md index 807231a338..1140a54ea8 100644 --- a/docs/docs/components/crates/ax-runtime.md +++ b/docs/docs/components/crates/ax-runtime.md @@ -71,10 +71,10 @@ graph LR - `ax-config-macros` - `ax-cpu` - `ax-dma` -- `ax-driver-base` -- `axdriver_block` -- `axdriver_display` -- `axdriver_input` +- `rdrive` +- `rd-block` +- `rd-net` +- `rdif-display` - 另外还有 `43` 个同类项未在此展开 ### 3.3 被依赖情况 diff --git a/docs/docs/components/crates/axplat-dyn.md b/docs/docs/components/crates/axplat-dyn.md index 0b6e0eac59..ac00cdd89c 100644 --- a/docs/docs/components/crates/axplat-dyn.md +++ b/docs/docs/components/crates/axplat-dyn.md @@ -46,7 +46,7 @@ | --- | --- | --- | --- | | 启动 glue | `somehal` | `boot.rs` | 把主核/次核入口收敛到 `ax_plat::call_main()` / `call_secondary_main()` | | 平台契约 glue | `axplat` | `init.rs`、`console.rs`、`mem.rs`、`generic_timer.rs`、`irq.rs`、`power.rs` | 用 `#[impl_plat_interface]` 把 `somehal` 能力接到 `axplat` trait | -| 设备 glue | `rdrive`、`rd-block`、`ax-driver-virtio` | `drivers/*` | 把 FDT/PCIe/VirtIO 探测结果转成 `ax-driver` 可消费的动态块设备 | +| 设备 glue | `rdrive`、`rd-block`、`ax-driver` | `drivers/*` | 把 FDT/PCIe/VirtIO 探测结果转成 `rdrive` 可查询的动态块设备 | | 链接 glue | `build.rs`、`link.ld` | crate 根目录 | 生成 `axplat.x`,插入所需段和符号,适配 ArceOS 链接约定 | 这套装配方式说明它承担的是“桥接与接线”职责,而不是“重新定义平台抽象”。 @@ -133,7 +133,7 @@ flowchart TD 3. `drivers/pci.rs` 通过 `module_driver!` 注册通用 PCIe ECAM 控制器探测器。 4. `drivers/blk/virtio.rs` 通过 `module_driver!` 注册 `virtio,mmio` block 设备探测器。 5. 探测出的 `rd_block::Block` 被包成实现 `ax_driver_block::BlockDriverOps` 的 `Block`,最终进入 `BLOCK_DEVICES`。 -6. `os/arceos/modules/axdriver/src/dyn_drivers/mod.rs` 再通过 `take_block_devices()` 把它们取走,转成 `ax-driver` 的动态设备集合。 +6. `drivers/ax-driver/src/dyn_drivers/mod.rs` 再通过 `take_block_devices()` 把它们取走,转成 `ax-driver` 的动态设备集合。 ```mermaid flowchart TD @@ -196,16 +196,15 @@ flowchart TD | `somehal` | 提供真实平台事实与入口宏,是本 crate 的核心下层 | | `ax-cpu` | 在 `hv` 等场景提供 CPU 模式支持 | | `axklib` | 提供 `iomap()` 等内核内存映射辅助 | -| `ax-alloc` | 为 `VirtIO` DMA 路径提供页分配 | +| `ax-driver` | 提供共享驱动 probe 与 adapter 入口 | | `rdrive`、`rd-block` | 提供运行时设备探测与块设备抽象 | -| `axdriver_block`、`ax-driver-virtio`、`ax-driver-base` | 将探测结果转接为 ArceOS 驱动接口 | | `dma-api` | 为设备 DMA 提供抽象接口 | | `heapless`、`spin` | 用于固定容量缓存与锁/一次初始化结构 | ### 主要消费者 - `os/arceos/modules/axhal`:通过 `plat-dyn` feature 选择该平台路径。 -- `os/arceos/modules/axdriver`:通过 `dyn` feature 调用其动态设备探测入口。 +- `drivers/ax-driver`:通过 `dyn` feature 调用其动态设备探测入口。 - 进一步依赖上述模块的 ArceOS 系统镜像,以及复用同一模块栈的其它内核工程。 ### 3.3 依赖关系示意 @@ -214,7 +213,7 @@ flowchart TD graph TD A[somehal / ax-cpu / axklib] --> B[axplat-dyn] C[axplat] --> B - D[rdrive / rd-block / dma-api / ax-driver-virtio] --> B + D[rdrive / rd-block / dma-api / ax-driver] --> B B --> E[ax-hal: plat-dyn] B --> F[ax-driver: dyn] diff --git a/docs/docs/components/crates/fxmac-rs.md b/docs/docs/components/crates/fxmac-rs.md index 69948d5497..5f5f43d1fb 100644 --- a/docs/docs/components/crates/fxmac-rs.md +++ b/docs/docs/components/crates/fxmac-rs.md @@ -38,7 +38,7 @@ graph LR current["fxmac_rs"] current --> ax_crate_interface["ax-crate-interface"] - ax_driver_net["ax-driver-net"] --> current + ax_driver["ax-driver"] --> current ``` ### 直接依赖 @@ -48,7 +48,7 @@ graph LR - 未检测到额外的间接本地依赖,或依赖深度主要停留在第一层。 ### 3.3 被依赖情况 -- `ax-driver-net` +- `ax-driver` ### 被依赖情况 - `arceos-affinity` diff --git a/docs/docs/components/layers.md b/docs/docs/components/layers.md index 5a94a3d3fe..cddaf7526f 100644 --- a/docs/docs/components/layers.md +++ b/docs/docs/components/layers.md @@ -51,16 +51,16 @@ flowchart TB L4["层级 4
堆叠层(依赖更底层 crate)
`ax-plat-aarch64-peripherals`、`ax-plat-loongarch64-qemu-virt`、`ax-plat-x86-pc`、`axdevice_base`、`axplat-dyn`、`axplat-x86-qemu-q35`、`axvisor_api`、`starry-signal`"] classDef ls4 fill:#e1bee7,stroke:#6a1b9a,stroke-width:2px,color:#000 class L4 ls4 - L3["层级 3
堆叠层(依赖更底层 crate)
`ax-alloc`、`ax-cpu`、`ax-driver-virtio`、`ax-log`、`ax-plat`、`axaddrspace`、`scope-local`、`starry-process`、`test-simple`、`test-weak`、`test-weak-partial`、`tg-xtask`"] + L3["层级 3
堆叠层(依赖更底层 crate)
`ax-alloc`、`ax-cpu`、`ax-log`、`ax-plat`、`axaddrspace`、`scope-local`、`starry-process`、`test-simple`、`test-weak`、`test-weak-partial`、`tg-xtask`"] classDef ls3 fill:#ffe0b2,stroke:#ef6c00,stroke-width:2px,color:#000 class L3 ls3 - L2["层级 2
堆叠层(依赖更底层 crate)
`ax-config`、`ax-driver-net`、`ax-fs-devfs`、`ax-fs-ramfs`、`ax-kspin`、`ax-page-table-multiarch`、`ax-percpu`、`axbuild`、`impl-simple-traits`、`impl-weak-partial`、`impl-weak-traits`"] + L2["层级 2
堆叠层(依赖更底层 crate)
`ax-config`、`ax-fs-devfs`、`ax-fs-ramfs`、`ax-kspin`、`ax-page-table-multiarch`、`ax-percpu`、`axbuild`、`impl-simple-traits`、`impl-weak-partial`、`impl-weak-traits`"] classDef ls2 fill:#fff9c4,stroke:#f9a825,stroke-width:2px,color:#000 class L2 ls2 - L1["层级 1
堆叠层(依赖更底层 crate)
`ax-allocator`、`ax-config-macros`、`ax-ctor-bare`、`ax-driver-block`、`ax-driver-display`、`ax-driver-input`、`ax-driver-vsock`、`ax-fs-vfs`、`ax-io`、`ax-kernel-guard`、`ax-memory-set`、`ax-page-table-entry`、`ax-plat-macros`、`ax-sched`、`axfs-ng-vfs`、`axhvc`、`axklib`、`axvmconfig`、`define-simple-traits`、`define-weak-traits` …共23个"] + L1["层级 1
堆叠层(依赖更底层 crate)
`ax-allocator`、`ax-config-macros`、`ax-ctor-bare`、`ax-fs-vfs`、`ax-io`、`ax-kernel-guard`、`ax-memory-set`、`ax-page-table-entry`、`ax-plat-macros`、`ax-sched`、`axfs-ng-vfs`、`axhvc`、`axklib`、`axvmconfig`、`define-simple-traits`、`define-weak-traits` …共19个"] classDef ls1 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000 class L1 ls1 - L0["层级 0
基础层(无仓库内直接依赖)
`aarch64_sysreg`、`ax-arm-pl011`、`ax-arm-pl031`、`ax-cap-access`、`ax-config-gen`、`ax-cpumask`、`ax-crate-interface`、`ax-crate-interface-lite`、`ax-ctor-bare-macros`、`ax-driver-base`、`ax-driver-pci`、`ax-errno`、`ax-handler-table`、`ax-int-ratio`、`ax-lazyinit`、`ax-linked-list-r4l`、`ax-memory-addr`、`ax-percpu-macros`、`ax-riscv-plic`、`ax-timer-list` …共32个"] + L0["层级 0
基础层(无仓库内直接依赖)
`aarch64_sysreg`、`ax-arm-pl011`、`ax-arm-pl031`、`ax-cap-access`、`ax-config-gen`、`ax-cpumask`、`ax-crate-interface`、`ax-crate-interface-lite`、`ax-ctor-bare-macros`、`ax-errno`、`ax-handler-table`、`ax-int-ratio`、`ax-lazyinit`、`ax-linked-list-r4l`、`ax-memory-addr`、`ax-percpu-macros`、`ax-riscv-plic`、`ax-timer-list` …共30个"] classDef ls0 fill:#eceff1,stroke:#455a64,stroke-width:2px,color:#000 class L0 ls0 L16 --> L15 @@ -98,8 +98,6 @@ flowchart TB | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-crate-interface` | `0.5.0` | `components/crate_interface` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-crate-interface-lite` | `0.3.0` | `components/crate_interface/crate_interface_lite` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-ctor-bare-macros` | `0.4.1` | `components/ctor_bare/ctor_bare_macros` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-driver-base` | `0.3.4` | `components/axdriver_crates/axdriver_base` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-driver-pci` | `0.3.4` | `components/axdriver_crates/axdriver_pci` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-errno` | `0.4.2` | `components/axerrno` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-handler-table` | `0.3.2` | `components/handler_table` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-int-ratio` | `0.3.2` | `components/int_ratio` | @@ -120,10 +118,6 @@ flowchart TB | 1 | 堆叠层 | 组件层 | `ax-allocator` | `0.4.0` | `components/axallocator` | | 1 | 堆叠层 | 组件层 | `ax-config-macros` | `0.4.1` | `components/axconfig-gen/axconfig-macros` | | 1 | 堆叠层 | 组件层 | `ax-ctor-bare` | `0.4.1` | `components/ctor_bare/ctor_bare` | -| 1 | 堆叠层 | 组件层 | `ax-driver-block` | `0.3.4` | `components/axdriver_crates/axdriver_block` | -| 1 | 堆叠层 | 组件层 | `ax-driver-display` | `0.3.4` | `components/axdriver_crates/axdriver_display` | -| 1 | 堆叠层 | 组件层 | `ax-driver-input` | `0.3.4` | `components/axdriver_crates/axdriver_input` | -| 1 | 堆叠层 | 组件层 | `ax-driver-vsock` | `0.3.4` | `components/axdriver_crates/axdriver_vsock` | | 1 | 堆叠层 | 组件层 | `ax-fs-vfs` | `0.3.2` | `components/axfs_crates/axfs_vfs` | | 1 | 堆叠层 | 组件层 | `ax-io` | `0.5.0` | `components/axio` | | 1 | 堆叠层 | 组件层 | `ax-kernel-guard` | `0.3.3` | `components/kernel_guard` | @@ -142,7 +136,6 @@ flowchart TB | 1 | 堆叠层 | 组件层 | `starry-vm` | `0.5.0` | `components/starry-vm` | | 2 | 堆叠层 | ArceOS 层 | `ax-config` | `0.5.0` | `os/arceos/modules/axconfig` | | 2 | 堆叠层 | 工具层 | `axbuild` | `0.4.0` | `scripts/axbuild` | -| 2 | 堆叠层 | 组件层 | `ax-driver-net` | `0.3.4` | `components/axdriver_crates/axdriver_net` | | 2 | 堆叠层 | 组件层 | `ax-fs-devfs` | `0.3.2` | `components/axfs_crates/axfs_devfs` | | 2 | 堆叠层 | 组件层 | `ax-fs-ramfs` | `0.3.2` | `components/axfs_crates/axfs_ramfs` | | 2 | 堆叠层 | 组件层 | `ax-kspin` | `0.3.1` | `components/kspin` | @@ -155,7 +148,6 @@ flowchart TB | 3 | 堆叠层 | ArceOS 层 | `ax-log` | `0.5.0` | `os/arceos/modules/axlog` | | 3 | 堆叠层 | 工具层 | `tg-xtask` | `0.5.0` | `xtask` | | 3 | 堆叠层 | 组件层 | `ax-cpu` | `0.5.0` | `components/axcpu` | -| 3 | 堆叠层 | 组件层 | `ax-driver-virtio` | `0.3.4` | `components/axdriver_crates/axdriver_virtio` | | 3 | 堆叠层 | 组件层 | `ax-plat` | `0.5.1` | `components/axplat_crates/axplat` | | 3 | 堆叠层 | 组件层 | `axaddrspace` | `0.5.0` | `components/axaddrspace` | | 3 | 堆叠层 | 组件层 | `scope-local` | `0.3.2` | `components/scope-local` | @@ -194,7 +186,7 @@ flowchart TB | 7 | 堆叠层 | 组件层 | `axvm` | `0.5.0` | `components/axvm` | | 8 | 堆叠层 | ArceOS 层 | `ax-dma` | `0.5.0` | `os/arceos/modules/axdma` | | 8 | 堆叠层 | ArceOS 层 | `ax-sync` | `0.5.0` | `os/arceos/modules/axsync` | -| 9 | 堆叠层 | ArceOS 层 | `ax-driver` | `0.5.0` | `os/arceos/modules/axdriver` | +| 9 | 堆叠层 | ArceOS 层 | `ax-driver` | `0.5.0` | `drivers/ax-driver` | | 10 | 堆叠层 | ArceOS 层 | `ax-display` | `0.5.0` | `os/arceos/modules/axdisplay` | | 10 | 堆叠层 | ArceOS 层 | `ax-fs` | `0.5.0` | `os/arceos/modules/axfs` | | 10 | 堆叠层 | ArceOS 层 | `ax-fs-ng` | `0.5.0` | `os/arceos/modules/axfs-ng` | @@ -237,10 +229,10 @@ flowchart TB | 层级 | 数 | 成员 | |------|-----|------| -| 0 | 32 | `aarch64_sysreg` `ax-arm-pl011` `ax-arm-pl031` `ax-cap-access` `ax-config-gen` `ax-cpumask` `ax-crate-interface` `ax-crate-interface-lite` `ax-ctor-bare-macros` `ax-driver-base` `ax-driver-pci` `ax-errno` `ax-handler-table` `ax-int-ratio` `ax-lazyinit` `ax-linked-list-r4l` `ax-memory-addr` `ax-percpu-macros` `ax-riscv-plic` `ax-timer-list` `axbacktrace` `axpoll` `axvisor_api_proc` `bitmap-allocator` `bwbench-client` `deptool` `mingo` `range-alloc-arceos` `riscv-h` `rsext4` `smoltcp` | -| 1 | 23 | `ax-allocator` `ax-config-macros` `ax-ctor-bare` `ax-driver-block` `ax-driver-display` `ax-driver-input` `ax-driver-vsock` `ax-fs-vfs` `ax-io` `ax-kernel-guard` `ax-memory-set` `ax-page-table-entry` `ax-plat-macros` `ax-sched` `axfs-ng-vfs` `axhvc` `axklib` `axvmconfig` `define-simple-traits` `define-weak-traits` `fxmac_rs` `smoltcp-fuzz` `starry-vm` | -| 2 | 11 | `ax-config` `ax-driver-net` `ax-fs-devfs` `ax-fs-ramfs` `ax-kspin` `ax-page-table-multiarch` `ax-percpu` `axbuild` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` | -| 3 | 12 | `ax-alloc` `ax-cpu` `ax-driver-virtio` `ax-log` `ax-plat` `axaddrspace` `scope-local` `starry-process` `test-simple` `test-weak` `test-weak-partial` `tg-xtask` | +| 0 | 30 | `aarch64_sysreg` `ax-arm-pl011` `ax-arm-pl031` `ax-cap-access` `ax-config-gen` `ax-cpumask` `ax-crate-interface` `ax-crate-interface-lite` `ax-ctor-bare-macros` `ax-errno` `ax-handler-table` `ax-int-ratio` `ax-lazyinit` `ax-linked-list-r4l` `ax-memory-addr` `ax-percpu-macros` `ax-riscv-plic` `ax-timer-list` `axbacktrace` `axpoll` `axvisor_api_proc` `bitmap-allocator` `bwbench-client` `deptool` `mingo` `range-alloc-arceos` `riscv-h` `rsext4` `smoltcp` | +| 1 | 19 | `ax-allocator` `ax-config-macros` `ax-ctor-bare` `ax-fs-vfs` `ax-io` `ax-kernel-guard` `ax-memory-set` `ax-page-table-entry` `ax-plat-macros` `ax-sched` `axfs-ng-vfs` `axhvc` `axklib` `axvmconfig` `define-simple-traits` `define-weak-traits` `fxmac_rs` `smoltcp-fuzz` `starry-vm` | +| 2 | 10 | `ax-config` `ax-fs-devfs` `ax-fs-ramfs` `ax-kspin` `ax-page-table-multiarch` `ax-percpu` `axbuild` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` | +| 3 | 11 | `ax-alloc` `ax-cpu` `ax-log` `ax-plat` `axaddrspace` `scope-local` `starry-process` `test-simple` `test-weak` `test-weak-partial` `tg-xtask` | | 4 | 8 | `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-x86-pc` `axdevice_base` `axplat-dyn` `axplat-x86-qemu-q35` `axvisor_api` `starry-signal` | | 5 | 10 | `arm_vgic` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-riscv64-qemu-virt` `ax-plat-riscv64-qemu-virt` `axvcpu` `riscv_vplic` `x86_vlapic` | | 6 | 8 | `arm_vcpu` `ax-hal` `axdevice` `hello-kernel` `irq-kernel` `riscv_vcpu` `smp-kernel` `x86_vcpu` | @@ -298,15 +290,7 @@ flowchart TB | `ax-ctor-bare-macros` | 0 | Macros for registering constructor functions for … | — | `ax-ctor-bare` | | `ax-display` | 10 | ArceOS graphics module | `ax-driver` `ax-lazyinit` `ax-sync` | `ax-api` `ax-feat` `ax-runtime` `starry-kernel` | | `ax-dma` | 8 | ArceOS global DMA allocator | `ax-alloc` `ax-allocator` `ax-config` `ax-hal` `ax-kspin` `ax-memory-addr` `ax-mm` | `ax-api` `ax-driver` | -| `ax-driver` | 9 | ArceOS device drivers | `ax-alloc` `ax-config` `ax-crate-interface` `ax-dma` `ax-driver-base` `ax-driver-block` `ax-driver-display` `ax-driver-input` `ax-driver-net` `ax-driver-pci` `ax-driver-virtio` `ax-driver-vsock` `ax-errno` `ax-hal` `axplat-dyn` | `ax-api` `ax-display` `ax-feat` `ax-fs` `ax-fs-ng` `ax-input` `ax-net` `ax-net-ng` `ax-runtime` `starry-kernel` | -| `ax-driver-base` | 0 | Common interfaces for all kinds of device drivers | — | `ax-driver` `ax-driver-block` `ax-driver-display` `ax-driver-input` `ax-driver-net` `ax-driver-virtio` `ax-driver-vsock` `axplat-dyn` | -| `ax-driver-block` | 1 | Common traits and types for block storage drivers | `ax-driver-base` | `ax-driver` `ax-driver-virtio` `axplat-dyn` | -| `ax-driver-display` | 1 | Common traits and types for graphics device drive… | `ax-driver-base` | `ax-driver` `ax-driver-virtio` | -| `ax-driver-input` | 1 | Common traits and types for input device drivers | `ax-driver-base` | `ax-driver` `ax-driver-virtio` | -| `ax-driver-net` | 2 | Common traits and types for network device (NIC) … | `ax-driver-base` `fxmac_rs` | `ax-driver` `ax-driver-virtio` | -| `ax-driver-pci` | 0 | Structures and functions for PCI bus operations | — | `ax-driver` | -| `ax-driver-virtio` | 3 | Wrappers of some devices in the `virtio-drivers` … | `ax-driver-base` `ax-driver-block` `ax-driver-display` `ax-driver-input` `ax-driver-net` `ax-driver-vsock` | `ax-driver` `axplat-dyn` | -| `ax-driver-vsock` | 1 | Common traits and types for vsock drivers | `ax-driver-base` | `ax-driver` `ax-driver-virtio` | +| `ax-driver` | 9 | ArceOS device drivers | `anyhow` `ax-alloc` `ax-crate-interface` `ax-dma` `ax-errno` `ax-kernel-guard` `ax-kspin` `axklib` `dma-api` `mmio-api` `rdrive` `rd-block` `rd-net` `rdif-*` `virtio-drivers` | `ax-api` `ax-display` `ax-feat` `ax-fs` `ax-fs-ng` `ax-input` `ax-net` `ax-net-ng` `ax-runtime` `starry-kernel` | | `ax-errno` | 0 | Generic error code representation. | — | `arm_vcpu` `arm_vgic` `ax-alloc` `ax-allocator` `ax-api` `ax-driver` `ax-fs` `ax-fs-ng` `ax-fs-vfs` `ax-io` `ax-libc` `ax-memory-set` `ax-mm` `ax-net` `ax-net-ng` `ax-page-table-multiarch` `ax-posix-api` `ax-std` `ax-task` `axaddrspace` `axdevice` `axdevice_base` `axfs-ng-vfs` `axhvc` `axklib` `axplat-dyn` `axvcpu` `axvisor` `axvm` `axvmconfig` `riscv_vcpu` `riscv_vplic` `starry-kernel` `starry-vm` `x86_vcpu` `x86_vlapic` | | `ax-feat` | 13 | Top-level feature selection for ArceOS | `ax-alloc` `ax-config` `ax-display` `ax-driver` `ax-fs` `ax-fs-ng` `ax-hal` `ax-input` `ax-ipi` `ax-kspin` `ax-log` `ax-net` `ax-runtime` `ax-sync` `ax-task` `axbacktrace` | `ax-api` `ax-libc` `ax-posix-api` `ax-std` `starry-kernel` `starryos` `starryos-test` | | `ax-fs` | 10 | ArceOS filesystem module | `ax-cap-access` `ax-driver` `ax-errno` `ax-fs-devfs` `ax-fs-ramfs` `ax-fs-vfs` `ax-hal` `ax-io` `ax-lazyinit` `rsext4` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` | @@ -367,7 +351,7 @@ flowchart TB | `axfs-ng-vfs` | 1 | Virtual filesystem layer for ArceOS | `ax-errno` `axpoll` | `ax-fs-ng` `ax-net-ng` `starry-kernel` | | `axhvc` | 1 | AxVisor HyperCall definitions for guest-hyperviso… | `ax-errno` | `axvisor` | | `axklib` | 1 | Small kernel-helper abstractions used across the … | `ax-errno` `ax-memory-addr` | `ax-runtime` `axplat-dyn` `axvisor` | -| `axplat-dyn` | 4 | A dynamic platform module for ArceOS, providing r… | `ax-alloc` `ax-config-macros` `ax-cpu` `ax-driver-base` `ax-driver-block` `ax-driver-virtio` `ax-errno` `ax-memory-addr` `ax-percpu` `ax-plat` `axklib` | `ax-driver` `ax-hal` | +| `axplat-dyn` | 4 | A dynamic platform module for ArceOS, providing r… | `ax-cpu` `ax-driver` `ax-errno` `ax-memory-addr` `ax-percpu` `ax-plat` `axklib` `rdrive` `somehal` | `ax-driver` `ax-hal` | | `axplat-x86-qemu-q35` | 4 | Hardware platform implementation for x86_64 QEMU … | `ax-config-macros` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-percpu` `ax-plat` | `axvisor` | | `axpoll` | 0 | A library for polling I/O events and waking up ta… | — | `ax-fs-ng` `ax-net-ng` `ax-task` `axfs-ng-vfs` `starry-kernel` | | `axvcpu` | 5 | Virtual CPU abstraction for ArceOS hypervisor | `ax-errno` `ax-memory-addr` `ax-percpu` `axaddrspace` `axvisor_api` | `arm_vcpu` `axvisor` `axvm` `riscv_vcpu` `x86_vcpu` | @@ -381,7 +365,7 @@ flowchart TB | `define-simple-traits` | 1 | Define simple traits without default implementati… | `ax-crate-interface` | `impl-simple-traits` `test-simple` | | `define-weak-traits` | 1 | Define traits with default implementations using … | `ax-crate-interface` | `impl-weak-partial` `impl-weak-traits` `test-weak` `test-weak-partial` | | `deptool` | 0 | ArceOS 配套工具与辅助程序 | — | — | -| `fxmac_rs` | 1 | FXMAC Ethernet driver in Rust for PhytiumPi (Phyt… | `ax-crate-interface` | `ax-driver-net` | +| `fxmac_rs` | 1 | FXMAC Ethernet driver in Rust for PhytiumPi (Phyt… | `ax-crate-interface` | `ax-driver` | | `hello-kernel` | 6 | 可复用基础组件 | `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | | `impl-simple-traits` | 2 | Implement the simple traits defined in define-sim… | `ax-crate-interface` `define-simple-traits` | `test-simple` | | `impl-weak-partial` | 2 | Partial implementation of WeakDefaultIf trait. Th… | `ax-crate-interface` `define-weak-traits` | `test-weak-partial` | diff --git a/docs/docs/components/overview.md b/docs/docs/components/overview.md index 1dbba0d346..89080de6ad 100644 --- a/docs/docs/components/overview.md +++ b/docs/docs/components/overview.md @@ -124,15 +124,7 @@ flowchart TB | `ax-ctor-bare-macros` | 组件层 | `components/ctor_bare/ctor_bare_macros` | 0 | 1 | [查看](crates/ax-ctor-bare-macros) | | `ax-display` | ArceOS 层 | `os/arceos/modules/axdisplay` | 3 | 4 | [查看](crates/ax-display) | | `ax-dma` | ArceOS 层 | `os/arceos/modules/axdma` | 7 | 2 | [查看](crates/ax-dma) | -| `ax-driver` | ArceOS 层 | `os/arceos/modules/axdriver` | 15 | 10 | [查看](crates/ax-driver) | -| `ax-driver-base` | 组件层 | `components/axdriver_crates/axdriver_base` | 0 | 8 | [查看](crates/ax-driver-base) | -| `ax-driver-block` | 组件层 | `components/axdriver_crates/axdriver_block` | 1 | 3 | [查看](crates/ax-driver-block) | -| `ax-driver-display` | 组件层 | `components/axdriver_crates/axdriver_display` | 1 | 2 | [查看](crates/ax-driver-display) | -| `ax-driver-input` | 组件层 | `components/axdriver_crates/axdriver_input` | 1 | 2 | [查看](crates/ax-driver-input) | -| `ax-driver-net` | 组件层 | `components/axdriver_crates/axdriver_net` | 2 | 2 | [查看](crates/ax-driver-net) | -| `ax-driver-pci` | 组件层 | `components/axdriver_crates/axdriver_pci` | 0 | 1 | [查看](crates/ax-driver-pci) | -| `ax-driver-virtio` | 组件层 | `components/axdriver_crates/axdriver_virtio` | 6 | 2 | [查看](crates/ax-driver-virtio) | -| `ax-driver-vsock` | 组件层 | `components/axdriver_crates/axdriver_vsock` | 1 | 2 | [查看](crates/ax-driver-vsock) | +| `ax-driver` | ArceOS 层 | `drivers/ax-driver` | 15 | 10 | [查看](crates/ax-driver) | | `ax-errno` | 组件层 | `components/axerrno` | 0 | 36 | [查看](crates/ax-errno) | | `ax-feat` | ArceOS 层 | `os/arceos/api/axfeat` | 16 | 7 | [查看](crates/ax-feat) | | `ax-fs` | ArceOS 层 | `os/arceos/modules/axfs` | 10 | 4 | [查看](crates/ax-fs) | diff --git a/docs/docs/development/components.md b/docs/docs/development/components.md index 773ded138d..619a6b4225 100644 --- a/docs/docs/development/components.md +++ b/docs/docs/development/components.md @@ -91,7 +91,7 @@ flowchart TD | 你要改什么 | 优先看哪里 | 常见影响面 | | --- | --- | --- | | 通用基础能力:错误、锁、页表、Per-CPU、容器 | `components/axerrno`、`components/kspin`、`components/page_table_multiarch`、`components/percpu` | 三套系统都可能受影响 | -| ArceOS 内核服务:调度、HAL、驱动、网络、文件系统 | `os/arceos/modules/*`,以及相关 `axdriver_crates` / `axmm_crates` / `axplat_crates` | ArceOS,且可能波及 StarryOS / Axvisor | +| ArceOS 内核服务:调度、HAL、驱动、网络、文件系统 | `os/arceos/modules/*`、`drivers/*`,以及相关 `axmm_crates` / `axplat_crates` | ArceOS,且可能波及 StarryOS / Axvisor | | ArceOS 的 feature 或应用接口 | `os/arceos/api/axfeat`、`os/arceos/ulib/axstd`、`os/arceos/ulib/axlibc` | ArceOS 应用与上层系统 | | StarryOS 的 Linux 兼容行为 | `components/starry-*`、`os/StarryOS/kernel/*` | StarryOS | | Hypervisor、vCPU、虚拟设备、VM 管理 | `components/axvm`、`components/axvcpu`、`components/axdevice`、`components/axvisor_api`、`os/axvisor/src/*` | Axvisor | @@ -271,7 +271,7 @@ my_component = { path = "components/my_component" } ### 5.5 遇到嵌套 workspace 时不要照抄 -`components/axplat_crates`、`components/axdriver_crates`、`components/axmm_crates` 这类目录本身是独立 workspace。给这类目录加新 crate 时,需要特别注意处理方式: +`components/axplat_crates`、`components/axmm_crates` 这类目录本身是独立 workspace。给这类目录加新 crate 时,需要特别注意处理方式: - 先在它自己的 workspace 里接好 - 再在根 `Cargo.toml` 里为具体 leaf crate 增加 patch 或 member diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index 7173f41ae0..d630d73234 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -738,17 +738,19 @@ function PlatformSection() { /* ── Driver Section ──────────────────────────────────────── */ function DriverSection() { const driverCategories = [ - { icon: 'server', title: '块设备驱动', desc: 'SD/MMC 存储支持', cssClass: 'blk', items: ['simple-sdmmc'] }, + { icon: 'server', title: '块设备驱动', desc: 'SD/MMC 存储支持', cssClass: 'blk', items: ['sdhci-host', 'dwmmc-host', 'sdmmc-protocol'] }, { icon: 'chip', title: 'NPU 驱动', desc: '神经网络加速', cssClass: 'npu', items: ['rockchip-npu'] }, { icon: 'layers', title: 'PCI 总线驱动', desc: 'PCIe 控制器适配', cssClass: 'pci', items: ['rk3588-pci'] }, { icon: 'grid', title: 'SoC 平台驱动', desc: '片上系统外设', cssClass: 'soc', items: ['rockchip (GPIO, clk, reset)'] }, ]; const driverSubsystems = [ - { name: 'block', label: '块设备' }, { name: 'display', label: '显示' }, - { name: 'input', label: '输入' }, { name: 'net', label: '网络' }, - { name: 'pci', label: 'PCI 总线' }, { name: 'virtio', label: 'VirtIO' }, - { name: 'vsock', label: '虚拟 Socket' }, { name: 'base', label: '驱动基础层' }, + { name: 'rd-block', label: '块设备' }, + { name: 'rd-net', label: '网络' }, + { name: 'rdif-display', label: '显示' }, + { name: 'rdif-input', label: '输入' }, + { name: 'rdif-vsock', label: '虚拟 Socket' }, + { name: 'rdrive', label: '设备注册' }, ]; return ( @@ -780,11 +782,11 @@ function DriverSection() {

驱动子系统抽象

-

axdriver_crates 提供的通用驱动接口层

+

rdrive 与 RDIF 提供的通用驱动能力层

{driverSubsystems.map((sub) => (
- axdriver_{sub.name} + {sub.name} {sub.label}
))} diff --git a/os/arceos/modules/axdriver/CHANGELOG.md b/drivers/ax-driver/CHANGELOG.md similarity index 73% rename from os/arceos/modules/axdriver/CHANGELOG.md rename to drivers/ax-driver/CHANGELOG.md index 2f73802918..84090c1b5c 100644 --- a/os/arceos/modules/axdriver/CHANGELOG.md +++ b/drivers/ax-driver/CHANGELOG.md @@ -7,13 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.5.16](https://github.com/rcore-os/tgoskits/compare/ax-driver-v0.5.15...ax-driver-v0.5.16) - 2026-05-22 - -### Other - -- updated the following local packages: ax-errno, ax-driver-block, axplat-dyn, ax-hal, ax-driver-virtio, ax-alloc, ax-dma - -## [0.5.15](https://github.com/rcore-os/tgoskits/compare/ax-driver-v0.5.14...ax-driver-v0.5.15) - 2026-05-19 +## [0.6.0](https://github.com/rcore-os/tgoskits/compare/ax-driver-v0.5.14...ax-driver-v0.6.0) - 2026-05-19 ### Fixed diff --git a/drivers/ax-driver/Cargo.toml b/drivers/ax-driver/Cargo.toml new file mode 100644 index 0000000000..d3d3b8a066 --- /dev/null +++ b/drivers/ax-driver/Cargo.toml @@ -0,0 +1,136 @@ +[package] +name = "ax-driver" +version = "0.6.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "ArceOS rdrive driver registration and rdif binding collection" + +[lib] +name = "ax_driver" + +[features] +default = [] + +fdt = ["dep:fdt-edit"] +acpi = [] +pci = ["dep:pcie", "dep:heapless", "dep:rdif-pcie", "dep:spin"] +pci-fdt = ["pci", "fdt", "dep:rdif-intc"] +irq = [] + +block = ["dep:ax-errno", "dep:ax-kspin", "dep:rd-block", "dep:spin"] +net = ["dep:rd-net", "dep:spin"] +display = ["dep:ax-errno", "dep:rdif-display"] +input = ["dep:ax-errno", "dep:rdif-input"] +vsock = ["dep:ax-errno", "dep:rdif-vsock"] + +virtio-core = ["dep:ax-alloc", "dep:virtio-drivers", "virtio-drivers/alloc"] +virtio = ["virtio-core"] +virtio-blk = ["block", "virtio"] +virtio-net = ["net", "virtio", "dep:ax-kernel-guard"] +virtio-gpu = ["display", "virtio"] +virtio-input = ["input", "virtio"] +virtio-socket = ["vsock", "virtio"] + +ramdisk = ["block", "dep:ramdisk"] +cvsd = ["block", "dep:sg200x-bsp"] +bcm2835-sdhci = ["block", "dep:bcm2835-sdhci"] +ahci = ["block", "pci", "dep:simple-ahci"] +ixgbe = ["net", "pci", "dep:ax-alloc", "dep:ixgbe-driver"] +fxmac = ["net", "dep:ax-alloc", "dep:ax-crate-interface", "dep:fxmac_rs"] +intel-net = ["net", "pci", "dep:eth-intel"] +realtek-rtl8125 = ["net", "pci", "dep:realtek-rtl8125"] + +usb = ["dep:crab-usb"] +rockchip-dwc-xhci = ["usb", "fdt", "rockchip-soc", "rockchip-pm"] +xhci-mmio = ["usb", "fdt"] +xhci-pci = ["usb", "pci"] +serial = ["fdt", "dep:some-serial"] +rtc = ["fdt", "dep:ax-arm-pl031"] +rknpu = ["fdt", "rockchip-pm", "rockchip-soc", "dep:rockchip-npu"] +rockchip-sdhci = [ + "block", + "fdt", + "rockchip-soc", + "dep:ax-kspin", + "dep:rdif-clk", + "dep:sdhci-host", + "dep:sdmmc-protocol", + "sdmmc-protocol/sdio", +] +phytium-mci = [ + "block", + "fdt", + "dep:ax-kspin", + "dep:phytium-mci-host", + "dep:sdmmc-protocol", + "sdmmc-protocol/sdio", +] +rockchip-dwmmc = [ + "block", + "fdt", + "rockchip-soc", + "dep:arm-scmi-rs", + "dep:ax-kspin", + "dep:dwmmc-host", + "dep:rdif-clk", + "dep:sdmmc-protocol", + "sdmmc-protocol/sdio", +] +rk3588-pcie = [ + "pci-fdt", + "rockchip-soc", + "rockchip-pm", + "dep:rdif-pcie", + "dep:rk3588-pci", +] +rockchip-soc = ["fdt", "dep:rdif-clk", "dep:rockchip-soc"] +rockchip-pm = ["rockchip-soc", "dep:rockchip-pm"] +pci-list-devices = ["pci"] + +[dependencies] +anyhow = { workspace = true } +arm-scmi-rs = { workspace = true, optional = true } +ax-alloc = { workspace = true, optional = true, features = ["default"] } +ax-crate-interface = { workspace = true, optional = true } +ax-errno = { workspace = true, optional = true } +ax-arm-pl031 = { workspace = true, optional = true } +ax-kernel-guard = { workspace = true, optional = true } +ax-kspin = { workspace = true, optional = true } +axklib.workspace = true +bcm2835-sdhci = { workspace = true, optional = true } +crab-usb = { workspace = true, optional = true } +dma-api.workspace = true +dwmmc-host = { workspace = true, optional = true } +eth-intel = { workspace = true, optional = true } +fdt-edit = { workspace = true, optional = true } +fxmac_rs = { workspace = true, optional = true } +heapless = { workspace = true, optional = true } +ixgbe-driver = { workspace = true, optional = true } +log.workspace = true +mmio-api.workspace = true +pcie = { workspace = true, optional = true } +ramdisk = { workspace = true, optional = true } +rd-block = { workspace = true, optional = true } +rd-net = { workspace = true, optional = true } +rdif-clk = { workspace = true, optional = true } +rdif-display = { workspace = true, optional = true } +rdif-input = { workspace = true, optional = true } +rdif-intc = { workspace = true, optional = true } +rdif-pcie = { workspace = true, optional = true } +rdif-vsock = { workspace = true, optional = true } +realtek-rtl8125 = { workspace = true, optional = true } +rdrive.workspace = true +rk3588-pci = { workspace = true, optional = true } +rockchip-npu = { workspace = true, optional = true } +rockchip-pm = { workspace = true, optional = true } +rockchip-soc = { workspace = true, optional = true } +phytium-mci-host = { workspace = true, optional = true } +sdhci-host = { workspace = true, optional = true } +sdmmc-protocol = { workspace = true, default-features = false, optional = true } +sg200x-bsp = { workspace = true, optional = true } +simple-ahci = { workspace = true, optional = true } +some-serial = { workspace = true, optional = true } +spin = { workspace = true, optional = true } +virtio-drivers = { workspace = true, optional = true } diff --git a/drivers/ax-driver/build.rs b/drivers/ax-driver/build.rs new file mode 100644 index 0000000000..da5dc6d3af --- /dev/null +++ b/drivers/ax-driver/build.rs @@ -0,0 +1,76 @@ +const VIRTIO_DEV_FEATURES: &[&str] = &[ + "virtio-blk", + "virtio-gpu", + "virtio-input", + "virtio-net", + "virtio-socket", +]; + +fn make_cfg_values(str_list: &[&str]) -> String { + str_list + .iter() + .map(|s| format!("{s:?}")) + .collect::>() + .join(", ") +} + +fn has_feature(feature: &str) -> bool { + std::env::var(format!( + "CARGO_FEATURE_{}", + feature.to_uppercase().replace('-', "_") + )) + .is_ok() +} + +fn has_any_feature(features: &[&str]) -> bool { + features.iter().any(|feature| has_feature(feature)) +} + +fn enable_cfg(key: &str, value: &str) { + println!("cargo:rustc-cfg={key}=\"{value}\""); +} + +fn enable_cfg_flag(key: &str) { + println!("cargo:rustc-cfg={key}"); +} + +fn main() { + let has_virtio_core = has_feature("virtio-core"); + let has_virtio_dev = has_any_feature(VIRTIO_DEV_FEATURES); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let target_has_cvsd = matches!(target_arch.as_str(), "riscv32" | "riscv64"); + let has_pci = has_feature("pci"); + let has_fdt = has_feature("fdt"); + let has_static = has_any_feature(&[ + "pci", + "virtio-blk", + "virtio-gpu", + "virtio-input", + "virtio-net", + "virtio-socket", + "cvsd", + ]); + + if has_pci { + enable_cfg("probe", "pci"); + } + if has_fdt { + enable_cfg("probe", "fdt"); + } + if has_static { + enable_cfg("probe", "static"); + } + if has_virtio_core || has_virtio_dev { + enable_cfg_flag("virtio_dev"); + } + if has_any_feature(&["ahci", "bcm2835-sdhci"]) || (has_feature("cvsd") && target_has_cvsd) { + enable_cfg_flag("sync_block_dev"); + } + + println!( + "cargo::rustc-check-cfg=cfg(probe, values({}))", + make_cfg_values(&["pci", "fdt", "static"]) + ); + println!("cargo::rustc-check-cfg=cfg(virtio_dev)"); + println!("cargo::rustc-check-cfg=cfg(sync_block_dev)"); +} diff --git a/drivers/ax-driver/src/block/ahci.rs b/drivers/ax-driver/src/block/ahci.rs new file mode 100644 index 0000000000..31404c922c --- /dev/null +++ b/drivers/ax-driver/src/block/ahci.rs @@ -0,0 +1,107 @@ +extern crate alloc; + +use alloc::format; + +use pcie::CommandRegister; +use rdrive::{ + PlatformDevice, + probe::{ + OnProbeError, + pci::{EndpointRc, FnOnProbe}, + }, +}; +use simple_ahci::{AhciDriver as SimpleAhciDriver, Hal as AhciHal}; + +use super::{SyncBlockOps, register_sync_block}; + +pub const DEVICE_NAME: &str = "ahci"; + +module_driver!( + name: "AHCI", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci as FnOnProbe, + }], +); + +struct AxAhciHal; + +impl AhciHal for AxAhciHal { + fn virt_to_phys(va: usize) -> usize { + axklib::mem::virt_to_phys(va.into()).as_usize() + } + + fn current_ms() -> u64 { + 0 + } + + fn flush_dcache() {} +} + +fn probe_pci(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let class = endpoint.revision_and_class(); + if (class.base_class, class.sub_class) != (0x01, 0x06) { + return Err(OnProbeError::NotMatch); + } + + let Some(bar) = endpoint.bar_mmio(5).or_else(|| endpoint.bar_mmio(0)) else { + return Err(OnProbeError::other("AHCI MMIO BAR missing")); + }; + + endpoint.update_command(|mut cmd| { + cmd.insert(CommandRegister::MEMORY_ENABLE | CommandRegister::BUS_MASTER_ENABLE); + cmd + }); + + let mmio = axklib::mmio::ioremap_raw(bar.start.into(), bar.count().max(1)) + .map_err(|err| OnProbeError::other(format!("failed to map AHCI BAR: {err:?}")))?; + let Some(driver) = (unsafe { SimpleAhciDriver::::try_new(mmio.as_ptr() as usize) }) + else { + return Err(OnProbeError::other("failed to initialize AHCI controller")); + }; + register_sync_block(plat_dev, AhciBlock(driver)); + Ok(()) +} + +struct AhciBlock(SimpleAhciDriver); + +impl SyncBlockOps for AhciBlock { + fn name(&self) -> &'static str { + DEVICE_NAME + } + + fn num_blocks(&self) -> u64 { + self.0.capacity() + } + + fn block_size(&self) -> usize { + self.0.block_size() + } + + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), rd_block::BlkError> { + if !buf.len().is_multiple_of(self.block_size()) + || !(buf.as_ptr() as usize).is_multiple_of(4) + { + return Err(rd_block::BlkError::NotSupported); + } + if self.0.read(block_id, buf) { + Ok(()) + } else { + Err(rd_block::BlkError::Other("AHCI read failed".into())) + } + } + + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), rd_block::BlkError> { + if !buf.len().is_multiple_of(self.block_size()) + || !(buf.as_ptr() as usize).is_multiple_of(4) + { + return Err(rd_block::BlkError::NotSupported); + } + if self.0.write(block_id, buf) { + Ok(()) + } else { + Err(rd_block::BlkError::Other("AHCI write failed".into())) + } + } +} diff --git a/drivers/ax-driver/src/block/bcm2835.rs b/drivers/ax-driver/src/block/bcm2835.rs new file mode 100644 index 0000000000..ee656103eb --- /dev/null +++ b/drivers/ax-driver/src/block/bcm2835.rs @@ -0,0 +1,82 @@ +use bcm2835_sdhci::{ + Bcm2835SDhci::{BLOCK_SIZE, EmmcCtl}, + SDHCIError, +}; +use rdrive::{PlatformDevice, probe::OnProbeError}; + +use super::{SyncBlockOps, register_sync_block}; + +pub const DEVICE_NAME: &str = "bcm2835_sdhci"; + +pub fn register(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let driver = Bcm2835Sdhci::try_new() + .map_err(|err| OnProbeError::other(alloc::format!("BCM2835 SDHCI init failed: {err:?}")))?; + register_sync_block(plat_dev, driver); + Ok(()) +} + +struct Bcm2835Sdhci(EmmcCtl); + +impl Bcm2835Sdhci { + fn try_new() -> Result { + let mut ctrl = EmmcCtl::new(); + if ctrl.init() == 0 { + Ok(Self(ctrl)) + } else { + Err(rd_block::BlkError::Other( + "BCM2835 SDHCI init failed".into(), + )) + } + } +} + +impl SyncBlockOps for Bcm2835Sdhci { + fn name(&self) -> &'static str { + DEVICE_NAME + } + + fn num_blocks(&self) -> u64 { + self.0.get_block_num() + } + + fn block_size(&self) -> usize { + self.0.get_block_size() + } + + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), rd_block::BlkError> { + let block_count = buf.len() / BLOCK_SIZE; + if block_count == 0 || !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::NotSupported); + } + let (prefix, aligned, suffix) = unsafe { buf.align_to_mut::() }; + if !prefix.is_empty() || !suffix.is_empty() { + return Err(rd_block::BlkError::NotSupported); + } + self.0 + .read_block(block_id as u32, block_count, aligned) + .map_err(map_sdhci_err) + } + + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), rd_block::BlkError> { + let block_count = buf.len() / BLOCK_SIZE; + if block_count == 0 || !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::NotSupported); + } + let (prefix, aligned, suffix) = unsafe { buf.align_to::() }; + if !prefix.is_empty() || !suffix.is_empty() { + return Err(rd_block::BlkError::NotSupported); + } + self.0 + .write_block(block_id as u32, block_count, aligned) + .map_err(map_sdhci_err) + } +} + +fn map_sdhci_err(err: SDHCIError) -> rd_block::BlkError { + match err { + SDHCIError::Again => rd_block::BlkError::Retry, + SDHCIError::NoMemory => rd_block::BlkError::NoMemory, + SDHCIError::Unsupported => rd_block::BlkError::NotSupported, + _ => rd_block::BlkError::Other("BCM2835 SDHCI I/O error".into()), + } +} diff --git a/platform/axplat-dyn/src/drivers/blk/mod.rs b/drivers/ax-driver/src/block/binding.rs similarity index 76% rename from platform/axplat-dyn/src/drivers/blk/mod.rs rename to drivers/ax-driver/src/block/binding.rs index 229c79117d..b5a13ecefb 100644 --- a/platform/axplat-dyn/src/drivers/blk/mod.rs +++ b/drivers/ax-driver/src/block/binding.rs @@ -15,23 +15,11 @@ use core::{ task::{Context, Poll, Waker}, }; -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use ax_driver_block::BlockDriverOps; +use ax_errno::{AxError, AxResult}; use ax_kspin::SpinNoIrq; -#[cfg(feature = "irq")] -use ax_plat::irq; +use log::{error, warn}; use rd_block::BlkError; use rdrive::Device; - -use super::DmaImpl; - -#[cfg(feature = "phytium-mci")] -mod phytium_mci; -#[cfg(any(feature = "rockchip-sdhci", feature = "rockchip-dwmmc"))] -mod rockchip; -mod virtio; -mod virtio_pci; - pub struct Block { name: String, irq_num: Option, @@ -99,6 +87,14 @@ static IRQ_SOURCES: [SpinNoIrq>>; BLOCK_IRQ_SLOTS] = const BLOCK_IRQ_REPOLL_SPINS: usize = 256; impl Block { + pub fn name(&self) -> &str { + &self.name + } + + pub const fn irq_num(&self) -> Option { + self.irq_num + } + fn use_irq_completion(&self) -> bool { #[cfg(feature = "irq")] { @@ -146,95 +142,79 @@ impl Block { queue.write_blocks_blocking(block_id, data) } -} -impl BaseDriverOps for Block { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - - fn device_name(&self) -> &str { - &self.name - } - - fn irq_num(&self) -> Option { - self.irq_num - } -} - -impl BlockDriverOps for Block { - fn num_blocks(&self) -> u64 { + pub fn num_blocks(&self) -> u64 { self.queue.lock().num_blocks() as _ } - fn block_size(&self) -> usize { + pub fn block_size(&self) -> usize { self.queue.lock().block_size() } - fn flush(&mut self) -> DevResult { + pub fn flush(&mut self) -> AxResult { Ok(()) } - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + pub fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { let block_size = self.block_size(); - if !buf.len().is_multiple_of(block_size) { - return Err(DevError::InvalidParam); + if block_size == 0 || !buf.len().is_multiple_of(block_size) { + return Err(AxError::InvalidInput); } let use_irq = self.use_irq_completion(); let mut queue = self.queue.lock(); - for (offset, chunk) in buf.chunks_mut(block_size).enumerate() { - let mut blocks = - Self::read_blocks_wait(&mut queue, block_id as usize + offset, 1, use_irq); - let block = blocks - .pop() - .ok_or(DevError::Io)? - .map_err(maping_blk_err_to_dev_err)?; - if block.len() != chunk.len() { - return Err(DevError::Io); + let block_count = buf.len() / block_size; + let blocks = Self::read_blocks_wait(&mut queue, block_id as usize, block_count, use_irq); + let mut copied = 0; + for block in blocks { + let block = block.map_err(map_blk_err_to_ax_err)?; + let end = copied + block.len(); + if end > buf.len() { + return Err(AxError::Io); } - chunk.copy_from_slice(&block); + buf[copied..end].copy_from_slice(&block); + copied = end; + } + if copied != buf.len() { + return Err(AxError::Io); } Ok(()) } - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + pub fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { let block_size = self.block_size(); - if !buf.len().is_multiple_of(block_size) { - return Err(DevError::InvalidParam); + if block_size == 0 || !buf.len().is_multiple_of(block_size) { + return Err(AxError::InvalidInput); } let use_irq = self.use_irq_completion(); let mut queue = self.queue.lock(); - for (offset, chunk) in buf.chunks(block_size).enumerate() { - let blocks = - Self::write_blocks_wait(&mut queue, block_id as usize + offset, chunk, use_irq); - for block in blocks { - block.map_err(maping_blk_err_to_dev_err)?; - } + let blocks = Self::write_blocks_wait(&mut queue, block_id as usize, buf, use_irq); + for block in blocks { + block.map_err(map_blk_err_to_ax_err)?; } Ok(()) } } impl TryFrom> for Block { - type Error = DevError; + type Error = AxError; fn try_from(base: Device) -> Result { - let mut dev = base.lock().map_err(|_| DevError::BadState)?; + let mut dev = base.lock().map_err(|_| AxError::BadState)?; let name = dev.name.clone(); let irq_num = dev.irq_num; - let mut block = dev.block.take().ok_or(DevError::BadState)?; + let mut block = dev.block.take().ok_or(AxError::BadState)?; #[cfg(feature = "irq")] let irq_handler = irq_num.map(|_| block.irq_handler()); - let queue = block.create_queue().ok_or(DevError::BadState)?; + let queue = block.create_queue().ok_or(AxError::BadState)?; drop(dev); #[cfg(feature = "irq")] let irq_state = if let (Some(irq_num), Some(handler)) = (irq_num, irq_handler) { let state = Arc::new(BlockIrqState { handler }); if let Some(slot) = reserve_block_irq_slot(&state) { - if irq::register(irq_num, BLOCK_IRQ_HANDLERS[slot]) { + if axklib::irq::register(irq_num, BLOCK_IRQ_HANDLERS[slot]) { Some(state) } else { release_block_irq_slot(slot, &state); @@ -289,30 +269,20 @@ impl PlatformDeviceBlock for rdrive::PlatformDevice { dev }; let name = dev.name().to_string(); - let dev = rd_block::Block::new(dev, &DmaImpl); + let block = rd_block::Block::new(dev, axklib::dma::op()); #[cfg(feature = "irq")] let registered_irq_num = irq_num; #[cfg(not(feature = "irq"))] let registered_irq_num = None; - self.register(PlatformBlockDevice::new(name, dev, registered_irq_num)); + self.register(PlatformBlockDevice::new(name, block, registered_irq_num)); } } -#[cfg(any( - feature = "rockchip-sdhci", - feature = "rockchip-dwmmc", - feature = "phytium-mci" -))] -pub(super) fn decode_fdt_irq(interrupts: &[rdrive::probe::fdt::InterruptRef]) -> Option { +pub fn decode_fdt_irq(interrupts: &[rdrive::probe::fdt::InterruptRef]) -> Option { let interrupt = interrupts.first()?; decode_irq_cells(&interrupt.specifier) } -#[cfg(any( - feature = "rockchip-sdhci", - feature = "rockchip-dwmmc", - feature = "phytium-mci" -))] fn decode_irq_cells(specifier: &[u32]) -> Option { match specifier { [irq] => Some(*irq as usize), @@ -325,6 +295,19 @@ fn decode_irq_cells(specifier: &[u32]) -> Option { } } +pub fn take_block_devices() -> Vec { + rdrive::get_list::() + .into_iter() + .filter_map(|dev| match Block::try_from(dev) { + Ok(block) => Some(block), + Err(err) => { + warn!("failed to take block device: {err:?}"); + None + } + }) + .collect() +} + #[cfg(feature = "irq")] fn handle_block_irq(slot: usize) { let Some(state) = IRQ_SOURCES[slot].lock().as_ref().and_then(Weak::upgrade) else { @@ -409,15 +392,15 @@ fn wait_on_block_irq(future: F) -> F::Output { } } -fn maping_blk_err_to_dev_err(err: BlkError) -> DevError { +fn map_blk_err_to_ax_err(err: BlkError) -> AxError { match err { - BlkError::NotSupported => DevError::Unsupported, - BlkError::Retry => DevError::Again, - BlkError::NoMemory => DevError::NoMemory, - BlkError::InvalidBlockIndex(_) => DevError::InvalidParam, + BlkError::NotSupported => AxError::Unsupported, + BlkError::Retry => AxError::WouldBlock, + BlkError::NoMemory => AxError::NoMemory, + BlkError::InvalidBlockIndex(_) => AxError::InvalidInput, BlkError::Other(error) => { error!("Block device error: {error}"); - DevError::Io + AxError::Io } } } diff --git a/drivers/ax-driver/src/block/cvsd.rs b/drivers/ax-driver/src/block/cvsd.rs new file mode 100644 index 0000000000..9074b4bd18 --- /dev/null +++ b/drivers/ax-driver/src/block/cvsd.rs @@ -0,0 +1,160 @@ +use rdrive::{PlatformDevice, probe::OnProbeError}; +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +use {alloc::format, sg200x_bsp::sdmmc::Sdmmc}; + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +use super::{SyncBlockOps, register_sync_block}; + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +const BLOCK_SIZE: usize = 512; +pub const DEVICE_NAME: &str = "cvsd"; + +#[cfg(probe = "static")] +module_driver!( + name: "Static CVSD", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe_static, + }], +); + +#[cfg(probe = "static")] +fn probe_static( + info: rdrive::probe::static_::StaticInfo, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + if info.name() != DEVICE_NAME { + return Err(OnProbeError::NotMatch); + } + let regs = info.regs(); + let Some((sdmmc_address, sdmmc_size)) = regs.first().copied() else { + return Err(OnProbeError::NotMatch); + }; + let Some((syscon_address, syscon_size)) = regs.get(1).copied() else { + return Err(OnProbeError::NotMatch); + }; + register_mmio( + plat_dev, + sdmmc_address, + sdmmc_size, + syscon_address, + syscon_size, + ) +} + +pub fn register_mmio( + plat_dev: PlatformDevice, + sdmmc_paddr: usize, + sdmmc_size: usize, + syscon_paddr: usize, + syscon_size: usize, +) -> Result<(), OnProbeError> { + if sdmmc_paddr == 0 || sdmmc_size == 0 || syscon_paddr == 0 || syscon_size == 0 { + return Err(OnProbeError::NotMatch); + } + register_mmio_target(plat_dev, sdmmc_paddr, sdmmc_size, syscon_paddr, syscon_size) +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +fn register_mmio_target( + plat_dev: PlatformDevice, + sdmmc_paddr: usize, + sdmmc_size: usize, + syscon_paddr: usize, + syscon_size: usize, +) -> Result<(), OnProbeError> { + let sdmmc = map_region(sdmmc_paddr, sdmmc_size, "CVSD")?; + let syscon = map_region(syscon_paddr, syscon_size, "SYSCON")?; + let driver = + CvsdDriver::new(sdmmc, syscon).map_err(|_| OnProbeError::other("CVSD init failed"))?; + register_sync_block(plat_dev, driver); + Ok(()) +} + +#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] +fn register_mmio_target( + _plat_dev: PlatformDevice, + _sdmmc_paddr: usize, + _sdmmc_size: usize, + _syscon_paddr: usize, + _syscon_size: usize, +) -> Result<(), OnProbeError> { + Err(OnProbeError::NotMatch) +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +fn map_region(address: usize, size: usize, name: &str) -> Result { + let mmio = axklib::mmio::ioremap_raw(address.into(), size) + .map_err(|err| OnProbeError::other(format!("failed to map {name}: {err:?}")))?; + Ok(mmio.as_ptr() as usize) +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +struct CvsdDriver(Sdmmc); + +// The SG2002 SD/MMC core stores MMIO registers as `UnsafeCell`-backed +// references, so the raw register block is intentionally not `Sync`. +// `CvsdDriver` is owned by `SyncBlockDevice`, which serializes all access +// through a mutex and never clones the driver, so moving that owner between +// execution contexts is sound. +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +unsafe impl Send for CvsdDriver {} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +impl CvsdDriver { + fn new(sdmmc: usize, syscon: usize) -> Result { + let sdmmc = unsafe { Sdmmc::new(sdmmc, syscon) }; + sdmmc.init().map_err(|_| ())?; + sdmmc.clk_en(true); + Ok(Self(sdmmc)) + } + + fn checked_lba(block_id: u64, offset: usize) -> Result { + let lba = block_id + .checked_add(offset as u64) + .ok_or(rd_block::BlkError::InvalidBlockIndex(block_id as usize))?; + u32::try_from(lba).map_err(|_| rd_block::BlkError::InvalidBlockIndex(block_id as usize)) + } +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +impl SyncBlockOps for CvsdDriver { + fn name(&self) -> &'static str { + DEVICE_NAME + } + + fn num_blocks(&self) -> u64 { + self.0.card_capacity_blocks() + } + + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), rd_block::BlkError> { + if !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::NotSupported); + } + + for (i, block) in buf.chunks_exact_mut(BLOCK_SIZE).enumerate() { + self.0 + .read_block(Self::checked_lba(block_id, i)?, block) + .map_err(|_| rd_block::BlkError::Other("CVSD read failed".into()))?; + } + Ok(()) + } + + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), rd_block::BlkError> { + if !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::NotSupported); + } + + for (i, block) in buf.chunks_exact(BLOCK_SIZE).enumerate() { + self.0 + .write_block(Self::checked_lba(block_id, i)?, block) + .map_err(|_| rd_block::BlkError::Other("CVSD write failed".into()))?; + } + Ok(()) + } +} diff --git a/drivers/ax-driver/src/block/mod.rs b/drivers/ax-driver/src/block/mod.rs new file mode 100644 index 0000000000..15a6ef7685 --- /dev/null +++ b/drivers/ax-driver/src/block/mod.rs @@ -0,0 +1,143 @@ +mod binding; + +#[cfg(feature = "ahci")] +pub mod ahci; +#[cfg(feature = "bcm2835-sdhci")] +pub mod bcm2835; +#[cfg(feature = "cvsd")] +pub mod cvsd; +#[cfg(feature = "phytium-mci")] +pub mod phytium_mci; +#[cfg(feature = "ramdisk")] +pub mod ramdisk; +#[cfg(feature = "rockchip-sdhci")] +mod rockchip; +#[cfg(feature = "rockchip-sdhci")] +pub mod rockchip_mmc; +#[cfg(feature = "rockchip-dwmmc")] +pub mod rockchip_sd; + +#[cfg(sync_block_dev)] +use alloc::{boxed::Box, sync::Arc}; + +pub use binding::*; +#[cfg(sync_block_dev)] +use rd_block::{BlkError, BuffConfig, DriverGeneric, Event, IQueue, Interface, Request, RequestId}; +#[cfg(sync_block_dev)] +use spin::Mutex; + +#[cfg(sync_block_dev)] +pub(crate) trait SyncBlockOps: Send + 'static { + fn name(&self) -> &'static str; + fn num_blocks(&self) -> u64; + fn block_size(&self) -> usize; + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), BlkError>; + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), BlkError>; +} + +#[cfg(sync_block_dev)] +pub(crate) fn register_sync_block(plat_dev: rdrive::PlatformDevice, driver: D) { + plat_dev.register_block(SyncBlockDevice::new(driver)); +} + +#[cfg(sync_block_dev)] +struct SyncBlockDevice { + inner: Arc>, + queue_created: bool, + irq_enabled: bool, +} + +#[cfg(sync_block_dev)] +impl SyncBlockDevice { + fn new(driver: D) -> Self { + Self { + inner: Arc::new(Mutex::new(driver)), + queue_created: false, + irq_enabled: false, + } + } +} + +#[cfg(sync_block_dev)] +impl DriverGeneric for SyncBlockDevice { + fn name(&self) -> &str { + self.inner.lock().name() + } +} + +#[cfg(sync_block_dev)] +impl Interface for SyncBlockDevice { + fn create_queue(&mut self) -> Option> { + if self.queue_created { + return None; + } + self.queue_created = true; + Some(Box::new(SyncBlockQueue { + id: 0, + inner: Arc::clone(&self.inner), + })) + } + + fn enable_irq(&mut self) { + self.irq_enabled = true; + } + + fn disable_irq(&mut self) { + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> Event { + Event::none() + } +} + +#[cfg(sync_block_dev)] +struct SyncBlockQueue { + id: usize, + inner: Arc>, +} + +#[cfg(sync_block_dev)] +impl IQueue for SyncBlockQueue { + fn id(&self) -> usize { + self.id + } + + fn num_blocks(&self) -> usize { + self.inner.lock().num_blocks() as usize + } + + fn block_size(&self) -> usize { + self.inner.lock().block_size() + } + + fn buff_config(&self) -> BuffConfig { + BuffConfig { + dma_mask: u64::MAX, + align: 0x1000, + size: self.block_size(), + } + } + + fn submit_request(&mut self, request: Request<'_>) -> Result { + match request.kind { + rd_block::RequestKind::Read(mut buffer) => self + .inner + .lock() + .read_blocks(request.block_id as u64, &mut buffer)?, + rd_block::RequestKind::Write(items) => self + .inner + .lock() + .write_blocks(request.block_id as u64, items)?, + } + Ok(RequestId::new(0)) + } + + fn poll_request(&mut self, _request: RequestId) -> Result<(), BlkError> { + Ok(()) + } +} diff --git a/platform/axplat-dyn/src/drivers/blk/phytium_mci.rs b/drivers/ax-driver/src/block/phytium_mci.rs similarity index 97% rename from platform/axplat-dyn/src/drivers/blk/phytium_mci.rs rename to drivers/ax-driver/src/block/phytium_mci.rs index a53836c116..5dfcb2db07 100644 --- a/platform/axplat-dyn/src/drivers/blk/phytium_mci.rs +++ b/drivers/ax-driver/src/block/phytium_mci.rs @@ -3,20 +3,18 @@ use core::{num::NonZeroUsize, ptr::NonNull, time::Duration}; use ax_kspin::SpinNoIrq; use dma_api::DeviceDma; +use log::{info, warn}; use phytium_mci_host::{BlockRequest, BlockRequestSlot, PhytiumMci, RequestId}; -use rdrive::{ - DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, -}; +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError, register::FdtInfo}; use sdmmc_protocol::{ BlockPoll, BlockTransferMode, Error, OperationPoll, error::Phase, sdio::{CardInfo, CardInitPreference, SdioHost, SdioInitScratch, SdioSdmmc}, }; -use crate::drivers::{ - DmaImpl, - blk::{PlatformDeviceBlock, decode_fdt_irq}, - iomap, +use crate::{ + block::{PlatformDeviceBlock, decode_fdt_irq}, + mmio::iomap, }; const BLOCK_SIZE: usize = 512; @@ -53,7 +51,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError base_reg.address as usize, mmio_size ); - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size as usize)?; + let mmio_base = iomap(base_reg.address as usize, mmio_size as usize)?; let mut host = unsafe { PhytiumMci::new(mmio_base) }; info!("phytium-mci: reset controller"); @@ -190,7 +188,7 @@ impl rd_block::Interface for MciBlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: DeviceDma::new(u32::MAX as u64, &DmaImpl), + dma: axklib::dma::device(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), diff --git a/drivers/ax-driver/src/block/ramdisk.rs b/drivers/ax-driver/src/block/ramdisk.rs new file mode 100644 index 0000000000..00e5444a9d --- /dev/null +++ b/drivers/ax-driver/src/block/ramdisk.rs @@ -0,0 +1,14 @@ +use rdrive::PlatformDevice; + +use crate::block::PlatformDeviceBlock; + +pub const BLOCK_SIZE: usize = 512; +pub const DEFAULT_SIZE: usize = 16 * 1024 * 1024; + +pub const DEVICE_NAME: &str = "ramdisk"; + +pub fn register(plat_dev: PlatformDevice) { + let blocks = DEFAULT_SIZE / BLOCK_SIZE; + plat_dev.register_block(ramdisk::RamDisk::with_name(DEVICE_NAME, BLOCK_SIZE, blocks)); + log::info!("registered ramdisk: {} bytes", DEFAULT_SIZE); +} diff --git a/drivers/ax-driver/src/block/rockchip/mod.rs b/drivers/ax-driver/src/block/rockchip/mod.rs new file mode 100644 index 0000000000..340262a46d --- /dev/null +++ b/drivers/ax-driver/src/block/rockchip/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "rockchip-sdhci")] +mod sdhci_rk3568; diff --git a/platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3568.rs b/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs similarity index 97% rename from platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3568.rs rename to drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs index 6cc364f309..b09fc93e32 100644 --- a/platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3568.rs +++ b/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs @@ -17,10 +17,9 @@ use core::{num::NonZeroUsize, ptr::NonNull, time::Duration}; use ax_kspin::SpinNoIrq; use dma_api::DeviceDma; +use log::{info, warn}; use rdif_clk::ClockId; -use rdrive::{ - Device, DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, -}; +use rdrive::{Device, DriverGeneric, PlatformDevice, probe::OnProbeError, register::FdtInfo}; use sdhci_host::{BlockRequest, BlockRequestSlot, HostClock, RequestId, Sdhci}; use sdmmc_protocol::{ BlockPoll, BlockTransferMode, Error, OperationPoll, @@ -29,10 +28,9 @@ use sdmmc_protocol::{ }; use spin::Once; -use crate::drivers::{ - DmaImpl, - blk::{PlatformDeviceBlock, decode_fdt_irq}, - iomap, +use crate::{ + block::{PlatformDeviceBlock, decode_fdt_irq}, + mmio::iomap, }; const BLOCK_SIZE: usize = 512; @@ -126,7 +124,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError base_reg.address as usize, mmio_size ); - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size as usize)?; + let mmio_base = iomap(base_reg.address as usize, mmio_size as usize)?; init_core_clock(&info)?; @@ -143,7 +141,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError init_dwcmshc_after_reset(mmio_base); host.set_power(SDHCI_POWER_330); host.enable_interrupts(); - host.set_dma(DeviceDma::new(u32::MAX as u64, &DmaImpl)); + host.set_dma(axklib::dma::device(u32::MAX as u64)); info!("rockchip-rk3568-sdhci: initialize card"); let mut card = SdioSdmmc::new(host); @@ -401,7 +399,7 @@ impl rd_block::Interface for BlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: DeviceDma::new(u32::MAX as u64, &DmaImpl), + dma: axklib::dma::device(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), @@ -670,7 +668,7 @@ fn submit_write_request( Ok(id) } -fn transfer_dma<'a>(mode: BlockTransferMode, dma: &'a DeviceDma) -> Option<&'a DeviceDma> { +fn transfer_dma(mode: BlockTransferMode, dma: &DeviceDma) -> Option<&DeviceDma> { match mode { BlockTransferMode::Dma => Some(dma), BlockTransferMode::Fifo => None, diff --git a/platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3588.rs b/drivers/ax-driver/src/block/rockchip_mmc.rs similarity index 97% rename from platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3588.rs rename to drivers/ax-driver/src/block/rockchip_mmc.rs index c5a7e2b785..2cfb6b6749 100644 --- a/platform/axplat-dyn/src/drivers/blk/rockchip/sdhci_rk3588.rs +++ b/drivers/ax-driver/src/block/rockchip_mmc.rs @@ -17,10 +17,9 @@ use core::{num::NonZeroUsize, ptr::NonNull, time::Duration}; use ax_kspin::SpinNoIrq; use dma_api::DeviceDma; +use log::{info, warn}; use rdif_clk::ClockId; -use rdrive::{ - Device, DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, -}; +use rdrive::{Device, DriverGeneric, PlatformDevice, probe::OnProbeError, register::FdtInfo}; use sdhci_host::{BlockRequest, BlockRequestSlot, HostClock, RequestId, Sdhci}; use sdmmc_protocol::{ BlockPoll, BlockTransferMode, Error, OperationPoll, @@ -29,10 +28,9 @@ use sdmmc_protocol::{ }; use spin::Once; -use crate::drivers::{ - DmaImpl, - blk::{PlatformDeviceBlock, decode_fdt_irq}, - iomap, +use crate::{ + block::{PlatformDeviceBlock, decode_fdt_irq}, + mmio::iomap, }; const BLOCK_SIZE: usize = 512; @@ -79,7 +77,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError base_reg.address as usize, mmio_size ); - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size as usize)?; + let mmio_base = iomap(base_reg.address as usize, mmio_size as usize)?; init_core_clock(&info)?; @@ -95,7 +93,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError .map_err(|e| init_error(base_reg.address, mmio_size, e))?; host.set_power(SDHCI_POWER_330); host.enable_interrupts(); - host.set_dma(DeviceDma::new(u32::MAX as u64, &DmaImpl)); + host.set_dma(axklib::dma::device(u32::MAX as u64)); info!("rockchip-sdhci: initialize card"); let mut card = SdioSdmmc::new(host); @@ -259,7 +257,7 @@ impl rd_block::Interface for BlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: DeviceDma::new(u32::MAX as u64, &DmaImpl), + dma: axklib::dma::device(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), diff --git a/drivers/ax-driver/src/block/rockchip_sd.rs b/drivers/ax-driver/src/block/rockchip_sd.rs new file mode 100644 index 0000000000..807f548cf4 --- /dev/null +++ b/drivers/ax-driver/src/block/rockchip_sd.rs @@ -0,0 +1,299 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use alloc::{format, sync::Arc}; +use core::time::Duration; + +use ax_kspin::SpinNoIrq; +use dwmmc_host::DwMmc; +use log::{info, warn}; +use rdif_clk::ClockId; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; +use sdmmc_protocol::{ + Error, OperationPoll, + error::Phase, + sdio::{CardInfo, SdioInitScratch, SdioSdmmc}, +}; + +use crate::{ + block::{PlatformDeviceBlock, decode_fdt_irq}, + mmio::iomap, + soc::scmi, +}; + +const BLOCK_SIZE: usize = 512; +const DWMMC_STABLE_REFERENCE_CLOCK: u32 = 50_000_000; +const ENABLE_SD_SPEED_SELECTION: bool = true; +const RK3588_CRU_BASE: usize = 0xfd7c_0000; +const RK3588_CRU_SIZE: usize = 0x5c000; +const RK3588_SDMMC_CON0: usize = 0x0c30; +const RK3588_SDMMC_CON1: usize = 0x0c34; +const RK3588_SDMMC_PHASE_SHIFT: u32 = 1; +const RK3588_SDMMC_DRV_PHASE_DEG: u32 = 90; +const RK3588_SDMMC_SAMPLE_PHASE_DEG: u32 = 0; +const RK3588_SDMMC_SAMPLE_PHASE_CANDIDATES: [u32; 8] = [0, 45, 90, 135, 180, 225, 270, 315]; + +type RockchipDwMmc = SdioSdmmc; + +mod block; +mod phase; + +use block::SdBlockDevice; +use phase::{init_rk3588_sdmmc_phase, tune_rk3588_sdmmc_sample_phase}; + +module_driver!( + name: "Rockchip SD", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-dw-mshc", "rockchip,rk3288-dw-mshc"], + on_probe: probe + } + ], +); + +fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let base_reg = info + .node + .regs() + .into_iter() + .next() + .ok_or(OnProbeError::other(alloc::format!( + "[{}] has no reg", + info.node.name() + )))?; + + let mmio_size = base_reg.size.unwrap_or(0x1000); + info!( + "rockchip-dwmmc probe: node={}, addr={:#x}, size={:#x}", + info.node.name(), + base_reg.address as usize, + mmio_size + ); + let mmio_base = iomap(base_reg.address as usize, mmio_size as usize)?; + + let mut host = unsafe { DwMmc::new(mmio_base) }; + let reference_clock = dwmmc_reference_clock(&info); + if let Some(reference_clock) = reference_clock { + info!( + "rockchip-dwmmc: using ciu reference clock {} Hz", + reference_clock + ); + host.set_reference_clock(reference_clock); + if is_rk3588_dwmmc(&info) { + init_rk3588_sdmmc_phase(&info, reference_clock)?; + } + } else { + warn!( + "rockchip-dwmmc: ciu clock not found; leaving DWMMC divider bypassed and relying on \ + CRU rate" + ); + } + info!("rockchip-dwmmc: reset controller"); + host.reset_and_init() + .map_err(|e| init_error(base_reg.address, mmio_size, e))?; + host.set_dma(axklib::dma::device(u32::MAX as u64)); + + info!("rockchip-dwmmc: initialize card"); + let mut sd = SdioSdmmc::new(host); + sd.set_sd_speed_selection_enabled(ENABLE_SD_SPEED_SELECTION); + let card_info = poll_card_init(&mut sd).map_err(|e| { + warn!("rockchip-dwmmc: card init failed: {:?}", e); + card_init_error(base_reg.address, mmio_size, e) + })?; + info!( + "rockchip-dwmmc card: kind={:?} high_capacity={} rca={} ocr={:#010x} capacity_blocks={:?} \ + cid={} ext_csd={}", + card_info.kind, + card_info.high_capacity, + card_info.rca, + card_info.ocr, + card_info.capacity_blocks, + card_info.cid.is_some(), + card_info.ext_csd.is_some() + ); + + if let Some(reference_clock) = reference_clock + && is_rk3588_dwmmc(&info) + { + tune_rk3588_sdmmc_sample_phase(&mut sd, reference_clock); + } + + let irq_num = decode_fdt_irq(&info.interrupts()); + let raw = Arc::new(SpinNoIrq::new(sd)); + let dev = SdBlockDevice { + raw: Some(raw.clone()), + capacity_blocks: card_info.capacity_blocks.unwrap_or(0), + irq_enabled: false, + queue_created: false, + }; + plat_dev.register_block_with_irq(dev, irq_num); + info!("rockchip-sd block device registered irq={:?}", irq_num); + Ok(()) +} + +fn poll_card_init(sd: &mut RockchipDwMmc) -> Result { + let mut scratch = SdioInitScratch::new(); + let mut request = sd.submit_init(&mut scratch)?; + loop { + match sd.poll_init_request(&mut request)? { + OperationPoll::Pending => { + if request.take_needs_pace() { + axklib::time::busy_wait(Duration::from_millis(10)); + } else { + core::hint::spin_loop(); + } + } + OperationPoll::Complete(info) => return Ok(info), + _ => return Err(Error::UnsupportedCommand), + } + } +} + +fn init_error(address: u64, size: u64, err: Error) -> OnProbeError { + OnProbeError::other(format!( + "failed to initialize DWMMC device at [PA:{:?}, SZ:0x{:x}): {err:?}", + address, size + )) +} + +fn card_init_error(address: u64, size: u64, err: Error) -> OnProbeError { + if is_absent_card_init_error(err) { + warn!( + "rockchip-dwmmc: no responsive card at [PA:{:?}, SZ:0x{:x}); skipping controller: \ + {err:?}", + address, size + ); + return OnProbeError::NotMatch; + } + + init_error(address, size, err) +} + +fn is_absent_card_init_error(err: Error) -> bool { + match err { + Error::NoCard => true, + Error::Timeout(ctx) | Error::Crc(ctx) | Error::BadResponse(ctx) => { + ctx.cmd.is_some() + && matches!( + ctx.phase, + Phase::CommandSend | Phase::ResponseWait | Phase::Init + ) + } + _ => false, + } +} + +fn dwmmc_reference_clock(info: &FdtInfo<'_>) -> Option { + let clk = info.find_clk_by_name("ciu")?; + let Some(device_id) = info.phandle_to_device_id(clk.phandle) else { + warn!( + "[{}] ciu clock phandle {} has no device id", + info.node.name(), + clk.phandle + ); + return None; + }; + let clk_dev = match rdrive::get::(device_id) { + Ok(clk_dev) => clk_dev, + Err(_) => { + let clock_id = clk.select().unwrap_or(0); + if scmi::set_clock_rate(clk.phandle, clock_id, DWMMC_STABLE_REFERENCE_CLOCK as u64) + .is_some() + { + return Some(DWMMC_STABLE_REFERENCE_CLOCK); + } + if let Some(rate) = scmi::clock_rate(clk.phandle, clock_id) { + return validate_reference_clock(info, rate); + } + warn!( + "[{}] ciu clock device {:?} is not registered", + info.node.name(), + device_id + ); + return None; + } + }; + let mut clk_guard = match clk_dev.lock() { + Ok(clk_guard) => clk_guard, + Err(_) => { + warn!( + "[{}] ciu clock device {:?} is locked", + info.node.name(), + device_id + ); + return None; + } + }; + let clock_id = ClockId::from(clk.select().unwrap_or(0) as usize); + if let Err(err) = clk_guard.set_rate(clock_id, DWMMC_STABLE_REFERENCE_CLOCK as u64) { + warn!( + "[{}] failed to set ciu clock {:?} to {} Hz: {:?}", + info.node.name(), + clock_id, + DWMMC_STABLE_REFERENCE_CLOCK, + err + ); + } + let rate = match clk_guard.get_rate(clock_id) { + Ok(rate) => rate, + Err(err) => { + warn!( + "[{}] failed to read ciu clock {:?}: {:?}", + info.node.name(), + clock_id, + err + ); + return None; + } + }; + validate_reference_clock(info, rate) +} + +fn is_rk3588_dwmmc(info: &FdtInfo<'_>) -> bool { + info.node + .as_node() + .compatibles() + .any(|compatible| compatible == "rockchip,rk3588-dw-mshc") +} + +fn validate_reference_clock(info: &FdtInfo<'_>, rate: u64) -> Option { + if rate == 0 || rate > u32::MAX as u64 { + warn!("[{}] invalid ciu clock rate {} Hz", info.node.name(), rate); + return None; + } + Some(rate as u32) +} + +#[cfg(test)] +mod tests { + use sdmmc_protocol::error::ErrorContext; + + use super::*; + + #[test] + fn command_timeout_during_card_init_is_absent_card() { + let err = Error::Timeout(ErrorContext::for_cmd(Phase::ResponseWait, 1)); + + assert!(is_absent_card_init_error(err)); + } + + #[test] + fn data_timeout_after_card_init_is_not_absent_card() { + let err = Error::Timeout(ErrorContext::for_cmd(Phase::DataRead, 17)); + + assert!(!is_absent_card_init_error(err)); + } +} diff --git a/drivers/ax-driver/src/block/rockchip_sd/block.rs b/drivers/ax-driver/src/block/rockchip_sd/block.rs new file mode 100644 index 0000000000..8c09af9e0e --- /dev/null +++ b/drivers/ax-driver/src/block/rockchip_sd/block.rs @@ -0,0 +1,334 @@ +use alloc::{sync::Arc, vec::Vec}; +use core::{num::NonZeroUsize, ptr::NonNull}; + +use ax_kspin::SpinNoIrq; +use dma_api::DeviceDma; +use dwmmc_host::{BlockPoll, BlockRequest, BlockRequestSlot, DwMmc, RequestId}; +use log::warn; +use rdrive::DriverGeneric; +use sdmmc_protocol::{BlockTransferMode, Error, sdio::SdioHost}; + +use super::{BLOCK_SIZE, RockchipDwMmc}; + +pub(super) struct SdBlockDevice { + pub(super) raw: Option>>, + pub(super) capacity_blocks: u64, + pub(super) irq_enabled: bool, + pub(super) queue_created: bool, +} + +struct SdBlockQueue { + raw: Arc>, + capacity_blocks: u64, + id: usize, + dma: DeviceDma, + slot: BlockRequestSlot, + pending: Option, + completed: Vec, +} + +impl DriverGeneric for SdBlockDevice { + fn name(&self) -> &str { + "rockchip-sd" + } +} + +impl rd_block::Interface for SdBlockDevice { + fn create_queue(&mut self) -> Option> { + if self.queue_created { + return None; + } + self.raw.as_ref().map(|dev| { + self.queue_created = true; + alloc::boxed::Box::new(SdBlockQueue { + raw: dev.clone(), + capacity_blocks: self.capacity_blocks, + id: 0, + dma: axklib::dma::device(u32::MAX as u64), + slot: BlockRequestSlot::default(), + pending: None, + completed: Vec::new(), + }) as _ + }) + } + + fn enable_irq(&mut self) { + if let Some(raw) = &self.raw { + let mut raw = raw.lock(); + if let Err(err) = SdioHost::enable_completion_irq(raw.host_mut()) { + warn!("rockchip-dwmmc: enable completion IRQ failed: {:?}", err); + return; + } + self.irq_enabled = true; + } + } + + fn disable_irq(&mut self) { + if let Some(raw) = &self.raw { + let mut raw = raw.lock(); + if let Err(err) = SdioHost::disable_completion_irq(raw.host_mut()) { + warn!("rockchip-dwmmc: disable completion IRQ failed: {:?}", err); + } + } + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> rd_block::Event { + let Some(raw) = &self.raw else { + return rd_block::Event::none(); + }; + let irq_event = raw.lock().host_mut().handle_irq(); + block_event_from_dwmmc_irq(irq_event) + } +} + +fn block_event_from_dwmmc_irq(irq_event: dwmmc_host::Event) -> rd_block::Event { + match irq_event { + dwmmc_host::Event::None => rd_block::Event::none(), + dwmmc_host::Event::CommandComplete + | dwmmc_host::Event::TransferComplete + | dwmmc_host::Event::ReceiveReady + | dwmmc_host::Event::TransmitReady + | dwmmc_host::Event::Error { .. } + | dwmmc_host::Event::Other { .. } => { + let mut event = rd_block::Event::none(); + event.queue.insert(0); + event + } + } +} + +impl rd_block::IQueue for SdBlockQueue { + fn num_blocks(&self) -> usize { + self.capacity_blocks as usize + } + + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn id(&self) -> usize { + self.id + } + + fn buff_config(&self) -> rd_block::BuffConfig { + rd_block::BuffConfig { + dma_mask: self.dma.dma_mask(), + align: BLOCK_SIZE, + size: BLOCK_SIZE, + } + } + + fn submit_request( + &mut self, + request: rd_block::Request<'_>, + ) -> Result { + self.reap_pending_request()?; + let mut raw = self.raw.lock(); + let start_block = block_addr_for_card(request.block_id, raw.is_high_capacity())?; + match request.kind { + rd_block::RequestKind::Read(buffer) => { + if !buffer.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::Other( + "read buffer is not block aligned".into(), + )); + } + let ptr = NonNull::new(buffer.virt).ok_or_else(|| { + rd_block::BlkError::Other("read buffer pointer is null".into()) + })?; + let size = NonZeroUsize::new(buffer.len()) + .ok_or_else(|| rd_block::BlkError::Other("read buffer is empty".into()))?; + let id = submit_read_request( + raw.host_mut(), + start_block, + ptr, + size, + &self.dma, + &mut self.slot, + &mut self.pending, + )?; + Ok(rd_block::RequestId::new(usize::from(id))) + } + rd_block::RequestKind::Write(items) => { + if !items.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::Other( + "write buffer is not block aligned".into(), + )); + } + let ptr = NonNull::new(items.as_ptr() as *mut u8).ok_or_else(|| { + rd_block::BlkError::Other("write buffer pointer is null".into()) + })?; + let size = NonZeroUsize::new(items.len()) + .ok_or_else(|| rd_block::BlkError::Other("write buffer is empty".into()))?; + let id = submit_write_request( + raw.host_mut(), + start_block, + ptr, + size, + &self.dma, + &mut self.slot, + &mut self.pending, + )?; + Ok(rd_block::RequestId::new(usize::from(id))) + } + } + } + + fn poll_request(&mut self, request: rd_block::RequestId) -> Result<(), rd_block::BlkError> { + if let Some(index) = self.completed.iter().position(|id| *id == request) { + self.completed.swap_remove(index); + return Ok(()); + } + self.poll_active_request(request) + } +} + +impl SdBlockQueue { + fn poll_active_request( + &mut self, + request: rd_block::RequestId, + ) -> Result<(), rd_block::BlkError> { + match self.raw.lock().host_mut().poll_block_request( + &mut self.pending, + RequestId::new(usize::from(request)), + &mut self.slot, + ) { + Ok(BlockPoll::Complete) => Ok(()), + Ok(BlockPoll::Pending) => Err(rd_block::BlkError::Retry), + Ok(_) => Err(rd_block::BlkError::Other( + "DWMMC returned an unknown poll state".into(), + )), + Err(err) => Err(map_dev_err_to_blk_err(err)), + } + } + + fn pending_id(&self) -> Option { + self.pending.as_ref().map(BlockRequest::id) + } + + fn reap_pending_request(&mut self) -> Result<(), rd_block::BlkError> { + let Some(active) = self.pending_id() else { + return Ok(()); + }; + let id = rd_block::RequestId::new(usize::from(active)); + match self.poll_active_request(id) { + Ok(()) => { + self.completed.push(id); + Ok(()) + } + Err(rd_block::BlkError::Retry) => Err(rd_block::BlkError::Retry), + Err(err) => Err(err), + } + } +} + +fn submit_read_request( + host: &mut DwMmc, + start_block: u32, + buffer: NonNull, + size: NonZeroUsize, + dma: &DeviceDma, + slot: &mut BlockRequestSlot, + pending: &mut Option, +) -> Result { + if pending.is_some() { + return Err(rd_block::BlkError::Retry); + } + let request = match host.submit_read_blocks( + start_block, + buffer, + size, + Some(dma), + BlockTransferMode::Dma, + slot, + ) { + Ok(request) => request, + Err(err) if can_fallback_to_fifo(err) => host + .submit_read_blocks( + start_block, + buffer, + size, + None, + BlockTransferMode::Fifo, + slot, + ) + .map_err(map_dev_err_to_blk_err)?, + Err(err) => return Err(map_dev_err_to_blk_err(err)), + }; + let id = request.id(); + *pending = Some(request); + Ok(id) +} + +fn submit_write_request( + host: &mut DwMmc, + start_block: u32, + buffer: NonNull, + size: NonZeroUsize, + dma: &DeviceDma, + slot: &mut BlockRequestSlot, + pending: &mut Option, +) -> Result { + if pending.is_some() { + return Err(rd_block::BlkError::Retry); + } + let request = match host.submit_write_blocks( + start_block, + buffer, + size, + Some(dma), + BlockTransferMode::Dma, + slot, + ) { + Ok(request) => request, + Err(err) if can_fallback_to_fifo(err) => host + .submit_write_blocks( + start_block, + buffer, + size, + None, + BlockTransferMode::Fifo, + slot, + ) + .map_err(map_dev_err_to_blk_err)?, + Err(err) => return Err(map_dev_err_to_blk_err(err)), + }; + let id = request.id(); + *pending = Some(request); + Ok(id) +} + +fn can_fallback_to_fifo(err: Error) -> bool { + matches!( + err, + Error::UnsupportedCommand | Error::InvalidArgument | Error::Misaligned + ) +} + +fn block_addr_for_card(block_id: usize, high_capacity: bool) -> Result { + let block_id = + u32::try_from(block_id).map_err(|_| rd_block::BlkError::InvalidBlockIndex(block_id))?; + if high_capacity { + Ok(block_id) + } else { + block_id + .checked_mul(BLOCK_SIZE as u32) + .ok_or(rd_block::BlkError::InvalidBlockIndex(block_id as usize)) + } +} + +fn map_dev_err_to_blk_err(err: Error) -> rd_block::BlkError { + match err { + Error::NoCard | Error::UnsupportedCommand | Error::CardLocked => { + rd_block::BlkError::NotSupported + } + Error::Misaligned | Error::InvalidArgument => { + rd_block::BlkError::Other("SD/MMC request is not block aligned".into()) + } + _ => rd_block::BlkError::Other("DWMMC I/O error".into()), + } +} diff --git a/drivers/ax-driver/src/block/rockchip_sd/phase.rs b/drivers/ax-driver/src/block/rockchip_sd/phase.rs new file mode 100644 index 0000000000..0a38292052 --- /dev/null +++ b/drivers/ax-driver/src/block/rockchip_sd/phase.rs @@ -0,0 +1,155 @@ +use core::ptr::NonNull; + +use log::{info, warn}; +use rdrive::{probe::OnProbeError, register::FdtInfo}; +use sdmmc_protocol::{DataCommandPoll, Error}; + +use super::{ + BLOCK_SIZE, RK3588_CRU_BASE, RK3588_CRU_SIZE, RK3588_SDMMC_CON0, RK3588_SDMMC_CON1, + RK3588_SDMMC_DRV_PHASE_DEG, RK3588_SDMMC_PHASE_SHIFT, RK3588_SDMMC_SAMPLE_PHASE_CANDIDATES, + RK3588_SDMMC_SAMPLE_PHASE_DEG, RockchipDwMmc, +}; +use crate::mmio::iomap; + +pub(super) fn init_rk3588_sdmmc_phase( + info: &FdtInfo<'_>, + parent_rate: u32, +) -> Result<(), OnProbeError> { + let has_drive_clk = info.find_clk_by_name("ciu-drive").is_some(); + let has_sample_clk = info.find_clk_by_name("ciu-sample").is_some(); + if !has_drive_clk || !has_sample_clk { + warn!( + "[{}] RK3588 SDMMC phase clocks missing: ciu-drive={} ciu-sample={}", + info.node.name(), + has_drive_clk, + has_sample_clk + ); + return Ok(()); + } + + let cru = iomap(RK3588_CRU_BASE, RK3588_CRU_SIZE)?; + set_rk3588_mmc_phase( + cru, + RK3588_SDMMC_CON0, + parent_rate, + RK3588_SDMMC_DRV_PHASE_DEG, + ); + set_rk3588_mmc_phase( + cru, + RK3588_SDMMC_CON1, + parent_rate, + RK3588_SDMMC_SAMPLE_PHASE_DEG, + ); + info!( + "rockchip-dwmmc: RK3588 SDMMC phase configured: drive={}deg sample={}deg parent={}Hz", + RK3588_SDMMC_DRV_PHASE_DEG, RK3588_SDMMC_SAMPLE_PHASE_DEG, parent_rate + ); + Ok(()) +} + +pub(super) fn tune_rk3588_sdmmc_sample_phase(sd: &mut RockchipDwMmc, parent_rate: u32) { + let Ok(cru) = iomap(RK3588_CRU_BASE, RK3588_CRU_SIZE) else { + warn!("rockchip-dwmmc: failed to map RK3588 CRU for sample phase scan"); + return; + }; + + for sample_phase in RK3588_SDMMC_SAMPLE_PHASE_CANDIDATES { + set_rk3588_mmc_phase(cru, RK3588_SDMMC_CON1, parent_rate, sample_phase); + + let mut block0 = [0; BLOCK_SIZE]; + let block0_result = read_block_sync(sd, 0, &mut block0); + let block0_valid = block0_result.is_ok() && has_mbr_signature(&block0); + + let mut block1 = [0; BLOCK_SIZE]; + let block1_result = read_block_sync(sd, 1, &mut block1); + let block1_valid = block1_result.is_ok() && has_gpt_header(&block1); + + info!( + "rockchip-dwmmc: sample phase probe {}deg: block0_ok={} mbr_sig={:02x}{:02x} \ + block0_head={:02x?} block1_ok={} gpt_head={:02x?}", + sample_phase, + block0_result.is_ok(), + block0[511], + block0[510], + &block0[..16], + block1_result.is_ok(), + &block1[..8] + ); + + if block0_valid || block1_valid { + set_rk3588_mmc_phase(cru, RK3588_SDMMC_CON1, parent_rate, sample_phase); + info!( + "rockchip-dwmmc: selected RK3588 SDMMC sample phase {}deg", + sample_phase + ); + return; + } + } + + set_rk3588_mmc_phase( + cru, + RK3588_SDMMC_CON1, + parent_rate, + RK3588_SDMMC_SAMPLE_PHASE_DEG, + ); + warn!( + "rockchip-dwmmc: no valid RK3588 SDMMC sample phase found; restored {}deg", + RK3588_SDMMC_SAMPLE_PHASE_DEG + ); +} + +fn read_block_sync( + sd: &mut RockchipDwMmc, + addr: u32, + buf: &mut [u8; BLOCK_SIZE], +) -> Result<(), Error> { + let mut request = sd.submit_read_blocks_into(addr, buf)?; + loop { + match sd.poll_data_request(&mut request)? { + DataCommandPoll::Pending => core::hint::spin_loop(), + DataCommandPoll::Complete(_) => return Ok(()), + _ => core::hint::spin_loop(), + } + } +} + +fn set_rk3588_mmc_phase(cru: NonNull, offset: usize, parent_rate: u32, degrees: u32) { + let delay_num = rk3588_mmc_delay_num(parent_rate, degrees); + let raw_value = if delay_num != 0 { 1 << 10 } else { 0 } + | ((delay_num & 0xff) << 2) + | ((degrees / 90) & 0x03); + let reg_value = + ((0x07ff_u32 << RK3588_SDMMC_PHASE_SHIFT) << 16) | (raw_value << RK3588_SDMMC_PHASE_SHIFT); + unsafe { + (cru.as_ptr().add(offset) as *mut u32).write_volatile(reg_value); + } +} + +fn rk3588_mmc_delay_num(parent_rate: u32, degrees: u32) -> u32 { + let degree = degrees % 360; + let remainder = degree % 90; + if parent_rate == 0 { + 0 + } else { + div_round_closest( + 10_000_000_u64 * remainder as u64, + (parent_rate as u64 / 1_000) * 36 * 6, + ) + .min(255) as u32 + } +} + +fn div_round_closest(numerator: u64, denominator: u64) -> u64 { + numerator + .saturating_add(denominator / 2) + .checked_div(denominator) + .unwrap_or(0) +} + +fn has_mbr_signature(block: &[u8; BLOCK_SIZE]) -> bool { + block[510] == 0x55 && block[511] == 0xaa +} + +fn has_gpt_header(block: &[u8; BLOCK_SIZE]) -> bool { + &block[..8] == b"EFI PART" +} diff --git a/drivers/ax-driver/src/display/binding.rs b/drivers/ax-driver/src/display/binding.rs new file mode 100644 index 0000000000..e80c0cf061 --- /dev/null +++ b/drivers/ax-driver/src/display/binding.rs @@ -0,0 +1,57 @@ +use alloc::{boxed::Box, string::String, vec::Vec}; + +use ax_errno::AxError; +use rdif_display::Interface; +use rdrive::{Device, DriverGeneric}; + +pub struct PlatformDisplayDevice { + name: String, + display: Option>, +} + +impl PlatformDisplayDevice { + fn new(name: String, display: Box) -> Self { + Self { + name, + display: Some(display), + } + } +} + +impl DriverGeneric for PlatformDisplayDevice { + fn name(&self) -> &str { + &self.name + } +} + +pub trait PlatformDeviceDisplay { + fn register_display(self, dev: T) + where + T: Interface + 'static; +} + +impl PlatformDeviceDisplay for rdrive::PlatformDevice { + fn register_display(self, dev: T) + where + T: Interface + 'static, + { + let name = dev.name().into(); + self.register(PlatformDisplayDevice::new(name, Box::new(dev))); + } +} + +pub fn take_display_devices() -> Result>, AxError> { + let mut devices = Vec::new(); + for dev in rdrive::get_list::() { + let display = take_display_device(dev)?; + devices.push(display); + } + Ok(devices) +} + +fn take_display_device( + device: Device, +) -> Result, AxError> { + let mut device = device.lock().map_err(|_| AxError::BadState)?; + device.display.take().ok_or(AxError::BadState) +} diff --git a/drivers/ax-driver/src/display/mod.rs b/drivers/ax-driver/src/display/mod.rs new file mode 100644 index 0000000000..e0d3e840a1 --- /dev/null +++ b/drivers/ax-driver/src/display/mod.rs @@ -0,0 +1,3 @@ +mod binding; + +pub use binding::*; diff --git a/drivers/ax-driver/src/error.rs b/drivers/ax-driver/src/error.rs new file mode 100644 index 0000000000..33f43724fd --- /dev/null +++ b/drivers/ax-driver/src/error.rs @@ -0,0 +1,30 @@ +#[derive(Debug)] +pub enum Error { + Driver(rdrive::error::DriverError), + Probe(rdrive::ProbeError), +} + +pub type Result = core::result::Result; + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Driver(err) => write!(f, "driver init failed: {err}"), + Self::Probe(err) => write!(f, "driver probe failed: {err}"), + } + } +} + +impl core::error::Error for Error {} + +impl From for Error { + fn from(value: rdrive::error::DriverError) -> Self { + Self::Driver(value) + } +} + +impl From for Error { + fn from(value: rdrive::ProbeError) -> Self { + Self::Probe(value) + } +} diff --git a/drivers/ax-driver/src/input/binding.rs b/drivers/ax-driver/src/input/binding.rs new file mode 100644 index 0000000000..02c7f03a8d --- /dev/null +++ b/drivers/ax-driver/src/input/binding.rs @@ -0,0 +1,54 @@ +use alloc::{boxed::Box, string::String, vec::Vec}; + +use ax_errno::AxError; +use rdif_input::Interface; +use rdrive::{Device, DriverGeneric}; + +pub struct PlatformInputDevice { + name: String, + input: Option>, +} + +impl PlatformInputDevice { + fn new(name: String, input: Box) -> Self { + Self { + name, + input: Some(input), + } + } +} + +impl DriverGeneric for PlatformInputDevice { + fn name(&self) -> &str { + &self.name + } +} + +pub trait PlatformDeviceInput { + fn register_input(self, dev: T) + where + T: Interface + 'static; +} + +impl PlatformDeviceInput for rdrive::PlatformDevice { + fn register_input(self, dev: T) + where + T: Interface + 'static, + { + let name = dev.name().into(); + self.register(PlatformInputDevice::new(name, Box::new(dev))); + } +} + +pub fn take_input_devices() -> Result>, AxError> { + let mut devices = Vec::new(); + for dev in rdrive::get_list::() { + devices.push(take_input_device(dev)?); + } + Ok(devices) +} + +fn take_input_device(device: Device) -> Result, AxError> { + let mut device = device.lock().map_err(|_| AxError::BadState)?; + device.input.take().ok_or(AxError::BadState) +} diff --git a/drivers/ax-driver/src/input/mod.rs b/drivers/ax-driver/src/input/mod.rs new file mode 100644 index 0000000000..e0d3e840a1 --- /dev/null +++ b/drivers/ax-driver/src/input/mod.rs @@ -0,0 +1,3 @@ +mod binding; + +pub use binding::*; diff --git a/drivers/ax-driver/src/lib.rs b/drivers/ax-driver/src/lib.rs new file mode 100644 index 0000000000..e568b03d22 --- /dev/null +++ b/drivers/ax-driver/src/lib.rs @@ -0,0 +1,64 @@ +//! rdrive + rdif host driver registration collection. + +#![no_std] +#![feature(used_with_arg)] + +extern crate alloc; +#[macro_use] +extern crate rdrive; + +module_driver!( + name: "ax-driver macro placeholder", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[], +); + +pub mod error; +#[cfg(any( + feature = "serial", + all(feature = "rtc", feature = "fdt"), + all(feature = "rockchip-soc", feature = "fdt"), + all(feature = "rockchip-pm", feature = "fdt"), + all(feature = "rockchip-dwmmc", feature = "fdt"), + all(feature = "rockchip-sdhci", feature = "fdt"), + all(feature = "phytium-mci", feature = "fdt"), + all(feature = "rk3588-pcie", feature = "fdt"), + all(feature = "rknpu", feature = "fdt"), + all(feature = "xhci-mmio", target_os = "none"), + all(feature = "xhci-pci", target_os = "none"), + all(virtio_dev, probe = "fdt") +))] +pub mod mmio; + +#[cfg(feature = "block")] +pub mod block; +#[cfg(feature = "display")] +pub mod display; +#[cfg(feature = "input")] +pub mod input; +#[cfg(feature = "net")] +pub mod net; +#[cfg(feature = "vsock")] +pub mod vsock; + +#[cfg(feature = "pci")] +pub mod pci; +#[cfg(feature = "rknpu")] +pub mod rknpu; +#[cfg(feature = "serial")] +pub mod serial; +#[cfg(any( + feature = "rockchip-soc", + feature = "rockchip-pm", + feature = "rockchip-dwmmc" +))] +pub mod soc; +#[cfg(feature = "rtc")] +pub mod time; +#[cfg(feature = "usb")] +pub mod usb; +#[cfg(virtio_dev)] +pub mod virtio; + +pub use error::{Error, Result}; diff --git a/drivers/ax-driver/src/mmio.rs b/drivers/ax-driver/src/mmio.rs new file mode 100644 index 0000000000..aac22ef36b --- /dev/null +++ b/drivers/ax-driver/src/mmio.rs @@ -0,0 +1,9 @@ +use core::ptr::NonNull; + +use rdrive::probe::OnProbeError; + +pub fn iomap(addr: usize, size: usize) -> Result, OnProbeError> { + axklib::mmio::ioremap_raw(addr.into(), size) + .map_err(|err| OnProbeError::Other(alloc::format!("{err:?}").into())) + .map(|mmio| mmio.as_nonnull_ptr()) +} diff --git a/drivers/ax-driver/src/net/binding.rs b/drivers/ax-driver/src/net/binding.rs new file mode 100644 index 0000000000..d03cec41ff --- /dev/null +++ b/drivers/ax-driver/src/net/binding.rs @@ -0,0 +1,87 @@ +extern crate alloc; + +use alloc::boxed::Box; + +use rd_net::{Interface, NetError}; +use rdrive::{Device, DriverGeneric, probe::pci::EndpointRc}; + +pub struct PlatformNetDevice { + name: &'static str, + net: Option, + irq_num: Option, +} + +impl PlatformNetDevice { + fn new(name: &'static str, net: rd_net::Net, irq_num: Option) -> Self { + Self { + name, + net: Some(net), + irq_num, + } + } + + pub fn take_net(&mut self) -> Option<(rd_net::Net, &'static str, Option)> { + Some((self.net.take()?, self.name, self.irq_num)) + } +} + +pub fn take_rd_net_device( + device: Device, +) -> Result<(rd_net::Net, &'static str, Option), NetError> { + let mut dev = device + .lock() + .map_err(|_| NetError::Other(Box::new(rd_net::KError::Unknown("device locked"))))?; + dev.take_net() + .ok_or_else(|| NetError::Other(Box::new(rd_net::KError::Unknown("device already taken")))) +} + +impl DriverGeneric for PlatformNetDevice { + fn name(&self) -> &str { + self.name + } +} + +pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { + #[cfg(feature = "pci")] + if let Some(irq) = + crate::pci::legacy_irq_for_endpoint(endpoint.address(), endpoint.interrupt_pin()) + { + return Some(irq); + } + + let line = endpoint.interrupt_line(); + if line == 0 || line == u8::MAX { + return None; + } + Some(pci_legacy_line_to_irq(line)) +} + +const fn pci_legacy_line_to_irq(line: u8) -> usize { + const PCI_IRQ_BASE: usize = if cfg!(target_arch = "x86_64") || cfg!(target_arch = "riscv64") { + if cfg!(target_arch = "x86_64") { + 0x20 + } else { + 0 + } + } else { + 0 + }; + + PCI_IRQ_BASE + line as usize +} + +pub trait PlatformDeviceNet { + fn register_net(self, name: &'static str, dev: T, irq_num: Option) + where + T: Interface + 'static; +} + +impl PlatformDeviceNet for rdrive::PlatformDevice { + fn register_net(self, name: &'static str, dev: T, irq_num: Option) + where + T: Interface + 'static, + { + let net = rd_net::Net::new(dev, axklib::dma::op()); + self.register(PlatformNetDevice::new(name, net, irq_num)); + } +} diff --git a/drivers/ax-driver/src/net/fxmac.rs b/drivers/ax-driver/src/net/fxmac.rs new file mode 100644 index 0000000000..ba1631d908 --- /dev/null +++ b/drivers/ax-driver/src/net/fxmac.rs @@ -0,0 +1,271 @@ +use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec, vec::Vec}; +use core::{alloc::Layout, cmp, ptr::NonNull}; + +use dma_api::{DmaAddr, DmaHandle, DmaOp}; +use fxmac_rs::{FXmac, FXmacGetMacAddress, FXmacLwipPortTx, FXmacRecvHandler, xmac_init}; +use rd_net::{DmaBuffer, Event, IRxQueue, ITxQueue, NetError, QueueConfig}; +use rdrive::{DriverGeneric, PlatformDevice}; + +use crate::net::PlatformDeviceNet; + +pub const DEVICE_NAME: &str = "fxmac"; + +const DRIVER_NAME: &str = "cdns,phytium-gem-1.0"; +const QUEUE_ID: usize = 0; +const QUEUE_SIZE: usize = 64; +const BUFFER_SIZE: usize = 2048; +const DMA_ALIGN: usize = 0x1000; +const DMA_MASK: u64 = u64::MAX; +const PAGE_SIZE: usize = 0x1000; + +pub fn register(plat_dev: PlatformDevice) { + let dev = FxmacNet::new(); + plat_dev.register_net(DRIVER_NAME, dev, None); + log::info!("registered FXmac network device"); +} + +struct FxmacNet { + inner: Arc>, + tx_created: bool, + rx_created: bool, + irq_enabled: bool, +} + +impl FxmacNet { + fn new() -> Self { + let mut hwaddr = [0; 6]; + FXmacGetMacAddress(&mut hwaddr, 0); + let device = xmac_init(&hwaddr); + Self { + inner: Arc::new(spin::Mutex::new(FxmacInner { + device, + hwaddr, + rx_buffers: VecDeque::with_capacity(QUEUE_SIZE), + rx_packets: VecDeque::with_capacity(QUEUE_SIZE), + tx_done: VecDeque::with_capacity(QUEUE_SIZE), + })), + tx_created: false, + rx_created: false, + irq_enabled: false, + } + } +} + +impl DriverGeneric for FxmacNet { + fn name(&self) -> &str { + DRIVER_NAME + } +} + +impl rd_net::Interface for FxmacNet { + fn mac_address(&self) -> [u8; 6] { + self.inner.lock().hwaddr + } + + fn create_tx_queue(&mut self) -> Option> { + if self.tx_created { + return None; + } + self.tx_created = true; + Some(Box::new(FxmacTxQueue { + inner: self.inner.clone(), + })) + } + + fn create_rx_queue(&mut self) -> Option> { + if self.rx_created { + return None; + } + self.rx_created = true; + Some(Box::new(FxmacRxQueue { + inner: self.inner.clone(), + })) + } + + fn enable_irq(&mut self) { + self.irq_enabled = true; + } + + fn disable_irq(&mut self) { + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> Event { + let mut event = Event::none(); + event.tx_queue.insert(QUEUE_ID); + event.rx_queue.insert(QUEUE_ID); + event + } +} + +struct FxmacInner { + device: &'static mut FXmac, + hwaddr: [u8; 6], + rx_buffers: VecDeque, + rx_packets: VecDeque>, + tx_done: VecDeque, +} + +unsafe impl Send for FxmacInner {} + +#[derive(Clone, Copy)] +struct RuntimeNetBuffer { + virt: usize, + bus_addr: u64, + len: usize, +} + +impl From for RuntimeNetBuffer { + fn from(buffer: DmaBuffer) -> Self { + Self { + virt: buffer.virt.as_ptr() as usize, + bus_addr: buffer.bus_addr, + len: buffer.len, + } + } +} + +struct FxmacTxQueue { + inner: Arc>, +} + +impl ITxQueue for FxmacTxQueue { + fn id(&self) -> usize { + QUEUE_ID + } + + fn config(&self) -> QueueConfig { + fxmac_queue_config() + } + + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + let packet = unsafe { core::slice::from_raw_parts(buffer.virt.as_ptr(), buffer.len) }; + let mut inner = self.inner.lock(); + let ret = FXmacLwipPortTx(inner.device, vec![packet.to_vec()]); + if ret < 0 { + return Err(NetError::Retry); + } + inner.tx_done.push_back(buffer.bus_addr); + Ok(()) + } + + fn reclaim(&mut self) -> Option { + self.inner.lock().tx_done.pop_front() + } +} + +struct FxmacRxQueue { + inner: Arc>, +} + +impl IRxQueue for FxmacRxQueue { + fn id(&self) -> usize { + QUEUE_ID + } + + fn config(&self) -> QueueConfig { + fxmac_queue_config() + } + + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + self.inner.lock().rx_buffers.push_back(buffer.into()); + Ok(()) + } + + fn reclaim(&mut self) -> Option<(u64, usize)> { + let mut inner = self.inner.lock(); + if inner.rx_buffers.is_empty() { + return None; + } + + if inner.rx_packets.is_empty() + && let Some(packets) = FXmacRecvHandler(inner.device) + { + inner.rx_packets.extend(packets); + } + + let packet = inner.rx_packets.pop_front()?; + let buffer = inner.rx_buffers.pop_front()?; + let len = cmp::min(packet.len(), buffer.len); + unsafe { + core::ptr::copy_nonoverlapping(packet.as_ptr(), buffer.virt as *mut u8, len); + } + Some((buffer.bus_addr, len)) + } +} + +fn fxmac_queue_config() -> QueueConfig { + QueueConfig { + dma_mask: DMA_MASK, + align: DMA_ALIGN, + buf_size: BUFFER_SIZE, + ring_size: QUEUE_SIZE, + } +} + +struct FxmacKernelFunc; + +const _: FxmacKernelFunc = FxmacKernelFunc; + +#[ax_crate_interface::impl_interface] +impl fxmac_rs::KernelFunc for FxmacKernelFunc { + fn virt_to_phys(addr: usize) -> usize { + axklib::mem::virt_to_phys(addr.into()).as_usize() + } + + fn phys_to_virt(addr: usize) -> usize { + let base = addr & !(PAGE_SIZE - 1); + let offset = addr - base; + axklib::mem::iomap(base.into(), PAGE_SIZE) + .map(|virt| virt.as_usize() + offset) + .unwrap_or(addr) + } + + fn dma_alloc_coherent(pages: usize) -> (usize, usize) { + let Some(size) = pages.checked_mul(PAGE_SIZE) else { + log::error!("FXmac DMA allocation size overflow: {pages} pages"); + return (0, 0); + }; + let Ok(layout) = Layout::from_size_align(size.max(1), DMA_ALIGN) else { + log::error!("FXmac DMA allocation layout is invalid: {size} bytes"); + return (0, 0); + }; + let Some(handle) = (unsafe { axklib::dma::op().alloc_coherent(DMA_MASK, layout) }) else { + log::error!("FXmac DMA allocation failed: {pages} pages"); + return (0, 0); + }; + ( + handle.as_ptr().as_ptr() as usize, + handle.dma_addr().as_u64() as usize, + ) + } + + fn dma_free_coherent(vaddr: usize, pages: usize) { + let Some(size) = pages.checked_mul(PAGE_SIZE) else { + log::error!("FXmac DMA free size overflow: {pages} pages"); + return; + }; + let Ok(layout) = Layout::from_size_align(size.max(1), DMA_ALIGN) else { + log::error!("FXmac DMA free layout is invalid: {size} bytes"); + return; + }; + let Some(vaddr) = NonNull::new(vaddr as *mut u8) else { + return; + }; + let paddr = axklib::mem::virt_to_phys((vaddr.as_ptr() as usize).into()).as_usize(); + let handle = unsafe { DmaHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) }; + unsafe { axklib::dma::op().dealloc_coherent(handle) }; + } + + fn dma_request_irq(irq: usize, handler: fn(usize)) { + if axklib::irq::register(irq, handler) { + axklib::irq::set_enable(irq, true); + } else { + log::warn!("failed to register FXmac irq {irq}"); + } + } +} diff --git a/platform/axplat-dyn/src/drivers/net/intel.rs b/drivers/ax-driver/src/net/intel.rs similarity index 69% rename from platform/axplat-dyn/src/drivers/net/intel.rs rename to drivers/ax-driver/src/net/intel.rs index db5c663838..0c3d722c2b 100644 --- a/platform/axplat-dyn/src/drivers/net/intel.rs +++ b/drivers/ax-driver/src/net/intel.rs @@ -1,15 +1,17 @@ +use alloc::format; + use eth_intel::E1000; +use log::debug; use pcie::CommandRegister; use rdrive::{ - PlatformDevice, module_driver, + PlatformDevice, probe::{ OnProbeError, pci::{EndpointRc, FnOnProbe}, }, }; -use super::PlatformDeviceNet; -use crate::{boot::Kernel, drivers::DmaImpl}; +use crate::net::{PlatformDeviceNet, pci_legacy_irq}; const DRIVER_NAME: &str = "eth-intel-e1000"; @@ -28,7 +30,8 @@ fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnPr } let address = endpoint.address(); - let irq = super::pci_legacy_irq_for_address(address); + let irq = pci_legacy_irq(endpoint) + .ok_or_else(|| OnProbeError::other(format!("failed to resolve IRQ for E1000 {address}")))?; let Some(bar) = endpoint.bar_mmio(0) else { return Err(OnProbeError::other("E1000 BAR0 MMIO region missing")); }; @@ -38,8 +41,14 @@ fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnPr cmd }); - let dev = E1000::new(bar.start as u64, bar.count(), u64::MAX, &DmaImpl, &Kernel) - .map_err(|err| OnProbeError::other(alloc::format!("failed to create e1000: {err:?}")))?; + let dev = E1000::new( + bar.start as u64, + bar.count(), + u64::MAX, + axklib::dma::op(), + axklib::mmio::op(), + ) + .map_err(|err| OnProbeError::other(alloc::format!("failed to create e1000: {err:?}")))?; plat_dev.register_net(DRIVER_NAME, dev, Some(irq)); debug!( diff --git a/drivers/ax-driver/src/net/ixgbe.rs b/drivers/ax-driver/src/net/ixgbe.rs new file mode 100644 index 0000000000..acdadad041 --- /dev/null +++ b/drivers/ax-driver/src/net/ixgbe.rs @@ -0,0 +1,312 @@ +use alloc::{boxed::Box, collections::VecDeque, format, sync::Arc}; +use core::{alloc::Layout, cmp, ptr::NonNull, time::Duration}; + +use dma_api::{DmaAddr, DmaHandle, DmaOp}; +use ixgbe_driver::{ + INTEL_82599, INTEL_VEND, IxgbeDevice, IxgbeError, IxgbeHal, IxgbeNetBuf, MemPool, NicDevice, + PhysAddr, +}; +use pcie::CommandRegister; +use rd_net::{DmaBuffer, Event, IRxQueue, ITxQueue, NetError, QueueConfig}; +use rdrive::{ + DriverGeneric, PlatformDevice, + probe::{ + OnProbeError, + pci::{EndpointRc, FnOnProbe}, + }, +}; + +use crate::net::{PlatformDeviceNet, pci_legacy_irq}; + +const DRIVER_NAME: &str = "ixgbe"; +const QUEUE_SIZE: usize = 512; +const QUEUE_ID: u16 = 0; +const RECV_BATCH_SIZE: usize = 64; +const RX_BUFFER_QUEUE_SIZE: usize = 1024; +const MEM_POOL_ENTRIES: usize = 4096; +const MEM_POOL_ENTRY_SIZE: usize = 2048; +const DMA_MASK: u64 = u64::MAX; + +module_driver!( + name: "Intel 82599 PCI Network", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci as FnOnProbe, + }], +); + +fn probe_pci(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + if endpoint.vendor_id() != INTEL_VEND || endpoint.device_id() != INTEL_82599 { + return Err(OnProbeError::NotMatch); + } + + let address = endpoint.address(); + let irq = pci_legacy_irq(endpoint) + .ok_or_else(|| OnProbeError::other(format!("failed to resolve IRQ for ixgbe {address}")))?; + let Some(bar) = endpoint.bar_mmio(0) else { + return Err(OnProbeError::other("ixgbe BAR0 MMIO region missing")); + }; + let bar_start = bar.start; + let bar_len = bar.end.saturating_sub(bar_start); + + endpoint.update_command(|mut cmd| { + cmd.insert(CommandRegister::MEMORY_ENABLE | CommandRegister::BUS_MASTER_ENABLE); + cmd.remove(CommandRegister::INTERRUPT_DISABLE); + cmd + }); + + let mmio = axklib::mmio::ioremap_raw(bar_start.into(), bar_len) + .map_err(|err| OnProbeError::other(format!("failed to map ixgbe BAR0: {err:?}")))?; + + let dev = IxgbeNet::new(mmio.as_ptr() as usize, bar_len) + .map_err(|err| OnProbeError::other(format!("failed to initialize ixgbe: {err:?}")))?; + plat_dev.register_net(DRIVER_NAME, dev, Some(irq)); + log::info!("registered ixgbe PCI network device at {address} with irq {irq:#x}"); + Ok(()) +} + +struct IxgbeNet { + inner: Arc>, + tx_created: bool, + rx_created: bool, + irq_enabled: bool, +} + +impl IxgbeNet { + fn new(base: usize, len: usize) -> Result { + let mem_pool = MemPool::allocate::(MEM_POOL_ENTRIES, MEM_POOL_ENTRY_SIZE)?; + let device = IxgbeDevice::::init( + base, + len, + QUEUE_ID + 1, + QUEUE_ID + 1, + &mem_pool, + )?; + Ok(Self { + inner: Arc::new(spin::Mutex::new(IxgbeInner { + device, + mem_pool, + rx_ready: VecDeque::with_capacity(RX_BUFFER_QUEUE_SIZE), + rx_buffers: VecDeque::with_capacity(RX_BUFFER_QUEUE_SIZE), + tx_done: VecDeque::with_capacity(QUEUE_SIZE), + })), + tx_created: false, + rx_created: false, + irq_enabled: false, + }) + } +} + +impl DriverGeneric for IxgbeNet { + fn name(&self) -> &str { + DRIVER_NAME + } +} + +impl rd_net::Interface for IxgbeNet { + fn mac_address(&self) -> [u8; 6] { + self.inner.lock().device.get_mac_addr() + } + + fn create_tx_queue(&mut self) -> Option> { + if self.tx_created { + return None; + } + self.tx_created = true; + Some(Box::new(IxgbeTxQueue { + inner: self.inner.clone(), + })) + } + + fn create_rx_queue(&mut self) -> Option> { + if self.rx_created { + return None; + } + self.rx_created = true; + Some(Box::new(IxgbeRxQueue { + inner: self.inner.clone(), + })) + } + + fn enable_irq(&mut self) { + self.irq_enabled = true; + } + + fn disable_irq(&mut self) { + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> Event { + let mut event = Event::none(); + event.tx_queue.insert(QUEUE_ID as usize); + event.rx_queue.insert(QUEUE_ID as usize); + event + } +} + +struct IxgbeInner { + device: IxgbeDevice, + mem_pool: Arc, + rx_ready: VecDeque, + rx_buffers: VecDeque, + tx_done: VecDeque, +} + +unsafe impl Send for IxgbeInner {} + +#[derive(Clone, Copy)] +struct RuntimeNetBuffer { + virt: usize, + bus_addr: u64, + len: usize, +} + +impl From for RuntimeNetBuffer { + fn from(buffer: DmaBuffer) -> Self { + Self { + virt: buffer.virt.as_ptr() as usize, + bus_addr: buffer.bus_addr, + len: buffer.len, + } + } +} + +struct IxgbeTxQueue { + inner: Arc>, +} + +impl ITxQueue for IxgbeTxQueue { + fn id(&self) -> usize { + QUEUE_ID as usize + } + + fn config(&self) -> QueueConfig { + ixgbe_queue_config() + } + + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + let mut inner = self.inner.lock(); + let _ = inner.device.recycle_tx_buffers(QUEUE_ID); + let mut tx = IxgbeNetBuf::alloc(&inner.mem_pool, buffer.len).map_err(map_error)?; + let source = unsafe { core::slice::from_raw_parts(buffer.virt.as_ptr(), buffer.len) }; + tx.packet_mut().copy_from_slice(source); + inner.device.send(QUEUE_ID, tx).map_err(map_error)?; + inner.tx_done.push_back(buffer.bus_addr); + Ok(()) + } + + fn reclaim(&mut self) -> Option { + let mut inner = self.inner.lock(); + let _ = inner.device.recycle_tx_buffers(QUEUE_ID); + inner.tx_done.pop_front() + } +} + +struct IxgbeRxQueue { + inner: Arc>, +} + +impl IRxQueue for IxgbeRxQueue { + fn id(&self) -> usize { + QUEUE_ID as usize + } + + fn config(&self) -> QueueConfig { + ixgbe_queue_config() + } + + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + self.inner.lock().rx_buffers.push_back(buffer.into()); + Ok(()) + } + + fn reclaim(&mut self) -> Option<(u64, usize)> { + let mut inner = self.inner.lock(); + if inner.rx_buffers.is_empty() { + return None; + } + + if inner.rx_ready.is_empty() { + let mut received = VecDeque::with_capacity(RECV_BATCH_SIZE); + inner + .device + .receive_packets(QUEUE_ID, RECV_BATCH_SIZE, |packet| { + received.push_back(packet); + }) + .ok()?; + inner.rx_ready.extend(received); + } + + let packet = inner.rx_ready.pop_front()?; + let buffer = inner.rx_buffers.pop_front()?; + let len = cmp::min(packet.packet_len(), buffer.len); + unsafe { + core::ptr::copy_nonoverlapping(packet.packet().as_ptr(), buffer.virt as *mut u8, len); + } + let bus_addr = buffer.bus_addr; + drop(packet); + Some((bus_addr, len)) + } +} + +fn ixgbe_queue_config() -> QueueConfig { + QueueConfig { + dma_mask: DMA_MASK, + align: 0x1000, + buf_size: MEM_POOL_ENTRY_SIZE, + ring_size: QUEUE_SIZE, + } +} + +fn map_error(err: IxgbeError) -> NetError { + match err { + IxgbeError::QueueFull | IxgbeError::NotReady => NetError::Retry, + IxgbeError::NoMemory => NetError::NoMemory, + IxgbeError::QueueNotAligned | IxgbeError::PageNotAligned | IxgbeError::InvalidQueue => { + NetError::Other(Box::new(rd_net::KError::Unknown("ixgbe error"))) + } + } +} + +struct IxgbeOsHal; + +unsafe impl IxgbeHal for IxgbeOsHal { + fn dma_alloc(size: usize) -> (PhysAddr, NonNull) { + let layout = + Layout::from_size_align(size.max(1), 0x1000).expect("ixgbe DMA layout should be valid"); + let handle = unsafe { axklib::dma::op().alloc_coherent(DMA_MASK, layout) } + .expect("ixgbe DMA allocation failed"); + let paddr = handle.dma_addr().as_u64() as usize; + let vaddr = handle.as_ptr(); + (paddr, vaddr) + } + + unsafe fn dma_dealloc(paddr: PhysAddr, vaddr: NonNull, size: usize) -> i32 { + let Ok(layout) = Layout::from_size_align(size.max(1), 0x1000) else { + return -1; + }; + let handle = unsafe { DmaHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) }; + unsafe { axklib::dma::op().dealloc_coherent(handle) }; + 0 + } + + unsafe fn mmio_phys_to_virt(paddr: PhysAddr, size: usize) -> NonNull { + axklib::mmio::ioremap_raw(paddr.into(), size) + .expect("ixgbe MMIO mapping failed") + .as_nonnull_ptr() + } + + unsafe fn mmio_virt_to_phys(vaddr: NonNull, _size: usize) -> PhysAddr { + axklib::mem::virt_to_phys((vaddr.as_ptr() as usize).into()).as_usize() + } + + fn wait_until(duration: Duration) -> Result<(), &'static str> { + axklib::time::busy_wait(duration); + Ok(()) + } +} diff --git a/drivers/ax-driver/src/net/mod.rs b/drivers/ax-driver/src/net/mod.rs new file mode 100644 index 0000000000..075ac1796a --- /dev/null +++ b/drivers/ax-driver/src/net/mod.rs @@ -0,0 +1,12 @@ +mod binding; + +#[cfg(feature = "fxmac")] +pub mod fxmac; +#[cfg(feature = "intel-net")] +pub mod intel; +#[cfg(feature = "ixgbe")] +pub mod ixgbe; +#[cfg(feature = "realtek-rtl8125")] +pub mod realtek; + +pub use binding::*; diff --git a/platform/axplat-dyn/src/drivers/net/realtek.rs b/drivers/ax-driver/src/net/realtek.rs similarity index 89% rename from platform/axplat-dyn/src/drivers/net/realtek.rs rename to drivers/ax-driver/src/net/realtek.rs index 93bb673332..c6f87b78b3 100644 --- a/platform/axplat-dyn/src/drivers/net/realtek.rs +++ b/drivers/ax-driver/src/net/realtek.rs @@ -1,8 +1,9 @@ use core::sync::atomic::{AtomicBool, Ordering}; +use log::{debug, info, warn}; use pcie::CommandRegister; use rdrive::{ - PlatformDevice, module_driver, + PlatformDevice, probe::{ OnProbeError, pci::{EndpointRc, FnOnProbe}, @@ -10,8 +11,7 @@ use rdrive::{ }; use realtek_rtl8125::Rtl8125; -use super::PlatformDeviceNet; -use crate::{boot::Kernel, drivers::DmaImpl}; +use crate::net::{PlatformDeviceNet, pci_legacy_irq}; const DRIVER_NAME: &str = "realtek-rtl8125"; const RTL8125_DMA_MASK: u64 = u32::MAX as u64; @@ -37,7 +37,11 @@ fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnPr return Err(OnProbeError::NotMatch); } - let irq = super::pci_legacy_irq_for_address(address); + let irq = pci_legacy_irq(endpoint).ok_or_else(|| { + OnProbeError::other(alloc::format!( + "failed to resolve IRQ for RTL8125 {address}" + )) + })?; let Some((bar_index, bar)) = first_mmio_bar(endpoint) else { warn!("RTL8125 at {address} left unused: no PCI MMIO BAR found"); return Err(OnProbeError::NotMatch); @@ -63,8 +67,8 @@ fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnPr bar.start as u64, bar.count(), RTL8125_DMA_MASK, - &DmaImpl, - &Kernel, + axklib::dma::op(), + axklib::mmio::op(), ) .map_err(|err| OnProbeError::other(alloc::format!("failed to create RTL8125: {err:?}")))?; diff --git a/drivers/ax-driver/src/pci/fdt.rs b/drivers/ax-driver/src/pci/fdt.rs new file mode 100644 index 0000000000..19cb8a1ec4 --- /dev/null +++ b/drivers/ax-driver/src/pci/fdt.rs @@ -0,0 +1,250 @@ +extern crate alloc; + +use alloc::format; +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +use alloc::vec::Vec; + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +use fdt_edit::Fdt; +use fdt_edit::{NodeType, PciRange, PciSpace}; +use log::{debug, trace, warn}; +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +use rdrive::probe::pci::PciAddress; +use rdrive::{ + PlatformDevice, + probe::{ + OnProbeError, + pci::{PciMem32, PciMem64, PcieController, new_driver_generic}, + }, + register::FdtInfo, +}; + +#[cfg(feature = "rk3588-pcie")] +#[path = "rk3588.rs"] +mod rk3588; + +module_driver!( + name: "Generic PCIe Controller Driver", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["pci-host-ecam-generic"], + on_probe: probe_generic_ecam + } + ], +); + +fn probe_generic_ecam(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let NodeType::Pci(node) = info.node else { + return Err(OnProbeError::NotMatch); + }; + + let regs = node.regs(); + for reg in ®s { + trace!( + "pcie reg: {:#x}, bus: {:#x}", + reg.address, reg.child_bus_address + ); + } + + let reg = regs + .first() + .ok_or_else(|| OnProbeError::other("PCIe controller has no regs"))?; + let mmio_base = reg.address as usize; + let mmio_size = reg.size.unwrap_or(0x1000) as usize; + let mut drv = new_driver_generic(mmio_base, mmio_size, axklib::mmio::op()) + .map_err(|e| OnProbeError::other(format!("failed to create PCIe controller: {e:?}")))?; + + for range in node.ranges().unwrap_or_default() { + debug!("pcie range {range:?}"); + set_pcie_mem_range(&mut drv, &range); + } + let logical_bus_end = regs + .iter() + .map(|reg| reg.child_bus_address as u8) + .max() + .unwrap_or(0); + register_fdt_legacy_irq(&info, logical_bus_end); + + plat_dev.register_pcie(drv); + + Ok(()) +} + +pub(super) fn set_pcie_mem_range(drv: &mut PcieController, range: &PciRange) { + match range.space { + PciSpace::Memory32 => { + drv.set_mem32( + PciMem32 { + address: range.cpu_address as _, + size: range.size as _, + }, + range.prefetchable, + ); + } + PciSpace::Memory64 => { + drv.set_mem64( + PciMem64 { + address: range.cpu_address, + size: range.size, + }, + range.prefetchable, + ); + } + PciSpace::IO => {} + } +} + +pub(super) fn register_fdt_legacy_irq(info: &FdtInfo<'_>, logical_bus_end: u8) { + let Some(interrupt) = info + .interrupts() + .into_iter() + .find(|interrupt| interrupt.name.as_deref() == Some("legacy")) + else { + return; + }; + let Some(parent) = info.phandle_to_device_id(interrupt.interrupt_parent) else { + warn!( + "failed to resolve PCIe legacy IRQ parent phandle {}", + interrupt.interrupt_parent + ); + return; + }; + + let Ok(intc) = rdrive::get::(parent) else { + warn!( + "failed to get PCIe legacy IRQ parent device {:?} for phandle {}", + parent, interrupt.interrupt_parent + ); + return; + }; + let Ok(mut intc) = intc.lock() else { + warn!( + "failed to lock PCIe legacy IRQ parent device {:?} for phandle {}", + parent, interrupt.interrupt_parent + ); + return; + }; + + let irq: usize = intc.setup_irq_by_fdt(&interrupt.specifier).into(); + super::register_legacy_irq_route(0, logical_bus_end, irq); +} + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +pub fn fdt_irq_for_endpoint( + address: PciAddress, + interrupt_pin: u8, +) -> Result, OnProbeError> { + let Some(result) = + rdrive::with_fdt(|fdt| resolve_pci_irq_from_fdt(fdt, address, interrupt_pin)) + else { + return Ok(None); + }; + result.map(Some) +} + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +fn resolve_pci_irq_from_fdt( + fdt: &Fdt, + address: PciAddress, + interrupt_pin: u8, +) -> Result { + if interrupt_pin == 0 { + return Err(OnProbeError::other(format!( + "PCI endpoint {address} has no interrupt pin" + ))); + } + + let bus = address.bus(); + let mut candidates = Vec::new(); + let mut exact_range_matches = Vec::new(); + for node in fdt.all_nodes() { + let NodeType::Pci(pci) = node else { + continue; + }; + + match pci.bus_range() { + Some(range) if range.contains(&(bus as u32)) => { + exact_range_matches.push(pci); + candidates.push(pci); + } + Some(_) => {} + None => candidates.push(pci), + } + } + + let pci_host = if exact_range_matches.len() == 1 { + exact_range_matches[0] + } else if exact_range_matches.len() > 1 { + return Err(OnProbeError::other(format!( + "multiple PCI host nodes in FDT match endpoint {address} with the same bus-range" + ))); + } else if candidates.len() == 1 { + candidates[0] + } else if candidates.is_empty() { + return Err(OnProbeError::other(format!( + "no PCI host node in FDT matches endpoint {address}" + ))); + } else { + return Err(OnProbeError::other(format!( + "multiple PCI host nodes in FDT match endpoint {address} without a unique bus-range \ + match" + ))); + }; + + let irq = pci_host + .child_interrupts( + address.bus(), + address.device(), + address.function(), + interrupt_pin, + ) + .map_err(|err| { + OnProbeError::other(format!( + "failed to resolve PCI interrupt-map entry for endpoint {address}: {err:?}" + )) + })?; + + decode_irq_cells(&irq.irqs).ok_or_else(|| { + OnProbeError::other(format!( + "unsupported PCI interrupt specifier {:?} for endpoint {address}", + irq.irqs + )) + }) +} + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +fn decode_irq_cells(specifier: &[u32]) -> Option { + match specifier { + [irq] => Some(*irq as usize), + [kind, irq, ..] => match *kind { + 0 => Some(*irq as usize + 32), + 1 => Some(*irq as usize + 16), + _ => Some(*irq as usize), + }, + _ => None, + } +} + +#[cfg(feature = "pci-list-devices")] +mod pci_list_devices { + use log::info; + use rdrive::probe::pci::{EndpointRc, FnOnProbe}; + + use super::*; + + module_driver!( + name: "PCI Device Lister", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe as FnOnProbe + }], + ); + + fn probe(endpoint: &mut EndpointRc, _plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + info!("PCIe endpoint: {} bars={:?}", &**endpoint, endpoint.bars()); + Err(OnProbeError::NotMatch) + } +} diff --git a/drivers/ax-driver/src/pci/mod.rs b/drivers/ax-driver/src/pci/mod.rs new file mode 100644 index 0000000000..9295c6489e --- /dev/null +++ b/drivers/ax-driver/src/pci/mod.rs @@ -0,0 +1,305 @@ +use alloc::format; +#[cfg(virtio_dev)] +use alloc::sync::Arc; + +use heapless::Vec as ArrayVec; +#[cfg(virtio_dev)] +use rdrive::probe::pci::{Endpoint, EndpointRc}; +use rdrive::{ + PlatformDevice, + probe::{ + OnProbeError, + pci::{PciAddress, PciMem32, PciMem64}, + }, +}; +#[cfg(virtio_dev)] +use spin::Mutex; +use spin::Mutex as SpinMutex; +#[cfg(virtio_dev)] +use virtio_drivers::transport::{ + DeviceType, Transport, + pci::{ + PciTransport, + bus::{ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, HeaderType, PciRoot}, + virtio_device_type, + }, +}; + +#[cfg(virtio_dev)] +use crate::virtio::VirtIoHalImpl; + +#[cfg(feature = "pci-fdt")] +mod fdt; +#[cfg(all(feature = "pci-fdt", feature = "xhci-pci", target_os = "none"))] +pub(crate) use fdt::fdt_irq_for_endpoint; + +const MAX_PCIE_LEGACY_IRQS: usize = 8; +const PCI_INTX_LINES: usize = 4; + +#[derive(Clone, Copy)] +struct LegacyIrqRoute { + bus_start: u8, + bus_end: u8, + irqs: [usize; PCI_INTX_LINES], + irq_count: u8, +} + +impl LegacyIrqRoute { + fn from_irqs(bus_start: u8, bus_end: u8, irq_list: &[usize]) -> Option { + let irq_count = irq_list.len().min(PCI_INTX_LINES); + if irq_count == 0 { + return None; + } + + let mut irqs = [0; PCI_INTX_LINES]; + irqs[..irq_count].copy_from_slice(&irq_list[..irq_count]); + Some(Self { + bus_start, + bus_end, + irqs, + irq_count: irq_count as u8, + }) + } + + fn matches(&self, bus_start: u8, bus_end: u8, irq_list: &[usize]) -> bool { + let irq_count = irq_list.len().min(PCI_INTX_LINES); + self.bus_start == bus_start + && self.bus_end == bus_end + && usize::from(self.irq_count) == irq_count + && self.irqs[..irq_count] == irq_list[..irq_count] + } + + fn irq_for(&self, address: PciAddress, interrupt_pin: u8) -> Option { + if address.bus() < self.bus_start + || address.bus() > self.bus_end + || !(1..=PCI_INTX_LINES as u8).contains(&interrupt_pin) + { + return None; + } + + let irq_count = usize::from(self.irq_count); + let route_index = if irq_count == 1 { + 0 + } else { + (usize::from(address.device()) + usize::from(interrupt_pin) - 1) % irq_count + }; + Some(self.irqs[route_index]) + } +} + +static LEGACY_IRQ_ROUTES: SpinMutex> = + SpinMutex::new(ArrayVec::new()); + +pub const DEVICE_NAME: &str = "pci-ecam"; + +#[cfg(probe = "static")] +module_driver!( + name: "Static PCIe ECAM", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe_static, + }], +); + +#[cfg(probe = "static")] +fn probe_static( + info: rdrive::probe::static_::StaticInfo, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + if info.name() != DEVICE_NAME { + return Err(OnProbeError::NotMatch); + } + let Some(ecam) = info.pci_ecam() else { + return Err(OnProbeError::NotMatch); + }; + let mem32 = ecam.mem32.or_else(|| pci_mem32_from_ranges(ecam.ranges)); + let mem64 = ecam.mem64.or_else(|| pci_mem64_from_ranges(ecam.ranges)); + register_static_legacy_irq_routes(info.irqs(), ecam.size); + register_ecam_controller(plat_dev, ecam.base, ecam.size, mem32, mem64) +} + +#[cfg(probe = "static")] +fn register_static_legacy_irq_routes(irqs: &[usize], ecam_size: usize) { + if irqs.is_empty() { + return; + } + + let bus_count = ecam_size >> 20; + let bus_end = bus_count.saturating_sub(1).min(usize::from(u8::MAX)) as u8; + register_legacy_irq_routes(0, bus_end, irqs); +} + +#[cfg(probe = "static")] +fn pci_mem32_from_ranges(ranges: &[(usize, usize)]) -> Option { + let (address, size) = ranges.get(1).copied()?; + if size == 0 { + return None; + } + Some(PciMem32 { + address: u32::try_from(address).ok()?, + size: u32::try_from(size).ok()?, + }) +} + +#[cfg(probe = "static")] +fn pci_mem64_from_ranges(ranges: &[(usize, usize)]) -> Option { + let (address, size) = ranges.get(2).copied()?; + if size == 0 || usize::BITS <= 32 { + return None; + } + Some(PciMem64 { + address: address as u64, + size: size as u64, + }) +} + +pub fn register_ecam_controller( + plat_dev: PlatformDevice, + ecam_base: usize, + ecam_size: usize, + mem32: Option, + mem64: Option, +) -> Result<(), OnProbeError> { + if ecam_base == 0 || ecam_size == 0 { + return Err(OnProbeError::NotMatch); + } + + let mut controller = + rdrive::probe::pci::new_driver_generic(ecam_base, ecam_size, axklib::mmio::op()).map_err( + |err| OnProbeError::other(format!("failed to create PCIe controller: {err:?}")), + )?; + + if let Some(mem32) = mem32 { + controller.set_mem32(mem32, false); + } + if let Some(mem64) = mem64 { + controller.set_mem64(mem64, true); + } + plat_dev.register_pcie(controller); + log::info!("registered PCIe ECAM controller"); + Ok(()) +} + +pub fn legacy_irq_for_endpoint(address: PciAddress, interrupt_pin: u8) -> Option { + LEGACY_IRQ_ROUTES + .lock() + .iter() + .find_map(|route| route.irq_for(address, interrupt_pin)) +} + +pub fn legacy_irq_for_address(address: PciAddress) -> Option { + legacy_irq_for_endpoint(address, 1) +} + +pub fn register_legacy_irq_route(bus_start: u8, bus_end: u8, irq: usize) { + register_legacy_irq_routes(bus_start, bus_end, &[irq]); +} + +pub fn register_legacy_irq_routes(bus_start: u8, bus_end: u8, irqs: &[usize]) { + let Some(route) = LegacyIrqRoute::from_irqs(bus_start, bus_end, irqs) else { + return; + }; + + let mut routes = LEGACY_IRQ_ROUTES.lock(); + if routes + .iter() + .any(|route| route.matches(bus_start, bus_end, irqs)) + { + return; + } + if routes.push(route).is_err() { + log::warn!("too many PCIe legacy IRQ routes; dropping IRQs {irqs:?}"); + } else { + log::info!("PCIe legacy IRQ route: logical bus {bus_start}..={bus_end} -> IRQs {irqs:?}"); + } +} + +#[cfg(virtio_dev)] +pub fn take_virtio_transport( + endpoint: &mut EndpointRc, + expected: DeviceType, +) -> Result { + match (endpoint.vendor_id(), endpoint.device_id()) { + (0x1af4, 0x1000..=0x107f) => {} + _ => return Err(OnProbeError::NotMatch), + } + + let bdf = as_device_function(endpoint.address()); + let dev_info = as_device_function_info(endpoint); + let ty = virtio_device_type(&dev_info).ok_or(OnProbeError::NotMatch)?; + if ty != expected { + return Err(OnProbeError::NotMatch); + } + + let mut root = PciRoot::new(EndpointConfigAccess::new(bdf, endpoint.take())); + PciTransport::new::(&mut root, bdf).map_err(|err| { + OnProbeError::other(format!( + "failed to create VirtIO PCI transport at {bdf}: {err:?}" + )) + }) +} + +#[cfg(virtio_dev)] +fn as_device_function(address: rdrive::probe::pci::PciAddress) -> DeviceFunction { + DeviceFunction { + bus: address.bus(), + device: address.device(), + function: address.function(), + } +} + +#[cfg(virtio_dev)] +fn as_device_function_info(endpoint: &Endpoint) -> DeviceFunctionInfo { + let class_info = endpoint.revision_and_class(); + let header_type = HeaderType::from(((endpoint.read(0x0c) >> 16) as u8) & 0x7f); + DeviceFunctionInfo { + vendor_id: endpoint.vendor_id(), + device_id: endpoint.device_id(), + class: class_info.base_class, + subclass: class_info.sub_class, + prog_if: class_info.interface, + revision: class_info.revision_id, + header_type, + } +} + +#[cfg(virtio_dev)] +struct EndpointConfigAccess { + bdf: DeviceFunction, + endpoint: Arc>, +} + +#[cfg(virtio_dev)] +impl EndpointConfigAccess { + fn new(bdf: DeviceFunction, endpoint: Endpoint) -> Self { + Self { + bdf, + endpoint: Arc::new(Mutex::new(endpoint)), + } + } + + fn assert_same_function(&self, device_function: DeviceFunction) { + assert_eq!(device_function, self.bdf); + } +} + +#[cfg(virtio_dev)] +impl ConfigurationAccess for EndpointConfigAccess { + fn read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 { + self.assert_same_function(device_function); + self.endpoint.lock().read(register_offset.into()) + } + + fn write_word(&mut self, device_function: DeviceFunction, register_offset: u8, data: u32) { + self.assert_same_function(device_function); + self.endpoint.lock().write(register_offset.into(), data); + } + + unsafe fn unsafe_clone(&self) -> Self { + Self { + bdf: self.bdf, + endpoint: Arc::clone(&self.endpoint), + } + } +} diff --git a/drivers/ax-driver/src/pci/rk3588.rs b/drivers/ax-driver/src/pci/rk3588.rs new file mode 100644 index 0000000000..b63115928a --- /dev/null +++ b/drivers/ax-driver/src/pci/rk3588.rs @@ -0,0 +1,11 @@ +pub(super) use super::{register_fdt_legacy_irq, set_pcie_mem_range}; + +mod body { + use log::{debug, info, warn}; + + include!("rk3588/resources.rs"); + include!("rk3588/clocks_reset_gpio.rs"); + include!("rk3588/phy.rs"); + include!("rk3588/windows.rs"); + include!("rk3588/slots.rs"); +} diff --git a/drivers/ax-driver/src/pci/rk3588/clocks_reset_gpio.rs b/drivers/ax-driver/src/pci/rk3588/clocks_reset_gpio.rs new file mode 100644 index 0000000000..dacfa3830d --- /dev/null +++ b/drivers/ax-driver/src/pci/rk3588/clocks_reset_gpio.rs @@ -0,0 +1,225 @@ +fn clock_specs_for_node(node: NodeType<'_>) -> Vec { + let assigned_clocks = node + .as_node() + .get_property("assigned-clocks") + .map(|prop| { + let vals = prop.get_u32_iter().collect::>(); + let mut ids = Vec::new(); + for cells in vals.chunks(2) { + if let [_, id] = cells { + ids.push(*id); + } + } + ids + }) + .unwrap_or_default(); + let assigned_rates = node + .as_node() + .get_property("assigned-clock-rates") + .map(|prop| prop.get_u32_iter().collect::>()) + .unwrap_or_default(); + + node.clocks() + .into_iter() + .filter_map(|clock| { + let assigned_rate = clock.specifier.first().and_then(|id| { + assigned_clocks + .iter() + .position(|assigned| assigned == id) + .and_then(|index| assigned_rates.get(index).copied()) + .filter(|rate| *rate != 0) + }); + let id = *clock.specifier.first()?; + Some(ClockSpec { + name: clock.name, + id, + assigned_rate, + }) + }) + .collect() +} + +fn clock_specs(clocks: Vec) -> Vec { + clocks + .into_iter() + .filter_map(|clock| { + let id = *clock.specifier.first()?; + Some(ClockSpec { + name: clock.name, + id, + assigned_rate: None, + }) + }) + .collect() +} + +fn enable_clocks(clocks: &[ClockSpec]) -> Result<(), OnProbeError> { + for clock in clocks { + let id = clock.id; + if id == 0 { + continue; + } + if let Some(rate) = clock.assigned_rate { + rk3588_set_clock_rate(id, u64::from(rate)).map_err(|err| { + OnProbeError::other(format!( + "failed to set RK3588 PCIe clock {:?} ({id:#x}) rate to {rate}: {err}", + clock.name + )) + })?; + } + rk3588_enable_clock(id).map_err(|err| { + OnProbeError::other(format!( + "failed to enable RK3588 PCIe clock {:?} ({id:#x}): {err}", + clock.name + )) + })?; + } + Ok(()) +} + +fn parse_resets(node: NodeType<'_>) -> Result, OnProbeError> { + let Some(prop) = node.as_node().get_property("resets") else { + return Ok(Vec::new()); + }; + let cells = prop.get_u32_iter().collect::>(); + if cells.len() % 2 != 0 { + return Err(OnProbeError::other(format!( + "[{}] has malformed resets", + node.name() + ))); + } + let reset_names = prop_str_list(node.as_node(), "reset-names"); + Ok(cells + .chunks(2) + .enumerate() + .map(|(idx, chunk)| ResetSpec { + name: reset_names.get(idx).cloned(), + id: u64::from(chunk[1]), + }) + .collect()) +} + +fn assert_resets(resets: &[ResetSpec]) -> Result<(), OnProbeError> { + for reset in resets { + rk3588_reset_assert(reset.id).map_err(|err| { + OnProbeError::other(format!( + "failed to assert RK3588 PCIe reset {:?} ({:#x}): {err}", + reset.name, reset.id + )) + })?; + } + Ok(()) +} + +fn deassert_resets(resets: &[ResetSpec]) -> Result<(), OnProbeError> { + for reset in resets { + rk3588_reset_deassert(reset.id).map_err(|err| { + OnProbeError::other(format!( + "failed to deassert RK3588 PCIe reset {:?} ({:#x}): {err}", + reset.name, reset.id + )) + })?; + } + Ok(()) +} + +fn parse_reset_gpio(info: &FdtInfo<'_>, apb_base: u64) -> Result, OnProbeError> { + if let Some(gpio) = parse_gpio_spec(info.node, "reset-gpios")? { + return Ok(Some(gpio)); + } + + if let Some(default) = rk3588_pcie_reset_pin(apb_base) { + warn!( + "Rockchip RK3588 PCIe host {:#x}: reset-gpios missing; using diagnostic fallback \ + GPIO{} pin {}", + apb_base, default.bank, default.pin + ); + return Ok(Some(GpioSpec { + bank: default.bank, + pin: default.pin, + active_high: default.active_high, + })); + } + + Ok(None) +} + +fn parse_gpio_spec( + node_type: NodeType<'_>, + prop_name: &str, +) -> Result, OnProbeError> { + let node = node_type.as_node(); + let Some(prop) = node.get_property(prop_name) else { + return Ok(None); + }; + let mut cells = prop.get_u32_iter(); + let phandle_raw = cells.next().ok_or_else(|| { + OnProbeError::other(format!("[{}] has malformed {prop_name}", node.name())) + })?; + let pin = cells.next().ok_or_else(|| { + OnProbeError::other(format!("[{}] has malformed {prop_name}", node.name())) + })?; + let flags = cells.next().unwrap_or(0); + let bank = gpio_bank_from_phandle(Phandle::from(phandle_raw))?; + Ok(Some(GpioSpec { + bank, + pin: pin.try_into().map_err(|_| { + OnProbeError::other(format!( + "[{}] {prop_name} pin {pin} does not fit RK3588 GPIO", + node.name() + )) + })?, + active_high: flags & 1 == 0, + })) +} + +fn gpio_bank_from_phandle(phandle: Phandle) -> Result { + let fdt = live_fdt()?; + let gpio = fdt + .get_by_phandle(phandle) + .ok_or_else(|| OnProbeError::other(format!("GPIO phandle {phandle:?} not found")))?; + gpio_bank_index(gpio.as_node()).ok_or_else(|| { + OnProbeError::other(format!( + "failed to resolve RK3588 GPIO bank for phandle {phandle:?}" + )) + }) +} + +fn gpio_bank_index(node: &Node) -> Option { + let name = node.name(); + if let Some(name) = name + .strip_prefix("gpio") + .filter(|name| !name.starts_with('@')) + && let Some(bank) = name + .chars() + .next() + .and_then(|ch| ch.to_digit(10)) + .and_then(|bank| u8::try_from(bank).ok()) + .filter(|bank| usize::from(*bank) < RK3588_GPIO_BASES.len()) + { + return Some(bank); + } + + let address = gpio_bank_address(node)?; + RK3588_GPIO_BASES + .iter() + .position(|base| *base == address) + .and_then(|bank| u8::try_from(bank).ok()) +} + +fn gpio_bank_address(node: &Node) -> Option { + if let Some(address) = node + .name() + .split_once('@') + .and_then(|(_, unit)| u64::from_str_radix(unit, 16).ok()) + { + return Some(address); + } + + let reg = node.get_property("reg")?.get_u32_iter().collect::>(); + match reg.as_slice() { + [addr] => Some(u64::from(*addr)), + cells if cells.len() >= 2 => Some((u64::from(cells[0]) << 32) | u64::from(cells[1])), + _ => None, + } +} diff --git a/drivers/ax-driver/src/pci/rk3588/phy.rs b/drivers/ax-driver/src/pci/rk3588/phy.rs new file mode 100644 index 0000000000..d23c42059c --- /dev/null +++ b/drivers/ax-driver/src/pci/rk3588/phy.rs @@ -0,0 +1,309 @@ +fn parse_phys(node_type: NodeType<'_>) -> Result, OnProbeError> { + let node = node_type.as_node(); + let Some(prop) = node.get_property("phys") else { + return Ok(Vec::new()); + }; + let cells = prop.get_u32_iter().collect::>(); + if cells.is_empty() { + return Ok(Vec::new()); + } + let phy_names = prop_str_list(node, "phy-names"); + let mut refs = Vec::new(); + let mut index = 0; + let mut offset = 0; + while offset < cells.len() { + let phandle = Phandle::from(cells[offset]); + offset += 1; + let specifier_cells = phy_cells(phandle)?; + if offset + specifier_cells > cells.len() { + return Err(OnProbeError::other(format!( + "[{}] has truncated phys entry for phandle {phandle:?}", + node.name() + ))); + } + let specifier = cells[offset..offset + specifier_cells].to_vec(); + offset += specifier_cells; + refs.push(PhyRef { + phandle, + specifier, + name: phy_names.get(index).cloned(), + }); + index += 1; + } + Ok(refs) +} + +fn init_phys(host_node: NodeType<'_>, phys: &[PhyRef]) -> Result<(), OnProbeError> { + if phys.is_empty() { + warn!( + "Rockchip RK3588 PCIe host {} has no phys property", + host_node.name() + ); + return Ok(()); + } + let fdt = live_fdt()?; + for phy_ref in phys { + let phy = fdt.get_by_phandle(phy_ref.phandle).ok_or_else(|| { + OnProbeError::other(format!( + "PCIe PHY phandle {:?} for {} not found", + phy_ref.phandle, + host_node.name() + )) + })?; + if is_compatible(phy.as_node(), "rockchip,rk3588-pcie3-phy") { + let resources = parse_pcie3_phy(phy)?; + init_pcie3_phy(&resources)?; + } else if is_compatible(phy.as_node(), "rockchip,rk3588-naneng-combphy") { + let Some(&phy_type) = phy_ref.specifier.first() else { + return Err(OnProbeError::other(format!( + "RK3588 combphy {} referenced by {} has no PHY type specifier", + phy.name(), + host_node.name() + ))); + }; + if phy_type != PHY_TYPE_PCIE { + return Err(OnProbeError::other(format!( + "RK3588 combphy {} referenced by {} is type {}, expected PCIe", + phy.name(), + host_node.name(), + phy_type + ))); + } + let resources = parse_combphy(phy)?; + init_combphy(&resources)?; + } else { + return Err(OnProbeError::other(format!( + "unsupported RK3588 PCIe PHY {} referenced by {}", + phy.name(), + host_node.name() + ))); + } + } + Ok(()) +} + +fn parse_pcie3_phy(phy: NodeType<'_>) -> Result { + let node = phy.as_node(); + let reg = phy + .regs() + .into_iter() + .next() + .ok_or_else(|| OnProbeError::other(format!("[{}] has no reg", phy.name())))?; + let phy_grf = prop_phandle(node, "rockchip,phy-grf") + .ok_or_else(|| OnProbeError::other(format!("[{}] has no rockchip,phy-grf", phy.name())))?; + let mut pcie30_phymode = + prop_u32(node, "rockchip,pcie30-phymode").unwrap_or(RK3588_PCIE3PHY_DEFAULT_MODE); + if pcie30_phymode > RK3588_PCIE3PHY_DEFAULT_MODE { + pcie30_phymode = RK3588_PCIE3PHY_DEFAULT_MODE; + } + Ok(Pcie3PhyResources { + name: phy.name().to_string(), + reg, + phy_grf, + pipe_grf: prop_phandle(node, "rockchip,pipe-grf"), + pcie30_phymode, + clocks: clock_specs_for_node(phy), + resets: parse_resets(phy)?, + }) +} + +fn init_pcie3_phy(phy: &Pcie3PhyResources) -> Result<(), OnProbeError> { + let _mmio = RegMmio::map_reg(phy.reg)?; + let phy_grf = RegMmio::map_phandle(phy.phy_grf, "rk3588-pcie3-phy rockchip,phy-grf")?; + let pipe_grf = phy + .pipe_grf + .map(|phandle| RegMmio::map_phandle(phandle, "rk3588-pcie3-phy rockchip,pipe-grf")) + .transpose()?; + + enable_clocks(&phy.clocks)?; + for reset in &phy.resets { + rk3588_reset_assert(reset.id).map_err(|err| { + OnProbeError::other(format!( + "failed to assert RK3588 PCIe3 PHY reset {:?} ({:#x}): {err}", + reset.name, reset.id + )) + })?; + } + axklib::time::busy_wait(Duration::from_micros(1)); + + phy_grf.write32( + RK3588_PCIE3PHY_CMN_CON0, + (0x7 << BIT_WRITEABLE_SHIFT) | phy.pcie30_phymode, + ); + if let Some(pipe_grf) = pipe_grf.as_ref() { + let mode = phy.pcie30_phymode & 3; + if mode != 0 { + pipe_grf.write32(PHP_GRF_PCIESEL_CON, (mode << BIT_WRITEABLE_SHIFT) | mode); + } + } + phy_grf.write32(RK3588_PCIE3PHY_CMN_CON0, (1 << 24) | (1 << 8)); + + for reset in &phy.resets { + rk3588_reset_deassert(reset.id).map_err(|err| { + OnProbeError::other(format!( + "failed to deassert RK3588 PCIe3 PHY reset {:?} ({:#x}): {err}", + reset.name, reset.id + )) + })?; + } + poll_pcie3_sram_ready(&phy_grf, RK3588_PCIE3PHY_PHY0_STATUS1, &phy.name)?; + poll_pcie3_sram_ready(&phy_grf, RK3588_PCIE3PHY_PHY1_STATUS1, &phy.name)?; + info!( + "RK3588 PCIe3 PHY {} initialized, mode={}", + phy.name, phy.pcie30_phymode + ); + Ok(()) +} + +fn poll_pcie3_sram_ready(phy_grf: &RegMmio, offset: usize, name: &str) -> Result<(), OnProbeError> { + for _ in 0..500 { + if phy_grf.read32(offset) & PCIE3PHY_SRAM_INIT_DONE != 0 { + return Ok(()); + } + axklib::time::busy_wait(Duration::from_micros(1)); + } + Err(OnProbeError::other(format!( + "RK3588 PCIe3 PHY {name} SRAM ready timeout at GRF offset {offset:#x}" + ))) +} + +fn parse_combphy(phy: NodeType<'_>) -> Result { + let node = phy.as_node(); + let reg = phy + .regs() + .into_iter() + .next() + .ok_or_else(|| OnProbeError::other(format!("[{}] has no reg", phy.name())))?; + let pipe_grf = prop_phandle(node, "rockchip,pipe-grf") + .ok_or_else(|| OnProbeError::other(format!("[{}] has no rockchip,pipe-grf", phy.name())))?; + let pipe_phy_grf = prop_phandle(node, "rockchip,pipe-phy-grf").ok_or_else(|| { + OnProbeError::other(format!("[{}] has no rockchip,pipe-phy-grf", phy.name())) + })?; + let pcie1ln_sel_bits = node + .get_property("rockchip,pcie1ln-sel-bits") + .map(|prop| { + let vals = prop.get_u32_iter().collect::>(); + if vals.len() != 4 { + return Err(OnProbeError::other(format!( + "[{}] malformed rockchip,pcie1ln-sel-bits", + phy.name() + ))); + } + Ok([vals[0], vals[1], vals[2], vals[3]]) + }) + .transpose()?; + + Ok(CombphyResources { + name: phy.name().to_string(), + reg, + pipe_grf, + pipe_phy_grf, + pcie1ln_sel_bits, + refclk_rate: assigned_clock_rate(node).unwrap_or(100_000_000), + clocks: clock_specs_for_node(phy), + resets: parse_resets(phy)?, + }) +} + +fn init_combphy(phy: &CombphyResources) -> Result<(), OnProbeError> { + let mmio = RegMmio::map_reg(phy.reg)?; + let pipe_grf = RegMmio::map_phandle(phy.pipe_grf, "rk3588-naneng-combphy rockchip,pipe-grf")?; + let phy_grf = RegMmio::map_phandle( + phy.pipe_phy_grf, + "rk3588-naneng-combphy rockchip,pipe-phy-grf", + )?; + + assert_resets(&phy.resets)?; + enable_clocks(&phy.clocks)?; + if let Some([offset, start, end, value]) = phy.pcie1ln_sel_bits { + let mask = bit_range_mask(start, end)?; + pipe_grf.write32( + offset as usize, + (mask << BIT_WRITEABLE_SHIFT) | (value << start), + ); + } + combphy_update(&mmio, 0x7c, bit_range_mask(4, 5)?, 1 << 4); + combphy_param_write(&phy_grf, 0x0000, 0, 15, 0x1000)?; + combphy_param_write(&phy_grf, 0x0004, 0, 15, 0x0000)?; + combphy_param_write(&phy_grf, 0x0008, 0, 15, 0x0101)?; + combphy_param_write(&phy_grf, 0x000c, 0, 15, 0x0200)?; + + match phy.refclk_rate { + 24_000_000 => init_combphy_refclk_24m(&mmio, &phy_grf)?, + 25_000_000 => combphy_param_write(&phy_grf, 0x0004, 13, 14, 0x01)?, + 100_000_000 => init_combphy_refclk_100m(&mmio, &phy_grf)?, + rate => { + return Err(OnProbeError::other(format!( + "RK3588 combphy {} unsupported refclk rate {}", + phy.name, rate + ))); + } + } + + combphy_update(&mmio, 0x19 << 2, 1 << 5, 1 << 5); + deassert_resets(&phy.resets)?; + info!( + "RK3588 Naneng combphy {} initialized for PCIe, refclk={}Hz", + phy.name, phy.refclk_rate + ); + Ok(()) +} + +fn init_combphy_refclk_24m(mmio: &RegMmio, phy_grf: &RegMmio) -> Result<(), OnProbeError> { + combphy_param_write(phy_grf, 0x0004, 13, 14, 0x00)?; + combphy_update(mmio, 0x20 << 2, bit_range_mask(2, 4)?, 0x4 << 2); + mmio.write32(0x1b << 2, 0x00); + mmio.write32(0x0a << 2, 0x90); + mmio.write32(0x0b << 2, 0x02); + mmio.write32(0x0d << 2, 0x57); + mmio.write32(0x0f << 2, 0x5f); + Ok(()) +} + +fn init_combphy_refclk_100m(mmio: &RegMmio, phy_grf: &RegMmio) -> Result<(), OnProbeError> { + combphy_param_write(phy_grf, 0x0004, 13, 14, 0x02)?; + mmio.write32(0x74, 0xc0); + combphy_update(mmio, 0x20 << 2, bit_range_mask(2, 4)?, 0x4 << 2); + mmio.write32(0x1b << 2, 0x4c); + mmio.write32(0x0a << 2, 0x90); + mmio.write32(0x0b << 2, 0x43); + mmio.write32(0x0c << 2, 0x88); + mmio.write32(0x0d << 2, 0x56); + Ok(()) +} + +fn combphy_param_write( + mmio: &RegMmio, + offset: usize, + start: u32, + end: u32, + value: u32, +) -> Result<(), OnProbeError> { + let mask = bit_range_mask(start, end)?; + mmio.write32(offset, (value << start) | (mask << BIT_WRITEABLE_SHIFT)); + Ok(()) +} + +fn combphy_update(mmio: &RegMmio, offset: usize, mask: u32, value: u32) { + mmio.update32(offset, mask, value); +} + +fn bit_range_mask(start: u32, end: u32) -> Result { + if start > end || end >= 32 { + return Err(OnProbeError::other(format!( + "invalid bit range {}..={}", + start, end + ))); + } + let width = end - start + 1; + Ok(if width == 32 { + u32::MAX + } else { + ((1_u32 << width) - 1) << start + }) +} + +fn assigned_clock_rate(node: &Node) -> Option { + node.get_property("assigned-clock-rates") + .and_then(|prop| prop.get_u32_iter().next()) +} diff --git a/drivers/ax-driver/src/pci/rk3588/resources.rs b/drivers/ax-driver/src/pci/rk3588/resources.rs new file mode 100644 index 0000000000..d2f9411854 --- /dev/null +++ b/drivers/ax-driver/src/pci/rk3588/resources.rs @@ -0,0 +1,437 @@ +extern crate alloc; + +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; +use core::{ + sync::atomic::{AtomicU32, Ordering}, + time::Duration, +}; + +use fdt_edit::{ClockRef, Fdt, Node, PciRange, PciSpace, Phandle, RegFixed}; +use mmio_api::{MmioAddr, MmioRaw}; +use rdif_pcie::{PciMem64, PcieController}; +use rdrive::{ + PlatformDevice, + probe::{OnProbeError, fdt::NodeType}, + register::FdtInfo, +}; +use rk3588_pci::{ + Delay, HostConfig, IatuMode, MEM_ATU_FIRST_REGION, OutboundWindow, ResetControl, Rk3588PcieHost, +}; + +use crate::soc::{ + RockchipPinCtrl, rk3588_enable_clock, rk3588_enable_power_domain, rk3588_reset_assert, + rk3588_reset_deassert, rk3588_set_clock_rate, +}; + +const RK3588_GPIO_BASES: [u64; 5] = [ + 0xfd8a_0000, + 0xfec2_0000, + 0xfec3_0000, + 0xfec4_0000, + 0xfec5_0000, +]; +const RK3588_GPIO_SIZE: usize = 0x110; +const RK3588_GPIO_SWPORT_DR_L: usize = 0x00; +const RK3588_GPIO_SWPORT_DR_H: usize = 0x04; +const RK3588_GPIO_SWPORT_DDR_L: usize = 0x08; +const RK3588_GPIO_SWPORT_DDR_H: usize = 0x0c; +const RK3588_PCIE_PERST_INACTIVE_MS: u64 = 200; +const DEFAULT_CFG_SIZE: u64 = 0x10_0000; +const PHY_TYPE_PCIE: u32 = 2; +const RK3588_PCIE3PHY_DEFAULT_MODE: u32 = 4; +const RK3588_PCIE3PHY_CMN_CON0: usize = 0x000; +const RK3588_PCIE3PHY_PHY0_STATUS1: usize = 0x904; +const RK3588_PCIE3PHY_PHY1_STATUS1: usize = 0xa04; +const PHP_GRF_PCIESEL_CON: usize = 0x100; +const PCIE3PHY_SRAM_INIT_DONE: u32 = 1; +const BIT_WRITEABLE_SHIFT: u32 = 16; +const RK3588_PCIE_MAX_HOSTS: u32 = 8; +static PROBED_HOST_MASK: AtomicU32 = AtomicU32::new(0); + +struct AxDelay; + +impl Delay for AxDelay { + fn delay_us(&self, us: u64) { + axklib::time::busy_wait(Duration::from_micros(us)); + } + + fn delay_ms(&self, ms: u64) { + axklib::time::busy_wait(Duration::from_millis(ms)); + } +} + +struct Rk3588GpioReset { + apb_phys: u64, + bank: u8, + pin: u8, + active_high: bool, + gpio: MmioRaw, +} + +impl Rk3588GpioReset { + fn map(apb_phys: u64, bank: u8, pin: u8, active_high: bool) -> Result { + let phys = *RK3588_GPIO_BASES + .get(usize::from(bank)) + .ok_or_else(|| OnProbeError::other(format!("invalid RK3588 GPIO bank {}", bank)))?; + Ok(Self { + apb_phys, + bank, + pin, + active_high, + gpio: map_mmio(phys, RK3588_GPIO_SIZE)?, + }) + } + + fn set_logical(&self, value: bool) { + let physical = if self.active_high { value } else { !value }; + self.write_masked_pair(RK3588_GPIO_SWPORT_DR_L, RK3588_GPIO_SWPORT_DR_H, physical); + self.write_masked_pair(RK3588_GPIO_SWPORT_DDR_L, RK3588_GPIO_SWPORT_DDR_H, true); + } + + fn write_masked_pair(&self, low_offset: usize, high_offset: usize, value: bool) { + let pin = u32::from(self.pin); + let (offset, shift) = if pin < 16 { + (low_offset, pin) + } else { + (high_offset, pin - 16) + }; + let mask = 1_u32 << (shift + 16); + let data = u32::from(value) << shift; + self.gpio.write::(offset, mask | data); + } +} + +impl ResetControl for Rk3588GpioReset { + fn assert_perst(&mut self) { + self.set_logical(false); + info!( + "Rockchip RK3588 PCIe host {:#x}: assert PERST via GPIO{} pin {}", + self.apb_phys, self.bank, self.pin + ); + } + + fn deassert_perst(&mut self) { + self.set_logical(true); + info!( + "Rockchip RK3588 PCIe host {:#x}: release PERST after {}ms", + self.apb_phys, RK3588_PCIE_PERST_INACTIVE_MS + ); + } +} + +struct RegMmio { + mmio: MmioRaw, + size: usize, +} + +impl RegMmio { + fn map_phandle(phandle: Phandle, context: &str) -> Result { + let fdt = live_fdt()?; + let node = fdt.get_by_phandle(phandle).ok_or_else(|| { + OnProbeError::other(format!("{context} phandle {phandle:?} not found")) + })?; + let reg = node.regs().into_iter().next().ok_or_else(|| { + OnProbeError::other(format!("[{}] has no reg for {context}", node.name())) + })?; + Self::map_reg(reg) + } + + fn map_reg(reg: RegFixed) -> Result { + let size = align_up_4k((reg.size.unwrap_or(0x1000) as usize).max(1)); + let mmio = map_mmio(reg.address, size)?; + Ok(Self { mmio, size }) + } + + fn read32(&self, offset: usize) -> u32 { + debug_assert!(offset + core::mem::size_of::() <= self.size); + self.mmio.read::(offset) + } + + fn write32(&self, offset: usize, value: u32) { + debug_assert!(offset + core::mem::size_of::() <= self.size); + self.mmio.write::(offset, value); + } + + fn update32(&self, offset: usize, mask: u32, value: u32) { + let current = self.read32(offset); + self.write32(offset, (current & !mask) | value); + } +} + +#[derive(Clone)] +struct ClockSpec { + name: Option, + id: u32, + assigned_rate: Option, +} + +#[derive(Clone)] +struct ResetSpec { + name: Option, + id: u64, +} + +#[derive(Clone, Copy)] +struct GpioSpec { + bank: u8, + pin: u8, + active_high: bool, +} + +struct HostResources<'a> { + name: String, + node: NodeType<'a>, + apb: RegFixed, + dbi: RegFixed, + cfg_phys: u64, + cfg_size: u64, + ranges: Vec, + bus_base: u8, + logical_bus_end: u8, + power_domains: Vec, + clocks: Vec, + resets: Vec, + pipe_grf: Option, + reset_gpio: Option, + supply: Option, + phys: Vec, +} + +#[derive(Clone)] +struct PhyRef { + phandle: Phandle, + specifier: Vec, + name: Option, +} + +struct Pcie3PhyResources { + name: String, + reg: RegFixed, + phy_grf: Phandle, + pipe_grf: Option, + pcie30_phymode: u32, + clocks: Vec, + resets: Vec, +} + +struct CombphyResources { + name: String, + reg: RegFixed, + pipe_grf: Phandle, + pipe_phy_grf: Phandle, + pcie1ln_sel_bits: Option<[u32; 4]>, + refclk_rate: u32, + clocks: Vec, + resets: Vec, +} + +fn probe_rk3588(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let NodeType::Pci(node) = info.node else { + return Err(OnProbeError::NotMatch); + }; + + let resources = parse_host_resources(&info, NodeType::Pci(node))?; + if !claim_host_probe(resources.apb.address) { + return Err(OnProbeError::NotMatch); + } + let mut reset = resources + .reset_gpio + .map(|gpio| { + Rk3588GpioReset::map(resources.apb.address, gpio.bank, gpio.pin, gpio.active_high) + }) + .transpose()?; + prepare_controller_resources(&resources)?; + + let apb_size = resources.apb.size.unwrap_or(0x10000) as usize; + let dbi_size = resources.dbi.size.unwrap_or(0x400000) as usize; + let apb = map_mmio(resources.apb.address, apb_size)?; + let dbi = map_mmio(resources.dbi.address, dbi_size)?; + let cfg = map_mmio(resources.cfg_phys, resources.cfg_size as usize)?; + + let mut host = Rk3588PcieHost::new( + apb, + dbi, + cfg, + HostConfig { + apb_phys: resources.apb.address, + cfg_phys: resources.cfg_phys, + cfg_size: resources.cfg_size as usize, + bus_base: resources.bus_base, + logical_bus_end: resources.logical_bus_end, + iatu_mode: IatuMode::Unroll, + }, + ); + + let delay = AxDelay; + match reset.as_mut() { + Some(reset) => { + host.init(&delay, Some(reset)); + } + None => { + host.init(&delay, None); + } + } + + program_memory_windows( + &host, + &resources.ranges, + resources.cfg_phys, + resources.cfg_size, + ); + host.unmask_legacy_intx_all(); + info!( + "Rockchip RK3588 PCIe host {:#x}: legacy INTx unmasked", + host.apb_phys() + ); + log_direct_endpoint(&host); + super::register_fdt_legacy_irq(&info, resources.logical_bus_end); + + let mut drv = PcieController::new(host); + for range in &resources.ranges { + if is_config_range(range, resources.cfg_phys, resources.cfg_size) { + continue; + } + set_rk3588_bar_range(&mut drv, range); + } + + info!( + "Rockchip RK3588 PCIe host {:#x}: registering config window {:#x}/{} bytes, DT buses \ + {:#x}..={:#x}, logical buses 0..={}", + resources.apb.address, + resources.cfg_phys, + resources.cfg_size, + resources.bus_base, + resources.bus_base.saturating_add(resources.logical_bus_end), + resources.logical_bus_end + ); + plat_dev.register_pcie(drv); + Ok(()) +} + +fn claim_host_probe(apb_base: u64) -> bool { + let Some(index) = apb_base + .checked_sub(0xfe15_0000) + .filter(|offset| offset % 0x1_0000 == 0) + .map(|offset| (offset / 0x1_0000) as u32) + .filter(|index| *index < RK3588_PCIE_MAX_HOSTS) + else { + return true; + }; + let bit = 1_u32 << index; + PROBED_HOST_MASK.fetch_or(bit, Ordering::AcqRel) & bit == 0 +} + +fn map_mmio(phys: u64, size: usize) -> Result { + let virt = crate::mmio::iomap(phys as usize, size)?; + Ok(unsafe { MmioRaw::new(MmioAddr::from(phys), virt, size) }) +} + +fn prepare_controller_resources(resources: &HostResources<'_>) -> Result<(), OnProbeError> { + let delay = AxDelay; + if let Some(gpio) = resources.reset_gpio { + let mut reset = + Rk3588GpioReset::map(resources.apb.address, gpio.bank, gpio.pin, gpio.active_high)?; + reset.assert_perst(); + } else { + warn!( + "Rockchip RK3588 PCIe host {:#x}: no PERST GPIO discovered", + resources.apb.address + ); + } + + enable_vpcie3v3_supply(resources.supply)?; + enable_power_domains(&resources.power_domains)?; + init_phys(resources.node, &resources.phys)?; + assert_resets(&resources.resets)?; + delay.delay_us(1); + deassert_resets(&resources.resets)?; + enable_clocks(&resources.clocks)?; + axklib::time::busy_wait(Duration::from_millis(1)); + log_resource_summary(resources); + Ok(()) +} + +fn parse_power_domains(node: &Node) -> Result, OnProbeError> { + let Some(prop) = node.get_property("power-domains") else { + return Ok(Vec::new()); + }; + let cells = prop.get_u32_iter().collect::>(); + if cells.len() % 2 != 0 { + return Err(OnProbeError::other(format!( + "[{}] has malformed power-domains", + node.name() + ))); + } + Ok(cells.chunks(2).map(|chunk| chunk[1] as usize).collect()) +} + +fn enable_power_domains(domains: &[usize]) -> Result<(), OnProbeError> { + if domains.is_empty() { + return Ok(()); + } + + for &domain in domains { + rk3588_enable_power_domain(domain).map_err(|err| { + OnProbeError::other(format!( + "failed to enable RK3588 PCIe power domain {domain}: {err}" + )) + })?; + } + Ok(()) +} + +fn enable_vpcie3v3_supply(supply: Option) -> Result<(), OnProbeError> { + let Some(supply) = supply else { + return Ok(()); + }; + let pinctrl = rdrive::get_one::() + .ok_or_else(|| OnProbeError::other("RockchipPinCtrl not found for PCIe regulator"))?; + let mut pinctrl = pinctrl + .lock() + .map_err(|err| OnProbeError::other(format!("failed to lock RockchipPinCtrl: {err}")))?; + pinctrl.enable_fixed_regulator(supply) +} + +fn parse_host_resources<'a>( + info: &FdtInfo<'a>, + node_type: NodeType<'a>, +) -> Result, OnProbeError> { + let NodeType::Pci(node) = node_type else { + return Err(OnProbeError::NotMatch); + }; + let raw_node = node_type.as_node(); + let node_name = raw_node.name().to_string(); + let regs = node.regs(); + let apb = *regs + .first() + .ok_or_else(|| OnProbeError::other(format!("{node_name} has no APB register")))?; + let dbi = *regs + .get(1) + .ok_or_else(|| OnProbeError::other(format!("{node_name} has no DBI register")))?; + let ranges = node.ranges().unwrap_or_default(); + let (cfg_phys, cfg_size) = config_window(®s, &ranges)?; + let (bus_base, logical_bus_end) = bus_range_info(node.bus_range()); + + Ok(HostResources { + name: node_name, + node: node_type, + apb, + dbi, + cfg_phys, + cfg_size, + ranges, + bus_base, + logical_bus_end, + power_domains: parse_power_domains(raw_node)?, + clocks: clock_specs(node.clocks()), + resets: parse_resets(node_type)?, + pipe_grf: prop_phandle(raw_node, "rockchip,pipe-grf"), + reset_gpio: parse_reset_gpio(info, apb.address)?, + supply: prop_phandle(raw_node, "vpcie3v3-supply"), + phys: parse_phys(node_type)?, + }) +} diff --git a/drivers/ax-driver/src/pci/rk3588/slots.rs b/drivers/ax-driver/src/pci/rk3588/slots.rs new file mode 100644 index 0000000000..9da250783d --- /dev/null +++ b/drivers/ax-driver/src/pci/rk3588/slots.rs @@ -0,0 +1,99 @@ +mod rk3588_pcie_slot0 { + use super::*; + + module_driver!( + name: "Rockchip RK3588 PCIe host slot0", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-pcie"], + on_probe: probe + } + ], + ); + + fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + probe_rk3588(info, plat_dev) + } +} + +mod rk3588_pcie_slot1 { + use super::*; + + module_driver!( + name: "Rockchip RK3588 PCIe host slot1", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-pcie"], + on_probe: probe + } + ], + ); + + fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + probe_rk3588(info, plat_dev) + } +} + +mod rk3588_pcie_slot2 { + use super::*; + + module_driver!( + name: "Rockchip RK3588 PCIe host slot2", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-pcie"], + on_probe: probe + } + ], + ); + + fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + probe_rk3588(info, plat_dev) + } +} + +mod rk3588_pcie_slot3 { + use super::*; + + module_driver!( + name: "Rockchip RK3588 PCIe host slot3", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-pcie"], + on_probe: probe + } + ], + ); + + fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + probe_rk3588(info, plat_dev) + } +} + +mod rk3588_pcie_slot4 { + use super::*; + + module_driver!( + name: "Rockchip RK3588 PCIe host slot4", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-pcie"], + on_probe: probe + } + ], + ); + + fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + probe_rk3588(info, plat_dev) + } +} diff --git a/drivers/ax-driver/src/pci/rk3588/windows.rs b/drivers/ax-driver/src/pci/rk3588/windows.rs new file mode 100644 index 0000000000..1fa87f9b7f --- /dev/null +++ b/drivers/ax-driver/src/pci/rk3588/windows.rs @@ -0,0 +1,209 @@ +fn log_resource_summary(resources: &HostResources<'_>) { + info!( + "Rockchip RK3588 PCIe host {:#x}: FDT resources node={}, dbi={:#x}/{:#x}, \ + cfg={:#x}/{:#x}, buses {:#x}..={:#x}, clocks={}, resets={}, power-domains={}, phys={}, \ + supply={:?}, pipe-grf={:?}, reset-gpio={}", + resources.apb.address, + resources.name, + resources.dbi.address, + resources.dbi.size.unwrap_or(0), + resources.cfg_phys, + resources.cfg_size, + resources.bus_base, + resources.bus_base.saturating_add(resources.logical_bus_end), + resources.clocks.len(), + resources.resets.len(), + resources.power_domains.len(), + resources.phys.len(), + resources.supply, + resources.pipe_grf, + reset_gpio_label(resources.reset_gpio) + ); + for phy in &resources.phys { + debug!( + "Rockchip RK3588 PCIe host {:#x}: PHY {:?} phandle={} specifier={:?}", + resources.apb.address, phy.name, phy.phandle, phy.specifier + ); + } +} + +fn reset_gpio_label(gpio: Option) -> String { + match gpio { + Some(gpio) => format!( + "GPIO{} pin {} active-{}", + gpio.bank, + gpio.pin, + if gpio.active_high { "high" } else { "low" } + ), + None => "none".to_string(), + } +} + +fn is_compatible(node: &Node, compatible: &str) -> bool { + node.compatibles().any(|item| item == compatible) +} + +fn phy_cells(phandle: Phandle) -> Result { + let fdt = live_fdt()?; + let phy = fdt + .get_by_phandle(phandle) + .ok_or_else(|| OnProbeError::other(format!("PHY phandle {phandle:?} not found")))?; + phy.as_node() + .get_property("#phy-cells") + .and_then(|prop| prop.get_u32()) + .map(|cells| cells as usize) + .ok_or_else(|| { + OnProbeError::other(format!( + "[{}] has no #phy-cells for phandle {phandle:?}", + phy.name() + )) + }) +} + +fn prop_phandle(node: &Node, prop_name: &str) -> Option { + node.get_property(prop_name) + .and_then(|prop| prop.get_u32()) + .map(Phandle::from) +} + +fn prop_u32(node: &Node, prop_name: &str) -> Option { + node.get_property(prop_name).and_then(|prop| prop.get_u32()) +} + +fn prop_str_list(node: &Node, prop_name: &str) -> Vec { + node.get_property(prop_name) + .map(|prop| prop.as_str_iter().map(|s| s.to_string()).collect()) + .unwrap_or_default() +} + +fn live_fdt() -> Result { + rdrive::with_fdt(Clone::clone).ok_or_else(|| OnProbeError::other("live FDT not found")) +} + +fn align_up_4k(size: usize) -> usize { + const MASK: usize = 0xfff; + (size + MASK) & !MASK +} + +#[derive(Clone, Copy)] +struct Rk3588ResetPin { + bank: u8, + pin: u8, + active_high: bool, +} + +fn rk3588_pcie_reset_pin(apb_base: u64) -> Option { + match apb_base { + 0xfe18_0000 => Some(Rk3588ResetPin { + bank: 3, + pin: 11, + active_high: true, + }), + 0xfe19_0000 => Some(Rk3588ResetPin { + bank: 4, + pin: 2, + active_high: true, + }), + _ => None, + } +} + +fn config_window(regs: &[RegFixed], ranges: &[PciRange]) -> Result<(u64, u64), OnProbeError> { + if let Some(reg) = regs.get(2) { + return Ok((reg.address, reg.size.unwrap_or(DEFAULT_CFG_SIZE))); + } + + ranges + .iter() + .find(|range| { + matches!(range.space, PciSpace::Memory32) + && range.size == DEFAULT_CFG_SIZE + && range.cpu_address == range.bus_address + }) + .map(|range| (range.cpu_address, range.size)) + .ok_or_else(|| OnProbeError::other("RK3588 PCIe host has no config window")) +} + +fn bus_range_info(bus_range: Option>) -> (u8, u8) { + let Some(bus_range) = bus_range else { + return (0, u8::MAX); + }; + let bus_base = bus_range.start.min(u32::from(u8::MAX)) as u8; + let logical_end = bus_range + .end + .saturating_sub(bus_range.start) + .clamp(1, u32::from(u8::MAX)) as u8; + (bus_base, logical_end) +} + +fn program_memory_windows( + host: &Rk3588PcieHost, + ranges: &[PciRange], + cfg_phys: u64, + cfg_size: u64, +) { + let mut region = MEM_ATU_FIRST_REGION; + for range in ranges { + if is_config_range(range, cfg_phys, cfg_size) { + continue; + } + match range.space { + PciSpace::Memory32 | PciSpace::Memory64 => { + let window = OutboundWindow { + cpu_base: range.cpu_address, + pci_base: range.bus_address, + size: range.size, + }; + if let Err(err) = host.program_memory_window(region, window) { + warn!( + "PCIe host {:#x}: invalid outbound iATU region {}: {err:?}", + host.apb_phys(), + region + ); + } + debug!( + "PCIe host {:#x}: iATU mem region {} cpu={:#x} pci={:#x} size={:#x}", + host.apb_phys(), + region, + range.cpu_address, + range.bus_address, + range.size + ); + region = region.saturating_add(1); + } + PciSpace::IO => {} + } + } +} + +fn log_direct_endpoint(host: &Rk3588PcieHost) { + if let Some(endpoint) = host.direct_endpoint_info() { + info!( + "PCIe endpoint: {} {:04x}:{:04x} (rev {:02x}, class {:02x}{:02x}{:02x})", + endpoint.address, + endpoint.vendor_id, + endpoint.device_id, + endpoint.revision_id, + endpoint.base_class, + endpoint.sub_class, + endpoint.prog_if + ); + } +} + +fn is_config_range(range: &PciRange, cfg_phys: u64, cfg_size: u64) -> bool { + range.cpu_address == cfg_phys && range.size == cfg_size +} + +fn set_rk3588_bar_range(drv: &mut PcieController, range: &PciRange) { + super::set_pcie_mem_range(drv, range); + if matches!(range.space, PciSpace::Memory32) { + drv.set_mem64( + PciMem64 { + address: range.cpu_address, + size: range.size, + }, + range.prefetchable, + ); + } +} diff --git a/drivers/ax-driver/src/rknpu.rs b/drivers/ax-driver/src/rknpu.rs new file mode 100644 index 0000000000..5386b2d698 --- /dev/null +++ b/drivers/ax-driver/src/rknpu.rs @@ -0,0 +1,119 @@ +use alloc::vec::Vec; + +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; +use rockchip_npu::{Rknpu, RknpuConfig, RknpuType}; +pub use rockchip_npu::{ + RknpuAction, + ioctrl::{RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit}, +}; +use rockchip_pm::{PowerDomain, RockchipPM}; + +use crate::mmio::iomap; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + NotFound, + Busy, + InvalidData, +} + +module_driver!( + name: "Rockchip NPU", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ + ProbeKind::Fdt { + compatibles: &["rockchip,rk3588-rknpu"], + on_probe: probe + } + ], +); + +fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let regs = info.node.regs(); + + let config = RknpuConfig { + rknpu_type: RknpuType::Rk3588, + }; + + let mut base_regs = Vec::new(); + let page_size = 0x1000; + for reg in ®s { + let start_raw = reg.address as usize; + let end = start_raw + reg.size.unwrap_or(0x1000) as usize; + + let start = start_raw & !(page_size - 1); + let offset = start_raw - start; + let end = (end + page_size - 1) & !(page_size - 1); + let size = end - start; + + base_regs.push(unsafe { iomap(start, size)?.add(offset) }); + } + + enable_pm(); + + info!("NPU power enabled"); + + let dma = axklib::dma::device(u32::MAX as u64); + let npu = Rknpu::new(&base_regs, config, dma); + plat_dev.register(npu); + info!("NPU registered successfully"); + Ok(()) +} + +fn enable_pm() { + let mut pm = rdrive::get_one::() + .expect("RockchipPM not found") + .lock() + .expect("RockchipPM lock failed"); + + // RK3588 NPU power domain IDs (from rockchip-pm rk3588 variant) + pm.power_domain_on(PowerDomain(9)).unwrap(); // NPUTOP + pm.power_domain_on(PowerDomain(8)).unwrap(); // NPU + pm.power_domain_on(PowerDomain(10)).unwrap(); // NPU1 + pm.power_domain_on(PowerDomain(11)).unwrap(); // NPU2 +} + +pub fn is_available() -> bool { + rdrive::get_one::().is_some() +} + +pub fn obj_addr_and_size(handle: u32) -> Result<(usize, usize), Error> { + with_npu(|npu| npu.get_obj_addr_and_size(handle).ok_or(Error::NotFound)) +} + +pub fn submit(args: &mut RknpuSubmit) -> Result<(), Error> { + with_npu(|npu| npu.submit_ioctrl(args).map_err(|_| Error::InvalidData)) +} + +pub fn mem_create(args: &mut RknpuMemCreate) -> Result<(), Error> { + with_npu(|npu| npu.create(args).map_err(|_| Error::InvalidData)) +} + +pub fn mem_sync(args: &mut RknpuMemSync) -> Result<(), Error> { + with_npu(|npu| npu.mem_sync(args).map_err(|_| Error::InvalidData)) +} + +pub fn mem_map_offset(handle: u32) -> Result { + with_npu(|npu| { + npu.get_phys_addr_and_size(handle) + .map(|_| (handle as u64) << 12) + .ok_or(Error::InvalidData) + }) +} + +pub fn action(flags: RknpuAction) -> Result { + with_npu(|npu| npu.action(flags).map_err(|_| Error::InvalidData)) +} + +fn with_npu(f: F) -> Result +where + F: FnOnce(&mut Rknpu) -> Result, +{ + let mut npu = rdrive::get_one::() + .ok_or(Error::NotFound)? + .try_lock() + .map_err(|_| Error::Busy)?; + f(&mut npu) +} diff --git a/drivers/ax-driver/src/serial/mod.rs b/drivers/ax-driver/src/serial/mod.rs new file mode 100644 index 0000000000..69a8a839b9 --- /dev/null +++ b/drivers/ax-driver/src/serial/mod.rs @@ -0,0 +1,54 @@ +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; +use some_serial::{BSerial, ns16550, pl011}; + +module_driver!( + name: "common serial", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["arm,pl011", "snps,dw-apb-uart"], + on_probe: probe + }], +); + +fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + info!("Probing serial device: {}", info.node.name()); + let base_reg = + info.node.regs().into_iter().next().ok_or_else(|| { + OnProbeError::other(alloc::format!("[{}] has no reg", info.node.name())) + })?; + + let mmio_size = base_reg.size.unwrap_or(0x1000); + let mmio_base = crate::mmio::iomap(base_reg.address as usize, mmio_size as usize)?; + + let node = info.node.as_node(); + let clock_freq = prop_u32(node, "clock-frequency").unwrap_or(24_000_000); + let reg_width = prop_u32(node, "reg-io-width").unwrap_or(1) as usize; + let mut serial: Option = None; + for compatible in node.compatibles() { + if compatible == "arm,pl011" { + serial = Some(pl011::Pl011::new_boxed(mmio_base, clock_freq)); + break; + } + + if compatible == "snps,dw-apb-uart" { + serial = Some(ns16550::Ns16550::new_mmio_boxed( + mmio_base, clock_freq, reg_width, + )); + break; + } + } + + if let Some(serial) = serial { + let base = serial.base_addr(); + info!("Serial@{base:#x} registered successfully"); + plat_dev.register(serial); + } + + Ok(()) +} + +fn prop_u32(node: &fdt_edit::Node, name: &str) -> Option { + node.get_property(name).and_then(|prop| prop.get_u32()) +} diff --git a/platform/axplat-dyn/src/drivers/soc/mod.rs b/drivers/ax-driver/src/soc/mod.rs similarity index 78% rename from platform/axplat-dyn/src/drivers/soc/mod.rs rename to drivers/ax-driver/src/soc/mod.rs index 1ce19f0216..050818eaf9 100644 --- a/platform/axplat-dyn/src/drivers/soc/mod.rs +++ b/drivers/ax-driver/src/soc/mod.rs @@ -12,41 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "rockchip-soc")] mod rockchip; -pub(crate) mod scmi; +#[cfg(feature = "rockchip-dwmmc")] +pub mod scmi; #[cfg(feature = "rockchip-soc")] -pub(crate) use rockchip::{ +pub use rockchip::{ RockchipPinCtrl, rk3588_enable_clock, rk3588_enable_power_domain, rk3588_reset_assert, rk3588_reset_deassert, rk3588_set_clock_rate, }; #[cfg(not(feature = "rockchip-soc"))] -pub(crate) fn rk3588_enable_clock(id: u32) -> Result<(), rdrive::probe::OnProbeError> { +pub fn rk3588_enable_clock(id: u32) -> Result<(), rdrive::probe::OnProbeError> { Err(rdrive::probe::OnProbeError::other(alloc::format!( "RK3588 clock support is not enabled for clock {id:#x}" ))) } #[cfg(not(feature = "rockchip-soc"))] -pub(crate) fn rk3588_set_clock_rate( - id: u32, - rate_hz: u64, -) -> Result<(), rdrive::probe::OnProbeError> { +pub fn rk3588_set_clock_rate(id: u32, rate_hz: u64) -> Result<(), rdrive::probe::OnProbeError> { Err(rdrive::probe::OnProbeError::other(alloc::format!( "RK3588 clock support is not enabled for clock {id:#x} rate {rate_hz}" ))) } #[cfg(not(feature = "rockchip-soc"))] -pub(crate) fn rk3588_enable_power_domain(domain: usize) -> Result<(), alloc::string::String> { +pub fn rk3588_enable_power_domain(domain: usize) -> Result<(), alloc::string::String> { Err(alloc::format!( "RK3588 power-domain support is not enabled for power domain {domain}" )) } #[cfg(not(feature = "rockchip-soc"))] -pub(crate) struct RockchipPinCtrl; +pub struct RockchipPinCtrl; #[cfg(not(feature = "rockchip-soc"))] impl rdrive::DriverGeneric for RockchipPinCtrl { @@ -57,7 +56,7 @@ impl rdrive::DriverGeneric for RockchipPinCtrl { #[cfg(not(feature = "rockchip-soc"))] impl RockchipPinCtrl { - pub(crate) fn enable_fixed_regulator( + pub fn enable_fixed_regulator( &mut self, phandle: fdt_edit::Phandle, ) -> Result<(), rdrive::probe::OnProbeError> { @@ -68,14 +67,14 @@ impl RockchipPinCtrl { } #[cfg(not(feature = "rockchip-soc"))] -pub(crate) fn rk3588_reset_assert(id: u64) -> Result<(), rdrive::probe::OnProbeError> { +pub fn rk3588_reset_assert(id: u64) -> Result<(), rdrive::probe::OnProbeError> { Err(rdrive::probe::OnProbeError::other(alloc::format!( "RK3588 reset support is not enabled for reset {id:#x}" ))) } #[cfg(not(feature = "rockchip-soc"))] -pub(crate) fn rk3588_reset_deassert(id: u64) -> Result<(), rdrive::probe::OnProbeError> { +pub fn rk3588_reset_deassert(id: u64) -> Result<(), rdrive::probe::OnProbeError> { Err(rdrive::probe::OnProbeError::other(alloc::format!( "RK3588 reset support is not enabled for reset {id:#x}" ))) diff --git a/platform/axplat-dyn/src/drivers/soc/rockchip/clk/mod.rs b/drivers/ax-driver/src/soc/rockchip/clk/mod.rs similarity index 89% rename from platform/axplat-dyn/src/drivers/soc/rockchip/clk/mod.rs rename to drivers/ax-driver/src/soc/rockchip/clk/mod.rs index 41dee9e375..a7f5764230 100644 --- a/platform/axplat-dyn/src/drivers/soc/rockchip/clk/mod.rs +++ b/drivers/ax-driver/src/soc/rockchip/clk/mod.rs @@ -90,22 +90,22 @@ fn with_clk_drv( f(drv) } -pub(crate) fn rk3588_enable_clock(id: u32) -> Result<(), OnProbeError> { +pub fn rk3588_enable_clock(id: u32) -> Result<(), OnProbeError> { with_clk_drv(|drv| drv.enable_clock(id)) } -pub(crate) fn rk3588_set_clock_rate(id: u32, rate: u64) -> Result<(), OnProbeError> { +pub fn rk3588_set_clock_rate(id: u32, rate: u64) -> Result<(), OnProbeError> { with_clk_drv(|drv| drv.set_clock_rate(id, rate)) } -pub(crate) fn rk3588_reset_assert(id: u64) -> Result<(), OnProbeError> { +pub fn rk3588_reset_assert(id: u64) -> Result<(), OnProbeError> { with_clk_drv(|drv| { drv.reset_assert(id); Ok(()) }) } -pub(crate) fn rk3588_reset_deassert(id: u64) -> Result<(), OnProbeError> { +pub fn rk3588_reset_deassert(id: u64) -> Result<(), OnProbeError> { with_clk_drv(|drv| { drv.reset_deassert(id); Ok(()) diff --git a/platform/axplat-dyn/src/drivers/soc/rockchip/clk/rk3568.rs b/drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs similarity index 86% rename from platform/axplat-dyn/src/drivers/soc/rockchip/clk/rk3568.rs rename to drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs index 99638e7bcc..5b5a727df3 100644 --- a/platform/axplat-dyn/src/drivers/soc/rockchip/clk/rk3568.rs +++ b/drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs @@ -1,8 +1,9 @@ -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; use rockchip_soc::{Cru, SocType}; use super::ClkDrv; -use crate::drivers::iomap; +use crate::mmio::iomap; const RK3568_CRU_GRF_BASE: usize = 0xfdc6_0000; const RK3568_CRU_GRF_SIZE: usize = 0x10000; @@ -48,10 +49,10 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError ); let mmio_base = iomap( - (base_reg.address as usize).into(), + base_reg.address as usize, base_reg.size.unwrap_or(0x1000) as usize, )?; - let grf_base = iomap(RK3568_CRU_GRF_BASE.into(), RK3568_CRU_GRF_SIZE)?; + let grf_base = iomap(RK3568_CRU_GRF_BASE, RK3568_CRU_GRF_SIZE)?; let mut cru = Cru::new(SocType::Rk3568, mmio_base, grf_base); if let Cru::Rk3568(ref mut rk3568) = cru { diff --git a/platform/axplat-dyn/src/drivers/soc/rockchip/clk/rk3588.rs b/drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs similarity index 64% rename from platform/axplat-dyn/src/drivers/soc/rockchip/clk/rk3588.rs rename to drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs index 684509eea2..4292716e92 100644 --- a/platform/axplat-dyn/src/drivers/soc/rockchip/clk/rk3588.rs +++ b/drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs @@ -1,8 +1,23 @@ -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; use rockchip_soc::{Cru, SocType}; use super::ClkDrv; -use crate::drivers::iomap; +use crate::mmio::iomap; const RK3588_CRU_GRF_BASE: usize = 0xfd5b_0000; const RK3588_CRU_GRF_SIZE: usize = 0x1000; @@ -48,10 +63,10 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError ); let mmio_base = iomap( - (base_reg.address as usize).into(), + base_reg.address as usize, base_reg.size.unwrap_or(0x5c000) as usize, )?; - let grf_base = iomap(RK3588_CRU_GRF_BASE.into(), RK3588_CRU_GRF_SIZE)?; + let grf_base = iomap(RK3588_CRU_GRF_BASE, RK3588_CRU_GRF_SIZE)?; let cru = Cru::new(SocType::Rk3588, mmio_base, grf_base); plat_dev.register(rdif_clk::Clk::new(ClkDrv::new("rk3588-cru", cru))); diff --git a/platform/axplat-dyn/src/drivers/soc/rockchip/mod.rs b/drivers/ax-driver/src/soc/rockchip/mod.rs similarity index 84% rename from platform/axplat-dyn/src/drivers/soc/rockchip/mod.rs rename to drivers/ax-driver/src/soc/rockchip/mod.rs index e4227848a3..855987d278 100644 --- a/platform/axplat-dyn/src/drivers/soc/rockchip/mod.rs +++ b/drivers/ax-driver/src/soc/rockchip/mod.rs @@ -22,16 +22,16 @@ mod pm; mod pinctrl; #[cfg(feature = "rockchip-soc")] -pub(crate) use clk::{ +pub use clk::{ rk3588_enable_clock, rk3588_reset_assert, rk3588_reset_deassert, rk3588_set_clock_rate, }; #[cfg(feature = "rockchip-soc")] -pub(crate) use pinctrl::RockchipPinCtrl; +pub use pinctrl::RockchipPinCtrl; #[cfg(all(feature = "rockchip-soc", feature = "rockchip-pm"))] -pub(crate) use pm::rk3588_enable_power_domain; +pub use pm::rk3588_enable_power_domain; #[cfg(all(feature = "rockchip-soc", not(feature = "rockchip-pm")))] -pub(crate) fn rk3588_enable_power_domain(domain: usize) -> Result<(), alloc::string::String> { +pub fn rk3588_enable_power_domain(domain: usize) -> Result<(), alloc::string::String> { Err(alloc::format!( "rockchip-pm feature is not enabled for power domain {domain}" )) diff --git a/platform/axplat-dyn/src/drivers/soc/rockchip/pinctrl.rs b/drivers/ax-driver/src/soc/rockchip/pinctrl.rs similarity index 80% rename from platform/axplat-dyn/src/drivers/soc/rockchip/pinctrl.rs rename to drivers/ax-driver/src/soc/rockchip/pinctrl.rs index a6e8faeb17..5a4d028fd8 100644 --- a/platform/axplat-dyn/src/drivers/soc/rockchip/pinctrl.rs +++ b/drivers/ax-driver/src/soc/rockchip/pinctrl.rs @@ -4,12 +4,13 @@ use alloc::{format, string::ToString, vec, vec::Vec}; use core::ptr::NonNull; use fdt_edit::{Fdt, Node, NodeType, Phandle, RegFixed}; -use rdrive::{ - DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, +use log::{info, warn}; +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError, register::FdtInfo}; +use rockchip_soc::{ + BankId, GpioDirection, Iomux, PinConfig, PinCtrl, PinCtrlOp, PinId, Pull, SocType, }; -use rockchip_soc::{BankId, GpioDirection, PinConfig, PinCtrl, PinCtrlOp, PinId, SocType}; -use crate::drivers::iomap; +use crate::mmio::iomap; const DRIVER_NAME: &str = "rk3588-pinctrl"; const GPIO_BANK_COUNT: usize = 5; @@ -26,20 +27,19 @@ module_driver!( ], ); -pub(crate) struct RockchipPinCtrl { +pub struct RockchipPinCtrl { inner: PinCtrl, - fdt_addr: NonNull, } unsafe impl Send for RockchipPinCtrl {} impl RockchipPinCtrl { - fn new(inner: PinCtrl, fdt_addr: NonNull) -> Self { - Self { inner, fdt_addr } + fn new(inner: PinCtrl) -> Self { + Self { inner } } - pub(crate) fn enable_fixed_regulator(&mut self, phandle: Phandle) -> Result<(), OnProbeError> { - let fdt = live_fdt(self.fdt_addr)?; + pub fn enable_fixed_regulator(&mut self, phandle: Phandle) -> Result<(), OnProbeError> { + let fdt = live_fdt()?; let node = fdt.get_by_phandle(phandle).ok_or_else(|| { OnProbeError::other(format!("regulator phandle {phandle:?} not found")) })?; @@ -105,7 +105,7 @@ impl RockchipPinCtrl { } fn apply_pinctrl_phandle(&mut self, phandle: Phandle) -> Result, OnProbeError> { - let fdt = live_fdt(self.fdt_addr)?; + let fdt = live_fdt()?; let node = fdt .get_by_phandle(phandle) .ok_or_else(|| OnProbeError::other(format!("pinctrl phandle {phandle:?} not found")))?; @@ -129,7 +129,7 @@ impl RockchipPinCtrl { let mut configured = Vec::new(); for cells in pins.chunks(4) { - let config = PinConfig::new_with_fdt(cells, self.fdt_addr); + let config = pin_config_from_cells(cells)?; let pin = config.id; self.inner.set_config(config).map_err(|err| { OnProbeError::other(format!( @@ -151,8 +151,7 @@ impl DriverGeneric for RockchipPinCtrl { } fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let fdt_addr = live_fdt_addr()?; - let fdt = live_fdt(fdt_addr)?; + let fdt = live_fdt()?; let grf_phandle = info .node @@ -180,19 +179,13 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError } let pinctrl = PinCtrl::new(SocType::Rk3588, ioc, &gpio_banks); - plat_dev.register(RockchipPinCtrl::new(pinctrl, fdt_addr)); + plat_dev.register(RockchipPinCtrl::new(pinctrl)); info!("Rockchip RK3588 pinctrl registered successfully"); Ok(()) } -fn live_fdt_addr() -> Result, OnProbeError> { - let ptr = somehal::fdt_addr().ok_or_else(|| OnProbeError::other("live FDT not found"))?; - NonNull::new(ptr).ok_or_else(|| OnProbeError::other("live FDT pointer is null")) -} - -fn live_fdt(fdt_addr: NonNull) -> Result { - unsafe { Fdt::from_ptr(fdt_addr.as_ptr()) } - .map_err(|err| OnProbeError::other(format!("failed to parse live FDT: {err:?}"))) +fn live_fdt() -> Result { + rdrive::with_fdt(Clone::clone).ok_or_else(|| OnProbeError::other("live FDT not found")) } fn map_phandle_reg( @@ -215,7 +208,7 @@ fn map_node_reg(node: NodeType<'_>, context: &str) -> Result, OnProb fn map_reg(reg: RegFixed) -> Result, OnProbeError> { let size = align_up_4k((reg.size.unwrap_or(0x1000) as usize).max(1)); - iomap((reg.address as usize).into(), size) + iomap(reg.address as usize, size) } fn fixed_regulator_active_value(node: &Node) -> bool { @@ -262,20 +255,52 @@ fn parse_gpio_pin(fdt: &Fdt, node: &Node, prop_name: &str) -> Option Result { + let [bank, pin, mux, conf_phandle] = cells else { + return Err(OnProbeError::other("malformed rockchip,pins cells")); + }; + let id = PinId::from_bank_pin(BankId::new(*bank).unwrap_or(BankId::from(*bank)), *pin) + .ok_or_else(|| OnProbeError::other(format!("invalid Rockchip pin {bank}:{pin}")))?; + let fdt = live_fdt()?; + let conf = fdt + .get_by_phandle(Phandle::from(*conf_phandle)) + .ok_or_else(|| { + OnProbeError::other(format!("pinconf phandle {conf_phandle:?} not found")) + })?; + let mut pull = Pull::Disabled; + let mut drive = None; + for prop in conf.as_node().properties() { + match prop.name() { + "bias-disable" => pull = Pull::Disabled, + "bias-bus-hold" => pull = Pull::BusHold, + "bias-pull-up" => pull = Pull::PullUp, + "bias-pull-down" => pull = Pull::PullDown, + "bias-pull-pin-default" => pull = Pull::PullPinDefault, + "drive-strength" => drive = prop.get_u32(), + "phandle" => {} + name => warn!("Unknown pinconf property: {}", name), + } + } + Ok(PinConfig { + id, + mux: Iomux::from_bits_truncate(*mux as u8), + pull, + drive, + }) +} + fn gpio_bank_index(node: &Node) -> Option { let name = node.name(); if let Some(name) = name .strip_prefix("gpio") .filter(|name| !name.starts_with('@')) - { - if let Some(bank) = name + && let Some(bank) = name .chars() .next() .and_then(|ch| ch.to_digit(10)) .filter(|bank| *bank < GPIO_BANK_COUNT as u32) - { - return Some(bank); - } + { + return Some(bank); } let address = gpio_bank_address(node)?; diff --git a/platform/axplat-dyn/src/drivers/soc/rockchip/pm.rs b/drivers/ax-driver/src/soc/rockchip/pm.rs similarity index 86% rename from platform/axplat-dyn/src/drivers/soc/rockchip/pm.rs rename to drivers/ax-driver/src/soc/rockchip/pm.rs index b7b479445b..a7d8803020 100644 --- a/platform/axplat-dyn/src/drivers/soc/rockchip/pm.rs +++ b/drivers/ax-driver/src/soc/rockchip/pm.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; use rockchip_pm::{PowerDomain, RkBoard, RockchipPM}; -use crate::drivers::iomap; +use crate::mmio::iomap; module_driver!( name: "Rockchip Pm", @@ -43,7 +44,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError let mmio_size = base_reg.size.unwrap_or(0x1000) as usize; let board = RkBoard::Rk3588; - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size)?; + let mmio_base = iomap(base_reg.address as usize, mmio_size)?; let pm = RockchipPM::new(mmio_base, board); plat_dev.register(pm); @@ -51,7 +52,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError Ok(()) } -pub(crate) fn rk3588_enable_power_domain(domain: usize) -> Result<(), alloc::string::String> { +pub fn rk3588_enable_power_domain(domain: usize) -> Result<(), alloc::string::String> { let pm = rdrive::get_one::() .ok_or_else(|| alloc::format!("RockchipPM not found for power domain {domain}"))?; let mut pm = pm diff --git a/platform/axplat-dyn/src/drivers/soc/scmi.rs b/drivers/ax-driver/src/soc/scmi.rs similarity index 97% rename from platform/axplat-dyn/src/drivers/soc/scmi.rs rename to drivers/ax-driver/src/soc/scmi.rs index d94313c361..5506d49284 100644 --- a/platform/axplat-dyn/src/drivers/soc/scmi.rs +++ b/drivers/ax-driver/src/soc/scmi.rs @@ -4,11 +4,12 @@ use core::sync::atomic::{AtomicBool, Ordering}; use arm_scmi_rs::{Scmi, Shmem, Smc}; use ax_kspin::SpinNoIrq as Mutex; use fdt_edit::Phandle; +use log::{info, warn}; use rdrive::{ DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, }; -use crate::drivers::iomap; +use crate::mmio::iomap; const SCMI_SHMEM_SIZE: usize = 0x100; const RK3588_SCMI_SHMEM_BASE: usize = 0x10f000; @@ -63,7 +64,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError ); (RK3588_SCMI_SHMEM_BASE, SCMI_SHMEM_SIZE) }); - let shmem_base = iomap(shmem_addr.into(), shmem_size)?; + let shmem_base = iomap(shmem_addr, shmem_size)?; let shmem = Shmem { address: shmem_base, diff --git a/platform/axplat-dyn/src/drivers/time/mod.rs b/drivers/ax-driver/src/time.rs similarity index 81% rename from platform/axplat-dyn/src/drivers/time/mod.rs rename to drivers/ax-driver/src/time.rs index 1a01bdca5f..110471e117 100644 --- a/platform/axplat-dyn/src/drivers/time/mod.rs +++ b/drivers/ax-driver/src/time.rs @@ -1,7 +1,8 @@ use ax_arm_pl031::Rtc; -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; +use log::{debug, info}; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; -use crate::drivers::iomap; +use crate::mmio::iomap; module_driver!( name: "pl031 rtc", @@ -23,7 +24,7 @@ fn probe(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeErro }; let mmio_size = base_reg.size.unwrap_or(0x1000); - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size as usize)?; + let mmio_base = iomap(base_reg.address as usize, mmio_size as usize)?; let rtc = unsafe { Rtc::new(mmio_base.as_ptr().cast()) }; let unix_timestamp = rtc.get_unix_timestamp(); if unix_timestamp == 0 { @@ -34,7 +35,7 @@ fn probe(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeErro } let epoch_time_nanos = u64::from(unix_timestamp) * 1_000_000_000; - if crate::generic_timer::try_init_epoch_offset(epoch_time_nanos) { + if axklib::time::try_init_epoch_offset(epoch_time_nanos) { info!("Initialized wall clock from {}", info.node.name()); } else { debug!( diff --git a/platform/axplat-dyn/src/drivers/usb/xhci_dwc.rs b/drivers/ax-driver/src/usb/dwc.rs similarity index 95% rename from platform/axplat-dyn/src/drivers/usb/xhci_dwc.rs rename to drivers/ax-driver/src/usb/dwc.rs index be10a1d26c..bcf26b05f0 100644 --- a/platform/axplat-dyn/src/drivers/usb/xhci_dwc.rs +++ b/drivers/ax-driver/src/usb/dwc.rs @@ -12,12 +12,13 @@ use crab_usb::{ usb_if::DrMode, }; use fdt_edit::{ClockRef, Fdt, Node, NodeType, Phandle, RegFixed}; -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; +use log::{debug, info, warn}; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; use rockchip_pm::{PowerDomain, RockchipPM}; -use super::{PlatformDeviceUsbHost, USB_KERNEL, decode_fdt_irq}; -use crate::drivers::{ - iomap, +use super::{PlatformDeviceUsbHost, decode_fdt_irq, usb_kernel}; +use crate::{ + mmio::iomap, soc::{RockchipPinCtrl, rk3588_enable_clock, rk3588_reset_assert, rk3588_reset_deassert}, }; @@ -111,8 +112,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError } } - let fdt_addr = live_fdt_addr()?; - let fdt = live_fdt(fdt_addr)?; + let fdt = live_fdt()?; let resources = collect_resources(&info, &fdt)?; enable_power_domains(&resources.power_domains)?; @@ -159,7 +159,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError cru: RockchipCru, rst_list: &ctrl_resets, params: resources.params, - kernel: &USB_KERNEL, + kernel: usb_kernel(), }) .map_err(|err| { OnProbeError::other(format!( @@ -533,14 +533,8 @@ fn has_prop(node: &Node, names: &[&str]) -> bool { names.iter().any(|name| node.get_property(name).is_some()) } -fn live_fdt_addr() -> Result, OnProbeError> { - let ptr = somehal::fdt_addr().ok_or_else(|| OnProbeError::other("live FDT not found"))?; - NonNull::new(ptr).ok_or_else(|| OnProbeError::other("live FDT pointer is null")) -} - -fn live_fdt(fdt_addr: NonNull) -> Result { - unsafe { Fdt::from_ptr(fdt_addr.as_ptr()) } - .map_err(|err| OnProbeError::other(format!("failed to parse live FDT: {err:?}"))) +fn live_fdt() -> Result { + rdrive::with_fdt(Clone::clone).ok_or_else(|| OnProbeError::other("live FDT not found")) } fn map_phandle_reg( @@ -559,7 +553,7 @@ fn map_phandle_reg( fn map_reg(reg: RegFixed) -> Result, OnProbeError> { let size = align_up_4k((reg.size.unwrap_or(0x1000) as usize).max(1)); - iomap((reg.address as usize).into(), size) + iomap(reg.address as usize, size) } fn align_up_4k(size: usize) -> usize { diff --git a/drivers/ax-driver/src/usb/mod.rs b/drivers/ax-driver/src/usb/mod.rs new file mode 100644 index 0000000000..546ea6d224 --- /dev/null +++ b/drivers/ax-driver/src/usb/mod.rs @@ -0,0 +1,229 @@ +extern crate alloc; + +#[cfg(target_os = "none")] +use core::time::Duration; + +#[cfg(target_os = "none")] +use crab_usb::USBHost; +#[cfg(target_os = "none")] +use dma_api::{DmaDirection, DmaError, DmaHandle, DmaMapHandle, DmaOp}; +use rdrive::DriverGeneric; + +#[cfg(all(feature = "rockchip-dwc-xhci", target_os = "none"))] +mod dwc; +#[cfg(all(feature = "xhci-mmio", target_os = "none"))] +mod xhci_mmio; +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +mod xhci_pci; + +pub type UsbHostDevice = rdrive::Device; +pub type UsbHostDeviceGuard = rdrive::DeviceGuard; + +#[cfg(target_os = "none")] +struct UsbKernel; + +#[cfg(target_os = "none")] +impl DmaOp for UsbKernel { + fn page_size(&self) -> usize { + axklib::dma::op().page_size() + } + + unsafe fn map_single( + &self, + dma_mask: u64, + addr: core::ptr::NonNull, + size: core::num::NonZeroUsize, + align: usize, + direction: DmaDirection, + ) -> Result { + unsafe { axklib::dma::op().map_single(dma_mask, addr, size, align, direction) } + } + + unsafe fn unmap_single(&self, handle: DmaMapHandle) { + unsafe { axklib::dma::op().unmap_single(handle) } + } + + fn flush(&self, addr: core::ptr::NonNull, size: usize) { + axklib::dma::op().flush(addr, size); + } + + fn invalidate(&self, addr: core::ptr::NonNull, size: usize) { + axklib::dma::op().invalidate(addr, size); + } + + fn flush_invalidate(&self, addr: core::ptr::NonNull, size: usize) { + axklib::dma::op().flush_invalidate(addr, size); + } + + fn prepare_read( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + axklib::dma::op().prepare_read(handle, offset, size, direction); + } + + fn confirm_write( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + axklib::dma::op().confirm_write(handle, offset, size, direction); + } + + unsafe fn alloc_coherent( + &self, + dma_mask: u64, + layout: core::alloc::Layout, + ) -> Option { + unsafe { axklib::dma::op().alloc_coherent(dma_mask, layout) } + } + + unsafe fn dealloc_coherent(&self, handle: DmaHandle) { + unsafe { axklib::dma::op().dealloc_coherent(handle) } + } +} + +#[cfg(target_os = "none")] +impl crab_usb::KernelOp for UsbKernel { + fn delay(&self, duration: Duration) { + axklib::time::busy_wait(duration); + } +} + +#[cfg(target_os = "none")] +static USB_KERNEL: UsbKernel = UsbKernel; + +#[cfg(target_os = "none")] +pub fn usb_kernel() -> &'static dyn crab_usb::KernelOp { + &USB_KERNEL +} + +#[cfg(target_os = "none")] +pub struct PlatformUsbHost { + name: &'static str, + irq_num: Option, + host: USBHost, +} + +#[cfg(not(target_os = "none"))] +pub struct PlatformUsbHost { + name: &'static str, + irq_num: Option, +} + +impl PlatformUsbHost { + #[cfg(target_os = "none")] + fn new(name: &'static str, host: USBHost, irq_num: Option) -> Self { + Self { + name, + irq_num, + host, + } + } + + #[cfg(not(target_os = "none"))] + fn new_stub(name: &'static str, irq_num: Option) -> Self { + Self { name, irq_num } + } + + #[cfg(target_os = "none")] + pub fn host(&self) -> &USBHost { + &self.host + } + + #[cfg(target_os = "none")] + pub fn host_mut(&mut self) -> &mut USBHost { + &mut self.host + } + + pub fn irq_num(&self) -> Option { + self.irq_num + } +} + +impl DriverGeneric for PlatformUsbHost { + fn name(&self) -> &str { + self.name + } +} + +pub trait PlatformDeviceUsbHost { + #[cfg(target_os = "none")] + fn register_usb_host(self, name: &'static str, host: USBHost, irq_num: Option); + + #[cfg(not(target_os = "none"))] + fn register_usb_host_stub(self, name: &'static str, irq_num: Option); +} + +impl PlatformDeviceUsbHost for rdrive::PlatformDevice { + #[cfg(target_os = "none")] + fn register_usb_host(self, name: &'static str, host: USBHost, irq_num: Option) { + self.register(PlatformUsbHost::new(name, host, irq_num)); + } + + #[cfg(not(target_os = "none"))] + fn register_usb_host_stub(self, name: &'static str, irq_num: Option) { + self.register(PlatformUsbHost::new_stub(name, irq_num)); + } +} + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +pub(crate) fn align_up_4k(size: usize) -> usize { + const MASK: usize = 0xfff; + (size + MASK) & !MASK +} + +pub fn decode_fdt_irq(interrupts: &[rdrive::probe::fdt::InterruptRef]) -> Option { + let interrupt = interrupts.first()?; + decode_irq_cells(&interrupt.specifier) +} + +fn decode_irq_cells(specifier: &[u32]) -> Option { + match specifier { + [irq] => Some(*irq as usize), + [kind, irq, ..] => match *kind { + 0 => Some(*irq as usize + 32), + 1 => Some(*irq as usize + 16), + _ => Some(*irq as usize), + }, + _ => None, + } +} + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +fn pci_static_irq(endpoint: &rdrive::probe::pci::EndpointRc) -> Option { + let interrupt_pin = endpoint.interrupt_pin(); + if let Some(irq) = crate::pci::legacy_irq_for_endpoint(endpoint.address(), interrupt_pin) { + return Some(irq); + } + let line = endpoint.interrupt_line(); + (line != 0 && line != u8::MAX).then_some(line as usize) +} + +#[cfg(all(feature = "xhci-pci", target_os = "none"))] +pub(crate) fn pci_irq_or_error( + endpoint: &rdrive::probe::pci::EndpointRc, +) -> Result { + #[cfg(feature = "pci-fdt")] + if let Some(irq) = + crate::pci::fdt_irq_for_endpoint(endpoint.address(), endpoint.interrupt_pin())? + { + return Ok(irq); + } + + pci_static_irq(endpoint).ok_or_else(|| { + rdrive::probe::OnProbeError::other(alloc::format!( + "failed to resolve IRQ for USB endpoint {}", + endpoint.address() + )) + }) +} + +pub fn usb_host_device() -> Option { + rdrive::get_one() +} diff --git a/drivers/ax-driver/src/usb/xhci_mmio.rs b/drivers/ax-driver/src/usb/xhci_mmio.rs new file mode 100644 index 0000000000..9ff962dcba --- /dev/null +++ b/drivers/ax-driver/src/usb/xhci_mmio.rs @@ -0,0 +1,46 @@ +extern crate alloc; + +use alloc::format; + +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; + +use super::{PlatformDeviceUsbHost, decode_fdt_irq, usb_kernel}; + +const DRIVER_NAME: &str = "usb-xhci-mmio"; + +module_driver!( + name: "USB xHCI MMIO", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["generic-xhci", "xhci-platform"], + on_probe: probe + }], +); + +fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let base_reg = + info.node.regs().into_iter().next().ok_or_else(|| { + OnProbeError::other(alloc::format!("[{}] has no reg", info.node.name())) + })?; + + let mmio_size = base_reg.size.unwrap_or(0x1000) as usize; + let mmio = crate::mmio::iomap(base_reg.address as usize, mmio_size)?; + let irq_num = decode_fdt_irq(&info.interrupts()); + + let host = crab_usb::USBHost::new_xhci(mmio, usb_kernel()).map_err(|err| { + OnProbeError::other(format!( + "failed to create xHCI host for [{}]: {err}", + info.node.name() + )) + })?; + + plat_dev.register_usb_host(DRIVER_NAME, host, irq_num); + info!( + "xHCI MMIO host registered successfully for {} with irq {:?}", + info.node.name(), + irq_num + ); + Ok(()) +} diff --git a/platform/axplat-dyn/src/drivers/usb/xhci_pci.rs b/drivers/ax-driver/src/usb/xhci_pci.rs similarity index 74% rename from platform/axplat-dyn/src/drivers/usb/xhci_pci.rs rename to drivers/ax-driver/src/usb/xhci_pci.rs index 925ec954cb..7d1047bf85 100644 --- a/platform/axplat-dyn/src/drivers/usb/xhci_pci.rs +++ b/drivers/ax-driver/src/usb/xhci_pci.rs @@ -2,17 +2,17 @@ extern crate alloc; use alloc::format; +use log::info; use pcie::CommandRegister; use rdrive::{ - PlatformDevice, module_driver, + PlatformDevice, probe::{ OnProbeError, pci::{EndpointRc, FnOnProbe}, }, }; -use super::{PlatformDeviceUsbHost, USB_KERNEL, align_up_4k, resolve_pci_irq_from_fdt}; -use crate::drivers::iomap; +use super::{PlatformDeviceUsbHost, align_up_4k, pci_irq_or_error, usb_kernel}; const DRIVER_NAME: &str = "usb-xhci-pci"; @@ -40,12 +40,9 @@ fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnPr cmd }); - let mmio = iomap(bar.start.into(), align_up_4k(bar.count().max(1)))?; - let irq_num = Some( - resolve_pci_irq_from_fdt(endpoint) - .map_err(|err| OnProbeError::other(alloc::format!("{err}")))?, - ); - let host = crab_usb::USBHost::new_xhci(mmio, &USB_KERNEL).map_err(|err| { + let mmio = crate::mmio::iomap(bar.start, align_up_4k(bar.count().max(1)))?; + let irq_num = Some(pci_irq_or_error(endpoint)?); + let host = crab_usb::USBHost::new_xhci(mmio, usb_kernel()).map_err(|err| { OnProbeError::other(format!( "failed to create xHCI host for PCI endpoint {}: {err}", endpoint.address() diff --git a/drivers/ax-driver/src/virtio/block.rs b/drivers/ax-driver/src/virtio/block.rs new file mode 100644 index 0000000000..c62f14b9ed --- /dev/null +++ b/drivers/ax-driver/src/virtio/block.rs @@ -0,0 +1,187 @@ +extern crate alloc; + +use alloc::format; + +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; +#[cfg(any(probe = "fdt", probe = "pci"))] +use virtio_drivers::transport::DeviceType; +use virtio_drivers::{ + Error as VirtIoError, + device::blk::{SECTOR_SIZE, VirtIOBlk}, + transport::Transport, +}; + +use crate::{block::PlatformDeviceBlock, virtio::VirtIoHalImpl}; + +const VIRTIO_BLK_DMA_BUFFER_SIZE: usize = 32 * SECTOR_SIZE; + +#[cfg(probe = "pci")] +module_driver!( + name: "VirtIO Block", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci, + }], +); + +#[cfg(probe = "pci")] +fn probe_pci( + endpoint: &mut rdrive::probe::pci::EndpointRc, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + let transport = crate::pci::take_virtio_transport(endpoint, DeviceType::Block)?; + register_transport(plat_dev, transport) +} + +#[cfg(probe = "fdt")] +module_driver!( + name: "VirtIO MMIO Block", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["virtio,mmio"], + on_probe: probe_fdt, + }], +); + +#[cfg(probe = "fdt")] +fn probe_fdt( + info: rdrive::register::FdtInfo<'_>, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + let (ty, transport) = crate::virtio::probe_fdt_mmio_device(&info)?; + if ty != DeviceType::Block { + return Err(OnProbeError::NotMatch); + } + register_transport(plat_dev, transport) +} + +pub fn register_transport( + plat_dev: PlatformDevice, + transport: T, +) -> Result<(), OnProbeError> { + let dev = VirtIoBlkDevice::new(transport) + .map_err(|err| OnProbeError::other(format!("failed to initialize virtio-blk: {err:?}")))?; + plat_dev.register_block(BlockDevice { + dev: Some(dev), + irq_enabled: false, + }); + log::info!("registered virtio block device"); + Ok(()) +} + +struct VirtIoBlkDevice { + raw: VirtIOBlk, +} + +unsafe impl Send for VirtIoBlkDevice {} + +impl VirtIoBlkDevice { + fn new(transport: T) -> Result { + let mut raw = VirtIOBlk::new(transport)?; + raw.disable_interrupts(); + Ok(Self { raw }) + } +} + +struct BlockDevice { + dev: Option>, + irq_enabled: bool, +} + +impl DriverGeneric for BlockDevice { + fn name(&self) -> &str { + "virtio-blk" + } +} + +impl rd_block::Interface for BlockDevice { + fn create_queue(&mut self) -> Option> { + self.dev + .take() + .map(|dev| alloc::boxed::Box::new(BlockQueue { raw: dev }) as _) + } + + fn enable_irq(&mut self) { + self.irq_enabled = true; + } + + fn disable_irq(&mut self) { + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> rd_block::Event { + rd_block::Event::none() + } +} + +struct BlockQueue { + raw: VirtIoBlkDevice, +} + +impl rd_block::IQueue for BlockQueue { + fn id(&self) -> usize { + 0 + } + + fn num_blocks(&self) -> usize { + self.raw.raw.capacity() as _ + } + + fn block_size(&self) -> usize { + SECTOR_SIZE + } + + fn buff_config(&self) -> rd_block::BuffConfig { + rd_block::BuffConfig { + dma_mask: u64::MAX, + align: 0x1000, + size: VIRTIO_BLK_DMA_BUFFER_SIZE, + } + } + + fn submit_request( + &mut self, + request: rd_block::Request<'_>, + ) -> Result { + match request.kind { + rd_block::RequestKind::Read(mut buffer) => { + self.raw + .raw + .read_blocks(request.block_id, &mut buffer) + .map_err(map_virtio_err_to_blk_err)?; + } + rd_block::RequestKind::Write(items) => { + self.raw + .raw + .write_blocks(request.block_id, items) + .map_err(map_virtio_err_to_blk_err)?; + } + } + Ok(rd_block::RequestId::new(0)) + } + + fn poll_request(&mut self, _request: rd_block::RequestId) -> Result<(), rd_block::BlkError> { + Ok(()) + } +} + +fn map_virtio_err_to_blk_err(err: VirtIoError) -> rd_block::BlkError { + match err { + VirtIoError::QueueFull | VirtIoError::NotReady => rd_block::BlkError::Retry, + VirtIoError::WrongToken + | VirtIoError::ConfigSpaceTooSmall + | VirtIoError::ConfigSpaceMissing => rd_block::BlkError::Other("bad internal state".into()), + VirtIoError::AlreadyUsed => rd_block::BlkError::Other("already exists".into()), + VirtIoError::InvalidParam => rd_block::BlkError::Other("invalid parameter".into()), + VirtIoError::DmaError => rd_block::BlkError::NoMemory, + VirtIoError::IoError => rd_block::BlkError::Other("I/O error".into()), + VirtIoError::Unsupported => rd_block::BlkError::NotSupported, + VirtIoError::SocketDeviceError(_) => rd_block::BlkError::Other("socket error".into()), + } +} diff --git a/drivers/ax-driver/src/virtio/display.rs b/drivers/ax-driver/src/virtio/display.rs new file mode 100644 index 0000000000..97e4a214c4 --- /dev/null +++ b/drivers/ax-driver/src/virtio/display.rs @@ -0,0 +1,99 @@ +extern crate alloc; + +use alloc::format; + +use rdif_display::{DisplayError, DisplayInfo, FrameBuffer, PixelFormat}; +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; +#[cfg(probe = "pci")] +use virtio_drivers::transport::DeviceType; +use virtio_drivers::{Error as VirtIoError, device::gpu::VirtIOGpu, transport::Transport}; + +use crate::{display::PlatformDeviceDisplay, virtio::VirtIoHalImpl}; + +#[cfg(probe = "pci")] +module_driver!( + name: "VirtIO GPU", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci, + }], +); + +#[cfg(probe = "pci")] +fn probe_pci( + endpoint: &mut rdrive::probe::pci::EndpointRc, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + let transport = crate::pci::take_virtio_transport(endpoint, DeviceType::GPU)?; + register_transport(plat_dev, transport) +} + +pub fn register_transport( + plat_dev: PlatformDevice, + transport: T, +) -> Result<(), OnProbeError> { + let dev = VirtIoDisplay::new(transport) + .map_err(|err| OnProbeError::other(format!("failed to initialize virtio-gpu: {err:?}")))?; + plat_dev.register_display(dev); + log::info!("registered virtio GPU device"); + Ok(()) +} + +struct VirtIoDisplay { + raw: VirtIOGpu, + info: DisplayInfo, + fb_base: *mut u8, +} + +unsafe impl Send for VirtIoDisplay {} + +impl VirtIoDisplay { + fn new(transport: T) -> Result { + let mut raw = VirtIOGpu::new(transport)?; + let framebuffer = raw.setup_framebuffer()?; + let fb_base = framebuffer.as_mut_ptr(); + let fb_size = framebuffer.len(); + let (width, height) = raw.resolution()?; + let info = DisplayInfo { + width, + height, + stride: width as usize * 4, + format: PixelFormat::Xrgb8888, + fb_size, + }; + Ok(Self { raw, info, fb_base }) + } +} + +impl DriverGeneric for VirtIoDisplay { + fn name(&self) -> &str { + "virtio-gpu" + } +} + +impl rdif_display::Interface for VirtIoDisplay { + fn info(&self) -> DisplayInfo { + self.info + } + + fn framebuffer(&mut self) -> Result, DisplayError> { + Ok(unsafe { FrameBuffer::from_raw_parts_mut(self.fb_base, self.info.fb_size) }) + } + + fn need_flush(&self) -> bool { + true + } + + fn flush(&mut self) -> Result<(), DisplayError> { + self.raw.flush().map_err(map_display_err) + } +} + +fn map_display_err(err: VirtIoError) -> DisplayError { + match err { + VirtIoError::Unsupported => DisplayError::NotSupported, + VirtIoError::NotReady => DisplayError::NotAvailable, + _ => DisplayError::Other(alloc::boxed::Box::new(err)), + } +} diff --git a/drivers/ax-driver/src/virtio/input.rs b/drivers/ax-driver/src/virtio/input.rs new file mode 100644 index 0000000000..44aa097a95 --- /dev/null +++ b/drivers/ax-driver/src/virtio/input.rs @@ -0,0 +1,148 @@ +extern crate alloc; + +use alloc::{borrow::ToOwned, format, string::String}; + +use rdif_input::{AbsInfo, EventType, InputDeviceId, InputError, InputEvent}; +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; +#[cfg(probe = "pci")] +use virtio_drivers::transport::DeviceType; +use virtio_drivers::{ + Error as VirtIoError, + device::input::{InputConfigSelect, VirtIOInput}, + transport::Transport, +}; + +use crate::{input::PlatformDeviceInput, virtio::VirtIoHalImpl}; + +#[cfg(probe = "pci")] +module_driver!( + name: "VirtIO Input", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci, + }], +); + +#[cfg(probe = "pci")] +fn probe_pci( + endpoint: &mut rdrive::probe::pci::EndpointRc, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + let transport = crate::pci::take_virtio_transport(endpoint, DeviceType::Input)?; + register_transport(plat_dev, transport) +} + +pub fn register_transport( + plat_dev: PlatformDevice, + transport: T, +) -> Result<(), OnProbeError> { + let dev = VirtIoInputDevice::new(transport).map_err(|err| { + OnProbeError::other(format!("failed to initialize virtio-input: {err:?}")) + })?; + plat_dev.register_input(dev); + log::info!("registered virtio input device"); + Ok(()) +} + +struct VirtIoInputDevice { + raw: VirtIOInput, + device_id: InputDeviceId, + name: String, + physical_location: String, + unique_id: String, +} + +unsafe impl Send for VirtIoInputDevice {} + +impl VirtIoInputDevice { + fn new(transport: T) -> Result { + let mut raw = VirtIOInput::new(transport)?; + let name = raw.name().unwrap_or_else(|_| "".to_owned()); + let id = raw.ids()?; + let device_id = InputDeviceId { + bus_type: id.bustype, + vendor: id.vendor, + product: id.product, + version: id.version, + }; + let physical_location = format!( + "virtio-input/{:04x}:{:04x}:{:04x}:{:04x}", + device_id.bus_type, device_id.vendor, device_id.product, device_id.version + ); + let unique_id = format!( + "virtio-{:04x}-{:04x}-{:04x}-{:04x}-{}", + device_id.bus_type, device_id.vendor, device_id.product, device_id.version, name + ); + Ok(Self { + raw, + device_id, + name, + physical_location, + unique_id, + }) + } +} + +impl DriverGeneric for VirtIoInputDevice { + fn name(&self) -> &str { + &self.name + } +} + +impl rdif_input::Interface for VirtIoInputDevice { + fn device_id(&self) -> InputDeviceId { + self.device_id + } + + fn physical_location(&self) -> &str { + &self.physical_location + } + + fn unique_id(&self) -> &str { + &self.unique_id + } + + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> Result { + self.raw + .query_config_select(InputConfigSelect::EvBits, ty as u8, out) + .map(|read| read != 0) + .map_err(map_input_err) + } + + fn read_event(&mut self) -> Result { + self.raw.ack_interrupt(); + let event = self.raw.pop_pending_event().ok_or(InputError::Again)?; + Ok(InputEvent { + event_type: event.event_type, + code: event.code, + value: event.value as i32, + }) + } + + fn get_prop_bits(&mut self, out: &mut [u8]) -> Result { + let bits = self.raw.prop_bits().map_err(map_input_err)?; + let len = bits.len().min(out.len()); + out[..len].copy_from_slice(&bits[..len]); + Ok(len) + } + + fn get_abs_info(&mut self, axis: u8) -> Result { + let info = self.raw.abs_info(axis).map_err(map_input_err)?; + Ok(AbsInfo { + min: info.min as i32, + max: info.max as i32, + fuzz: info.fuzz as i32, + flat: info.flat as i32, + res: info.res as i32, + }) + } +} + +fn map_input_err(err: VirtIoError) -> InputError { + match err { + VirtIoError::Unsupported => InputError::NotSupported, + VirtIoError::NotReady => InputError::Again, + _ => InputError::Other(alloc::boxed::Box::new(err)), + } +} diff --git a/drivers/ax-driver/src/virtio/mod.rs b/drivers/ax-driver/src/virtio/mod.rs new file mode 100644 index 0000000000..01e5be1029 --- /dev/null +++ b/drivers/ax-driver/src/virtio/mod.rs @@ -0,0 +1,187 @@ +use core::{marker::PhantomData, ptr::NonNull}; + +use ax_alloc::{UsageKind, global_allocator}; +#[cfg(feature = "virtio-net")] +use virtio_drivers::Error as VirtIoError; +use virtio_drivers::{ + BufferDirection, Hal as VirtIoHal, PhysAddr as VirtIoPhysAddr, + transport::{DeviceType, Transport, mmio::MmioTransport}, +}; + +#[cfg(feature = "virtio-blk")] +pub mod block; +#[cfg(feature = "virtio-gpu")] +pub mod display; +#[cfg(feature = "virtio-input")] +pub mod input; +#[cfg(feature = "virtio-net")] +pub mod net; +#[cfg(feature = "virtio-socket")] +pub mod vsock; + +pub const MMIO_DEVICE_NAME: &str = "virtio-mmio"; + +#[cfg(all( + probe = "static", + any( + feature = "virtio-blk", + feature = "virtio-net", + feature = "virtio-gpu", + feature = "virtio-input", + feature = "virtio-socket", + ) +))] +module_driver!( + name: "Static VirtIO MMIO", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe_static_mmio, + }], +); + +pub struct VirtIoHalImpl(PhantomData<()>); + +unsafe impl VirtIoHal for VirtIoHalImpl { + fn dma_alloc(pages: usize, _direction: BufferDirection) -> (VirtIoPhysAddr, NonNull) { + let Ok(vaddr) = global_allocator().alloc_pages(pages, 0x1000, UsageKind::Dma) else { + return (0, NonNull::dangling()); + }; + let paddr = axklib::mem::virt_to_phys(vaddr.into()).as_usize() as VirtIoPhysAddr; + let ptr = NonNull::new(vaddr as _).expect("DMA allocator returned null"); + (paddr, ptr) + } + + unsafe fn dma_dealloc(_paddr: VirtIoPhysAddr, vaddr: NonNull, pages: usize) -> i32 { + global_allocator().dealloc_pages(vaddr.as_ptr() as usize, pages, UsageKind::Dma); + 0 + } + + unsafe fn mmio_phys_to_virt(paddr: VirtIoPhysAddr, size: usize) -> NonNull { + axklib::mmio::ioremap_raw((paddr as usize).into(), size) + .map(|mmio| mmio.as_nonnull_ptr()) + .expect("failed to map VirtIO MMIO") + } + + unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> VirtIoPhysAddr { + let vaddr = buffer.as_ptr() as *mut u8 as usize; + axklib::mem::virt_to_phys(vaddr.into()).as_usize() as VirtIoPhysAddr + } + + unsafe fn unshare(_paddr: VirtIoPhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { + } +} + +pub fn probe_mmio_device( + reg_base: *mut u8, + reg_size: usize, +) -> Option<(DeviceType, MmioTransport<'static>)> { + if reg_base.is_null() || reg_size == 0 { + return None; + } + + let header = NonNull::new(reg_base as *mut virtio_drivers::transport::mmio::VirtIOHeader)?; + let transport = unsafe { MmioTransport::new(header, reg_size) }.ok()?; + Some((transport.device_type(), transport)) +} + +#[cfg(all( + probe = "static", + any( + feature = "virtio-blk", + feature = "virtio-net", + feature = "virtio-gpu", + feature = "virtio-input", + feature = "virtio-socket", + ) +))] +fn probe_static_mmio( + info: rdrive::probe::static_::StaticInfo, + plat_dev: rdrive::PlatformDevice, +) -> Result<(), rdrive::probe::OnProbeError> { + if info.name() != MMIO_DEVICE_NAME { + return Err(rdrive::probe::OnProbeError::NotMatch); + } + let reg = info + .resource_index() + .and_then(|index| info.regs().get(index)) + .or_else(|| info.regs().first()) + .copied() + .ok_or(rdrive::probe::OnProbeError::NotMatch)?; + let (base, size) = reg; + let mmio = axklib::mmio::ioremap_raw(base.into(), size).map_err(|err| { + rdrive::probe::OnProbeError::other(alloc::format!( + "failed to map virtio-mmio {base:#x}: {err:?}", + )) + })?; + let Some((ty, transport)) = probe_mmio_device(mmio.as_ptr(), size) else { + return Err(rdrive::probe::OnProbeError::NotMatch); + }; + register_static_transport(plat_dev, ty, transport) +} + +#[cfg(all( + probe = "static", + any( + feature = "virtio-blk", + feature = "virtio-net", + feature = "virtio-gpu", + feature = "virtio-input", + feature = "virtio-socket", + ) +))] +fn register_static_transport( + plat_dev: rdrive::PlatformDevice, + ty: DeviceType, + transport: T, +) -> Result<(), rdrive::probe::OnProbeError> { + match ty { + #[cfg(feature = "virtio-blk")] + DeviceType::Block => block::register_transport(plat_dev, transport), + #[cfg(feature = "virtio-net")] + DeviceType::Network => net::register_transport(plat_dev, transport), + #[cfg(feature = "virtio-gpu")] + DeviceType::GPU => display::register_transport(plat_dev, transport), + #[cfg(feature = "virtio-input")] + DeviceType::Input => input::register_transport(plat_dev, transport), + #[cfg(feature = "virtio-socket")] + DeviceType::Socket => vsock::register_transport(plat_dev, transport), + _ => Err(rdrive::probe::OnProbeError::NotMatch), + } +} + +#[cfg(probe = "fdt")] +pub fn probe_fdt_mmio_device( + info: &rdrive::register::FdtInfo<'_>, +) -> Result<(DeviceType, MmioTransport<'static>), rdrive::probe::OnProbeError> { + let base_reg = info.node.regs().into_iter().next().ok_or_else(|| { + rdrive::probe::OnProbeError::other(alloc::format!("[{}] has no reg", info.node.name())) + })?; + + let mmio_size = base_reg.size.unwrap_or(0x1000) as usize; + let mmio_base = crate::mmio::iomap(base_reg.address as usize, mmio_size)?.as_ptr(); + probe_mmio_device(mmio_base, mmio_size).ok_or(rdrive::probe::OnProbeError::NotMatch) +} + +#[cfg(feature = "virtio-net")] +pub fn map_virtio_error(err: VirtIoError) -> &'static str { + match err { + VirtIoError::QueueFull => "virtio queue full", + VirtIoError::NotReady => "virtio device not ready", + VirtIoError::WrongToken => "virtio queue returned a wrong token", + VirtIoError::AlreadyUsed => "virtio resource is already used", + VirtIoError::InvalidParam => "virtio invalid parameter", + VirtIoError::DmaError => "virtio DMA error", + VirtIoError::IoError => "virtio I/O error", + VirtIoError::Unsupported => "virtio operation unsupported", + VirtIoError::ConfigSpaceTooSmall => "virtio config space too small", + VirtIoError::ConfigSpaceMissing => "virtio config space missing", + VirtIoError::SocketDeviceError(_) => "virtio socket device error", + } +} + +#[cfg(feature = "virtio-net")] +pub trait VirtIoTransport: Transport + 'static {} + +#[cfg(feature = "virtio-net")] +impl VirtIoTransport for T {} diff --git a/drivers/ax-driver/src/virtio/net.rs b/drivers/ax-driver/src/virtio/net.rs new file mode 100644 index 0000000000..714673edcb --- /dev/null +++ b/drivers/ax-driver/src/virtio/net.rs @@ -0,0 +1,352 @@ +extern crate alloc; + +use alloc::{boxed::Box, collections::BTreeMap, format, sync::Arc}; +use core::{ + cell::UnsafeCell, + sync::atomic::{AtomicBool, Ordering}, +}; + +use ax_kernel_guard::NoPreemptIrqSave; +use rd_net::{DmaBuffer, Event, IRxQueue, ITxQueue, Interface, NetError, QueueConfig}; +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; +#[cfg(probe = "pci")] +use virtio_drivers::transport::DeviceType; +use virtio_drivers::{Error as VirtIoError, device::net::VirtIONetRaw, transport::Transport}; + +use crate::{ + net::PlatformDeviceNet, + virtio::{self, VirtIoHalImpl, VirtIoTransport}, +}; + +const QUEUE_SIZE: usize = 64; +const BUFFER_SIZE: usize = 2048; + +#[cfg(probe = "pci")] +module_driver!( + name: "VirtIO Net", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci, + }], +); + +struct VirtIoNetDevice { + inner: Arc>, + irq_enabled: bool, + tx_created: bool, + rx_created: bool, +} + +impl VirtIoNetDevice { + fn new(transport: T) -> Result { + let mut raw = VirtIONetRaw::new(transport)?; + raw.disable_interrupts(); + Ok(Self { + inner: Arc::new(VirtioNetInnerCell::new(NetInner::new(raw))), + irq_enabled: false, + tx_created: false, + rx_created: false, + }) + } +} + +impl DriverGeneric for VirtIoNetDevice { + fn name(&self) -> &str { + "virtio-net" + } +} + +impl rd_net::Interface for VirtIoNetDevice { + fn mac_address(&self) -> [u8; 6] { + self.inner.with_task(|inner| inner.raw.mac_address()) + } + + fn create_tx_queue(&mut self) -> Option> { + if self.tx_created { + return None; + } + self.tx_created = true; + Some(Box::new(NetTxQueue { + inner: Arc::clone(&self.inner), + })) + } + + fn create_rx_queue(&mut self) -> Option> { + if self.rx_created { + return None; + } + self.rx_created = true; + Some(Box::new(NetRxQueue { + inner: Arc::clone(&self.inner), + })) + } + + fn enable_irq(&mut self) { + self.irq_enabled = true; + self.inner.with_task(|inner| inner.raw.enable_interrupts()); + } + + fn disable_irq(&mut self) { + self.irq_enabled = false; + self.inner.with_task(|inner| inner.raw.disable_interrupts()); + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> Event { + self.inner.with_irq(|inner| { + let _ = inner.raw.ack_interrupt(); + + let mut event = Event::none(); + if inner.raw.poll_transmit().is_some() { + event.tx_queue.insert(0); + } + if inner.raw.poll_receive().is_some() { + event.rx_queue.insert(0); + } + event + }) + } +} + +struct VirtioNetInnerCell { + inner: UnsafeCell>, + task_active: AtomicBool, +} + +unsafe impl Send for VirtioNetInnerCell {} +unsafe impl Sync for VirtioNetInnerCell {} + +impl VirtioNetInnerCell { + fn new(inner: NetInner) -> Self { + Self { + inner: UnsafeCell::new(inner), + task_active: AtomicBool::new(false), + } + } + + fn with_task(&self, f: impl FnOnce(&mut NetInner) -> R) -> R { + let _guard = NoPreemptIrqSave::new(); + let _active = TaskAccessFlag::enter(&self.task_active); + // SAFETY: task-side callers enter with local IRQ/preemption disabled, + // and upper rd-net users serialize queue/device calls with SpinNoIrq. + f(unsafe { &mut *self.inner.get() }) + } + + fn with_irq(&self, f: impl FnOnce(&mut NetInner) -> R) -> R { + debug_assert!( + !self.task_active.load(Ordering::Acquire), + "virtio-net IRQ access interrupted task access" + ); + // SAFETY: IRQ-side access never waits on a task lock. The task side + // excludes local IRQ reentry while it mutates the shared raw queues. + f(unsafe { &mut *self.inner.get() }) + } +} + +struct TaskAccessFlag<'a>(&'a AtomicBool); + +impl<'a> TaskAccessFlag<'a> { + fn enter(active: &'a AtomicBool) -> Self { + debug_assert!( + !active.swap(true, Ordering::AcqRel), + "virtio-net inner task access is already active" + ); + Self(active) + } +} + +impl Drop for TaskAccessFlag<'_> { + fn drop(&mut self) { + self.0.store(false, Ordering::Release); + } +} + +struct NetInner { + raw: VirtIONetRaw, + tx_inflight: BTreeMap, + rx_inflight: BTreeMap, +} + +unsafe impl Send for NetInner {} + +impl NetInner { + fn new(raw: VirtIONetRaw) -> Self { + Self { + raw, + tx_inflight: BTreeMap::new(), + rx_inflight: BTreeMap::new(), + } + } + + fn queue_config() -> QueueConfig { + QueueConfig { + dma_mask: u64::MAX, + align: 0x1000, + buf_size: BUFFER_SIZE, + ring_size: QUEUE_SIZE, + } + } + + fn submit_tx(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + let packet = unsafe { core::slice::from_raw_parts(buffer.virt.as_ptr(), buffer.len) }; + let mut staging = alloc::vec![0; self.raw_header_len()? + buffer.len]; + let header_len = self + .raw + .fill_buffer_header(&mut staging) + .map_err(map_net_error)?; + staging[header_len..header_len + buffer.len].copy_from_slice(packet); + let token = unsafe { self.raw.transmit_begin(&staging) }.map_err(map_net_error)?; + self.tx_inflight.insert( + token, + TxInflight { + bus_addr: buffer.bus_addr, + staging, + }, + ); + Ok(()) + } + + fn reclaim_tx(&mut self) -> Option { + let token = self.raw.poll_transmit()?; + let inflight = self.tx_inflight.remove(&token)?; + let _ = unsafe { self.raw.transmit_complete(token, &inflight.staging) }; + Some(inflight.bus_addr) + } + + fn submit_rx(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + let rx_buffer = + unsafe { core::slice::from_raw_parts_mut(buffer.virt.as_ptr(), buffer.len) }; + let token = unsafe { self.raw.receive_begin(rx_buffer) }.map_err(map_net_error)?; + self.rx_inflight.insert( + token, + RxInflight { + virt_addr: buffer.virt.as_ptr() as usize, + bus_addr: buffer.bus_addr, + len: buffer.len, + }, + ); + Ok(()) + } + + fn reclaim_rx(&mut self) -> Option<(u64, usize)> { + let token = self.raw.poll_receive()?; + let inflight = self.rx_inflight.remove(&token)?; + let buffer = + unsafe { core::slice::from_raw_parts_mut(inflight.virt_addr as *mut u8, inflight.len) }; + let (header_len, packet_len) = unsafe { self.raw.receive_complete(token, buffer) }.ok()?; + buffer.copy_within(header_len..header_len + packet_len, 0); + Some((inflight.bus_addr, packet_len)) + } + + fn raw_header_len(&mut self) -> Result { + let mut header = [0_u8; 16]; + self.raw + .fill_buffer_header(&mut header) + .map_err(map_net_error) + } +} + +struct NetTxQueue { + inner: Arc>, +} + +impl ITxQueue for NetTxQueue { + fn id(&self) -> usize { + 0 + } + + fn config(&self) -> QueueConfig { + NetInner::::queue_config() + } + + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + self.inner.with_task(|inner| inner.submit_tx(buffer)) + } + + fn reclaim(&mut self) -> Option { + self.inner.with_task(NetInner::reclaim_tx) + } +} + +struct NetRxQueue { + inner: Arc>, +} + +impl IRxQueue for NetRxQueue { + fn id(&self) -> usize { + 0 + } + + fn config(&self) -> QueueConfig { + NetInner::::queue_config() + } + + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> { + self.inner.with_task(|inner| inner.submit_rx(buffer)) + } + + fn reclaim(&mut self) -> Option<(u64, usize)> { + self.inner.with_task(NetInner::reclaim_rx) + } +} + +struct TxInflight { + bus_addr: u64, + staging: alloc::vec::Vec, +} + +struct RxInflight { + virt_addr: usize, + bus_addr: u64, + len: usize, +} + +#[cfg(probe = "pci")] +fn probe_pci( + endpoint: &mut rdrive::probe::pci::EndpointRc, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + let irq = crate::net::pci_legacy_irq(endpoint); + let transport = crate::pci::take_virtio_transport(endpoint, DeviceType::Network)?; + register_transport_with_irq(plat_dev, transport, irq) +} + +pub fn register_transport( + plat_dev: PlatformDevice, + transport: T, +) -> Result<(), OnProbeError> { + register_transport_with_irq(plat_dev, transport, None) +} + +pub fn register_transport_with_irq( + plat_dev: PlatformDevice, + transport: T, + irq_num: Option, +) -> Result<(), OnProbeError> { + let mut net = VirtIoNetDevice::new(transport).map_err(|err| { + OnProbeError::other(format!( + "failed to initialize static VirtIO net device: {err:?}" + )) + })?; + if irq_num.is_some() { + net.enable_irq(); + } + plat_dev.register_net("virtio-net", net, irq_num); + log::info!("registered virtio network device irq={irq_num:?}"); + Ok(()) +} + +fn map_net_error(err: VirtIoError) -> NetError { + match err { + VirtIoError::QueueFull | VirtIoError::NotReady => NetError::Retry, + VirtIoError::DmaError => NetError::NoMemory, + VirtIoError::Unsupported => NetError::NotSupported, + other => NetError::Other(Box::new(rd_net::KError::Unknown(virtio::map_virtio_error( + other, + )))), + } +} diff --git a/drivers/ax-driver/src/virtio/vsock.rs b/drivers/ax-driver/src/virtio/vsock.rs new file mode 100644 index 0000000000..c241cb0f9a --- /dev/null +++ b/drivers/ax-driver/src/virtio/vsock.rs @@ -0,0 +1,210 @@ +extern crate alloc; + +use alloc::format; + +use rdif_vsock::{VsockAddr as RdifVsockAddr, VsockConnId, VsockError, VsockEvent}; +use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; +#[cfg(probe = "pci")] +use virtio_drivers::transport::DeviceType; +use virtio_drivers::{ + Error as VirtIoError, + device::socket::{ + DisconnectReason, VirtIOSocket, VsockAddr, VsockConnectionManager, + VsockEvent as RawVsockEvent, VsockEventType, + }, + transport::Transport, +}; + +use crate::{virtio::VirtIoHalImpl, vsock::PlatformDeviceVsock}; + +const DEFAULT_RX_BUFFER_CAPACITY: u32 = 32 * 1024; + +#[cfg(probe = "pci")] +module_driver!( + name: "VirtIO Socket", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Pci { + on_probe: probe_pci, + }], +); + +#[cfg(probe = "pci")] +fn probe_pci( + endpoint: &mut rdrive::probe::pci::EndpointRc, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + let transport = crate::pci::take_virtio_transport(endpoint, DeviceType::Socket)?; + register_transport(plat_dev, transport) +} + +pub fn register_transport( + plat_dev: PlatformDevice, + transport: T, +) -> Result<(), OnProbeError> { + let dev = VirtIoVsock::new(transport).map_err(|err| { + OnProbeError::other(format!("failed to initialize virtio-socket: {err:?}")) + })?; + plat_dev.register_vsock(dev); + log::info!("registered virtio socket device"); + Ok(()) +} + +struct VirtIoVsock { + inner: VsockConnectionManager, +} + +unsafe impl Send for VirtIoVsock {} + +impl VirtIoVsock { + fn new(transport: T) -> Result { + let socket = VirtIOSocket::::new(transport)?; + Ok(Self { + inner: VsockConnectionManager::new_with_capacity(socket, DEFAULT_RX_BUFFER_CAPACITY), + }) + } +} + +impl DriverGeneric for VirtIoVsock { + fn name(&self) -> &str { + "virtio-socket" + } +} + +impl rdif_vsock::Interface for VirtIoVsock { + fn guest_cid(&self) -> u64 { + self.inner.guest_cid() + } + + fn listen(&mut self, port: u32) -> Result<(), VsockError> { + validate_port(port)?; + self.inner.listen(port); + Ok(()) + } + + fn connect(&mut self, id: VsockConnId) -> Result<(), VsockError> { + let (peer, local_port) = map_conn_id(id)?; + self.inner + .connect(peer, local_port) + .map_err(map_vsock_error) + } + + fn send(&mut self, id: VsockConnId, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + let (peer, local_port) = map_conn_id(id)?; + self.inner + .send(peer, local_port, buf) + .map(|()| buf.len()) + .map_err(map_vsock_error) + } + + fn recv(&mut self, id: VsockConnId, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + let (peer, local_port) = map_conn_id(id)?; + let read = self + .inner + .recv(peer, local_port, buf) + .map_err(map_vsock_error)?; + if read != 0 { + let _ = self.inner.update_credit(peer, local_port); + } + Ok(read) + } + + fn recv_avail(&mut self, id: VsockConnId) -> Result { + let (peer, local_port) = map_conn_id(id)?; + let available = self + .inner + .recv_buffer_available_bytes(peer, local_port) + .map_err(map_vsock_error)?; + let _ = self.inner.update_credit(peer, local_port); + Ok(available) + } + + fn disconnect(&mut self, id: VsockConnId) -> Result<(), VsockError> { + let (peer, local_port) = map_conn_id(id)?; + self.inner + .shutdown(peer, local_port) + .map_err(map_vsock_error) + } + + fn abort(&mut self, id: VsockConnId) -> Result<(), VsockError> { + let (peer, local_port) = map_conn_id(id)?; + self.inner + .force_close(peer, local_port) + .map_err(map_vsock_error) + } + + fn poll_event(&mut self) -> Result, VsockError> { + self.inner + .poll() + .map(|event| event.map(map_event)) + .map_err(map_vsock_error) + } +} + +fn validate_port(port: u32) -> Result<(), VsockError> { + if port == 0 { + return Err(VsockError::NotAvailable); + } + Ok(()) +} + +fn map_conn_id(id: VsockConnId) -> Result<(VsockAddr, u32), VsockError> { + validate_port(id.peer_addr.port)?; + validate_port(id.local_port)?; + Ok(( + VsockAddr { + cid: id.peer_addr.cid, + port: id.peer_addr.port, + }, + id.local_port, + )) +} + +fn map_rdif_addr(addr: VsockAddr) -> RdifVsockAddr { + RdifVsockAddr { + cid: addr.cid, + port: addr.port, + } +} + +fn map_event_conn(event: &RawVsockEvent) -> VsockConnId { + VsockConnId { + peer_addr: map_rdif_addr(event.source), + local_port: event.destination.port, + } +} + +fn map_event(event: RawVsockEvent) -> VsockEvent { + let conn = map_event_conn(&event); + match event.event_type { + VsockEventType::ConnectionRequest => VsockEvent::ConnectionRequest(conn), + VsockEventType::Connected => VsockEvent::Connected(conn), + VsockEventType::Received { length } => VsockEvent::Received(conn, length), + VsockEventType::Disconnected { reason } => { + let _ = map_disconnect_reason(reason); + VsockEvent::Disconnected(conn) + } + VsockEventType::CreditUpdate => VsockEvent::CreditUpdate(conn), + VsockEventType::CreditRequest => VsockEvent::Unknown, + } +} + +fn map_disconnect_reason(reason: DisconnectReason) -> DisconnectReason { + reason +} + +fn map_vsock_error(err: VirtIoError) -> VsockError { + match err { + VirtIoError::Unsupported => VsockError::NotSupported, + VirtIoError::QueueFull | VirtIoError::NotReady => VsockError::Retry, + VirtIoError::AlreadyUsed => VsockError::AlreadyExists, + VirtIoError::SocketDeviceError(_) => VsockError::NotConnected, + _ => VsockError::Other(alloc::boxed::Box::new(err)), + } +} diff --git a/drivers/ax-driver/src/vsock/binding.rs b/drivers/ax-driver/src/vsock/binding.rs new file mode 100644 index 0000000000..1dcea29073 --- /dev/null +++ b/drivers/ax-driver/src/vsock/binding.rs @@ -0,0 +1,54 @@ +use alloc::{boxed::Box, string::String, vec::Vec}; + +use ax_errno::AxError; +use rdif_vsock::Interface; +use rdrive::{Device, DriverGeneric}; + +pub struct PlatformVsockDevice { + name: String, + vsock: Option>, +} + +impl PlatformVsockDevice { + fn new(name: String, vsock: Box) -> Self { + Self { + name, + vsock: Some(vsock), + } + } +} + +impl DriverGeneric for PlatformVsockDevice { + fn name(&self) -> &str { + &self.name + } +} + +pub trait PlatformDeviceVsock { + fn register_vsock(self, dev: T) + where + T: Interface + 'static; +} + +impl PlatformDeviceVsock for rdrive::PlatformDevice { + fn register_vsock(self, dev: T) + where + T: Interface + 'static, + { + let name = dev.name().into(); + self.register(PlatformVsockDevice::new(name, Box::new(dev))); + } +} + +pub fn take_vsock_devices() -> Result>, AxError> { + let mut devices = Vec::new(); + for dev in rdrive::get_list::() { + devices.push(take_vsock_device(dev)?); + } + Ok(devices) +} + +fn take_vsock_device(device: Device) -> Result, AxError> { + let mut device = device.lock().map_err(|_| AxError::BadState)?; + device.vsock.take().ok_or(AxError::BadState) +} diff --git a/drivers/ax-driver/src/vsock/mod.rs b/drivers/ax-driver/src/vsock/mod.rs new file mode 100644 index 0000000000..e0d3e840a1 --- /dev/null +++ b/drivers/ax-driver/src/vsock/mod.rs @@ -0,0 +1,3 @@ +mod binding; + +pub use binding::*; diff --git a/drivers/blk/nvme-driver/Cargo.toml b/drivers/blk/nvme-driver/Cargo.toml index 33f56b33e8..bd5c4657b2 100644 --- a/drivers/blk/nvme-driver/Cargo.toml +++ b/drivers/blk/nvme-driver/Cargo.toml @@ -11,11 +11,11 @@ repository.workspace = true version = "0.4.2" [dependencies] -ax-kspin.workspace = true dma-api.workspace = true log = "0.4" mmio-api.workspace = true rd-block.workspace = true +spin.workspace = true tock-registers = "0.10" [dev-dependencies] diff --git a/drivers/blk/nvme-driver/src/block.rs b/drivers/blk/nvme-driver/src/block.rs index 9be6af7124..5288bbf404 100644 --- a/drivers/blk/nvme-driver/src/block.rs +++ b/drivers/blk/nvme-driver/src/block.rs @@ -1,11 +1,11 @@ use alloc::{boxed::Box, collections::BTreeSet, sync::Arc}; use core::any::Any; -use ax_kspin::SpinNoIrq as Mutex; use rd_block::{ BlkError, Block as RdBlock, BuffConfig, DriverGeneric, Event, IQueue, IdList, Interface, Request, RequestId, RequestKind, }; +use spin::Mutex; use crate::{Namespace, Nvme, err::Result}; diff --git a/drivers/blk/ramdisk/Cargo.toml b/drivers/blk/ramdisk/Cargo.toml index 322cc13e41..0c6b540c72 100644 --- a/drivers/blk/ramdisk/Cargo.toml +++ b/drivers/blk/ramdisk/Cargo.toml @@ -11,8 +11,8 @@ version = "0.1.1" publish = false [dependencies] -ax-kspin = { workspace = true } rdif-block = { workspace = true } +spin = { workspace = true } [dev-dependencies] dma-api = { workspace = true } diff --git a/drivers/blk/ramdisk/src/lib.rs b/drivers/blk/ramdisk/src/lib.rs index 510c275b70..b27148d4bc 100644 --- a/drivers/blk/ramdisk/src/lib.rs +++ b/drivers/blk/ramdisk/src/lib.rs @@ -4,11 +4,11 @@ extern crate alloc; use alloc::{boxed::Box, sync::Arc, vec::Vec}; -use ax_kspin::SpinNoIrq as Mutex; use rdif_block::{ BlkError, BuffConfig, DriverGeneric, Event, IQueue, IdList, Interface, Request, RequestId, RequestKind, }; +use spin::Mutex; struct RamInner { storage: Vec, diff --git a/drivers/blk/rd-block-volume/Cargo.toml b/drivers/blk/rd-block-volume/Cargo.toml new file mode 100644 index 0000000000..5c2c7d2562 --- /dev/null +++ b/drivers/blk/rd-block-volume/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rd-block-volume" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +categories = ["embedded", "no-std"] +keywords = ["os", "driver", "block", "partition"] +description = "no_std block volume and partition scanner for rd-block style adapters." + +[dependencies] diff --git a/drivers/blk/rd-block-volume/src/error.rs b/drivers/blk/rd-block-volume/src/error.rs new file mode 100644 index 0000000000..fc6b155cb8 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/error.rs @@ -0,0 +1,24 @@ +use core::fmt; + +pub type Result = core::result::Result; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + InvalidBlockSize, + BufferSizeMismatch, + OutOfRange, + Reader, + InvalidPartitionTable, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidBlockSize => f.write_str("invalid block size"), + Self::BufferSizeMismatch => f.write_str("buffer size does not match block count"), + Self::OutOfRange => f.write_str("block access is out of range"), + Self::Reader => f.write_str("block reader failed"), + Self::InvalidPartitionTable => f.write_str("invalid partition table"), + } + } +} diff --git a/drivers/blk/rd-block-volume/src/gpt.rs b/drivers/blk/rd-block-volume/src/gpt.rs new file mode 100644 index 0000000000..3c0bc108f2 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/gpt.rs @@ -0,0 +1,192 @@ +use alloc::{format, string::String, vec::Vec}; + +use crate::{ + BlockReader, BlockRegion, BlockVolume, DiskId, Error, PartitionId, PartitionLabel, + PartitionTableKind, PartitionUuid, Result, mbr, +}; + +const GPT_HEADER_BLOCK: u64 = 1; +const GPT_SIGNATURE: &[u8; 8] = b"EFI PART"; +const MIN_HEADER_SIZE: usize = 92; +const PARTITION_TYPE_GUID_OFFSET: usize = 0; +const UNIQUE_PARTITION_GUID_OFFSET: usize = 16; +const FIRST_LBA_OFFSET: usize = 32; +const LAST_LBA_OFFSET: usize = 40; +const PARTITION_NAME_OFFSET: usize = 56; +const CORE_ENTRY_SIZE: usize = 128; + +pub(crate) fn scan_gpt( + reader: &mut R, + disk_id: DiskId, +) -> Result>> { + let Some(sector0) = mbr::read_sector0(reader)? else { + return Ok(None); + }; + if !mbr::has_protective_mbr(§or0) { + return Ok(None); + } + + let block_size = reader.block_size(); + if block_size < 512 || reader.num_blocks() <= GPT_HEADER_BLOCK { + return Err(Error::InvalidPartitionTable); + } + + let mut header = alloc::vec![0; block_size]; + reader.read_block(GPT_HEADER_BLOCK, &mut header)?; + let layout = parse_header(&header, reader.num_blocks())?; + let entry_bytes = layout + .entry_count + .checked_mul(layout.entry_size) + .ok_or(Error::InvalidPartitionTable)?; + let entry_blocks = u64::try_from(entry_bytes.div_ceil(block_size)) + .map_err(|_| Error::InvalidPartitionTable)?; + if layout + .entries_start_lba + .checked_add(entry_blocks) + .is_none_or(|end| end > reader.num_blocks()) + { + return Err(Error::InvalidPartitionTable); + } + + let mut volumes = Vec::new(); + for index in 0..layout.entry_count { + let entry = read_entry(reader, &layout, index)?; + if is_unused_entry(&entry) { + continue; + } + + let first_lba = le_u64(&entry[FIRST_LBA_OFFSET..FIRST_LBA_OFFSET + 8]); + let last_lba = le_u64(&entry[LAST_LBA_OFFSET..LAST_LBA_OFFSET + 8]); + if first_lba < layout.first_usable_lba + || last_lba > layout.last_usable_lba + || first_lba > last_lba + { + return Err(Error::InvalidPartitionTable); + } + + volumes.push(BlockVolume { + disk_id, + partition_id: PartitionId(index as u32 + 1), + region: BlockRegion::new(first_lba, last_lba - first_lba + 1), + table_kind: PartitionTableKind::Gpt, + bootable: false, + partuuid: Some(PartitionUuid(format_guid( + &entry[UNIQUE_PARTITION_GUID_OFFSET..UNIQUE_PARTITION_GUID_OFFSET + 16], + ))), + partlabel: decode_partition_name(&entry[PARTITION_NAME_OFFSET..CORE_ENTRY_SIZE]) + .map(PartitionLabel), + }); + } + + Ok(Some(volumes)) +} + +fn read_entry(reader: &mut R, layout: &GptLayout, index: usize) -> Result> { + let block_size = reader.block_size(); + let byte_offset = index + .checked_mul(layout.entry_size) + .ok_or(Error::InvalidPartitionTable)?; + let block_offset = byte_offset / block_size; + let offset_in_block = byte_offset % block_size; + let first_block = layout + .entries_start_lba + .checked_add(u64::try_from(block_offset).map_err(|_| Error::InvalidPartitionTable)?) + .ok_or(Error::InvalidPartitionTable)?; + let blocks_to_read = (offset_in_block + CORE_ENTRY_SIZE).div_ceil(block_size); + let mut buf = alloc::vec![0; blocks_to_read * block_size]; + reader.read_blocks(first_block, blocks_to_read as u64, &mut buf)?; + let entry_start = offset_in_block; + let entry_end = entry_start + CORE_ENTRY_SIZE; + + Ok(buf[entry_start..entry_end].to_vec()) +} + +#[derive(Clone, Copy)] +struct GptLayout { + first_usable_lba: u64, + last_usable_lba: u64, + entries_start_lba: u64, + entry_count: usize, + entry_size: usize, +} + +fn parse_header(header: &[u8], disk_blocks: u64) -> Result { + if header.get(0..8) != Some(GPT_SIGNATURE) { + return Err(Error::InvalidPartitionTable); + } + + let header_size = le_u32(&header[12..16]) as usize; + if !(MIN_HEADER_SIZE..=header.len()).contains(&header_size) { + return Err(Error::InvalidPartitionTable); + } + + let current_lba = le_u64(&header[24..32]); + let backup_lba = le_u64(&header[32..40]); + let first_usable_lba = le_u64(&header[40..48]); + let last_usable_lba = le_u64(&header[48..56]); + let entries_start_lba = le_u64(&header[72..80]); + let entry_count = le_u32(&header[80..84]) as usize; + let entry_size = le_u32(&header[84..88]) as usize; + + if current_lba != GPT_HEADER_BLOCK + || backup_lba >= disk_blocks + || first_usable_lba > last_usable_lba + || last_usable_lba >= disk_blocks + || entries_start_lba >= disk_blocks + || entry_count == 0 + || entry_size < CORE_ENTRY_SIZE + || !entry_size.is_multiple_of(8) + { + return Err(Error::InvalidPartitionTable); + } + + Ok(GptLayout { + first_usable_lba, + last_usable_lba, + entries_start_lba, + entry_count, + entry_size, + }) +} + +fn is_unused_entry(entry: &[u8]) -> bool { + entry[PARTITION_TYPE_GUID_OFFSET..PARTITION_TYPE_GUID_OFFSET + 16] + .iter() + .all(|byte| *byte == 0) +} + +fn decode_partition_name(bytes: &[u8]) -> Option { + let mut name = String::new(); + for chunk in bytes.chunks_exact(2) { + let unit = u16::from_le_bytes(chunk.try_into().expect("slice length is fixed")); + if unit == 0 { + break; + } + if let Some(ch) = char::from_u32(unit as u32) { + name.push(ch); + } + } + (!name.is_empty()).then_some(name) +} + +fn format_guid(bytes: &[u8]) -> String { + let data1 = le_u32(&bytes[0..4]); + let data2 = le_u16(&bytes[4..6]); + let data3 = le_u16(&bytes[6..8]); + format!( + "{data1:08x}-{data2:04x}-{data3:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], + ) +} + +fn le_u16(bytes: &[u8]) -> u16 { + u16::from_le_bytes(bytes.try_into().expect("slice length is fixed")) +} + +fn le_u32(bytes: &[u8]) -> u32 { + u32::from_le_bytes(bytes.try_into().expect("slice length is fixed")) +} + +fn le_u64(bytes: &[u8]) -> u64 { + u64::from_le_bytes(bytes.try_into().expect("slice length is fixed")) +} diff --git a/drivers/blk/rd-block-volume/src/lib.rs b/drivers/blk/rd-block-volume/src/lib.rs new file mode 100644 index 0000000000..bff977ea52 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/lib.rs @@ -0,0 +1,18 @@ +#![no_std] + +extern crate alloc; + +mod error; +mod gpt; +mod mbr; +mod reader; +mod scan; +mod types; + +pub use error::{Error, Result}; +pub use reader::BlockReader; +pub use scan::scan_volumes; +pub use types::{ + BlockRegion, BlockVolume, DiskId, PartitionId, PartitionLabel, PartitionTableKind, + PartitionUuid, +}; diff --git a/drivers/blk/rd-block-volume/src/mbr.rs b/drivers/blk/rd-block-volume/src/mbr.rs new file mode 100644 index 0000000000..78d68d6731 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/mbr.rs @@ -0,0 +1,216 @@ +use alloc::{collections::BTreeSet, format, vec::Vec}; + +use crate::{ + BlockReader, BlockRegion, BlockVolume, DiskId, Error, PartitionId, PartitionTableKind, + PartitionUuid, Result, +}; + +pub(crate) const MBR_SIGNATURE_OFFSET: usize = 510; +pub(crate) const PARTITION_TABLE_OFFSET: usize = 446; +pub(crate) const PARTITION_ENTRY_SIZE: usize = 16; +pub(crate) const PARTITION_ENTRY_COUNT: usize = 4; +pub(crate) const GPT_PROTECTIVE_TYPE: u8 = 0xee; +const DISK_SIGNATURE_OFFSET: usize = 440; +const EXTENDED_TYPES: &[u8] = &[0x05, 0x0f, 0x85]; +const FIRST_LOGICAL_PARTITION_ID: u32 = 5; + +pub(crate) fn scan_mbr( + reader: &mut R, + disk_id: DiskId, +) -> Result>> { + let Some(mbr) = read_sector0(reader)? else { + return Ok(None); + }; + if has_protective_mbr(&mbr) { + return Ok(None); + } + + let mut volumes = Vec::new(); + let disk_signature = le_u32(&mbr[DISK_SIGNATURE_OFFSET..DISK_SIGNATURE_OFFSET + 4]); + let mut extended_root = None; + for index in 0..PARTITION_ENTRY_COUNT { + let entry = entry_at(&mbr, index); + let bootable = entry[0] == 0x80; + let partition_type = entry[4]; + let start_block = le_u32(&entry[8..12]) as u64; + let num_blocks = le_u32(&entry[12..16]) as u64; + if partition_type == 0 || num_blocks == 0 { + continue; + } + if !region_within_disk(reader.num_blocks(), start_block, num_blocks) { + return Err(Error::InvalidPartitionTable); + } + if is_extended_type(partition_type) { + if extended_root.is_some() { + return Err(Error::InvalidPartitionTable); + } + extended_root = Some(BlockRegion::new(start_block, num_blocks)); + continue; + } + + volumes.push(BlockVolume { + disk_id, + partition_id: PartitionId(index as u32 + 1), + region: BlockRegion::new(start_block, num_blocks), + table_kind: PartitionTableKind::Mbr, + bootable, + partuuid: mbr_partuuid(disk_signature, index), + partlabel: None, + }); + } + + if let Some(extended) = extended_root { + scan_ebr_chain(reader, disk_id, disk_signature, extended, &mut volumes)?; + } + + Ok((!volumes.is_empty()).then_some(volumes)) +} + +pub(crate) fn has_protective_mbr(mbr: &[u8]) -> bool { + if !has_signature(mbr) { + return false; + } + + (0..PARTITION_ENTRY_COUNT).any(|index| { + let entry = entry_at(mbr, index); + entry[4] == GPT_PROTECTIVE_TYPE && le_u32(&entry[8..12]) == 1 + }) +} + +pub(crate) fn read_sector0(reader: &mut R) -> Result>> { + let block_size = reader.block_size(); + if block_size < 512 { + return Err(Error::InvalidBlockSize); + } + let mut block = alloc::vec![0; block_size]; + reader.read_block(0, &mut block)?; + Ok(has_signature(&block).then_some(block)) +} + +fn has_signature(block: &[u8]) -> bool { + block + .get(MBR_SIGNATURE_OFFSET..MBR_SIGNATURE_OFFSET + 2) + .is_some_and(|sig| sig == [0x55, 0xaa]) +} + +fn entry_at(mbr: &[u8], index: usize) -> &[u8] { + let start = PARTITION_TABLE_OFFSET + index * PARTITION_ENTRY_SIZE; + &mbr[start..start + PARTITION_ENTRY_SIZE] +} + +fn region_within_disk(disk_blocks: u64, start_block: u64, num_blocks: u64) -> bool { + start_block + .checked_add(num_blocks) + .is_some_and(|end| end <= disk_blocks) +} + +fn scan_ebr_chain( + reader: &mut R, + disk_id: DiskId, + disk_signature: u32, + extended: BlockRegion, + volumes: &mut Vec, +) -> Result<()> { + let mut next_ebr = extended.start_block; + let mut partition_id = FIRST_LOGICAL_PARTITION_ID; + let mut visited = BTreeSet::new(); + + while next_ebr != 0 { + if !visited.insert(next_ebr) { + return Err(Error::InvalidPartitionTable); + } + if !region_within_region(extended, next_ebr, 1) + || !region_within_disk(reader.num_blocks(), next_ebr, 1) + { + return Err(Error::InvalidPartitionTable); + } + + let Some(ebr) = read_sector(reader, next_ebr)? else { + return Err(Error::InvalidPartitionTable); + }; + let data_entry = entry_at(&ebr, 0); + let data_type = data_entry[4]; + let data_start = le_u32(&data_entry[8..12]) as u64; + let data_blocks = le_u32(&data_entry[12..16]) as u64; + if data_type != 0 && data_blocks != 0 { + if is_extended_type(data_type) { + return Err(Error::InvalidPartitionTable); + } + + let absolute_start = next_ebr + .checked_add(data_start) + .ok_or(Error::InvalidPartitionTable)?; + if !region_within_region(extended, absolute_start, data_blocks) + || !region_within_disk(reader.num_blocks(), absolute_start, data_blocks) + { + return Err(Error::InvalidPartitionTable); + } + + volumes.push(BlockVolume { + disk_id, + partition_id: PartitionId(partition_id), + region: BlockRegion::new(absolute_start, data_blocks), + table_kind: PartitionTableKind::Mbr, + bootable: data_entry[0] == 0x80, + partuuid: mbr_partuuid(disk_signature, partition_id as usize - 1), + partlabel: None, + }); + partition_id += 1; + } + + let link_entry = entry_at(&ebr, 1); + let link_type = link_entry[4]; + let link_start = le_u32(&link_entry[8..12]) as u64; + let link_blocks = le_u32(&link_entry[12..16]) as u64; + if link_type == 0 || link_blocks == 0 { + break; + } + if !is_extended_type(link_type) { + return Err(Error::InvalidPartitionTable); + } + + let next = extended + .start_block + .checked_add(link_start) + .ok_or(Error::InvalidPartitionTable)?; + if !region_within_region(extended, next, link_blocks) { + return Err(Error::InvalidPartitionTable); + } + next_ebr = next; + } + + Ok(()) +} + +fn read_sector(reader: &mut R, block_id: u64) -> Result>> { + let block_size = reader.block_size(); + if block_size < 512 { + return Err(Error::InvalidBlockSize); + } + let mut block = alloc::vec![0; block_size]; + reader.read_block(block_id, &mut block)?; + Ok(has_signature(&block).then_some(block)) +} + +fn is_extended_type(partition_type: u8) -> bool { + EXTENDED_TYPES.contains(&partition_type) +} + +fn region_within_region(region: BlockRegion, start_block: u64, num_blocks: u64) -> bool { + let Some(end) = start_block.checked_add(num_blocks) else { + return false; + }; + let Some(region_end) = region.start_block.checked_add(region.num_blocks) else { + return false; + }; + + start_block >= region.start_block && end <= region_end +} + +fn mbr_partuuid(disk_signature: u32, index: usize) -> Option { + (disk_signature != 0).then(|| PartitionUuid(format!("{disk_signature:08x}-{:02}", index + 1))) +} + +fn le_u32(bytes: &[u8]) -> u32 { + u32::from_le_bytes(bytes.try_into().expect("slice length is fixed")) +} diff --git a/drivers/blk/rd-block-volume/src/reader.rs b/drivers/blk/rd-block-volume/src/reader.rs new file mode 100644 index 0000000000..508882f716 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/reader.rs @@ -0,0 +1,33 @@ +use crate::{Error, Result}; + +pub trait BlockReader { + fn block_size(&self) -> usize; + fn num_blocks(&self) -> u64; + fn read_block(&mut self, block: u64, buf: &mut [u8]) -> Result<()>; + + fn read_blocks(&mut self, start_block: u64, blocks: u64, buf: &mut [u8]) -> Result<()> { + let block_size = self.block_size(); + if block_size == 0 { + return Err(Error::InvalidBlockSize); + } + let block_count = usize::try_from(blocks).map_err(|_| Error::OutOfRange)?; + if buf.len() + != block_size + .checked_mul(block_count) + .ok_or(Error::OutOfRange)? + { + return Err(Error::BufferSizeMismatch); + } + if start_block + .checked_add(blocks) + .is_none_or(|end| end > self.num_blocks()) + { + return Err(Error::OutOfRange); + } + + for (idx, block_buf) in buf.chunks_exact_mut(block_size).enumerate() { + self.read_block(start_block + idx as u64, block_buf)?; + } + Ok(()) + } +} diff --git a/drivers/blk/rd-block-volume/src/scan.rs b/drivers/blk/rd-block-volume/src/scan.rs new file mode 100644 index 0000000000..9acbfcf619 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/scan.rs @@ -0,0 +1,30 @@ +use alloc::vec::Vec; + +use crate::{ + BlockReader, BlockRegion, BlockVolume, DiskId, Error, PartitionId, PartitionTableKind, Result, + gpt, mbr, +}; + +pub fn scan_volumes(reader: &mut R, disk_id: DiskId) -> Result> { + if reader.block_size() == 0 || reader.num_blocks() == 0 { + return Err(Error::InvalidBlockSize); + } + + if let Some(volumes) = gpt::scan_gpt(reader, disk_id)? { + return Ok(volumes); + } + + if let Some(volumes) = mbr::scan_mbr(reader, disk_id)? { + return Ok(volumes); + } + + Ok(Vec::from([BlockVolume { + disk_id, + partition_id: PartitionId(0), + region: BlockRegion::new(0, reader.num_blocks()), + table_kind: PartitionTableKind::Raw, + bootable: false, + partuuid: None, + partlabel: None, + }])) +} diff --git a/drivers/blk/rd-block-volume/src/types.rs b/drivers/blk/rd-block-volume/src/types.rs new file mode 100644 index 0000000000..5bd09b2e57 --- /dev/null +++ b/drivers/blk/rd-block-volume/src/types.rs @@ -0,0 +1,50 @@ +use alloc::string::String; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct DiskId(pub u64); + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct PartitionId(pub u32); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct BlockRegion { + pub start_block: u64, + pub num_blocks: u64, +} + +impl BlockRegion { + pub const fn new(start_block: u64, num_blocks: u64) -> Self { + Self { + start_block, + num_blocks, + } + } + + pub const fn is_empty(self) -> bool { + self.num_blocks == 0 + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PartitionTableKind { + Raw, + Gpt, + Mbr, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PartitionUuid(pub String); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct PartitionLabel(pub String); + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BlockVolume { + pub disk_id: DiskId, + pub partition_id: PartitionId, + pub region: BlockRegion, + pub table_kind: PartitionTableKind, + pub bootable: bool, + pub partuuid: Option, + pub partlabel: Option, +} diff --git a/drivers/blk/rd-block-volume/tests/scan.rs b/drivers/blk/rd-block-volume/tests/scan.rs new file mode 100644 index 0000000000..cbf2faaad3 --- /dev/null +++ b/drivers/blk/rd-block-volume/tests/scan.rs @@ -0,0 +1,220 @@ +use rd_block_volume::{ + BlockReader, BlockRegion, DiskId, Error, PartitionId, PartitionTableKind, Result, scan_volumes, +}; + +const BLOCK_SIZE: usize = 512; + +struct MemReader { + data: Vec, + block_size: usize, +} + +impl MemReader { + fn new(blocks: usize) -> Self { + Self { + data: vec![0; blocks * BLOCK_SIZE], + block_size: BLOCK_SIZE, + } + } + + fn block_mut(&mut self, block: usize) -> &mut [u8] { + let start = block * self.block_size; + &mut self.data[start..start + self.block_size] + } +} + +impl BlockReader for MemReader { + fn block_size(&self) -> usize { + self.block_size + } + + fn num_blocks(&self) -> u64 { + (self.data.len() / self.block_size) as u64 + } + + fn read_block(&mut self, block: u64, buf: &mut [u8]) -> Result<()> { + if buf.len() != self.block_size { + return Err(Error::BufferSizeMismatch); + } + let start = block as usize * self.block_size; + let end = start + self.block_size; + let src = self.data.get(start..end).ok_or(Error::OutOfRange)?; + buf.copy_from_slice(src); + Ok(()) + } +} + +fn write_mbr_signature(block: &mut [u8]) { + block[510] = 0x55; + block[511] = 0xaa; +} + +fn write_mbr_entry( + block: &mut [u8], + index: usize, + partition_type: u8, + start: u32, + blocks: u32, + bootable: bool, +) { + let offset = 446 + index * 16; + block[offset] = if bootable { 0x80 } else { 0 }; + block[offset + 4] = partition_type; + block[offset + 8..offset + 12].copy_from_slice(&start.to_le_bytes()); + block[offset + 12..offset + 16].copy_from_slice(&blocks.to_le_bytes()); +} + +#[test] +fn raw_disk_fallback_covers_entire_reader() { + let mut reader = MemReader::new(32); + let volumes = scan_volumes(&mut reader, DiskId(7)).unwrap(); + + assert_eq!(volumes.len(), 1); + assert_eq!(volumes[0].disk_id, DiskId(7)); + assert_eq!(volumes[0].partition_id, PartitionId(0)); + assert_eq!(volumes[0].table_kind, PartitionTableKind::Raw); + assert_eq!(volumes[0].region, BlockRegion::new(0, 32)); + assert!(!volumes[0].bootable); + assert_eq!(volumes[0].partuuid, None); + assert_eq!(volumes[0].partlabel, None); +} + +#[test] +fn scans_mbr_primary_partition() { + let mut reader = MemReader::new(128); + let mbr = reader.block_mut(0); + mbr[440..444].copy_from_slice(&0x1234_abcd_u32.to_le_bytes()); + write_mbr_entry(mbr, 0, 0x83, 8, 40, true); + write_mbr_signature(mbr); + + let volumes = scan_volumes(&mut reader, DiskId(2)).unwrap(); + + assert_eq!(volumes.len(), 1); + assert_eq!(volumes[0].disk_id, DiskId(2)); + assert_eq!(volumes[0].partition_id, PartitionId(1)); + assert_eq!(volumes[0].table_kind, PartitionTableKind::Mbr); + assert_eq!(volumes[0].region, BlockRegion::new(8, 40)); + assert!(volumes[0].bootable); + assert_eq!( + volumes[0].partuuid.as_ref().map(|uuid| uuid.0.as_str()), + Some("1234abcd-01") + ); +} + +#[test] +fn scans_mbr_logical_partitions_without_exposing_extended_container() { + let mut reader = MemReader::new(160); + let mbr = reader.block_mut(0); + mbr[440..444].copy_from_slice(&0x1234_abcd_u32.to_le_bytes()); + write_mbr_entry(mbr, 0, 0x83, 8, 8, false); + write_mbr_entry(mbr, 1, 0x0f, 32, 96, false); + write_mbr_signature(mbr); + + let ebr0 = reader.block_mut(32); + write_mbr_entry(ebr0, 0, 0x83, 1, 10, true); + write_mbr_entry(ebr0, 1, 0x05, 20, 40, false); + write_mbr_signature(ebr0); + + let ebr1 = reader.block_mut(52); + write_mbr_entry(ebr1, 0, 0x83, 1, 5, false); + write_mbr_signature(ebr1); + + let volumes = scan_volumes(&mut reader, DiskId(9)).unwrap(); + + assert_eq!(volumes.len(), 3); + assert_eq!(volumes[0].partition_id, PartitionId(1)); + assert_eq!(volumes[0].region, BlockRegion::new(8, 8)); + assert_eq!(volumes[1].partition_id, PartitionId(5)); + assert_eq!(volumes[1].region, BlockRegion::new(33, 10)); + assert!(volumes[1].bootable); + assert_eq!( + volumes[1].partuuid.as_ref().map(|uuid| uuid.0.as_str()), + Some("1234abcd-05") + ); + assert_eq!(volumes[2].partition_id, PartitionId(6)); + assert_eq!(volumes[2].region, BlockRegion::new(53, 5)); + assert!(!volumes[2].bootable); + assert_eq!( + volumes[2].partuuid.as_ref().map(|uuid| uuid.0.as_str()), + Some("1234abcd-06") + ); +} + +#[test] +fn scans_gpt_single_partition() { + let mut reader = MemReader::new(128); + let mbr = reader.block_mut(0); + mbr[446 + 4] = 0xee; + mbr[446 + 8..446 + 12].copy_from_slice(&1u32.to_le_bytes()); + mbr[446 + 12..446 + 16].copy_from_slice(&127u32.to_le_bytes()); + mbr[510] = 0x55; + mbr[511] = 0xaa; + + let header = reader.block_mut(1); + header[0..8].copy_from_slice(b"EFI PART"); + header[8..12].copy_from_slice(&0x0001_0000u32.to_le_bytes()); + header[12..16].copy_from_slice(&92u32.to_le_bytes()); + header[24..32].copy_from_slice(&1u64.to_le_bytes()); + header[32..40].copy_from_slice(&127u64.to_le_bytes()); + header[40..48].copy_from_slice(&34u64.to_le_bytes()); + header[48..56].copy_from_slice(&126u64.to_le_bytes()); + header[72..80].copy_from_slice(&2u64.to_le_bytes()); + header[80..84].copy_from_slice(&4u32.to_le_bytes()); + header[84..88].copy_from_slice(&128u32.to_le_bytes()); + + let entry = &mut reader.block_mut(2)[0..128]; + entry[0] = 0xaf; + entry[16..32].copy_from_slice(&[ + 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, + 0xef, + ]); + entry[32..40].copy_from_slice(&40u64.to_le_bytes()); + entry[40..48].copy_from_slice(&63u64.to_le_bytes()); + let name = "root"; + for (idx, unit) in name.encode_utf16().enumerate() { + entry[56 + idx * 2..58 + idx * 2].copy_from_slice(&unit.to_le_bytes()); + } + + let volumes = scan_volumes(&mut reader, DiskId(3)).unwrap(); + + assert_eq!(volumes.len(), 1); + assert_eq!(volumes[0].disk_id, DiskId(3)); + assert_eq!(volumes[0].partition_id, PartitionId(1)); + assert_eq!(volumes[0].table_kind, PartitionTableKind::Gpt); + assert_eq!(volumes[0].region, BlockRegion::new(40, 24)); + assert_eq!( + volumes[0].partuuid.as_ref().map(|uuid| uuid.0.as_str()), + Some("76543210-ba98-fedc-0123-456789abcdef") + ); + assert_eq!( + volumes[0].partlabel.as_ref().map(|label| label.0.as_str()), + Some("root") + ); +} + +#[test] +fn gpt_entry_array_must_fit_disk() { + let mut reader = MemReader::new(128); + let mbr = reader.block_mut(0); + mbr[446 + 4] = 0xee; + mbr[446 + 8..446 + 12].copy_from_slice(&1u32.to_le_bytes()); + mbr[446 + 12..446 + 16].copy_from_slice(&127u32.to_le_bytes()); + mbr[510] = 0x55; + mbr[511] = 0xaa; + + let header = reader.block_mut(1); + header[0..8].copy_from_slice(b"EFI PART"); + header[8..12].copy_from_slice(&0x0001_0000u32.to_le_bytes()); + header[12..16].copy_from_slice(&92u32.to_le_bytes()); + header[24..32].copy_from_slice(&1u64.to_le_bytes()); + header[32..40].copy_from_slice(&127u64.to_le_bytes()); + header[40..48].copy_from_slice(&34u64.to_le_bytes()); + header[48..56].copy_from_slice(&126u64.to_le_bytes()); + header[72..80].copy_from_slice(&127u64.to_le_bytes()); + header[80..84].copy_from_slice(&8u32.to_le_bytes()); + header[84..88].copy_from_slice(&128u32.to_le_bytes()); + + let err = scan_volumes(&mut reader, DiskId(4)).unwrap_err(); + + assert_eq!(err, Error::InvalidPartitionTable); +} diff --git a/drivers/blk/rd-block/src/lib.rs b/drivers/blk/rd-block/src/lib.rs index 2bb062c1d4..fec815d18c 100644 --- a/drivers/blk/rd-block/src/lib.rs +++ b/drivers/blk/rd-block/src/lib.rs @@ -1,6 +1,8 @@ #![no_std] extern crate alloc; +#[cfg(test)] +extern crate std; use alloc::{ boxed::Box, @@ -121,13 +123,17 @@ impl Block { let queue = self.interface().create_queue()?; let queue_id = queue.id(); let config = queue.buff_config(); + let block_size = queue.block_size(); + if block_size == 0 || config.size < block_size { + return None; + } let layout = Layout::from_size_align(config.size, config.align).ok()?; let dma = DeviceDma::new(config.dma_mask, self.inner.dma_op); let pool = dma.new_pool(layout, DmaDirection::FromDevice, capacity); let waker = self.inner.queue_waker_map.register(queue_id); drop(irq_guard); - Some(CmdQueue::new(queue, waker, pool)) + Some(CmdQueue::new(queue, waker, pool, config.size)) } pub fn create_queue(&mut self) -> Option { @@ -161,14 +167,21 @@ pub struct CmdQueue { interface: Box, waker: Arc, pool: DArrayPool, + buffer_size: usize, } impl CmdQueue { - fn new(interface: Box, waker: Arc, pool: DArrayPool) -> Self { + fn new( + interface: Box, + waker: Arc, + pool: DArrayPool, + buffer_size: usize, + ) -> Self { Self { interface, waker, pool, + buffer_size, } } @@ -184,13 +197,21 @@ impl CmdQueue { self.interface.block_size() } + pub fn max_blocks_per_request(&self) -> usize { + let block_size = self.block_size(); + debug_assert!(block_size > 0); + debug_assert!(self.buffer_size >= block_size); + self.buffer_size / block_size + } + pub fn read_blocks( &mut self, blk_id: usize, blk_count: usize, ) -> impl core::future::Future>> { - let block_id_ls = (blk_id..blk_id + blk_count).collect(); - ReadFuture::new(self, block_id_ls) + let block_size = self.block_size(); + let request_ls = block_ranges(blk_id, blk_count, self.max_blocks_per_request(), block_size); + ReadFuture::new(self, request_ls) } pub fn read_blocks_blocking( @@ -208,12 +229,12 @@ impl CmdQueue { ) -> Vec> { let block_size = self.block_size(); assert_eq!(data.len() % block_size, 0); - let count = data.len() / block_size; - let mut block_vecs = Vec::with_capacity(count); - for i in 0..count { - let blk_id = start_blk_id + i; - let blk_data = &data[i * block_size..(i + 1) * block_size]; - block_vecs.push((blk_id, blk_data)); + let max_blocks = self.max_blocks_per_request(); + let max_bytes = max_blocks * block_size; + let mut block_vecs = Vec::new(); + for (i, chunk) in data.chunks(max_bytes).enumerate() { + let blk_id = start_blk_id + i * max_blocks; + block_vecs.push((blk_id, chunk)); } WriteFuture::new(self, block_vecs).await } @@ -230,21 +251,22 @@ impl CmdQueue { pub struct BlockData { block_id: usize, data: DBuff, + len: usize, } pub struct ReadFuture<'a> { queue: &'a mut CmdQueue, - blk_ls: Vec, - requested: BTreeMap>, + req_ls: Vec<(usize, usize)>, + requested: BTreeMap>, map: BTreeMap, results: BTreeMap>, } impl<'a> ReadFuture<'a> { - fn new(queue: &'a mut CmdQueue, blk_ls: Vec) -> Self { + fn new(queue: &'a mut CmdQueue, req_ls: Vec<(usize, usize)>) -> Self { Self { queue, - blk_ls, + req_ls, requested: BTreeMap::new(), map: BTreeMap::new(), results: BTreeMap::new(), @@ -261,7 +283,7 @@ impl<'a> core::future::Future for ReadFuture<'a> { ) -> Poll { let this = self.get_mut(); - for &blk_id in &this.blk_ls { + for &(blk_id, len) in &this.req_ls { if this.results.contains_key(&blk_id) { continue; } @@ -275,7 +297,7 @@ impl<'a> core::future::Future for ReadFuture<'a> { let kind = RequestKind::Read(Buffer { virt: buff.as_ptr().as_ptr(), bus: buff.dma_addr().as_u64(), - size: buff.len(), + size: len, }); match this.queue.interface.submit_request(Request { @@ -284,7 +306,7 @@ impl<'a> core::future::Future for ReadFuture<'a> { }) { Ok(req_id) => { this.map.insert(blk_id, req_id); - this.requested.insert(blk_id, Some(buff)); + this.requested.insert(blk_id, Some((buff, len))); } Err(BlkError::Retry) => { this.queue.waker.register(cx.waker()); @@ -310,13 +332,15 @@ impl<'a> core::future::Future for ReadFuture<'a> { match this.queue.interface.poll_request(req_id) { Ok(_) => { + let (data, len) = buff + .take() + .expect("DMA read buffer should exist until completion"); this.results.insert( *blk_id, Ok(BlockData { block_id: *blk_id, - data: buff - .take() - .expect("DMA read buffer should exist until completion"), + data, + len, }), ); } @@ -330,8 +354,8 @@ impl<'a> core::future::Future for ReadFuture<'a> { } } - let mut out = Vec::with_capacity(this.blk_ls.len()); - for blk_id in &this.blk_ls { + let mut out = Vec::with_capacity(this.req_ls.len()); + for (blk_id, _) in &this.req_ls { let result = this .results .remove(blk_id) @@ -449,12 +473,154 @@ impl Deref for BlockData { type Target = [u8]; fn deref(&self) -> &Self::Target { - unsafe { core::slice::from_raw_parts(self.data.as_ptr().as_ptr(), self.data.len()) } + unsafe { core::slice::from_raw_parts(self.data.as_ptr().as_ptr(), self.len) } } } impl DerefMut for BlockData { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { core::slice::from_raw_parts_mut(self.data.as_ptr().as_ptr(), self.data.len()) } + unsafe { core::slice::from_raw_parts_mut(self.data.as_ptr().as_ptr(), self.len) } + } +} + +fn block_ranges( + start_blk_id: usize, + block_count: usize, + max_blocks: usize, + block_size: usize, +) -> Vec<(usize, usize)> { + let max_blocks = max_blocks.max(1); + let mut out = Vec::new(); + let mut blk_id = start_blk_id; + let mut remaining = block_count; + while remaining > 0 { + let count = remaining.min(max_blocks); + out.push((blk_id, count * block_size)); + blk_id += count; + remaining -= count; + } + out +} + +#[cfg(test)] +mod tests { + use core::{alloc::Layout, num::NonZeroUsize, ptr::NonNull}; + + use dma_api::{DmaError, DmaHandle, DmaMapHandle}; + + use super::*; + + struct TestDma; + + static TEST_DMA: TestDma = TestDma; + + impl DmaOp for TestDma { + fn page_size(&self) -> usize { + 4096 + } + + unsafe fn map_single( + &self, + _dma_mask: u64, + addr: NonNull, + size: NonZeroUsize, + _align: usize, + _direction: DmaDirection, + ) -> Result { + let layout = Layout::from_size_align(size.get(), 8)?; + Ok(unsafe { DmaMapHandle::new(addr, (addr.as_ptr() as u64).into(), layout, None) }) + } + + unsafe fn unmap_single(&self, _handle: DmaMapHandle) {} + + unsafe fn alloc_coherent(&self, _dma_mask: u64, layout: Layout) -> Option { + let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + let ptr = NonNull::new(ptr)?; + Some(unsafe { DmaHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) + } + + unsafe fn dealloc_coherent(&self, handle: DmaHandle) { + unsafe { std::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; + } + } + + struct TestBlock { + block_size: usize, + buffer_size: usize, + } + + impl DriverGeneric for TestBlock { + fn name(&self) -> &str { + "test-block" + } + } + + impl Interface for TestBlock { + fn create_queue(&mut self) -> Option> { + Some(Box::new(TestQueue { + block_size: self.block_size, + buffer_size: self.buffer_size, + })) + } + + fn enable_irq(&mut self) {} + + fn disable_irq(&mut self) {} + + fn is_irq_enabled(&self) -> bool { + false + } + + fn handle_irq(&mut self) -> Event { + Event::none() + } + } + + struct TestQueue { + block_size: usize, + buffer_size: usize, + } + + impl IQueue for TestQueue { + fn id(&self) -> usize { + 0 + } + + fn num_blocks(&self) -> usize { + 1 + } + + fn block_size(&self) -> usize { + self.block_size + } + + fn buff_config(&self) -> BuffConfig { + BuffConfig { + dma_mask: u64::MAX, + align: 8, + size: self.buffer_size, + } + } + + fn submit_request(&mut self, _request: Request<'_>) -> Result { + Ok(RequestId::new(0)) + } + + fn poll_request(&mut self, _request: RequestId) -> Result<(), BlkError> { + Ok(()) + } + } + + #[test] + fn create_queue_rejects_buffer_smaller_than_block_size() { + let mut block = Block::new( + TestBlock { + block_size: 512, + buffer_size: 256, + }, + &TEST_DMA, + ); + + assert!(block.create_queue_with_capacity(1).is_none()); } } diff --git a/drivers/firmware/arm-scmi-rs/Cargo.toml b/drivers/firmware/arm-scmi-rs/Cargo.toml index fa43b92472..8bab313e5e 100644 --- a/drivers/firmware/arm-scmi-rs/Cargo.toml +++ b/drivers/firmware/arm-scmi-rs/Cargo.toml @@ -11,11 +11,11 @@ keywords = ["arm", "scmi", "no_std", "embedded", "clock"] categories = ["embedded", "hardware-support", "no-std", "os"] [dependencies] -ax-kspin = { workspace = true } bitflags = "2" log = "0.4" mbarrier = "0.1" smccc = "0.2" +spin = { workspace = true } thiserror = {version = "2", default-features = false} tock-registers = "0.10" nb = "1" diff --git a/drivers/firmware/arm-scmi-rs/src/lib.rs b/drivers/firmware/arm-scmi-rs/src/lib.rs index 6101ce38d6..055db17146 100644 --- a/drivers/firmware/arm-scmi-rs/src/lib.rs +++ b/drivers/firmware/arm-scmi-rs/src/lib.rs @@ -48,7 +48,7 @@ mod transport; use alloc::sync::Arc; -use ax_kspin::SpinNoIrq as Mutex; +use spin::Mutex; pub use transport::{Smc, Transport}; type Data = Arc>>; diff --git a/drivers/firmware/arm-scmi-rs/src/protocol/mod.rs b/drivers/firmware/arm-scmi-rs/src/protocol/mod.rs index 50eb34015d..767e569a74 100644 --- a/drivers/firmware/arm-scmi-rs/src/protocol/mod.rs +++ b/drivers/firmware/arm-scmi-rs/src/protocol/mod.rs @@ -1,8 +1,8 @@ use alloc::vec::Vec; use core::sync::atomic::{AtomicI32, Ordering}; -use ax_kspin::SpinNoIrq as Mutex; use mbarrier::smp_mb; +use spin::Mutex; use crate::{Data, Transport, err::ScmiError}; diff --git a/drivers/interface/rdif-display/Cargo.toml b/drivers/interface/rdif-display/Cargo.toml new file mode 100644 index 0000000000..265e6a2b9b --- /dev/null +++ b/drivers/interface/rdif-display/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["周睿 "] +categories = ["embedded", "no-std"] +description = "Driver Interface display definition." +edition.workspace = true +keywords = ["os", "driver"] +license = "MIT" +name = "rdif-display" +repository.workspace = true +version = "0.1.0" + +[dependencies] +rdif-base = { workspace = true } +thiserror = { version = "2", default-features = false } diff --git a/drivers/interface/rdif-display/src/error.rs b/drivers/interface/rdif-display/src/error.rs new file mode 100644 index 0000000000..a2e38a7c7c --- /dev/null +++ b/drivers/interface/rdif-display/src/error.rs @@ -0,0 +1,26 @@ +use alloc::boxed::Box; + +use crate::io; + +#[derive(thiserror::Error, Debug)] +pub enum DisplayError { + #[error("operation not supported")] + NotSupported, + #[error("device is not available")] + NotAvailable, + #[error("invalid framebuffer")] + InvalidFramebuffer, + #[error("other error: {0}")] + Other(Box), +} + +impl From for io::ErrorKind { + fn from(value: DisplayError) -> Self { + match value { + DisplayError::NotSupported => io::ErrorKind::Unsupported, + DisplayError::NotAvailable => io::ErrorKind::NotAvailable, + DisplayError::InvalidFramebuffer => io::ErrorKind::InvalidData, + DisplayError::Other(error) => io::ErrorKind::Other(error), + } + } +} diff --git a/drivers/interface/rdif-display/src/interface.rs b/drivers/interface/rdif-display/src/interface.rs new file mode 100644 index 0000000000..494fb06e8f --- /dev/null +++ b/drivers/interface/rdif-display/src/interface.rs @@ -0,0 +1,38 @@ +use crate::{DisplayError, DisplayInfo, DriverGeneric, FrameBuffer}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Event { + pub changed: bool, +} + +impl Event { + pub const fn none() -> Self { + Self { changed: false } + } +} + +pub trait Interface: DriverGeneric { + fn info(&self) -> DisplayInfo; + + fn framebuffer(&mut self) -> Result, DisplayError>; + + fn need_flush(&self) -> bool { + false + } + + fn flush(&mut self) -> Result<(), DisplayError> { + Ok(()) + } + + fn enable_irq(&mut self) {} + + fn disable_irq(&mut self) {} + + fn is_irq_enabled(&self) -> bool { + false + } + + fn handle_irq(&mut self) -> Event { + Event::none() + } +} diff --git a/drivers/interface/rdif-display/src/lib.rs b/drivers/interface/rdif-display/src/lib.rs new file mode 100644 index 0000000000..e73d9e85e5 --- /dev/null +++ b/drivers/interface/rdif-display/src/lib.rs @@ -0,0 +1,57 @@ +#![no_std] + +extern crate alloc; + +mod error; +mod interface; +mod types; + +pub use error::*; +pub use interface::*; +pub use rdif_base::{DriverGeneric, KError, io}; +pub use types::*; + +#[cfg(test)] +mod tests { + use super::*; + + struct TestDisplay { + fb: [u8; 16], + } + + impl DriverGeneric for TestDisplay { + fn name(&self) -> &str { + "test-display" + } + } + + impl Interface for TestDisplay { + fn info(&self) -> DisplayInfo { + DisplayInfo { + width: 2, + height: 2, + stride: 8, + format: PixelFormat::Xrgb8888, + fb_size: self.fb.len(), + } + } + + fn framebuffer(&mut self) -> Result, DisplayError> { + Ok(FrameBuffer::from_slice(&mut self.fb)) + } + } + + #[test] + fn display_interface_exposes_layout_and_framebuffer() { + let mut display = TestDisplay { fb: [0; 16] }; + let info = display.info(); + assert_eq!(info.stride, 8); + assert_eq!(info.format, PixelFormat::Xrgb8888); + assert_eq!(info.fb_size, 16); + + let mut fb = display.framebuffer().unwrap(); + fb.as_mut_slice()[0] = 0xaa; + assert_eq!(fb.as_slice()[0], 0xaa); + assert_eq!(display.handle_irq(), Event::none()); + } +} diff --git a/drivers/interface/rdif-display/src/types.rs b/drivers/interface/rdif-display/src/types.rs new file mode 100644 index 0000000000..7d40a28f64 --- /dev/null +++ b/drivers/interface/rdif-display/src/types.rs @@ -0,0 +1,62 @@ +use core::ops::{Deref, DerefMut}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PixelFormat { + Rgb565, + Rgb888, + Xrgb8888, + Argb8888, + Bgr888, + Xbgr8888, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DisplayInfo { + pub width: u32, + pub height: u32, + pub stride: usize, + pub format: PixelFormat, + pub fb_size: usize, +} + +pub struct FrameBuffer<'a> { + raw: &'a mut [u8], +} + +impl<'a> FrameBuffer<'a> { + /// # Safety + /// + /// The caller must ensure that `ptr..ptr + len` is valid, uniquely + /// borrowed for the lifetime `'a`, and points to framebuffer memory. + pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Self { + Self { + raw: unsafe { core::slice::from_raw_parts_mut(ptr, len) }, + } + } + + pub fn from_slice(slice: &'a mut [u8]) -> Self { + Self { raw: slice } + } + + pub fn as_slice(&self) -> &[u8] { + self.raw + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + self.raw + } +} + +impl Deref for FrameBuffer<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.raw + } +} + +impl DerefMut for FrameBuffer<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.raw + } +} diff --git a/drivers/interface/rdif-eth/src/lib.rs b/drivers/interface/rdif-eth/src/lib.rs index e6b645247a..1af860fe41 100644 --- a/drivers/interface/rdif-eth/src/lib.rs +++ b/drivers/interface/rdif-eth/src/lib.rs @@ -3,6 +3,7 @@ extern crate alloc; use alloc::boxed::Box; +use core::ptr::NonNull; pub use dma_api; pub use rdif_base::{DriverGeneric, KError, io}; @@ -76,6 +77,18 @@ pub struct QueueConfig { pub ring_size: usize, } +/// DMA buffer passed from the runtime queue layer to a driver queue. +#[derive(Clone, Copy, Debug)] +pub struct DmaBuffer { + /// CPU virtual address for drivers that need to build descriptors from a + /// slice or write transport-specific headers. + pub virt: NonNull, + /// Device-visible DMA address for hardware descriptors. + pub bus_addr: u64, + /// Buffer length in bytes. + pub len: usize, +} + /// Bitmask tracking up to 64 queue identifiers. #[repr(transparent)] #[derive(Debug, Clone, Copy)] @@ -172,7 +185,7 @@ pub trait ITxQueue: Send + 'static { /// /// `bus_addr` must point to a DMA-capable buffer whose first `len` bytes /// contain the packet to be transmitted. - fn submit(&mut self, bus_addr: u64, len: usize) -> Result<(), NetError>; + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError>; /// Reclaim the next completed transmit buffer. /// @@ -198,7 +211,7 @@ pub trait IRxQueue: Send + 'static { /// Submit an empty DMA buffer to hardware. /// /// `bus_addr` must point to a DMA-capable buffer whose total size is `len`. - fn submit(&mut self, bus_addr: u64, len: usize) -> Result<(), NetError>; + fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError>; /// Reclaim the next completed receive buffer. /// diff --git a/drivers/interface/rdif-input/Cargo.toml b/drivers/interface/rdif-input/Cargo.toml new file mode 100644 index 0000000000..3a871c151f --- /dev/null +++ b/drivers/interface/rdif-input/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["周睿 "] +categories = ["embedded", "no-std"] +description = "Driver Interface input definition." +edition.workspace = true +keywords = ["os", "driver"] +license = "MIT" +name = "rdif-input" +repository.workspace = true +version = "0.1.0" + +[dependencies] +rdif-base = { workspace = true } +thiserror = { version = "2", default-features = false } diff --git a/drivers/interface/rdif-input/src/error.rs b/drivers/interface/rdif-input/src/error.rs new file mode 100644 index 0000000000..61ce59f2bc --- /dev/null +++ b/drivers/interface/rdif-input/src/error.rs @@ -0,0 +1,29 @@ +use alloc::boxed::Box; + +use crate::io; + +#[derive(thiserror::Error, Debug)] +pub enum InputError { + #[error("operation not supported")] + NotSupported, + #[error("no event available")] + Again, + #[error("device is not available")] + NotAvailable, + #[error("invalid event")] + InvalidEvent, + #[error("other error: {0}")] + Other(Box), +} + +impl From for io::ErrorKind { + fn from(value: InputError) -> Self { + match value { + InputError::NotSupported => io::ErrorKind::Unsupported, + InputError::Again => io::ErrorKind::Interrupted, + InputError::NotAvailable => io::ErrorKind::NotAvailable, + InputError::InvalidEvent => io::ErrorKind::InvalidData, + InputError::Other(error) => io::ErrorKind::Other(error), + } + } +} diff --git a/drivers/interface/rdif-input/src/event.rs b/drivers/interface/rdif-input/src/event.rs new file mode 100644 index 0000000000..ac7006edb5 --- /dev/null +++ b/drivers/interface/rdif-input/src/event.rs @@ -0,0 +1,50 @@ +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum EventType { + Synchronization = 0x00, + Key = 0x01, + Relative = 0x02, + Absolute = 0x03, + Misc = 0x04, + Switch = 0x05, + Led = 0x11, + Sound = 0x12, + ForceFeedback = 0x15, +} + +impl EventType { + pub const MAX: u8 = 0x1f; + pub const COUNT: u8 = Self::MAX + 1; + + pub const fn bits_count(self) -> usize { + match self { + EventType::Synchronization => 0x10, + EventType::Key => 0x300, + EventType::Relative => 0x10, + EventType::Absolute => 0x40, + EventType::Misc => 0x08, + EventType::Switch => 0x12, + EventType::Led => 0x10, + EventType::Sound => 0x08, + EventType::ForceFeedback => 0x80, + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct InputEvent { + pub event_type: u16, + pub code: u16, + pub value: i32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct AbsInfo { + pub min: i32, + pub max: i32, + pub fuzz: i32, + pub flat: i32, + pub res: i32, +} diff --git a/drivers/interface/rdif-input/src/id.rs b/drivers/interface/rdif-input/src/id.rs new file mode 100644 index 0000000000..1b6b98cbf5 --- /dev/null +++ b/drivers/interface/rdif-input/src/id.rs @@ -0,0 +1,8 @@ +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct InputDeviceId { + pub bus_type: u16, + pub vendor: u16, + pub product: u16, + pub version: u16, +} diff --git a/drivers/interface/rdif-input/src/interface.rs b/drivers/interface/rdif-input/src/interface.rs new file mode 100644 index 0000000000..2da4d20976 --- /dev/null +++ b/drivers/interface/rdif-input/src/interface.rs @@ -0,0 +1,44 @@ +use crate::{AbsInfo, DriverGeneric, EventType, InputDeviceId, InputError, InputEvent}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct Event { + pub input_ready: bool, +} + +impl Event { + pub const fn none() -> Self { + Self { input_ready: false } + } +} + +pub trait Interface: DriverGeneric { + fn device_id(&self) -> InputDeviceId; + + fn physical_location(&self) -> &str; + + fn unique_id(&self) -> &str; + + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> Result; + + fn read_event(&mut self) -> Result; + + fn get_prop_bits(&mut self, _out: &mut [u8]) -> Result { + Ok(0) + } + + fn get_abs_info(&mut self, _axis: u8) -> Result { + Err(InputError::NotSupported) + } + + fn enable_irq(&mut self) {} + + fn disable_irq(&mut self) {} + + fn is_irq_enabled(&self) -> bool { + false + } + + fn handle_irq(&mut self) -> Event { + Event::none() + } +} diff --git a/drivers/interface/rdif-input/src/lib.rs b/drivers/interface/rdif-input/src/lib.rs new file mode 100644 index 0000000000..b09ae9ade7 --- /dev/null +++ b/drivers/interface/rdif-input/src/lib.rs @@ -0,0 +1,89 @@ +#![no_std] + +extern crate alloc; + +mod error; +mod event; +mod id; +mod interface; + +pub use error::*; +pub use event::*; +pub use id::*; +pub use interface::*; +pub use rdif_base::{DriverGeneric, KError, io}; + +#[cfg(test)] +mod tests { + use super::*; + + struct TestInput; + + impl DriverGeneric for TestInput { + fn name(&self) -> &str { + "test-input" + } + } + + impl Interface for TestInput { + fn device_id(&self) -> InputDeviceId { + InputDeviceId { + bus_type: 3, + vendor: 1, + product: 2, + version: 1, + } + } + + fn physical_location(&self) -> &str { + "virtio/input0" + } + + fn unique_id(&self) -> &str { + "input0" + } + + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> Result { + if ty == EventType::Key && !out.is_empty() { + out[0] = 1; + Ok(true) + } else { + Ok(false) + } + } + + fn read_event(&mut self) -> Result { + Ok(InputEvent { + event_type: EventType::Key as u16, + code: 30, + value: -1, + }) + } + + fn get_abs_info(&mut self, _axis: u8) -> Result { + Ok(AbsInfo { + min: -100, + max: 100, + fuzz: 0, + flat: 0, + res: 10, + }) + } + } + + #[test] + fn input_interface_exposes_identity_and_events() { + let mut input = TestInput; + assert_eq!(input.device_id().vendor, 1); + assert_eq!(input.physical_location(), "virtio/input0"); + + let mut bits = [0; 2]; + assert!(input.get_event_bits(EventType::Key, &mut bits).unwrap()); + assert_eq!(bits[0], 1); + let event = input.read_event().unwrap(); + assert_eq!(event.code, 30); + assert_eq!(event.value, -1); + assert_eq!(input.get_abs_info(0).unwrap().min, -100); + assert_eq!(input.handle_irq(), Event { input_ready: false }); + } +} diff --git a/drivers/interface/rdif-pcie/src/bar_alloc.rs b/drivers/interface/rdif-pcie/src/bar_alloc.rs index 20b093ae48..499a365fb9 100644 --- a/drivers/interface/rdif-pcie/src/bar_alloc.rs +++ b/drivers/interface/rdif-pcie/src/bar_alloc.rs @@ -45,34 +45,93 @@ impl SimpleBarAllocator { } pub fn alloc_memory32(&mut self, size: u32, prefetchable: bool) -> Option { - if prefetchable - && let Some(set) = self.mem32_pref.as_mut() - && let Ok(addr) = set.allocate(size as _, size as _, AllocPolicy::FirstMatch) - { - return Some(addr.start() as _); + self.alloc_from_32(size, prefetchable) + } + + pub fn alloc_memory64(&mut self, size: u64, prefetchable: bool) -> Option { + if let Some(addr) = self.alloc_from_64(size, prefetchable) { + return Some(addr); + } + + // A 64-bit BAR can legally be programmed with an address below 4 GiB. + // Keep the fallback on the same 32-bit allocator so 32-bit and 64-bit + // BARs do not receive overlapping low MMIO ranges. + if let Ok(size) = u32::try_from(size) { + return self.alloc_from_32(size, prefetchable).map(u64::from); } - let res = self - .mem32 - .as_mut()? - .allocate(size as _, size as _, AllocPolicy::FirstMatch) - .ok()?; - Some(res.start() as _) + None } - pub fn alloc_memory64(&mut self, size: u64, prefetchable: bool) -> Option { - if prefetchable - && let Some(set) = self.mem64_pref.as_mut() - && let Ok(addr) = set.allocate(size as _, size as _, AllocPolicy::FirstMatch) - { - return Some(addr.start() as _); + fn alloc_from_32(&mut self, size: u32, prefetchable: bool) -> Option { + if prefetchable && let Some(addr) = alloc_from(&mut self.mem32_pref, size as u64) { + return Some(addr as u32); + } + + alloc_from(&mut self.mem32, size as u64).map(|addr| addr as u32) + } + + fn alloc_from_64(&mut self, size: u64, prefetchable: bool) -> Option { + if prefetchable && let Some(addr) = alloc_from(&mut self.mem64_pref, size) { + return Some(addr); } - let res = self - .mem64 - .as_mut()? - .allocate(size as _, size as _, AllocPolicy::FirstMatch) - .ok()?; - Some(res.start() as _) + alloc_from(&mut self.mem64, size) + } +} + +fn alloc_from(allocator: &mut Option, size: u64) -> Option { + allocator + .as_mut()? + .allocate(size, size, AllocPolicy::FirstMatch) + .ok() + .map(|range| range.start()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn memory64_falls_back_to_shared_memory32_window() { + let mut allocator = SimpleBarAllocator::default(); + allocator + .set_mem32( + PciMem32 { + address: 0x1000_0000, + size: 0x2000, + }, + false, + ) + .unwrap(); + + assert_eq!(allocator.alloc_memory64(0x1000, false), Some(0x1000_0000)); + assert_eq!(allocator.alloc_memory32(0x1000, false), Some(0x1000_1000)); + } + + #[test] + fn memory64_prefers_native_memory64_window() { + let mut allocator = SimpleBarAllocator::default(); + allocator + .set_mem32( + PciMem32 { + address: 0x1000_0000, + size: 0x2000, + }, + false, + ) + .unwrap(); + allocator + .set_mem64( + PciMem64 { + address: 0x8_0000_0000, + size: 0x2000, + }, + false, + ) + .unwrap(); + + assert_eq!(allocator.alloc_memory64(0x1000, false), Some(0x8_0000_0000)); + assert_eq!(allocator.alloc_memory32(0x1000, false), Some(0x1000_0000)); } } diff --git a/drivers/interface/rdif-pcie/src/lib.rs b/drivers/interface/rdif-pcie/src/lib.rs index da2c19d530..27bc8df8ae 100644 --- a/drivers/interface/rdif-pcie/src/lib.rs +++ b/drivers/interface/rdif-pcie/src/lib.rs @@ -14,13 +14,13 @@ mod bar_alloc; pub use bar_alloc::SimpleBarAllocator; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PciMem32 { pub address: u32, pub size: u32, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PciMem64 { pub address: u64, pub size: u64, diff --git a/drivers/interface/rdif-serial/Cargo.toml b/drivers/interface/rdif-serial/Cargo.toml index ce593b0da7..549cfda6a3 100644 --- a/drivers/interface/rdif-serial/Cargo.toml +++ b/drivers/interface/rdif-serial/Cargo.toml @@ -10,9 +10,9 @@ repository.workspace = true version = "0.7.1" [dependencies] -ax-kspin.workspace = true bitflags = "2.8" futures = {version = "0.3", default-features = false, features = ["alloc"]} rdif-base = {workspace = true} +spin.workspace = true thiserror = {version = "2", default-features = false} heapless = "0.9" diff --git a/drivers/interface/rdif-serial/src/serial.rs b/drivers/interface/rdif-serial/src/serial.rs index c329b3e1a4..8d6d7d106a 100644 --- a/drivers/interface/rdif-serial/src/serial.rs +++ b/drivers/interface/rdif-serial/src/serial.rs @@ -1,9 +1,9 @@ use alloc::{boxed::Box, sync::Arc}; use core::{cell::UnsafeCell, num::NonZeroU32}; -use ax_kspin::SpinNoIrq as Mutex; use heapless::Deque; use rdif_base::DriverGeneric; +use spin::Mutex; use super::{ BIrqHandler, BReciever, BSender, BSerial, InterfaceRaw, InterruptMask, TransBytesError, diff --git a/drivers/interface/rdif-vsock/Cargo.toml b/drivers/interface/rdif-vsock/Cargo.toml new file mode 100644 index 0000000000..b57ab7ed54 --- /dev/null +++ b/drivers/interface/rdif-vsock/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["周睿 "] +categories = ["embedded", "no-std"] +description = "Driver Interface vsock definition." +edition.workspace = true +keywords = ["os", "driver"] +license = "MIT" +name = "rdif-vsock" +repository.workspace = true +version = "0.1.0" + +[dependencies] +rdif-base = { workspace = true } +thiserror = { version = "2", default-features = false } diff --git a/drivers/interface/rdif-vsock/src/addr.rs b/drivers/interface/rdif-vsock/src/addr.rs new file mode 100644 index 0000000000..ff63c3e4b1 --- /dev/null +++ b/drivers/interface/rdif-vsock/src/addr.rs @@ -0,0 +1,20 @@ +#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct VsockAddr { + pub cid: u64, + pub port: u32, +} + +#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct VsockConnId { + pub peer_addr: VsockAddr, + pub local_port: u32, +} + +impl VsockConnId { + pub const fn listening(local_port: u32) -> Self { + Self { + peer_addr: VsockAddr { cid: 0, port: 0 }, + local_port, + } + } +} diff --git a/drivers/interface/rdif-vsock/src/error.rs b/drivers/interface/rdif-vsock/src/error.rs new file mode 100644 index 0000000000..2e9fbf9d8b --- /dev/null +++ b/drivers/interface/rdif-vsock/src/error.rs @@ -0,0 +1,32 @@ +use alloc::boxed::Box; + +use crate::io; + +#[derive(thiserror::Error, Debug)] +pub enum VsockError { + #[error("operation not supported")] + NotSupported, + #[error("operation should be retried")] + Retry, + #[error("connection not found")] + NotConnected, + #[error("connection already exists")] + AlreadyExists, + #[error("device is not available")] + NotAvailable, + #[error("other error: {0}")] + Other(Box), +} + +impl From for io::ErrorKind { + fn from(value: VsockError) -> Self { + match value { + VsockError::NotSupported => io::ErrorKind::Unsupported, + VsockError::Retry => io::ErrorKind::Interrupted, + VsockError::NotConnected => io::ErrorKind::BrokenPipe, + VsockError::AlreadyExists => io::ErrorKind::NotAvailable, + VsockError::NotAvailable => io::ErrorKind::NotAvailable, + VsockError::Other(error) => io::ErrorKind::Other(error), + } + } +} diff --git a/drivers/interface/rdif-vsock/src/event.rs b/drivers/interface/rdif-vsock/src/event.rs new file mode 100644 index 0000000000..8b0ffc088d --- /dev/null +++ b/drivers/interface/rdif-vsock/src/event.rs @@ -0,0 +1,26 @@ +use crate::VsockConnId; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VsockEvent { + ConnectionRequest(VsockConnId), + Connected(VsockConnId), + Received(VsockConnId, usize), + Disconnected(VsockConnId), + CreditUpdate(VsockConnId), + Unknown, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Event { + pub connection_changed: bool, + pub data_available: bool, +} + +impl Event { + pub const fn none() -> Self { + Self { + connection_changed: false, + data_available: false, + } + } +} diff --git a/drivers/interface/rdif-vsock/src/interface.rs b/drivers/interface/rdif-vsock/src/interface.rs new file mode 100644 index 0000000000..4737779ff9 --- /dev/null +++ b/drivers/interface/rdif-vsock/src/interface.rs @@ -0,0 +1,33 @@ +use crate::{DriverGeneric, Event, VsockConnId, VsockError, VsockEvent}; + +pub trait Interface: DriverGeneric { + fn guest_cid(&self) -> u64; + + fn listen(&mut self, port: u32) -> Result<(), VsockError>; + + fn connect(&mut self, id: VsockConnId) -> Result<(), VsockError>; + + fn send(&mut self, id: VsockConnId, buf: &[u8]) -> Result; + + fn recv(&mut self, id: VsockConnId, buf: &mut [u8]) -> Result; + + fn recv_avail(&mut self, id: VsockConnId) -> Result; + + fn disconnect(&mut self, id: VsockConnId) -> Result<(), VsockError>; + + fn abort(&mut self, id: VsockConnId) -> Result<(), VsockError>; + + fn poll_event(&mut self) -> Result, VsockError>; + + fn enable_irq(&mut self) {} + + fn disable_irq(&mut self) {} + + fn is_irq_enabled(&self) -> bool { + false + } + + fn handle_irq(&mut self) -> Event { + Event::none() + } +} diff --git a/drivers/interface/rdif-vsock/src/lib.rs b/drivers/interface/rdif-vsock/src/lib.rs new file mode 100644 index 0000000000..eae6c91613 --- /dev/null +++ b/drivers/interface/rdif-vsock/src/lib.rs @@ -0,0 +1,89 @@ +#![no_std] + +extern crate alloc; + +mod addr; +mod error; +mod event; +mod interface; + +pub use addr::*; +pub use error::*; +pub use event::*; +pub use interface::*; +pub use rdif_base::{DriverGeneric, KError, io}; + +#[cfg(test)] +mod tests { + use super::*; + + struct TestVsock; + + impl DriverGeneric for TestVsock { + fn name(&self) -> &str { + "test-vsock" + } + } + + impl Interface for TestVsock { + fn guest_cid(&self) -> u64 { + 3 + } + + fn listen(&mut self, _port: u32) -> Result<(), VsockError> { + Ok(()) + } + + fn connect(&mut self, _id: VsockConnId) -> Result<(), VsockError> { + Ok(()) + } + + fn send(&mut self, _id: VsockConnId, buf: &[u8]) -> Result { + Ok(buf.len()) + } + + fn recv(&mut self, _id: VsockConnId, buf: &mut [u8]) -> Result { + if !buf.is_empty() { + buf[0] = 7; + } + Ok(buf.len().min(1)) + } + + fn recv_avail(&mut self, _id: VsockConnId) -> Result { + Ok(1) + } + + fn disconnect(&mut self, _id: VsockConnId) -> Result<(), VsockError> { + Ok(()) + } + + fn abort(&mut self, _id: VsockConnId) -> Result<(), VsockError> { + Ok(()) + } + + fn poll_event(&mut self) -> Result, VsockError> { + Ok(Some(VsockEvent::Connected(VsockConnId::listening(1024)))) + } + } + + #[test] + fn vsock_interface_exposes_connections_and_events() { + let mut vsock = TestVsock; + let id = VsockConnId::listening(1024); + assert_eq!(vsock.guest_cid(), 3); + assert_eq!(vsock.send(id, &[1, 2, 3]).unwrap(), 3); + + let mut buf = [0; 4]; + assert_eq!(vsock.recv(id, &mut buf).unwrap(), 1); + assert_eq!(buf[0], 7); + assert_eq!(vsock.poll_event().unwrap(), Some(VsockEvent::Connected(id))); + assert_eq!(vsock.handle_irq(), Event::none()); + } + + #[test] + fn already_exists_maps_to_not_available() { + let kind: io::ErrorKind = VsockError::AlreadyExists.into(); + + assert!(matches!(kind, io::ErrorKind::NotAvailable)); + } +} diff --git a/drivers/net/eth-intel/src/e1000/mod.rs b/drivers/net/eth-intel/src/e1000/mod.rs index 23222f058a..8bfb969971 100644 --- a/drivers/net/eth-intel/src/e1000/mod.rs +++ b/drivers/net/eth-intel/src/e1000/mod.rs @@ -5,7 +5,7 @@ use core::mem::size_of; use dma_api::{DArray, DeviceDma, DmaDirection, DmaOp}; use mmio_api::{Mmio, MmioAddr, MmioOp}; -use rdif_eth::{Event, IRxQueue, ITxQueue, Interface, NetError, QueueConfig}; +use rdif_eth::{DmaBuffer, Event, IRxQueue, ITxQueue, Interface, NetError, QueueConfig}; use crate::err::{Error, Result}; @@ -201,8 +201,8 @@ impl ITxQueue for E1000TxQueue { } } - fn submit(&mut self, bus_addr: u64, len: usize) -> core::result::Result<(), NetError> { - if len > MAX_PACKET { + fn submit(&mut self, buffer: DmaBuffer) -> core::result::Result<(), NetError> { + if buffer.len > MAX_PACKET { return Err(NetError::Other(Box::new(Error::InvalidArgument( "tx packet too large", )))); @@ -216,8 +216,9 @@ impl ITxQueue for E1000TxQueue { return Err(NetError::Retry); } - self.desc.set(idx, TxDesc::new(bus_addr, len as u16)); - self.bus_addrs[idx] = Some(bus_addr); + self.desc + .set(idx, TxDesc::new(buffer.bus_addr, buffer.len as u16)); + self.bus_addrs[idx] = Some(buffer.bus_addr); self.next_submit = next; self.regs.write(TDT, next as u32); @@ -259,8 +260,8 @@ impl IRxQueue for E1000RxQueue { } } - fn submit(&mut self, bus_addr: u64, len: usize) -> core::result::Result<(), NetError> { - if len > MAX_PACKET { + fn submit(&mut self, buffer: DmaBuffer) -> core::result::Result<(), NetError> { + if buffer.len > MAX_PACKET { return Err(NetError::Other(Box::new(Error::InvalidArgument( "rx buffer too large", )))); @@ -274,8 +275,8 @@ impl IRxQueue for E1000RxQueue { return Err(NetError::Retry); } - self.desc.set(idx, RxDesc::new(bus_addr)); - self.bus_addrs[idx] = Some(bus_addr); + self.desc.set(idx, RxDesc::new(buffer.bus_addr)); + self.bus_addrs[idx] = Some(buffer.bus_addr); self.next_submit = next; self.regs.write(RDT, next as u32); diff --git a/drivers/net/rd-net/src/lib.rs b/drivers/net/rd-net/src/lib.rs index be7e0a1c47..4577aff083 100644 --- a/drivers/net/rd-net/src/lib.rs +++ b/drivers/net/rd-net/src/lib.rs @@ -60,6 +60,20 @@ pub struct Net { inner: Arc, } +impl DriverGeneric for Net { + fn name(&self) -> &str { + self.interface().name() + } + + fn raw_any(&self) -> Option<&dyn core::any::Any> { + Some(self) + } + + fn raw_any_mut(&mut self) -> Option<&mut dyn core::any::Any> { + Some(self) + } +} + impl Net { pub fn new(interface: impl Interface, dma_op: &'static dyn DmaOp) -> Self { Self { @@ -255,7 +269,15 @@ impl TxPending<'_> { pub fn try_submit(&mut self) -> Result<(), NetError> { self.queue.reclaim_bounded(self.queue.capacity().max(1))?; - self.queue.interface.submit(self.bus_addr, self.len)?; + let buff = self + .buff + .as_ref() + .expect("tx pending buffer should exist until submit succeeds"); + self.queue.interface.submit(DmaBuffer { + virt: buff.as_ptr(), + bus_addr: self.bus_addr, + len: self.len, + })?; let buff = self .buff .take() @@ -294,7 +316,11 @@ impl RxQueue { fn submit_buffer(&mut self, buff: DBuff) -> Result<(), NetError> { let bus_addr = buff.dma_addr().as_u64(); let len = self.config.buf_size.min(buff.len()); - self.interface.submit(bus_addr, len)?; + self.interface.submit(DmaBuffer { + virt: buff.as_ptr(), + bus_addr, + len, + })?; self.inflight.insert(bus_addr, buff); Ok(()) } diff --git a/drivers/net/realtek-rtl8125/Cargo.toml b/drivers/net/realtek-rtl8125/Cargo.toml index af62780e2a..72156b29ec 100644 --- a/drivers/net/realtek-rtl8125/Cargo.toml +++ b/drivers/net/realtek-rtl8125/Cargo.toml @@ -10,10 +10,10 @@ license = "MIT" repository.workspace = true [dependencies] -ax-kspin.workspace = true dma-api.workspace = true log.workspace = true mmio-api.workspace = true rdif-eth.workspace = true +spin.workspace = true thiserror = { version = "2", default-features = false } tock-registers = "0.10" diff --git a/drivers/net/realtek-rtl8125/src/hw.rs b/drivers/net/realtek-rtl8125/src/hw.rs new file mode 100644 index 0000000000..a292d11477 --- /dev/null +++ b/drivers/net/realtek-rtl8125/src/hw.rs @@ -0,0 +1,527 @@ +use log::warn; + +use crate::{ChipVersion, Error, Result, Rtl8125}; + +const OCP_STD_PHY_BASE: u32 = 0xa400; +const EEE_TXIDLE_TIMER_VALUE: u16 = 1500 + 14 + 0x20; +const CSI_PCIE_CTRL: u32 = 0x070c; +const CSI_PCIE_ZRXDC_NONCOMPL: u32 = 1 << 20; +const CSI_L1_ENTRY_LATENCY: u32 = 0x0719; +const CSI_L1_ENTRY_LATENCY_DEFAULT: u8 = 0x27; + +impl Rtl8125 { + pub(crate) fn hw_init_8125(&self) -> Result<()> { + self.enable_rxdv_gate(); + self.regs.disable_tx_rx(); + spin_delay(10_000); + self.regs.clear_now_is_oob(); + + self.mac_ocp_modify(0xe8de, 1 << 14, 0)?; + self.wait_link_list_ready(); + self.mac_ocp_write(0xc0aa, 0x07d0)?; + self.mac_ocp_write(0xc0a6, 0x0150)?; + self.mac_ocp_write(0xc01e, 0x5555)?; + self.wait_link_list_ready(); + Ok(()) + } + + pub(crate) fn hw_start_8125(&mut self) -> Result<()> { + for offset in (0x0a00..0x0b00).step_by(4) { + self.regs.write_vendor_u32(offset, 0); + } + + self.set_aspm_clkreq(false)?; + match self.chip { + ChipVersion::Rtl8125A => self.ephy_init(&RTL8125A_EPHY)?, + ChipVersion::Rtl8125B | ChipVersion::Unknown(_) => self.ephy_init(&RTL8125B_EPHY)?, + } + + self.hw_start_8125_common() + } + + fn hw_start_8125_common(&self) -> Result<()> { + self.regs.clear_ready_to_l23(); + self.regs.write_vendor_u16(0x0382, 0x221b); + self.regs.write_vendor_u8(0x4500, 0); + self.regs.write_vendor_u16(0x4800, 0); + self.mac_ocp_modify(0xd40a, 0x0010, 0)?; + self.regs.clear_speed_down(); + self.mac_ocp_write(0xc140, 0xffff)?; + self.mac_ocp_write(0xc142, 0xffff)?; + self.mac_ocp_modify(0xd3e2, 0x0fff, 0x03a9)?; + self.mac_ocp_modify(0xd3e4, 0x00ff, 0)?; + self.mac_ocp_modify(0xe860, 0, 0x0080)?; + self.mac_ocp_modify(0xeb58, 0x0001, 0)?; + self.disable_zrxdc_noncompliance_timeout(); + self.set_l1_entry_latency(CSI_L1_ENTRY_LATENCY_DEFAULT); + + if self.chip == ChipVersion::Rtl8125B { + self.mac_ocp_modify(0xe614, 0x0700, 0x0200)?; + self.mac_ocp_modify(0xe63e, 0x0c30, 0)?; + } else { + self.mac_ocp_modify(0xe614, 0x0700, 0x0400)?; + self.mac_ocp_modify(0xe63e, 0x0c30, 0x0020)?; + } + + self.mac_ocp_modify(0xc0b4, 0, 0x000c)?; + self.mac_ocp_modify(0xeb6a, 0x00ff, 0x0033)?; + self.mac_ocp_modify(0xeb50, 0x03e0, 0x0040)?; + self.mac_ocp_modify(0xe056, 0x00f0, 0x0030)?; + self.mac_ocp_modify(0xe040, 0x1000, 0)?; + self.mac_ocp_modify(0xea1c, 0x0003, 0x0001)?; + self.mac_ocp_modify(0xe0c0, 0x4f0f, 0x4403)?; + self.mac_ocp_modify(0xe052, 0x0080, 0x0068)?; + self.mac_ocp_modify(0xd430, 0x0fff, 0x047f)?; + self.mac_ocp_modify(0xea1c, 0x0004, 0)?; + self.mac_ocp_modify(0xeb54, 0, 0x0001)?; + spin_delay(100); + self.mac_ocp_modify(0xeb54, 0x0001, 0)?; + self.regs.clear_vendor_u16_bits(0x1880, 0x0030); + self.mac_ocp_write(0xe098, 0xc302)?; + self.wait_mac_ocp_e00e_low(); + self.config_eee_mac()?; + self.disable_rxdv_gate(); + Ok(()) + } + + fn disable_zrxdc_noncompliance_timeout(&self) { + let Some(value) = self.regs.csi_read(CSI_PCIE_CTRL) else { + warn!("RTL8125: failed to read CSI {CSI_PCIE_CTRL:#x}"); + return; + }; + if !self + .regs + .csi_write(CSI_PCIE_CTRL, value & !CSI_PCIE_ZRXDC_NONCOMPL) + { + warn!("RTL8125: failed to write CSI {CSI_PCIE_CTRL:#x}"); + } + } + + fn set_l1_entry_latency(&self, value: u8) { + let addr = CSI_L1_ENTRY_LATENCY & !0x3; + let shift = (CSI_L1_ENTRY_LATENCY & 0x3) * 8; + let Some(old) = self.regs.csi_read(addr) else { + warn!("RTL8125: failed to read CSI {addr:#x}"); + return; + }; + let new = (old & !(0xff << shift)) | (u32::from(value) << shift); + if !self.regs.csi_write(addr, new) { + warn!("RTL8125: failed to write CSI {addr:#x}"); + } + } + + pub(crate) fn maybe_start_queues(&mut self) { + crate::queue::try_start_queues(self.regs, self.dma.dma_mask(), &self.queue_start); + } + + fn enable_rxdv_gate(&self) { + self.regs.enable_rxdv_gate(); + spin_delay(2_000); + self.wait_rxtx_empty(); + } + + fn disable_rxdv_gate(&self) { + self.regs.disable_rxdv_gate(); + } + + fn wait_rxtx_empty(&self) { + for _ in 0..4_200 { + if self.regs.rxtx_empty() { + return; + } + core::hint::spin_loop(); + } + warn!("RTL8125: timed out waiting for RX/TX FIFO empty"); + } + + fn wait_mac_ocp_e00e_low(&self) { + for _ in 0..10 { + match self.mac_ocp_read(0xe00e) { + Ok(value) if value & (1 << 13) == 0 => return, + Ok(_) => spin_delay(1_000), + Err(err) => { + warn!("RTL8125: failed to read MAC OCP 0xe00e: {err:?}"); + return; + } + } + } + warn!("RTL8125: timed out waiting for MAC OCP 0xe00e bit 13 to clear"); + } + + fn set_aspm_clkreq(&self, enable: bool) -> Result<()> { + if enable { + self.regs.set_aspm_clkreq(true); + self.mac_ocp_modify(0xe094, 0xff00, 0)?; + self.mac_ocp_modify(0xe092, 0x00ff, 1 << 2)?; + } else { + self.mac_ocp_modify(0xe092, 0x00ff, 0)?; + self.regs.set_aspm_clkreq(false); + } + spin_delay(100); + Ok(()) + } + + fn config_eee_mac(&self) -> Result<()> { + if self.chip == ChipVersion::Rtl8125B { + self.regs.write_eee_txidle_timer(EEE_TXIDLE_TIMER_VALUE); + } else { + self.mac_ocp_modify(0xeb62, 0, (1 << 2) | (1 << 1))?; + } + self.mac_ocp_modify(0xe040, 0, (1 << 1) | 1) + } + + pub(crate) fn ack_events(&self, bits: u32) { + self.regs.write_interrupt_status(bits); + } + + fn mac_ocp_write(&self, reg: u32, data: u16) -> Result<()> { + validate_ocp_reg(reg)?; + self.regs.start_mac_ocp_write(reg, data); + Ok(()) + } + + fn mac_ocp_read(&self, reg: u32) -> Result { + validate_ocp_reg(reg)?; + self.regs.start_mac_ocp_read(reg); + Ok(self.regs.read_mac_ocp_data()) + } + + fn mac_ocp_modify(&self, reg: u32, mask: u16, set: u16) -> Result<()> { + let data = self.mac_ocp_read(reg)?; + self.mac_ocp_write(reg, (data & !mask) | set) + } + + fn ephy_read(&self, reg: u32) -> Result { + self.regs.start_ephy_read(reg); + for _ in 0..1_000 { + if self.regs.ephy_ready() { + return Ok(self.regs.read_ephy_data()); + } + core::hint::spin_loop(); + } + Err(Error::HardwareTimeout { + operation: "EPHY read", + }) + } + + fn ephy_write(&self, reg: u32, value: u16) -> Result<()> { + self.regs.start_ephy_write(reg, value); + for _ in 0..1_000 { + if !self.regs.ephy_ready() { + spin_delay(1_000); + return Ok(()); + } + core::hint::spin_loop(); + } + Err(Error::HardwareTimeout { + operation: "EPHY write", + }) + } + + fn ephy_init(&self, entries: &[EphyInfo]) -> Result<()> { + for entry in entries { + let value = (self.ephy_read(entry.offset.into())? & !entry.mask) | entry.bits; + self.ephy_write(entry.offset.into(), value)?; + } + Ok(()) + } + + fn phy_ocp_write(&self, reg: u32, data: u16) -> Result<()> { + validate_ocp_reg(reg)?; + self.regs.start_phy_ocp_write(reg, data); + for _ in 0..1_000 { + if !self.regs.phy_ocp_busy() { + return Ok(()); + } + core::hint::spin_loop(); + } + Err(Error::HardwareTimeout { + operation: "PHY OCP write", + }) + } + + fn phy_ocp_read(&self, reg: u32) -> Result { + validate_ocp_reg(reg)?; + self.regs.start_phy_ocp_read(reg); + for _ in 0..1_000 { + if self.regs.phy_ocp_busy() { + return Ok(self.regs.read_phy_ocp_data()); + } + core::hint::spin_loop(); + } + Err(Error::HardwareTimeout { + operation: "PHY OCP read", + }) + } + + fn phy_reg_addr(&self, reg: u32) -> u32 { + let reg = if self.phy_ocp_base == OCP_STD_PHY_BASE { + reg + } else { + reg.saturating_sub(0x10) + }; + self.phy_ocp_base + reg * 2 + } + + fn phy_write(&mut self, reg: u32, value: u16) -> Result<()> { + if reg == 0x1f { + self.phy_ocp_base = if value == 0 { + OCP_STD_PHY_BASE + } else { + u32::from(value) << 4 + }; + return Ok(()); + } + + self.phy_ocp_write(self.phy_reg_addr(reg), value) + } + + fn phy_read(&self, reg: u32) -> Result { + if reg == 0x1f { + return Ok(if self.phy_ocp_base == OCP_STD_PHY_BASE { + 0 + } else { + (self.phy_ocp_base >> 4) as u16 + }); + } + + self.phy_ocp_read(self.phy_reg_addr(reg)) + } + + fn phy_modify(&mut self, reg: u32, mask: u16, set: u16) -> Result<()> { + let data = self.phy_read(reg)?; + self.phy_write(reg, (data & !mask) | set) + } + + fn phy_write_paged(&mut self, page: u16, reg: u32, value: u16) -> Result<()> { + let old_page = self.phy_read(0x1f)?; + self.phy_write(0x1f, page)?; + self.phy_write(reg, value)?; + self.phy_write(0x1f, old_page) + } + + fn phy_modify_paged(&mut self, page: u16, reg: u32, mask: u16, set: u16) -> Result<()> { + let old_page = self.phy_read(0x1f)?; + self.phy_write(0x1f, page)?; + self.phy_modify(reg, mask, set)?; + self.phy_write(0x1f, old_page) + } + + fn phy_param(&mut self, param: u16, mask: u16, set: u16) -> Result<()> { + let old_page = self.phy_read(0x1f)?; + self.phy_write(0x1f, 0x0a43)?; + self.phy_write(0x13, param)?; + self.phy_modify(0x14, mask, set)?; + self.phy_write(0x1f, old_page) + } + + fn config_eee_phy_8125a(&mut self) -> Result<()> { + self.phy_modify_paged(0x0a43, 0x11, 0, 1 << 4)?; + self.phy_modify_paged(0x0a4a, 0x11, 0, 1 << 9)?; + self.phy_modify_paged(0x0a42, 0x14, 0, 1 << 7)?; + self.phy_modify_paged(0x0a6d, 0x12, 1, 0)?; + self.phy_modify_paged(0x0a6d, 0x14, 1 << 4, 0) + } + + fn config_eee_phy_8125b(&mut self) -> Result<()> { + self.phy_modify_paged(0x0a6d, 0x12, 1, 0)?; + self.phy_modify_paged(0x0a6d, 0x14, 1 << 4, 0)?; + self.phy_modify_paged(0x0a42, 0x14, 1 << 7, 0)?; + self.phy_modify_paged(0x0a4a, 0x11, 1 << 9, 0) + } + + pub(crate) fn hw_phy_config(&mut self) -> Result<()> { + match self.chip { + ChipVersion::Rtl8125A => self.hw_phy_config_8125a(), + ChipVersion::Rtl8125B | ChipVersion::Unknown(_) => self.hw_phy_config_8125b(), + } + } + + fn hw_phy_config_8125a(&mut self) -> Result<()> { + self.phy_modify_paged(0x0ad4, 0x17, 0, 0x0010)?; + self.phy_modify_paged(0x0ad1, 0x13, 0x03ff, 0x03ff)?; + self.phy_modify_paged(0x0ad3, 0x11, 0x003f, 0x0006)?; + self.phy_modify_paged(0x0ac0, 0x14, 0x1100, 0)?; + self.phy_modify_paged(0x0acc, 0x10, 0x0003, 0x0002)?; + self.phy_modify_paged(0x0ad4, 0x10, 0x00e7, 0x0044)?; + self.phy_modify_paged(0x0ac1, 0x12, 0x0080, 0)?; + self.phy_modify_paged(0x0ac8, 0x10, 0x0300, 0)?; + self.phy_modify_paged(0x0ac5, 0x17, 0x0007, 0x0002)?; + self.phy_write_paged(0x0ad4, 0x16, 0x00a8)?; + self.phy_write_paged(0x0ac5, 0x16, 0x01ff)?; + self.phy_modify_paged(0x0ac8, 0x15, 0x00f0, 0x0030)?; + + self.phy_write(0x1f, 0x0b87)?; + self.phy_write(0x16, 0x80a2)?; + self.phy_write(0x17, 0x0153)?; + self.phy_write(0x16, 0x809c)?; + self.phy_write(0x17, 0x0153)?; + self.phy_write(0x1f, 0)?; + + self.phy_param(0x8257, 0xffff, 0x020f)?; + self.phy_param(0x80ea, 0xffff, 0x7843)?; + self.phy_modify_paged(0x0d06, 0x14, 0, 0x2000)?; + self.phy_param(0x81a2, 0, 0x0100)?; + self.phy_modify_paged(0x0b54, 0x16, 0xff00, 0xdb00)?; + self.phy_modify_paged(0x0a45, 0x12, 0x0001, 0)?; + self.phy_modify_paged(0x0a5d, 0x12, 0, 0x0020)?; + self.phy_modify_paged(0x0ad4, 0x17, 0x0010, 0)?; + self.phy_modify_paged(0x0a86, 0x15, 0x0001, 0)?; + self.phy_modify_paged(0x0a44, 0x11, 0, 1 << 11)?; + self.config_eee_phy_8125a() + } + + fn hw_phy_config_8125b(&mut self) -> Result<()> { + self.phy_modify_paged(0x0a44, 0x11, 0, 0x0800)?; + self.phy_modify_paged(0x0ac4, 0x13, 0x00f0, 0x0090)?; + self.phy_modify_paged(0x0ad3, 0x10, 0x0003, 0x0001)?; + + self.phy_write(0x1f, 0x0b87)?; + self.phy_write(0x16, 0x80f5)?; + self.phy_write(0x17, 0x760e)?; + self.phy_write(0x16, 0x8107)?; + self.phy_write(0x17, 0x360e)?; + self.phy_write(0x16, 0x8551)?; + self.phy_modify(0x17, 0xff00, 0x0800)?; + self.phy_write(0x1f, 0)?; + + self.phy_modify_paged(0x0bf0, 0x10, 0xe000, 0xa000)?; + self.phy_modify_paged(0x0bf4, 0x13, 0x0f00, 0x0300)?; + for param in [ + 0x8044, 0x804a, 0x8050, 0x8056, 0x805c, 0x8062, 0x8068, 0x806e, 0x8074, 0x807a, + ] { + self.phy_param(param, 0xffff, 0x2417)?; + } + self.phy_modify_paged(0x0a4c, 0x15, 0, 0x0040)?; + self.phy_modify_paged(0x0bf8, 0x12, 0xe000, 0xa000)?; + self.phy_modify_paged(0x0a5b, 0x12, 1 << 15, 0)?; + self.config_eee_phy_8125b() + } + + fn wait_link_list_ready(&self) { + for _ in 0..4_200 { + if self.regs.link_list_ready() { + return; + } + core::hint::spin_loop(); + } + warn!("RTL8125: timed out waiting for link-list FIFO ready"); + } +} + +fn validate_ocp_reg(reg: u32) -> Result<()> { + if reg & 0xffff_0001 == 0 { + Ok(()) + } else { + Err(Error::InvalidOcpAddress { reg }) + } +} + +fn spin_delay(iterations: usize) { + for _ in 0..iterations { + core::hint::spin_loop(); + } +} + +#[derive(Clone, Copy)] +struct EphyInfo { + offset: u8, + mask: u16, + bits: u16, +} + +const RTL8125A_EPHY: [EphyInfo; 12] = [ + EphyInfo { + offset: 0x04, + mask: 0xffff, + bits: 0xd000, + }, + EphyInfo { + offset: 0x0a, + mask: 0xffff, + bits: 0x8653, + }, + EphyInfo { + offset: 0x23, + mask: 0xffff, + bits: 0xab66, + }, + EphyInfo { + offset: 0x20, + mask: 0xffff, + bits: 0x9455, + }, + EphyInfo { + offset: 0x21, + mask: 0xffff, + bits: 0x99ff, + }, + EphyInfo { + offset: 0x29, + mask: 0xffff, + bits: 0xfe04, + }, + EphyInfo { + offset: 0x44, + mask: 0xffff, + bits: 0xd000, + }, + EphyInfo { + offset: 0x4a, + mask: 0xffff, + bits: 0x8653, + }, + EphyInfo { + offset: 0x63, + mask: 0xffff, + bits: 0xab66, + }, + EphyInfo { + offset: 0x60, + mask: 0xffff, + bits: 0x9455, + }, + EphyInfo { + offset: 0x61, + mask: 0xffff, + bits: 0x99ff, + }, + EphyInfo { + offset: 0x69, + mask: 0xffff, + bits: 0xfe04, + }, +]; + +const RTL8125B_EPHY: [EphyInfo; 6] = [ + EphyInfo { + offset: 0x0b, + mask: 0xffff, + bits: 0xa908, + }, + EphyInfo { + offset: 0x1e, + mask: 0xffff, + bits: 0x20eb, + }, + EphyInfo { + offset: 0x4b, + mask: 0xffff, + bits: 0xa908, + }, + EphyInfo { + offset: 0x5e, + mask: 0xffff, + bits: 0x20eb, + }, + EphyInfo { + offset: 0x22, + mask: 0x0030, + bits: 0x0020, + }, + EphyInfo { + offset: 0x62, + mask: 0x0030, + bits: 0x0020, + }, +]; diff --git a/drivers/net/realtek-rtl8125/src/lib.rs b/drivers/net/realtek-rtl8125/src/lib.rs index f120501118..a1b4f98e81 100644 --- a/drivers/net/realtek-rtl8125/src/lib.rs +++ b/drivers/net/realtek-rtl8125/src/lib.rs @@ -3,17 +3,19 @@ extern crate alloc; use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; -use core::sync::atomic::{Ordering as AtomicOrdering, fence}; -use ax_kspin::SpinNoIrq as Mutex; use descriptor::{RING_END, RxDesc, TxDesc}; -use dma_api::{DArray, DeviceDma, DmaDirection, DmaOp}; -use log::{debug, info, warn}; +use dma_api::{DeviceDma, DmaDirection, DmaOp}; +use log::info; use mmio_api::{Mmio, MmioAddr, MmioOp}; -use rdif_eth::{Event, IRxQueue, ITxQueue, Interface, NetError, QueueConfig}; +use queue::{QueueStart, QueueStartState, Rtl8125RxQueue, Rtl8125TxQueue}; +use rdif_eth::{Event, IRxQueue, ITxQueue, Interface}; use registers::*; +use spin::Mutex; mod descriptor; +mod hw; +mod queue; mod registers; const DRIVER_NAME: &str = "realtek-rtl8125"; @@ -27,8 +29,6 @@ const DMA_ALIGN: usize = 256; const DMA_CACHE_LINE_SIZE: usize = 64; const RX_DESC_PER_CACHE_LINE: usize = DMA_CACHE_LINE_SIZE / core::mem::size_of::(); const RX_DEFERRED_REFILL_CAPACITY: usize = QUEUE_SIZE; -const OCP_STD_PHY_BASE: u32 = 0xa400; -const EEE_TXIDLE_TIMER_VALUE: u16 = 1500 + 14 + 0x20; const LINK_DOWN_DROP_LOG_INTERVAL: u64 = 64; const EARLY_PACKET_LOG_COUNT: u64 = 8; const TX_SUBMIT_LOG_INTERVAL: u64 = 16; @@ -37,10 +37,7 @@ const RX_RECLAIM_LOG_INTERVAL: u64 = 64; const RX_IDLE_LOG_INTERVAL: u64 = 262_144; const RX_OVERFLOW_REARM_IDLE_POLLS: u64 = 2048; const TX_LINK_SAMPLE_INTERVAL: u64 = 64; -const CSI_PCIE_CTRL: u32 = 0x070c; -const CSI_PCIE_ZRXDC_NONCOMPL: u32 = 1 << 20; -const CSI_L1_ENTRY_LATENCY: u32 = 0x0719; -const CSI_L1_ENTRY_LATENCY_DEFAULT: u8 = 0x27; +const OCP_STD_PHY_BASE: u32 = 0xa400; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ChipVersion { @@ -100,16 +97,6 @@ pub struct Rtl8125 { queue_start: QueueStart, } -type QueueStart = Arc>; - -#[derive(Default)] -struct QueueStartState { - tx_base: Option, - rx_base: Option, - rx_ready: bool, - started: bool, -} - impl Rtl8125 { pub fn check_vid_did(vendor: u16, device: u16) -> bool { vendor == VENDOR_ID && device == DEVICE_ID_RTL8125 @@ -221,404 +208,6 @@ impl Rtl8125 { } Err(Error::ResetTimeout) } - - fn hw_init_8125(&self) -> Result<()> { - self.enable_rxdv_gate(); - self.regs.disable_tx_rx(); - spin_delay(10_000); - self.regs.clear_now_is_oob(); - - self.mac_ocp_modify(0xe8de, 1 << 14, 0)?; - self.wait_link_list_ready(); - self.mac_ocp_write(0xc0aa, 0x07d0)?; - self.mac_ocp_write(0xc0a6, 0x0150)?; - self.mac_ocp_write(0xc01e, 0x5555)?; - self.wait_link_list_ready(); - Ok(()) - } - - fn hw_start_8125(&mut self) -> Result<()> { - for offset in (0x0a00..0x0b00).step_by(4) { - self.regs.write_vendor_u32(offset, 0); - } - - self.set_aspm_clkreq(false)?; - match self.chip { - ChipVersion::Rtl8125A => self.ephy_init(&RTL8125A_EPHY)?, - ChipVersion::Rtl8125B | ChipVersion::Unknown(_) => self.ephy_init(&RTL8125B_EPHY)?, - } - - self.hw_start_8125_common() - } - - fn hw_start_8125_common(&self) -> Result<()> { - self.regs.clear_ready_to_l23(); - self.regs.write_vendor_u16(0x0382, 0x221b); - self.regs.write_vendor_u8(0x4500, 0); - self.regs.write_vendor_u16(0x4800, 0); - self.mac_ocp_modify(0xd40a, 0x0010, 0)?; - self.regs.clear_speed_down(); - self.mac_ocp_write(0xc140, 0xffff)?; - self.mac_ocp_write(0xc142, 0xffff)?; - self.mac_ocp_modify(0xd3e2, 0x0fff, 0x03a9)?; - self.mac_ocp_modify(0xd3e4, 0x00ff, 0)?; - self.mac_ocp_modify(0xe860, 0, 0x0080)?; - self.mac_ocp_modify(0xeb58, 0x0001, 0)?; - self.disable_zrxdc_noncompliance_timeout(); - self.set_l1_entry_latency(CSI_L1_ENTRY_LATENCY_DEFAULT); - - if self.chip == ChipVersion::Rtl8125B { - self.mac_ocp_modify(0xe614, 0x0700, 0x0200)?; - self.mac_ocp_modify(0xe63e, 0x0c30, 0)?; - } else { - self.mac_ocp_modify(0xe614, 0x0700, 0x0400)?; - self.mac_ocp_modify(0xe63e, 0x0c30, 0x0020)?; - } - - self.mac_ocp_modify(0xc0b4, 0, 0x000c)?; - self.mac_ocp_modify(0xeb6a, 0x00ff, 0x0033)?; - self.mac_ocp_modify(0xeb50, 0x03e0, 0x0040)?; - self.mac_ocp_modify(0xe056, 0x00f0, 0x0030)?; - self.mac_ocp_modify(0xe040, 0x1000, 0)?; - self.mac_ocp_modify(0xea1c, 0x0003, 0x0001)?; - self.mac_ocp_modify(0xe0c0, 0x4f0f, 0x4403)?; - self.mac_ocp_modify(0xe052, 0x0080, 0x0068)?; - self.mac_ocp_modify(0xd430, 0x0fff, 0x047f)?; - self.mac_ocp_modify(0xea1c, 0x0004, 0)?; - self.mac_ocp_modify(0xeb54, 0, 0x0001)?; - spin_delay(100); - self.mac_ocp_modify(0xeb54, 0x0001, 0)?; - self.regs.clear_vendor_u16_bits(0x1880, 0x0030); - self.mac_ocp_write(0xe098, 0xc302)?; - self.wait_mac_ocp_e00e_low(); - self.config_eee_mac()?; - self.disable_rxdv_gate(); - Ok(()) - } - - fn disable_zrxdc_noncompliance_timeout(&self) { - let Some(value) = self.regs.csi_read(CSI_PCIE_CTRL) else { - warn!("RTL8125: failed to read CSI {CSI_PCIE_CTRL:#x}"); - return; - }; - if !self - .regs - .csi_write(CSI_PCIE_CTRL, value & !CSI_PCIE_ZRXDC_NONCOMPL) - { - warn!("RTL8125: failed to write CSI {CSI_PCIE_CTRL:#x}"); - } - } - - fn set_l1_entry_latency(&self, value: u8) { - let addr = CSI_L1_ENTRY_LATENCY & !0x3; - let shift = (CSI_L1_ENTRY_LATENCY & 0x3) * 8; - let Some(old) = self.regs.csi_read(addr) else { - warn!("RTL8125: failed to read CSI {addr:#x}"); - return; - }; - let new = (old & !(0xff << shift)) | (u32::from(value) << shift); - if !self.regs.csi_write(addr, new) { - warn!("RTL8125: failed to write CSI {addr:#x}"); - } - } - - fn maybe_start_queues(&mut self) { - try_start_queues(self.regs, self.dma.dma_mask(), &self.queue_start); - } - - fn enable_rxdv_gate(&self) { - self.regs.enable_rxdv_gate(); - spin_delay(2_000); - self.wait_rxtx_empty(); - } - - fn disable_rxdv_gate(&self) { - self.regs.disable_rxdv_gate(); - } - - fn wait_rxtx_empty(&self) { - for _ in 0..4_200 { - if self.regs.rxtx_empty() { - return; - } - core::hint::spin_loop(); - } - warn!("RTL8125: timed out waiting for RX/TX FIFO empty"); - } - - fn wait_mac_ocp_e00e_low(&self) { - for _ in 0..10 { - match self.mac_ocp_read(0xe00e) { - Ok(value) if value & (1 << 13) == 0 => return, - Ok(_) => spin_delay(1_000), - Err(err) => { - warn!("RTL8125: failed to read MAC OCP 0xe00e: {err:?}"); - return; - } - } - } - warn!("RTL8125: timed out waiting for MAC OCP 0xe00e bit 13 to clear"); - } - - fn set_aspm_clkreq(&self, enable: bool) -> Result<()> { - if enable { - self.regs.set_aspm_clkreq(true); - self.mac_ocp_modify(0xe094, 0xff00, 0)?; - self.mac_ocp_modify(0xe092, 0x00ff, 1 << 2)?; - } else { - self.mac_ocp_modify(0xe092, 0x00ff, 0)?; - self.regs.set_aspm_clkreq(false); - } - spin_delay(100); - Ok(()) - } - - fn config_eee_mac(&self) -> Result<()> { - if self.chip == ChipVersion::Rtl8125B { - self.regs.write_eee_txidle_timer(EEE_TXIDLE_TIMER_VALUE); - } else { - self.mac_ocp_modify(0xeb62, 0, (1 << 2) | (1 << 1))?; - } - self.mac_ocp_modify(0xe040, 0, (1 << 1) | 1) - } - - fn ack_events(&self, bits: u32) { - self.regs.write_interrupt_status(bits); - } - - fn mac_ocp_write(&self, reg: u32, data: u16) -> Result<()> { - validate_ocp_reg(reg)?; - self.regs.start_mac_ocp_write(reg, data); - Ok(()) - } - - fn mac_ocp_read(&self, reg: u32) -> Result { - validate_ocp_reg(reg)?; - self.regs.start_mac_ocp_read(reg); - Ok(self.regs.read_mac_ocp_data()) - } - - fn mac_ocp_modify(&self, reg: u32, mask: u16, set: u16) -> Result<()> { - let data = self.mac_ocp_read(reg)?; - self.mac_ocp_write(reg, (data & !mask) | set) - } - - fn ephy_read(&self, reg: u32) -> Result { - self.regs.start_ephy_read(reg); - for _ in 0..1_000 { - if self.regs.ephy_ready() { - return Ok(self.regs.read_ephy_data()); - } - core::hint::spin_loop(); - } - Err(Error::HardwareTimeout { - operation: "EPHY read", - }) - } - - fn ephy_write(&self, reg: u32, value: u16) -> Result<()> { - self.regs.start_ephy_write(reg, value); - for _ in 0..1_000 { - if !self.regs.ephy_ready() { - spin_delay(1_000); - return Ok(()); - } - core::hint::spin_loop(); - } - Err(Error::HardwareTimeout { - operation: "EPHY write", - }) - } - - fn ephy_init(&self, entries: &[EphyInfo]) -> Result<()> { - for entry in entries { - let value = (self.ephy_read(entry.offset.into())? & !entry.mask) | entry.bits; - self.ephy_write(entry.offset.into(), value)?; - } - Ok(()) - } - - fn phy_ocp_write(&self, reg: u32, data: u16) -> Result<()> { - validate_ocp_reg(reg)?; - self.regs.start_phy_ocp_write(reg, data); - for _ in 0..1_000 { - if !self.regs.phy_ocp_busy() { - return Ok(()); - } - core::hint::spin_loop(); - } - Err(Error::HardwareTimeout { - operation: "PHY OCP write", - }) - } - - fn phy_ocp_read(&self, reg: u32) -> Result { - validate_ocp_reg(reg)?; - self.regs.start_phy_ocp_read(reg); - for _ in 0..1_000 { - if self.regs.phy_ocp_busy() { - return Ok(self.regs.read_phy_ocp_data()); - } - core::hint::spin_loop(); - } - Err(Error::HardwareTimeout { - operation: "PHY OCP read", - }) - } - - fn phy_reg_addr(&self, reg: u32) -> u32 { - let reg = if self.phy_ocp_base == OCP_STD_PHY_BASE { - reg - } else { - reg.saturating_sub(0x10) - }; - self.phy_ocp_base + reg * 2 - } - - fn phy_write(&mut self, reg: u32, value: u16) -> Result<()> { - if reg == 0x1f { - self.phy_ocp_base = if value == 0 { - OCP_STD_PHY_BASE - } else { - u32::from(value) << 4 - }; - return Ok(()); - } - - self.phy_ocp_write(self.phy_reg_addr(reg), value) - } - - fn phy_read(&self, reg: u32) -> Result { - if reg == 0x1f { - return Ok(if self.phy_ocp_base == OCP_STD_PHY_BASE { - 0 - } else { - (self.phy_ocp_base >> 4) as u16 - }); - } - - self.phy_ocp_read(self.phy_reg_addr(reg)) - } - - fn phy_modify(&mut self, reg: u32, mask: u16, set: u16) -> Result<()> { - let data = self.phy_read(reg)?; - self.phy_write(reg, (data & !mask) | set) - } - - fn phy_write_paged(&mut self, page: u16, reg: u32, value: u16) -> Result<()> { - let old_page = self.phy_read(0x1f)?; - self.phy_write(0x1f, page)?; - self.phy_write(reg, value)?; - self.phy_write(0x1f, old_page) - } - - fn phy_modify_paged(&mut self, page: u16, reg: u32, mask: u16, set: u16) -> Result<()> { - let old_page = self.phy_read(0x1f)?; - self.phy_write(0x1f, page)?; - self.phy_modify(reg, mask, set)?; - self.phy_write(0x1f, old_page) - } - - fn phy_param(&mut self, param: u16, mask: u16, set: u16) -> Result<()> { - let old_page = self.phy_read(0x1f)?; - self.phy_write(0x1f, 0x0a43)?; - self.phy_write(0x13, param)?; - self.phy_modify(0x14, mask, set)?; - self.phy_write(0x1f, old_page) - } - - fn config_eee_phy_8125a(&mut self) -> Result<()> { - self.phy_modify_paged(0x0a43, 0x11, 0, 1 << 4)?; - self.phy_modify_paged(0x0a4a, 0x11, 0, 1 << 9)?; - self.phy_modify_paged(0x0a42, 0x14, 0, 1 << 7)?; - self.phy_modify_paged(0x0a6d, 0x12, 1, 0)?; - self.phy_modify_paged(0x0a6d, 0x14, 1 << 4, 0) - } - - fn config_eee_phy_8125b(&mut self) -> Result<()> { - self.phy_modify_paged(0x0a6d, 0x12, 1, 0)?; - self.phy_modify_paged(0x0a6d, 0x14, 1 << 4, 0)?; - self.phy_modify_paged(0x0a42, 0x14, 1 << 7, 0)?; - self.phy_modify_paged(0x0a4a, 0x11, 1 << 9, 0) - } - - fn hw_phy_config(&mut self) -> Result<()> { - match self.chip { - ChipVersion::Rtl8125A => self.hw_phy_config_8125a(), - ChipVersion::Rtl8125B | ChipVersion::Unknown(_) => self.hw_phy_config_8125b(), - } - } - - fn hw_phy_config_8125a(&mut self) -> Result<()> { - self.phy_modify_paged(0x0ad4, 0x17, 0, 0x0010)?; - self.phy_modify_paged(0x0ad1, 0x13, 0x03ff, 0x03ff)?; - self.phy_modify_paged(0x0ad3, 0x11, 0x003f, 0x0006)?; - self.phy_modify_paged(0x0ac0, 0x14, 0x1100, 0)?; - self.phy_modify_paged(0x0acc, 0x10, 0x0003, 0x0002)?; - self.phy_modify_paged(0x0ad4, 0x10, 0x00e7, 0x0044)?; - self.phy_modify_paged(0x0ac1, 0x12, 0x0080, 0)?; - self.phy_modify_paged(0x0ac8, 0x10, 0x0300, 0)?; - self.phy_modify_paged(0x0ac5, 0x17, 0x0007, 0x0002)?; - self.phy_write_paged(0x0ad4, 0x16, 0x00a8)?; - self.phy_write_paged(0x0ac5, 0x16, 0x01ff)?; - self.phy_modify_paged(0x0ac8, 0x15, 0x00f0, 0x0030)?; - - self.phy_write(0x1f, 0x0b87)?; - self.phy_write(0x16, 0x80a2)?; - self.phy_write(0x17, 0x0153)?; - self.phy_write(0x16, 0x809c)?; - self.phy_write(0x17, 0x0153)?; - self.phy_write(0x1f, 0)?; - - self.phy_param(0x8257, 0xffff, 0x020f)?; - self.phy_param(0x80ea, 0xffff, 0x7843)?; - self.phy_modify_paged(0x0d06, 0x14, 0, 0x2000)?; - self.phy_param(0x81a2, 0, 0x0100)?; - self.phy_modify_paged(0x0b54, 0x16, 0xff00, 0xdb00)?; - self.phy_modify_paged(0x0a45, 0x12, 0x0001, 0)?; - self.phy_modify_paged(0x0a5d, 0x12, 0, 0x0020)?; - self.phy_modify_paged(0x0ad4, 0x17, 0x0010, 0)?; - self.phy_modify_paged(0x0a86, 0x15, 0x0001, 0)?; - self.phy_modify_paged(0x0a44, 0x11, 0, 1 << 11)?; - self.config_eee_phy_8125a() - } - - fn hw_phy_config_8125b(&mut self) -> Result<()> { - self.phy_modify_paged(0x0a44, 0x11, 0, 0x0800)?; - self.phy_modify_paged(0x0ac4, 0x13, 0x00f0, 0x0090)?; - self.phy_modify_paged(0x0ad3, 0x10, 0x0003, 0x0001)?; - - self.phy_write(0x1f, 0x0b87)?; - self.phy_write(0x16, 0x80f5)?; - self.phy_write(0x17, 0x760e)?; - self.phy_write(0x16, 0x8107)?; - self.phy_write(0x17, 0x360e)?; - self.phy_write(0x16, 0x8551)?; - self.phy_modify(0x17, 0xff00, 0x0800)?; - self.phy_write(0x1f, 0)?; - - self.phy_modify_paged(0x0bf0, 0x10, 0xe000, 0xa000)?; - self.phy_modify_paged(0x0bf4, 0x13, 0x0f00, 0x0300)?; - for param in [ - 0x8044, 0x804a, 0x8050, 0x8056, 0x805c, 0x8062, 0x8068, 0x806e, 0x8074, 0x807a, - ] { - self.phy_param(param, 0xffff, 0x2417)?; - } - self.phy_modify_paged(0x0a4c, 0x15, 0, 0x0040)?; - self.phy_modify_paged(0x0bf8, 0x12, 0xe000, 0xa000)?; - self.phy_modify_paged(0x0a5b, 0x12, 1 << 15, 0)?; - self.config_eee_phy_8125b() - } - - fn wait_link_list_ready(&self) { - for _ in 0..4_200 { - if self.regs.link_list_ready() { - return; - } - core::hint::spin_loop(); - } - warn!("RTL8125: timed out waiting for link-list FIFO ready"); - } } impl rdif_eth::DriverGeneric for Rtl8125 { @@ -657,7 +246,7 @@ impl Interface for Rtl8125 { self.tx_created = true; self.maybe_start_queues(); - Some(Box::new(Rtl8125TxQueue { + Some(queue::boxed_tx(Rtl8125TxQueue { regs: self.regs, desc, dma_mask: self.dma.dma_mask(), @@ -688,7 +277,7 @@ impl Interface for Rtl8125 { self.rx_created = true; self.maybe_start_queues(); - Some(Box::new(Rtl8125RxQueue { + Some(queue::boxed_rx(Rtl8125RxQueue { regs: self.regs, desc, dma_mask: self.dma.dma_mask(), @@ -740,314 +329,6 @@ impl Interface for Rtl8125 { } } -struct Rtl8125TxQueue { - regs: Regs, - desc: DArray, - dma_mask: u64, - bus_addrs: [Option; QUEUE_SIZE], - next_submit: usize, - next_reclaim: usize, - link_up: Option, - link_down_drops: u64, - submitted: u64, - reclaimed: u64, -} - -impl ITxQueue for Rtl8125TxQueue { - fn id(&self) -> usize { - QUEUE_ID0 - } - - fn config(&self) -> QueueConfig { - QueueConfig { - dma_mask: self.dma_mask, - align: DMA_ALIGN, - buf_size: MAX_PACKET, - ring_size: QUEUE_SIZE, - } - } - - fn submit(&mut self, bus_addr: u64, len: usize) -> core::result::Result<(), NetError> { - if len > MAX_PACKET { - return Err(NetError::NotSupported); - } - - if !self.observe_link_before_tx(len) { - self.link_down_drops = self.link_down_drops.saturating_add(1); - return Err(NetError::Retry); - } - - let idx = self.next_submit; - let next = (idx + 1) % QUEUE_SIZE; - if self.bus_addrs[idx].is_some() { - return Err(NetError::Retry); - } - - let ring_end = idx == QUEUE_SIZE - 1; - let desc = TxDesc::new_cpu_owned(bus_addr, len, ring_end); - self.desc.set(idx, desc); - release_dma_descriptor(); - self.desc.set(idx, desc.release_to_hw()); - self.bus_addrs[idx] = Some(bus_addr); - self.next_submit = next; - self.submitted = self.submitted.saturating_add(1); - self.regs.poll_tx(); - if self.submitted <= EARLY_PACKET_LOG_COUNT - || self.submitted.is_multiple_of(TX_SUBMIT_LOG_INTERVAL) - { - info!( - "RTL8125 tx submitted: idx={idx}, len={len}, submitted={}, reclaimed={}, \ - status={:?}", - self.submitted, - self.reclaimed, - read_status(self.regs), - ); - } - Ok(()) - } - - fn reclaim(&mut self) -> Option { - let idx = self.next_reclaim; - self.bus_addrs[idx]?; - let desc = self.desc.read(idx)?; - if desc.is_owned_by_hw() { - return None; - } - - self.next_reclaim = (idx + 1) % QUEUE_SIZE; - let bus_addr = self.bus_addrs[idx].take()?; - self.reclaimed = self.reclaimed.saturating_add(1); - if self.reclaimed <= EARLY_PACKET_LOG_COUNT - || self.reclaimed.is_multiple_of(TX_RECLAIM_LOG_INTERVAL) - { - info!( - "RTL8125 tx reclaimed: idx={idx}, len={}, submitted={}, reclaimed={}, status={:?}", - desc.len(), - self.submitted, - self.reclaimed, - read_status(self.regs), - ); - } - Some(bus_addr) - } -} - -impl Rtl8125TxQueue { - fn observe_link_before_tx(&mut self, len: usize) -> bool { - let must_sample = self.link_up != Some(true) - || self.submitted == 0 - || self.submitted.is_multiple_of(TX_LINK_SAMPLE_INTERVAL); - if !must_sample { - return true; - } - - let link_up = self.regs.link_up(); - let changed = self.link_up.replace(link_up) != Some(link_up); - - if link_up { - if changed { - let status = read_status(self.regs); - info!("RTL8125 tx link up before submit: len={len}, status={status:?}"); - } - } else if changed - || self.link_down_drops == 0 - || self - .link_down_drops - .is_multiple_of(LINK_DOWN_DROP_LOG_INTERVAL) - { - let status = read_status(self.regs); - warn!( - "RTL8125 tx link down before submit: len={len}, dropped_tx={}, status={status:?}", - self.link_down_drops - ); - } - - link_up - } -} - -struct Rtl8125RxQueue { - regs: Regs, - desc: DArray, - dma_mask: u64, - start: QueueStart, - bus_addrs: [Option; QUEUE_SIZE], - next_submit: usize, - next_reclaim: usize, - idle_polls: u64, - last_rx_rearm_idle: u64, - submitted: usize, - reclaimed: u64, - rx_errors: u64, - deferred_refill: VecDeque, -} - -impl IRxQueue for Rtl8125RxQueue { - fn id(&self) -> usize { - QUEUE_ID0 - } - - fn config(&self) -> QueueConfig { - QueueConfig { - dma_mask: self.dma_mask, - align: DMA_ALIGN, - buf_size: RX_BUF_SIZE, - ring_size: RX_QUEUE_CONFIG_SIZE, - } - } - - fn submit(&mut self, bus_addr: u64, len: usize) -> core::result::Result<(), NetError> { - if len < RX_BUF_SIZE { - return Err(NetError::NotSupported); - } - - self.flush_deferred_refill(); - if self.submitted >= RX_START_THRESHOLD { - self.deferred_refill.push_back(bus_addr); - self.flush_deferred_refill(); - return Ok(()); - } - - let idx = self.next_submit; - let next = (idx + 1) % QUEUE_SIZE; - if self.bus_addrs[idx].is_some() { - return Err(NetError::Retry); - } - - let ring_end = idx == QUEUE_SIZE - 1; - let desc = RxDesc::new_cpu_owned(bus_addr, RX_BUF_SIZE, ring_end); - self.desc.set(idx, desc); - release_dma_descriptor(); - self.desc.set(idx, desc.release_to_hw()); - self.bus_addrs[idx] = Some(bus_addr); - self.next_submit = next; - self.submitted = self.submitted.saturating_add(1); - if self.submitted >= RX_START_THRESHOLD { - let was_ready = { - let mut start = self.start.lock(); - let was_ready = start.rx_ready; - start.rx_ready = true; - was_ready - }; - if !was_ready { - let last_opts1 = self.desc.read(QUEUE_SIZE - 1).map_or(0, |desc| desc.opts1); - info!( - "RTL8125 rx ring ready: submitted={}, last_desc_opts1={:#x}", - self.submitted, last_opts1 - ); - } - try_start_queues(self.regs, self.dma_mask, &self.start); - } - Ok(()) - } - - fn reclaim(&mut self) -> Option<(u64, usize)> { - let idx = self.next_reclaim; - let bus_addr = self.bus_addrs[idx]?; - let desc = self.desc.read(idx)?; - if desc.is_owned_by_hw() { - self.idle_polls = self.idle_polls.saturating_add(1); - let status = read_status(self.regs); - if irq_has_rx_overflow(status.intr_status) - && self.idle_polls.saturating_sub(self.last_rx_rearm_idle) - >= RX_OVERFLOW_REARM_IDLE_POLLS - { - self.last_rx_rearm_idle = self.idle_polls; - warn!( - "RTL8125 rx overflow rearm: idx={idx}, opts1={:#x}, submitted={}, \ - reclaimed={}, status={status:?}", - desc.opts1, self.submitted, self.reclaimed - ); - self.regs.write_interrupt_status(status.intr_status); - set_rx_mode(self.regs); - self.regs.enable_tx_rx(); - self.regs.commit(); - } - if self.idle_polls.is_multiple_of(RX_IDLE_LOG_INTERVAL) { - debug!( - "RTL8125 rx idle: idx={idx}, opts1={:#x}, submitted={}, reclaimed={}, \ - status={:?}", - desc.opts1, self.submitted, self.reclaimed, status, - ); - } - return None; - } - acquire_dma_descriptor(); - let desc = self.desc.read(idx)?; - self.idle_polls = 0; - self.last_rx_rearm_idle = 0; - - self.next_reclaim = (idx + 1) % QUEUE_SIZE; - self.bus_addrs[idx] = None; - - if desc.has_error() || !desc.is_whole_packet() { - self.rx_errors = self.rx_errors.saturating_add(1); - warn!( - "RTL8125 rx error: idx={idx}, opts1={:#x}, submitted={}, reclaimed={}, errors={}, \ - status={:?}", - desc.opts1, - self.submitted, - self.reclaimed, - self.rx_errors, - read_status(self.regs), - ); - return Some((bus_addr, 0)); - } - let len = desc.packet_len(); - self.reclaimed = self.reclaimed.saturating_add(1); - if self.reclaimed.is_multiple_of(RX_RECLAIM_LOG_INTERVAL) { - info!( - "RTL8125 rx packet: idx={idx}, len={len}, submitted={}, reclaimed={}, status={:?}", - self.submitted, - self.reclaimed, - read_status(self.regs), - ); - } - Some((bus_addr, len)) - } -} - -impl Rtl8125RxQueue { - fn flush_deferred_refill(&mut self) { - while self.deferred_refill.len() >= RX_DESC_PER_CACHE_LINE { - let Some(bus_addr) = self.deferred_refill.pop_front() else { - break; - }; - if let Err(err) = self.submit_deferred_buffer(bus_addr) { - warn!("RTL8125 rx deferred refill failed: {err:?}"); - self.deferred_refill.push_front(bus_addr); - break; - } - } - } - - fn submit_deferred_buffer(&mut self, bus_addr: u64) -> core::result::Result<(), NetError> { - let idx = self.next_submit; - let next = (idx + 1) % QUEUE_SIZE; - if self.bus_addrs[idx].is_some() { - return Err(NetError::Retry); - } - - let ring_end = idx == QUEUE_SIZE - 1; - let desc = RxDesc::new_cpu_owned(bus_addr, RX_BUF_SIZE, ring_end); - self.desc.set(idx, desc); - release_dma_descriptor(); - self.desc.set(idx, desc.release_to_hw()); - self.bus_addrs[idx] = Some(bus_addr); - self.next_submit = next; - self.submitted = self.submitted.saturating_add(1); - Ok(()) - } -} - -fn release_dma_descriptor() { - fence(AtomicOrdering::Release); -} - -fn acquire_dma_descriptor() { - fence(AtomicOrdering::Acquire); -} - fn rtl8125_xid(regs: Regs) -> u16 { ((regs.read_tx_config() >> 20) & 0x0fcf) as u16 } @@ -1066,37 +347,7 @@ fn read_status(regs: Regs) -> Rtl8125Status { } } -fn try_start_queues(regs: Regs, dma_mask: u64, start: &QueueStart) { - let (tx_base, rx_base) = { - let mut start = start.lock(); - if start.started || !start.rx_ready { - return; - } - let (Some(tx_base), Some(rx_base)) = (start.tx_base, start.rx_base) else { - return; - }; - start.started = true; - (tx_base, rx_base) - }; - - regs.unlock_config(); - regs.write_tx_desc_base(tx_base); - regs.write_rx_desc_base(rx_base); - regs.lock_config(); - - info!("RTL8125 queue DMA bases: tx={tx_base:#x}, rx={rx_base:#x}, mask={dma_mask:#x}"); - regs.write_rx_max_size(RX_BUF_SIZE as u16 + 1); - regs.enable_tx_rx(); - regs.write_default_rx_config_8125b(); - regs.write_default_tx_config(); - regs.write_interrupt_status(u32::MAX); - set_rx_mode(regs); - regs.write_interrupt_mask(DEFAULT_IRQ_MASK); - regs.commit(); - info!("RTL8125 queues started: status={:?}", read_status(regs)); -} - -fn set_rx_mode(regs: Regs) { +pub(crate) fn set_rx_mode(regs: Regs) { regs.set_multicast_filter_all(); regs.set_rx_accept_mode(); } @@ -1115,123 +366,6 @@ fn is_valid_mac(mac: [u8; 6]) -> bool { mac != [0; 6] && mac != [0xff; 6] && mac[0] & 1 == 0 } -fn validate_ocp_reg(reg: u32) -> Result<()> { - if reg & 0xffff_0001 == 0 { - Ok(()) - } else { - Err(Error::InvalidOcpAddress { reg }) - } -} - -fn spin_delay(iterations: usize) { - for _ in 0..iterations { - core::hint::spin_loop(); - } -} - -#[derive(Clone, Copy)] -struct EphyInfo { - offset: u8, - mask: u16, - bits: u16, -} - -const RTL8125A_EPHY: [EphyInfo; 12] = [ - EphyInfo { - offset: 0x04, - mask: 0xffff, - bits: 0xd000, - }, - EphyInfo { - offset: 0x0a, - mask: 0xffff, - bits: 0x8653, - }, - EphyInfo { - offset: 0x23, - mask: 0xffff, - bits: 0xab66, - }, - EphyInfo { - offset: 0x20, - mask: 0xffff, - bits: 0x9455, - }, - EphyInfo { - offset: 0x21, - mask: 0xffff, - bits: 0x99ff, - }, - EphyInfo { - offset: 0x29, - mask: 0xffff, - bits: 0xfe04, - }, - EphyInfo { - offset: 0x44, - mask: 0xffff, - bits: 0xd000, - }, - EphyInfo { - offset: 0x4a, - mask: 0xffff, - bits: 0x8653, - }, - EphyInfo { - offset: 0x63, - mask: 0xffff, - bits: 0xab66, - }, - EphyInfo { - offset: 0x60, - mask: 0xffff, - bits: 0x9455, - }, - EphyInfo { - offset: 0x61, - mask: 0xffff, - bits: 0x99ff, - }, - EphyInfo { - offset: 0x69, - mask: 0xffff, - bits: 0xfe04, - }, -]; - -const RTL8125B_EPHY: [EphyInfo; 6] = [ - EphyInfo { - offset: 0x0b, - mask: 0xffff, - bits: 0xa908, - }, - EphyInfo { - offset: 0x1e, - mask: 0xffff, - bits: 0x20eb, - }, - EphyInfo { - offset: 0x4b, - mask: 0xffff, - bits: 0xa908, - }, - EphyInfo { - offset: 0x5e, - mask: 0xffff, - bits: 0x20eb, - }, - EphyInfo { - offset: 0x22, - mask: 0x0030, - bits: 0x0020, - }, - EphyInfo { - offset: 0x62, - mask: 0x0030, - bits: 0x0020, - }, -]; - const _: () = { assert!(size_of::() == 16); assert!(size_of::() == 16); diff --git a/drivers/net/realtek-rtl8125/src/queue.rs b/drivers/net/realtek-rtl8125/src/queue.rs new file mode 100644 index 0000000000..bbb5e22981 --- /dev/null +++ b/drivers/net/realtek-rtl8125/src/queue.rs @@ -0,0 +1,374 @@ +use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; +use core::sync::atomic::{Ordering as AtomicOrdering, fence}; + +use dma_api::DArray; +use log::{debug, info, warn}; +use rdif_eth::{DmaBuffer, IRxQueue, ITxQueue, NetError, QueueConfig}; +use spin::Mutex; + +use crate::{ + DMA_ALIGN, EARLY_PACKET_LOG_COUNT, LINK_DOWN_DROP_LOG_INTERVAL, MAX_PACKET, QUEUE_ID0, + QUEUE_SIZE, RX_BUF_SIZE, RX_DESC_PER_CACHE_LINE, RX_IDLE_LOG_INTERVAL, + RX_OVERFLOW_REARM_IDLE_POLLS, RX_QUEUE_CONFIG_SIZE, RX_RECLAIM_LOG_INTERVAL, + RX_START_THRESHOLD, TX_LINK_SAMPLE_INTERVAL, TX_RECLAIM_LOG_INTERVAL, TX_SUBMIT_LOG_INTERVAL, + descriptor::{RxDesc, TxDesc}, + read_status, + registers::{DEFAULT_IRQ_MASK, Regs, irq_has_rx_overflow}, + set_rx_mode, +}; + +pub(crate) type QueueStart = Arc>; + +#[derive(Default)] +pub(crate) struct QueueStartState { + pub(crate) tx_base: Option, + pub(crate) rx_base: Option, + pub(crate) rx_ready: bool, + pub(crate) started: bool, +} + +pub(crate) struct Rtl8125TxQueue { + pub(crate) regs: Regs, + pub(crate) desc: DArray, + pub(crate) dma_mask: u64, + pub(crate) bus_addrs: [Option; QUEUE_SIZE], + pub(crate) next_submit: usize, + pub(crate) next_reclaim: usize, + pub(crate) link_up: Option, + pub(crate) link_down_drops: u64, + pub(crate) submitted: u64, + pub(crate) reclaimed: u64, +} + +impl ITxQueue for Rtl8125TxQueue { + fn id(&self) -> usize { + QUEUE_ID0 + } + + fn config(&self) -> QueueConfig { + QueueConfig { + dma_mask: self.dma_mask, + align: DMA_ALIGN, + buf_size: MAX_PACKET, + ring_size: QUEUE_SIZE, + } + } + + fn submit(&mut self, buffer: DmaBuffer) -> core::result::Result<(), NetError> { + if buffer.len > MAX_PACKET { + return Err(NetError::NotSupported); + } + + if !self.observe_link_before_tx(buffer.len) { + self.link_down_drops = self.link_down_drops.saturating_add(1); + return Err(NetError::Retry); + } + + let idx = self.next_submit; + let next = (idx + 1) % QUEUE_SIZE; + if self.bus_addrs[idx].is_some() { + return Err(NetError::Retry); + } + + let ring_end = idx == QUEUE_SIZE - 1; + let desc = TxDesc::new_cpu_owned(buffer.bus_addr, buffer.len, ring_end); + self.desc.set(idx, desc); + release_dma_descriptor(); + self.desc.set(idx, desc.release_to_hw()); + self.bus_addrs[idx] = Some(buffer.bus_addr); + self.next_submit = next; + self.submitted = self.submitted.saturating_add(1); + self.regs.poll_tx(); + if self.submitted <= EARLY_PACKET_LOG_COUNT + || self.submitted.is_multiple_of(TX_SUBMIT_LOG_INTERVAL) + { + info!( + "RTL8125 tx submitted: idx={idx}, len={}, submitted={}, reclaimed={}, status={:?}", + buffer.len, + self.submitted, + self.reclaimed, + read_status(self.regs), + ); + } + Ok(()) + } + + fn reclaim(&mut self) -> Option { + let idx = self.next_reclaim; + self.bus_addrs[idx]?; + let desc = self.desc.read(idx)?; + if desc.is_owned_by_hw() { + return None; + } + + self.next_reclaim = (idx + 1) % QUEUE_SIZE; + let bus_addr = self.bus_addrs[idx].take()?; + self.reclaimed = self.reclaimed.saturating_add(1); + if self.reclaimed <= EARLY_PACKET_LOG_COUNT + || self.reclaimed.is_multiple_of(TX_RECLAIM_LOG_INTERVAL) + { + info!( + "RTL8125 tx reclaimed: idx={idx}, len={}, submitted={}, reclaimed={}, status={:?}", + desc.len(), + self.submitted, + self.reclaimed, + read_status(self.regs), + ); + } + Some(bus_addr) + } +} + +impl Rtl8125TxQueue { + fn observe_link_before_tx(&mut self, len: usize) -> bool { + let must_sample = self.link_up != Some(true) + || self.submitted == 0 + || self.submitted.is_multiple_of(TX_LINK_SAMPLE_INTERVAL); + if !must_sample { + return true; + } + + let link_up = self.regs.link_up(); + let changed = self.link_up.replace(link_up) != Some(link_up); + + if link_up { + if changed { + let status = read_status(self.regs); + info!("RTL8125 tx link up before submit: len={len}, status={status:?}"); + } + } else if changed + || self.link_down_drops == 0 + || self + .link_down_drops + .is_multiple_of(LINK_DOWN_DROP_LOG_INTERVAL) + { + let status = read_status(self.regs); + warn!( + "RTL8125 tx link down before submit: len={len}, dropped_tx={}, status={status:?}", + self.link_down_drops + ); + } + + link_up + } +} + +pub(crate) struct Rtl8125RxQueue { + pub(crate) regs: Regs, + pub(crate) desc: DArray, + pub(crate) dma_mask: u64, + pub(crate) start: QueueStart, + pub(crate) bus_addrs: [Option; QUEUE_SIZE], + pub(crate) next_submit: usize, + pub(crate) next_reclaim: usize, + pub(crate) idle_polls: u64, + pub(crate) last_rx_rearm_idle: u64, + pub(crate) submitted: usize, + pub(crate) reclaimed: u64, + pub(crate) rx_errors: u64, + pub(crate) deferred_refill: VecDeque, +} + +impl IRxQueue for Rtl8125RxQueue { + fn id(&self) -> usize { + QUEUE_ID0 + } + + fn config(&self) -> QueueConfig { + QueueConfig { + dma_mask: self.dma_mask, + align: DMA_ALIGN, + buf_size: RX_BUF_SIZE, + ring_size: RX_QUEUE_CONFIG_SIZE, + } + } + + fn submit(&mut self, buffer: DmaBuffer) -> core::result::Result<(), NetError> { + if buffer.len < RX_BUF_SIZE { + return Err(NetError::NotSupported); + } + + self.flush_deferred_refill(); + if self.submitted >= RX_START_THRESHOLD { + self.deferred_refill.push_back(buffer.bus_addr); + self.flush_deferred_refill(); + return Ok(()); + } + + let idx = self.next_submit; + let next = (idx + 1) % QUEUE_SIZE; + if self.bus_addrs[idx].is_some() { + return Err(NetError::Retry); + } + + let ring_end = idx == QUEUE_SIZE - 1; + let desc = RxDesc::new_cpu_owned(buffer.bus_addr, RX_BUF_SIZE, ring_end); + self.desc.set(idx, desc); + release_dma_descriptor(); + self.desc.set(idx, desc.release_to_hw()); + self.bus_addrs[idx] = Some(buffer.bus_addr); + self.next_submit = next; + self.submitted = self.submitted.saturating_add(1); + if self.submitted >= RX_START_THRESHOLD { + let was_ready = { + let mut start = self.start.lock(); + let was_ready = start.rx_ready; + start.rx_ready = true; + was_ready + }; + if !was_ready { + let last_opts1 = self.desc.read(QUEUE_SIZE - 1).map_or(0, |desc| desc.opts1); + info!( + "RTL8125 rx ring ready: submitted={}, last_desc_opts1={:#x}", + self.submitted, last_opts1 + ); + } + try_start_queues(self.regs, self.dma_mask, &self.start); + } + Ok(()) + } + + fn reclaim(&mut self) -> Option<(u64, usize)> { + let idx = self.next_reclaim; + let bus_addr = self.bus_addrs[idx]?; + let desc = self.desc.read(idx)?; + if desc.is_owned_by_hw() { + self.idle_polls = self.idle_polls.saturating_add(1); + let status = read_status(self.regs); + if irq_has_rx_overflow(status.intr_status) + && self.idle_polls.saturating_sub(self.last_rx_rearm_idle) + >= RX_OVERFLOW_REARM_IDLE_POLLS + { + self.last_rx_rearm_idle = self.idle_polls; + warn!( + "RTL8125 rx overflow rearm: idx={idx}, opts1={:#x}, submitted={}, \ + reclaimed={}, status={status:?}", + desc.opts1, self.submitted, self.reclaimed + ); + self.regs.write_interrupt_status(status.intr_status); + set_rx_mode(self.regs); + self.regs.enable_tx_rx(); + self.regs.commit(); + } + if self.idle_polls.is_multiple_of(RX_IDLE_LOG_INTERVAL) { + debug!( + "RTL8125 rx idle: idx={idx}, opts1={:#x}, submitted={}, reclaimed={}, \ + status={:?}", + desc.opts1, self.submitted, self.reclaimed, status, + ); + } + return None; + } + acquire_dma_descriptor(); + let desc = self.desc.read(idx)?; + self.idle_polls = 0; + self.last_rx_rearm_idle = 0; + + self.next_reclaim = (idx + 1) % QUEUE_SIZE; + self.bus_addrs[idx] = None; + + if desc.has_error() || !desc.is_whole_packet() { + self.rx_errors = self.rx_errors.saturating_add(1); + warn!( + "RTL8125 rx error: idx={idx}, opts1={:#x}, submitted={}, reclaimed={}, errors={}, \ + status={:?}", + desc.opts1, + self.submitted, + self.reclaimed, + self.rx_errors, + read_status(self.regs), + ); + return Some((bus_addr, 0)); + } + let len = desc.packet_len(); + self.reclaimed = self.reclaimed.saturating_add(1); + if self.reclaimed.is_multiple_of(RX_RECLAIM_LOG_INTERVAL) { + info!( + "RTL8125 rx packet: idx={idx}, len={len}, submitted={}, reclaimed={}, status={:?}", + self.submitted, + self.reclaimed, + read_status(self.regs), + ); + } + Some((bus_addr, len)) + } +} + +impl Rtl8125RxQueue { + fn flush_deferred_refill(&mut self) { + while self.deferred_refill.len() >= RX_DESC_PER_CACHE_LINE { + let Some(bus_addr) = self.deferred_refill.pop_front() else { + break; + }; + if let Err(err) = self.submit_deferred_buffer(bus_addr) { + warn!("RTL8125 rx deferred refill failed: {err:?}"); + self.deferred_refill.push_front(bus_addr); + break; + } + } + } + + fn submit_deferred_buffer(&mut self, bus_addr: u64) -> core::result::Result<(), NetError> { + let idx = self.next_submit; + let next = (idx + 1) % QUEUE_SIZE; + if self.bus_addrs[idx].is_some() { + return Err(NetError::Retry); + } + + let ring_end = idx == QUEUE_SIZE - 1; + let desc = RxDesc::new_cpu_owned(bus_addr, RX_BUF_SIZE, ring_end); + self.desc.set(idx, desc); + release_dma_descriptor(); + self.desc.set(idx, desc.release_to_hw()); + self.bus_addrs[idx] = Some(bus_addr); + self.next_submit = next; + self.submitted = self.submitted.saturating_add(1); + Ok(()) + } +} + +pub(crate) fn release_dma_descriptor() { + fence(AtomicOrdering::Release); +} + +fn acquire_dma_descriptor() { + fence(AtomicOrdering::Acquire); +} + +pub(crate) fn try_start_queues(regs: Regs, dma_mask: u64, start: &QueueStart) { + let (tx_base, rx_base) = { + let mut start = start.lock(); + if start.started || !start.rx_ready { + return; + } + let (Some(tx_base), Some(rx_base)) = (start.tx_base, start.rx_base) else { + return; + }; + start.started = true; + (tx_base, rx_base) + }; + + regs.unlock_config(); + regs.write_tx_desc_base(tx_base); + regs.write_rx_desc_base(rx_base); + regs.lock_config(); + + info!("RTL8125 queue DMA bases: tx={tx_base:#x}, rx={rx_base:#x}, mask={dma_mask:#x}"); + regs.write_rx_max_size(RX_BUF_SIZE as u16 + 1); + regs.enable_tx_rx(); + regs.write_default_rx_config_8125b(); + regs.write_default_tx_config(); + regs.write_interrupt_status(u32::MAX); + set_rx_mode(regs); + regs.write_interrupt_mask(DEFAULT_IRQ_MASK); + regs.commit(); + info!("RTL8125 queues started: status={:?}", read_status(regs)); +} + +pub(crate) fn boxed_tx(queue: Rtl8125TxQueue) -> Box { + Box::new(queue) +} + +pub(crate) fn boxed_rx(queue: Rtl8125RxQueue) -> Box { + Box::new(queue) +} diff --git a/drivers/rdrive/Cargo.toml b/drivers/rdrive/Cargo.toml index 5f6005ba54..08aa0560cc 100644 --- a/drivers/rdrive/Cargo.toml +++ b/drivers/rdrive/Cargo.toml @@ -10,7 +10,6 @@ repository.workspace = true version = "0.20.1" [dependencies] -ax-kspin.workspace = true fdt-edit.workspace = true fdt-raw.workspace = true log = "0.4" diff --git a/drivers/rdrive/src/error.rs b/drivers/rdrive/src/error.rs index dbff55646a..e03a5ed3cb 100644 --- a/drivers/rdrive/src/error.rs +++ b/drivers/rdrive/src/error.rs @@ -6,6 +6,8 @@ use fdt_raw::FdtError; pub enum DriverError { #[error("FDT error: {0}")] Fdt(String), + #[error("unsupported driver source: {0}")] + Unsupported(&'static str), #[error("Unknown driver error: {0}")] Unknown(String), } diff --git a/drivers/rdrive/src/lib.rs b/drivers/rdrive/src/lib.rs index 0cb1698408..d7f4abfb0e 100644 --- a/drivers/rdrive/src/lib.rs +++ b/drivers/rdrive/src/lib.rs @@ -7,10 +7,9 @@ extern crate log; use core::ptr::NonNull; -use ax_kspin::SpinNoIrq as Mutex; -pub use fdt_edit::Phandle; +pub use fdt_edit::{Fdt, Phandle}; use register::{DriverRegister, ProbeLevel}; -use spin::Once; +use spin::{Mutex, Once}; mod descriptor; pub mod driver; @@ -38,19 +37,53 @@ static CONTAINER: Once> = Once::new(); #[derive(Debug, Clone)] pub enum Platform { + Static(&'static [probe::static_::StaticDeviceDesc]), Fdt { addr: NonNull }, + Acpi(probe::acpi::AcpiRoot), } unsafe impl Send for Platform {} +#[derive(Debug, Clone, Copy)] +pub enum PlatformSource { + Static(&'static [probe::static_::StaticDeviceDesc]), + Fdt(NonNull), + Acpi(probe::acpi::AcpiRoot), +} + +unsafe impl Send for PlatformSource {} + pub(crate) fn container() -> &'static Mutex { CONTAINER.get().expect("rdrive not init") } +pub fn is_initialized() -> bool { + CONTAINER.get().is_some() +} + pub fn init(platform: Platform) -> Result<(), DriverError> { match platform { - Platform::Fdt { addr } => { - probe::fdt::init(addr)?; + Platform::Static(devices) => init_sources(&[PlatformSource::Static(devices)])?, + Platform::Fdt { addr } => init_sources(&[PlatformSource::Fdt(addr)])?, + Platform::Acpi(root) => init_sources(&[PlatformSource::Acpi(root)])?, + } + Ok(()) +} + +pub fn init_sources(sources: &[PlatformSource]) -> Result<(), DriverError> { + for source in sources { + match source { + PlatformSource::Static(_) => {} + PlatformSource::Fdt(addr) => probe::fdt::check_addr(*addr)?, + PlatformSource::Acpi(root) => probe::acpi::check_root(*root)?, + } + } + + for source in sources { + match source { + PlatformSource::Static(devices) => probe::static_::init(devices)?, + PlatformSource::Fdt(addr) => probe::fdt::init(*addr)?, + PlatformSource::Acpi(root) => probe::acpi::init(*root)?, } } @@ -100,24 +133,32 @@ fn probe_system<'a>( stop_if_fail: bool, ) -> Result<(), ProbeError> { for one in registers { - // let system = edit(|manager| manager.enum_system.clone()); + probe_backend(one, probe::static_::try_probe_register(one), stop_if_fail)?; + probe_backend(one, probe::fdt::try_probe_register(one), stop_if_fail)?; + probe_backend(one, probe::acpi::try_probe_register(one), stop_if_fail)?; + } - // let res = system.probe_register(one)?; + Ok(()) +} - let res = probe::fdt::probe_register(one)?; +fn probe_backend( + register: &DriverRegister, + results: Option>, ProbeError>>, + stop_if_fail: bool, +) -> Result<(), ProbeError> { + let Some(results) = results else { + return Ok(()); + }; - for r in res { - match r { - Ok(_) => {} - Err(OnProbeError::NotMatch) => { - // Not a match, skip to the next probe - } - Err(e) => { - if stop_if_fail { - return Err(e.into()); - } else { - warn!("Probe failed for [{}]: {}", one.name, e); - } + for r in results? { + match r { + Ok(_) => {} + Err(OnProbeError::NotMatch) => {} + Err(e) => { + if stop_if_fail { + return Err(e.into()); + } else { + warn!("Probe failed for [{}]: {}", register.name, e); } } } @@ -149,7 +190,11 @@ pub fn get_one() -> Option> { } pub fn fdt_phandle_to_device_id(phandle: Phandle) -> Option { - probe::fdt::system().phandle_to_device_id(phandle) + probe::fdt::try_system().and_then(|system| system.phandle_to_device_id(phandle)) +} + +pub fn with_fdt(f: impl FnOnce(&Fdt) -> T) -> Option { + probe::fdt::try_system().map(|system| f(system.fdt())) } /// Macro for generating a driver module. diff --git a/drivers/rdrive/src/manager.rs b/drivers/rdrive/src/manager.rs index 109355ae39..0db748f016 100644 --- a/drivers/rdrive/src/manager.rs +++ b/drivers/rdrive/src/manager.rs @@ -136,7 +136,7 @@ mod tests { impl IrqTest { fn is_ok(&mut self) -> bool { - true // Placeholder for actual logic + true } } diff --git a/drivers/rdrive/src/probe/acpi.rs b/drivers/rdrive/src/probe/acpi.rs new file mode 100644 index 0000000000..924d32c311 --- /dev/null +++ b/drivers/rdrive/src/probe/acpi.rs @@ -0,0 +1,37 @@ +use crate::{ + PlatformDevice, + error::DriverError, + probe::{OnProbeError, ProbeError}, + register::DriverRegister, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AcpiRoot { + pub rsdp: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AcpiId { + pub hid: &'static str, + pub cids: &'static [&'static str], +} + +pub struct AcpiInfo<'a> { + _private: core::marker::PhantomData<&'a ()>, +} + +pub type FnOnProbe = fn(AcpiInfo<'_>, PlatformDevice) -> Result<(), OnProbeError>; + +pub fn check_root(_root: AcpiRoot) -> Result<(), DriverError> { + Err(DriverError::Unsupported("acpi")) +} + +pub fn init(root: AcpiRoot) -> Result<(), DriverError> { + check_root(root) +} + +pub(crate) fn try_probe_register( + _register: &DriverRegister, +) -> Option>, ProbeError>> { + None +} diff --git a/drivers/rdrive/src/probe/fdt/mod.rs b/drivers/rdrive/src/probe/fdt/mod.rs index 17c1269a5e..923d4b8150 100644 --- a/drivers/rdrive/src/probe/fdt/mod.rs +++ b/drivers/rdrive/src/probe/fdt/mod.rs @@ -4,9 +4,8 @@ use alloc::{ }; use core::ptr::NonNull; -use ax_kspin::SpinNoIrq as Mutex; pub use fdt_edit::{ClockRef, Fdt, InterruptRef, NodeType, Phandle, RegInfo, Status}; -use spin::Once; +use spin::{Mutex, Once}; use super::ProbeError; use crate::{ @@ -24,6 +23,12 @@ pub fn init(fdt_addr: NonNull) -> Result<(), DriverError> { Ok(()) } +pub fn check_addr(fdt_addr: NonNull) -> Result<(), DriverError> { + unsafe { Fdt::from_ptr(fdt_addr.as_ptr()) } + .map(|_| ()) + .map_err(|error| DriverError::Fdt(format!("{error:?}"))) +} + pub fn probe_register( register: &DriverRegister, ) -> Result>, ProbeError> { @@ -31,10 +36,20 @@ pub fn probe_register( sys.probe_register(register) } +pub(crate) fn try_probe_register( + register: &DriverRegister, +) -> Option>, ProbeError>> { + SYSTEM.get().map(|system| system.probe_register(register)) +} + pub(crate) fn system() -> &'static System { SYSTEM.get().expect("rdrive not init") } +pub(crate) fn try_system() -> Option<&'static System> { + SYSTEM.get() +} + pub struct FdtInfo<'a> { pub node: NodeType<'a>, phandle_2_device_id: BTreeMap, @@ -68,6 +83,10 @@ pub struct System { unsafe impl Send for System {} impl System { + pub fn fdt(&self) -> &Fdt { + &self.fdt + } + pub fn phandle_to_device_id(&self, phandle: Phandle) -> Option { self.phandle_2_device_id.get(&phandle).copied() } diff --git a/drivers/rdrive/src/probe/mod.rs b/drivers/rdrive/src/probe/mod.rs index 395b3ac97a..e902dc7e67 100644 --- a/drivers/rdrive/src/probe/mod.rs +++ b/drivers/rdrive/src/probe/mod.rs @@ -6,8 +6,10 @@ use core::error::Error; use fdt_raw::FdtError; +pub mod acpi; pub mod fdt; pub mod pci; +pub mod static_; #[derive(thiserror::Error, Debug)] pub enum ProbeError { @@ -19,6 +21,8 @@ pub enum ProbeError { OnProbe(#[from] OnProbeError), #[error("open device fail")] OpenFail(#[from] rdif_base::KError), + #[error("unsupported probe backend: {0}")] + Unsupported(&'static str), } impl From for ProbeError { @@ -37,6 +41,8 @@ pub enum OnProbeError { Other(#[from] Box), #[error("fdt parse error: {0}")] Fdt(String), + #[error("unsupported probe backend: {0}")] + Unsupported(&'static str), } impl From for OnProbeError { diff --git a/drivers/rdrive/src/probe/pci/mod.rs b/drivers/rdrive/src/probe/pci/mod.rs index 09db562266..0369478478 100644 --- a/drivers/rdrive/src/probe/pci/mod.rs +++ b/drivers/rdrive/src/probe/pci/mod.rs @@ -3,10 +3,9 @@ use core::ops::{Deref, DerefMut}; use ::pcie::*; pub use ::pcie::{Endpoint, PciCapability, PcieGeneric}; -use ax_kspin::SpinNoIrq as Mutex; use mmio_api::{MapError, MmioOp}; pub use rdif_pcie::{DriverGeneric, PciAddress, PciMem32, PciMem64, PcieController}; -use spin::Once; +use spin::{Mutex, Once}; use crate::{ Descriptor, Device, PlatformDevice, ProbeError, get_list, diff --git a/drivers/rdrive/src/probe/static_.rs b/drivers/rdrive/src/probe/static_.rs new file mode 100644 index 0000000000..4b14c5f472 --- /dev/null +++ b/drivers/rdrive/src/probe/static_.rs @@ -0,0 +1,232 @@ +use alloc::{collections::btree_set::BTreeSet, vec::Vec}; + +use spin::{Mutex, Once}; + +use crate::{ + Descriptor, DeviceId, PlatformDevice, + error::DriverError, + probe::{ + OnProbeError, ProbeError, + pci::{PciMem32, PciMem64}, + }, + register::{DriverRegister, ProbeKind}, +}; + +static SYSTEM: Once = Once::new(); + +pub type StaticMmioRegion = (usize, usize); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StaticPciEcam { + pub base: usize, + pub size: usize, + pub ranges: &'static [StaticMmioRegion], + pub mem32: Option, + pub mem64: Option, +} + +impl StaticPciEcam { + pub const fn new(base: usize, size: usize) -> Self { + Self { + base, + size, + ranges: &[], + mem32: None, + mem64: None, + } + } + + pub const fn with_ranges(mut self, ranges: &'static [StaticMmioRegion]) -> Self { + self.ranges = ranges; + self + } + + pub const fn with_mem32(mut self, mem32: Option) -> Self { + self.mem32 = mem32; + self + } + + pub const fn with_mem64(mut self, mem64: Option) -> Self { + self.mem64 = mem64; + self + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StaticDeviceDesc { + pub name: &'static str, + pub irq_parent: Option, + pub regs: &'static [StaticMmioRegion], + pub irqs: &'static [usize], + pub pci_ecam: Option, + pub probe_each_reg: bool, +} + +impl StaticDeviceDesc { + pub const fn new(name: &'static str) -> Self { + Self { + name, + irq_parent: None, + regs: &[], + irqs: &[], + pci_ecam: None, + probe_each_reg: false, + } + } + + pub const fn with_regs(mut self, regs: &'static [StaticMmioRegion]) -> Self { + self.regs = regs; + self + } + + pub const fn with_irqs(mut self, irqs: &'static [usize]) -> Self { + self.irqs = irqs; + self + } + + pub const fn with_irq_parent(mut self, irq_parent: Option) -> Self { + self.irq_parent = irq_parent; + self + } + + pub const fn with_pci_ecam(mut self, pci_ecam: StaticPciEcam) -> Self { + self.pci_ecam = Some(pci_ecam); + self + } + + pub const fn with_probe_each_reg(mut self) -> Self { + self.probe_each_reg = true; + self + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StaticInfo { + index: usize, + resource_index: Option, + desc: StaticDeviceDesc, +} + +impl StaticInfo { + pub const fn index(&self) -> usize { + self.index + } + + pub const fn resource_index(&self) -> Option { + self.resource_index + } + + pub const fn desc(&self) -> StaticDeviceDesc { + self.desc + } + + pub const fn name(&self) -> &'static str { + self.desc.name + } + + pub const fn irq_parent(&self) -> Option { + self.desc.irq_parent + } + + pub const fn regs(&self) -> &'static [StaticMmioRegion] { + self.desc.regs + } + + pub const fn irqs(&self) -> &'static [usize] { + self.desc.irqs + } + + pub const fn pci_ecam(&self) -> Option { + self.desc.pci_ecam + } +} + +pub type FnOnProbe = fn(info: StaticInfo, plat_dev: PlatformDevice) -> Result<(), OnProbeError>; + +pub fn init(devices: &'static [StaticDeviceDesc]) -> Result<(), DriverError> { + SYSTEM.call_once(|| System::new(devices)); + Ok(()) +} + +pub(crate) fn try_probe_register( + register: &DriverRegister, +) -> Option>, ProbeError>> { + SYSTEM.get().map(|system| system.probe_register(register)) +} + +struct System { + devices: &'static [StaticDeviceDesc], + probed_names: Mutex)>>, +} + +impl System { + fn new(devices: &'static [StaticDeviceDesc]) -> Self { + Self { + devices, + probed_names: Mutex::new(BTreeSet::new()), + } + } +} + +impl System { + fn probe_register( + &self, + register: &DriverRegister, + ) -> Result>, ProbeError> { + let mut out = Vec::new(); + for probe in register.probe_kinds { + let ProbeKind::Static { on_probe } = probe else { + continue; + }; + let on_probe = *on_probe; + + for (index, device) in self.devices.iter().enumerate() { + if device.probe_each_reg && !device.regs.is_empty() { + for resource_index in 0..device.regs.len() { + out.push(self.probe_one( + register, + on_probe, + index, + Some(resource_index), + *device, + )); + } + } else { + out.push(self.probe_one(register, on_probe, index, None, *device)); + } + } + } + + Ok(out) + } + + fn probe_one( + &self, + register: &DriverRegister, + on_probe: FnOnProbe, + index: usize, + resource_index: Option, + device: StaticDeviceDesc, + ) -> Result<(), OnProbeError> { + let probed_key = (register.name, index, resource_index); + if self.probed_names.lock().contains(&probed_key) { + return Err(OnProbeError::NotMatch); + } + + let mut descriptor = Descriptor::new(); + descriptor.name = device.name; + descriptor.irq_parent = device.irq_parent; + let info = StaticInfo { + index, + resource_index, + desc: device, + }; + let res = on_probe(info, PlatformDevice::new(descriptor)); + + if res.is_ok() { + self.probed_names.lock().insert(probed_key); + } + + res + } +} diff --git a/drivers/rdrive/src/register/mod.rs b/drivers/rdrive/src/register/mod.rs index ba21faa876..dac96e5f8f 100644 --- a/drivers/rdrive/src/register/mod.rs +++ b/drivers/rdrive/src/register/mod.rs @@ -4,7 +4,7 @@ use core::ops::Deref; pub use fdt::NodeType as Node; pub use crate::probe::fdt::FdtInfo; -use crate::probe::{fdt, pci}; +use crate::probe::{acpi, fdt, pci, static_}; #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] @@ -52,10 +52,17 @@ unsafe impl Send for DriverRegister {} unsafe impl Sync for DriverRegister {} pub enum ProbeKind { + Static { + on_probe: static_::FnOnProbe, + }, Fdt { compatibles: &'static [&'static str], on_probe: fdt::FnOnProbe, }, + Acpi { + ids: &'static [acpi::AcpiId], + on_probe: acpi::FnOnProbe, + }, Pci { on_probe: pci::FnOnProbe, }, diff --git a/drivers/rdrive/tests/init_sources.rs b/drivers/rdrive/tests/init_sources.rs new file mode 100644 index 0000000000..e00d00b0c6 --- /dev/null +++ b/drivers/rdrive/tests/init_sources.rs @@ -0,0 +1,57 @@ +use rdrive::{ + DriverGeneric, Platform, PlatformDevice, PlatformSource, get_one, init_sources, + probe::{OnProbeError, acpi::AcpiRoot, static_::StaticDeviceDesc}, + probe_all, + register::{DriverRegister, ProbeKind, ProbeLevel, ProbePriority}, +}; + +struct MixedSourceDevice; + +impl DriverGeneric for MixedSourceDevice { + fn name(&self) -> &str { + "MixedSourceDevice" + } +} + +static FAILED_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("failed-device")]; +static SUCCESS_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("success-device")]; + +fn probe_static( + info: rdrive::probe::static_::StaticInfo, + plat_dev: PlatformDevice, +) -> Result<(), OnProbeError> { + if info.name() != "success-device" { + return Err(OnProbeError::NotMatch); + } + + plat_dev.register(MixedSourceDevice); + Ok(()) +} + +static STATIC_REGISTER: DriverRegister = DriverRegister { + name: "mixed source static driver", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe_static, + }], +}; + +#[test] +fn unsupported_source_does_not_leave_static_backend_initialized() { + let err = init_sources(&[ + PlatformSource::Static(FAILED_DEVICES), + PlatformSource::Acpi(AcpiRoot { rsdp: 0 }), + ]) + .expect_err("acpi should reject the source set before committing static state"); + assert!(matches!( + err, + rdrive::error::DriverError::Unsupported("acpi") + )); + + rdrive::init(Platform::Static(SUCCESS_DEVICES)).expect("static platform should init"); + rdrive::register_add(STATIC_REGISTER.clone()); + probe_all(true).expect("static probe should succeed"); + + assert!(get_one::().is_some()); +} diff --git a/drivers/rdrive/tests/phase1.rs b/drivers/rdrive/tests/phase1.rs new file mode 100644 index 0000000000..f3b61a0e8f --- /dev/null +++ b/drivers/rdrive/tests/phase1.rs @@ -0,0 +1,62 @@ +use rdrive::{ + DriverGeneric, Platform, PlatformDevice, PlatformSource, get_one, init_sources, + probe::{acpi::AcpiRoot, static_::StaticDeviceDesc}, + probe_all, + register::{DriverRegister, ProbeKind, ProbeLevel, ProbePriority}, +}; + +struct StaticTestDevice; + +impl DriverGeneric for StaticTestDevice { + fn name(&self) -> &str { + "StaticTestDevice" + } +} + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("static-test-device")]; + +fn probe_static( + info: rdrive::probe::static_::StaticInfo, + plat_dev: PlatformDevice, +) -> Result<(), rdrive::probe::OnProbeError> { + assert_eq!(info.name(), "static-test-device"); + plat_dev.register(StaticTestDevice); + Ok(()) +} + +static STATIC_REGISTER: DriverRegister = DriverRegister { + name: "static test driver", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe_static, + }], +}; + +#[test] +fn static_probe_registers_device() { + rdrive::init(Platform::Static(STATIC_DEVICES)).expect("static platform should init"); + rdrive::register_add(STATIC_REGISTER.clone()); + + probe_all(true).expect("static probe should succeed"); + + assert!(get_one::().is_some()); +} + +#[test] +fn acpi_source_is_unsupported() { + let err = + rdrive::init(Platform::Acpi(AcpiRoot { rsdp: 0 })).expect_err("acpi is not supported yet"); + + assert!(matches!( + err, + rdrive::error::DriverError::Unsupported("acpi") + )); +} + +#[test] +fn fdt_phandle_lookup_is_none_without_fdt_source() { + init_sources(&[PlatformSource::Static(STATIC_DEVICES)]).expect("static source should init"); + + assert!(rdrive::fdt_phandle_to_device_id(1.into()).is_none()); +} diff --git a/drivers/soc/rockchip/rockchip-soc/src/lib.rs b/drivers/soc/rockchip/rockchip-soc/src/lib.rs index 4271ee8169..88fd47ffb2 100644 --- a/drivers/soc/rockchip/rockchip-soc/src/lib.rs +++ b/drivers/soc/rockchip/rockchip-soc/src/lib.rs @@ -25,7 +25,9 @@ pub use clock::{ ClkId, ClockError, ClockResult, Cru, CruOp, pll::{PllClock, PllRateParams, PllRateTable, RockchipPllType}, }; -pub use pinctrl::{GpioDirection, PinConfig, PinCtrl, PinCtrlOp, PinctrlResult, Pull, id::*}; +pub use pinctrl::{ + GpioDirection, Iomux, PinConfig, PinCtrl, PinCtrlOp, PinctrlResult, Pull, id::*, +}; pub use rst::{ResetRockchip, RstId}; pub use variants::*; diff --git a/drivers/usb/usb-host/Cargo.toml b/drivers/usb/usb-host/Cargo.toml index e418257f91..b899f2f5ab 100644 --- a/drivers/usb/usb-host/Cargo.toml +++ b/drivers/usb/usb-host/Cargo.toml @@ -15,7 +15,6 @@ default = ["aggressive_usb_reset"] libusb = ["libusb1-sys"] [dependencies] -ax-kspin.workspace = true bitflags = "2.8" crossbeam = {version = "0.8", features = ["alloc"], default-features = false} crossbeam-skiplist = {version = "0.1", features = [ diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/cmd.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/cmd.rs index 5eb8b2f207..27f0aca6bb 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/cmd.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/cmd.rs @@ -1,8 +1,7 @@ use alloc::sync::Arc; -use ax_kspin::SpinNoIrq as Mutex; use mbarrier::wmb; -use spin::RwLock; +use spin::{Mutex, RwLock}; use usb_if::err::TransferError; use xhci::{ registers::doorbell, diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/device.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/device.rs index c795ed559d..34ac735380 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/device.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/device.rs @@ -1,8 +1,8 @@ use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; -use ax_kspin::SpinNoIrq as Mutex; use futures::{FutureExt, future::BoxFuture}; use mbarrier::mb; +use spin::Mutex; use usb_if::{ descriptor::{ ConfigurationDescriptor, DescriptorType, DeviceDescriptor, DeviceDescriptorBase, diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs index f1fa8a7001..5c11453e58 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs @@ -1,8 +1,8 @@ use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; -use ax_kspin::SpinNoIrq as Mutex; use dma_api::DmaDirection; use mbarrier::mb; +use spin::Mutex; use usb_if::{ descriptor::{self, EndpointDescriptor}, endpoint::{RequestId, TransferCompletion, TransferRequest}, diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs index 745cc25279..2763ced048 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs @@ -1,8 +1,7 @@ use alloc::sync::Arc; use core::cell::UnsafeCell; -use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; -use spin::RwLock; +use spin::{Mutex, MutexGuard, RwLock}; use super::reg::{DisableIrqGuard, XhciRegisters}; @@ -34,6 +33,13 @@ impl IrqLock { } } + /// # Safety + /// + /// The caller must run from the xHCI interrupt/event path while no task + /// side `lock()` guard is alive. Task-side mutation uses `lock()`, which + /// disables the controller interrupter before taking the mutex. This + /// method deliberately avoids taking the mutex so the interrupt hot path + /// can only touch state that was pre-registered under that protocol. #[allow(clippy::mut_from_ref)] pub unsafe fn force_use(&self) -> &mut T { unsafe { &mut *self.data.get() } diff --git a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml index 87ec9f6e98..2e3d5a70a8 100644 --- a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml +++ b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml @@ -1,5 +1,9 @@ env = { UIMAGE = "y" } -features = ["sg2002", "myplat", "ax-feat/bus-mmio", "ax-feat/driver-cvsd"] +features = [ + "sg2002", + "myplat", + "ax-driver/cvsd", +] log = "Info" plat_dyn = false target = "riscv64gc-unknown-none-elf" diff --git a/os/StarryOS/configs/board/orangepi-5-plus.toml b/os/StarryOS/configs/board/orangepi-5-plus.toml index 4cd5081c07..d3d5a0aa93 100644 --- a/os/StarryOS/configs/board/orangepi-5-plus.toml +++ b/os/StarryOS/configs/board/orangepi-5-plus.toml @@ -3,16 +3,15 @@ target = "aarch64-unknown-none-softfloat" # Runtime U-Boot settings live in `orangepi-5-plus-uboot.toml`. env = {} features = [ - "ax-feat/bus-mmio", - "ax-feat/bus-pci", - "ax-feat/driver-sdmmc", + "ax-hal/plat-dyn", "starry-kernel/plat-dyn", - "axplat-dyn/pci-list-devices", - "axplat-dyn/realtek-rtl8125", - "axplat-dyn/rockchip-soc", - "axplat-dyn/rockchip-dwc-xhci", - "axplat-dyn/rockchip-sdhci", - "axplat-dyn/rockchip-dwmmc", + "ax-driver/pci-list-devices", + "ax-driver/rk3588-pcie", + "ax-driver/realtek-rtl8125", + "ax-driver/rockchip-soc", + "ax-driver/rockchip-dwc-xhci", + "ax-driver/rockchip-sdhci", + "ax-driver/rockchip-dwmmc", "rknpu", ] log = "Info" diff --git a/os/StarryOS/configs/board/qemu-aarch64-dyn.toml b/os/StarryOS/configs/board/qemu-aarch64-dyn.toml index fbb936b86f..d94a5bb8e3 100644 --- a/os/StarryOS/configs/board/qemu-aarch64-dyn.toml +++ b/os/StarryOS/configs/board/qemu-aarch64-dyn.toml @@ -1,10 +1,12 @@ env = {} features = [ + "ax-hal/plat-dyn", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", "starry-kernel/input", "starry-kernel/plat-dyn", - "starry-kernel/vsock", # auxilary features - "axplat-dyn/intel-net", - "axplat-dyn/virtio-net-pci", + "starry-kernel/vsock", + "ax-driver/intel-net", ] log = "Warn" plat_dyn = true diff --git a/os/StarryOS/configs/board/qemu-aarch64.toml b/os/StarryOS/configs/board/qemu-aarch64.toml index 8f670fb134..100371542c 100644 --- a/os/StarryOS/configs/board/qemu-aarch64.toml +++ b/os/StarryOS/configs/board/qemu-aarch64.toml @@ -1,6 +1,13 @@ env = {} features = [ + "ax-hal/aarch64-qemu-virt", "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/os/StarryOS/configs/board/qemu-loongarch64.toml b/os/StarryOS/configs/board/qemu-loongarch64.toml index 6c5d740359..4c35f73930 100644 --- a/os/StarryOS/configs/board/qemu-loongarch64.toml +++ b/os/StarryOS/configs/board/qemu-loongarch64.toml @@ -1,5 +1,14 @@ target = "loongarch64-unknown-none-softfloat" env = {} log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/os/StarryOS/configs/board/qemu-riscv64.toml b/os/StarryOS/configs/board/qemu-riscv64.toml index 5bbff72754..4902ae62f3 100644 --- a/os/StarryOS/configs/board/qemu-riscv64.toml +++ b/os/StarryOS/configs/board/qemu-riscv64.toml @@ -1,6 +1,13 @@ env = {} features = [ - "qemu", # auxilary features + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/os/StarryOS/configs/board/qemu-x86_64.toml b/os/StarryOS/configs/board/qemu-x86_64.toml index ffb6581a2a..7efd9aa1bd 100644 --- a/os/StarryOS/configs/board/qemu-x86_64.toml +++ b/os/StarryOS/configs/board/qemu-x86_64.toml @@ -1,5 +1,14 @@ target = "x86_64-unknown-none" env = {} log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 66cd458835..64ac6c5bf1 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -16,12 +16,17 @@ license.workspace = true [features] default = ["dynamic_debug"] dev-log = [] -ext4 = ["ax-fs-ng/ext4"] +ext4 = ["ax-fs/ext4"] input = ["dep:ax-input", "ax-feat/input"] kcov = ["ax-hal/starry-kcov"] memtrack = ["ax-feat/dwarf", "ax-alloc/tracking", "dep:gimli"] -rknpu = ["dep:axplat-dyn", "axplat-dyn/rknpu"] -plat-dyn = ["dep:axplat-dyn", "axplat-dyn/usb", "dep:crab-usb", "dep:rdrive"] +rknpu = ["dep:ax-driver", "ax-driver/rknpu"] +plat-dyn = [ + "dep:ax-driver", + "ax-driver/usb", + "dep:crab-usb", + "dep:rdrive", +] vsock = ["ax-feat/vsock"] dynamic_debug = ["ax-feat/ipi"] sg2002 = [ @@ -55,14 +60,14 @@ ax-feat = { workspace = true, features = [ ax-alloc = { workspace = true, features = ["default"] } ax-config.workspace = true ax-display.workspace = true -ax-driver.workspace = true -ax-fs-ng.workspace = true +ax-fs = { version = "0.5.15", path = "../../arceos/modules/axfs-ng", package = "ax-fs-ng" } ax-hal.workspace = true ax-input = { workspace = true, optional = true } ax-lazyinit.workspace = true ax-log.workspace = true ax-mm.workspace = true -ax-net-ng.workspace = true +axnet = { version = "0.6.1", path = "../../arceos/modules/axnet-ng", package = "ax-net-ng" } +ax-driver = { workspace = true, optional = true } axplat-dyn = { workspace = true, optional = true } ax-runtime.workspace = true ax-sync.workspace = true diff --git a/os/StarryOS/kernel/src/lib.rs b/os/StarryOS/kernel/src/lib.rs index e6707d7db9..10295f3eaf 100644 --- a/os/StarryOS/kernel/src/lib.rs +++ b/os/StarryOS/kernel/src/lib.rs @@ -7,8 +7,6 @@ #![allow(clippy::not_unsafe_ptr_arg_deref)] extern crate alloc; -extern crate ax_fs_ng as ax_fs; -extern crate ax_net_ng as axnet; extern crate ax_runtime; #[macro_use] diff --git a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs index fe7fb4a1f1..66ff22ffd8 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs @@ -7,13 +7,11 @@ use core::{ task::Context, }; +use ax_driver::rknpu::{self, RknpuAction, RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit}; use ax_errno::{AxError, AxResult}; use ax_hal::mem::virt_to_phys; use ax_memory_addr::PhysAddrRange; use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; -use axplat_dyn::rknpu::{ - self, RknpuAction, RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit, -}; use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::O_CLOEXEC; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/event.rs b/os/StarryOS/kernel/src/pseudofs/dev/event.rs index be8f8035b5..1d3bc2fc86 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/event.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/event.rs @@ -16,13 +16,9 @@ pub fn input_device_count() -> u32 { EVENT_DEVICE_COUNT.load(Ordering::Acquire) } -#[allow(unused_imports)] -use ax_driver::prelude::{ - AbsInfo, AxInputDevice, BaseDriverOps, DevError, Event, EventType, InputDeviceId, - InputDriverOps, -}; use ax_errno::{AxError, AxResult}; use ax_hal::time::wall_time; +use ax_input::{ErasedInputDevice, Event, EventType, InputDevice, InputDeviceId, InputError}; use ax_sync::Mutex; use axfs_ng_vfs::{DeviceId, NodeFlags, NodeType, VfsResult}; use axpoll::{IoEvents, Pollable}; @@ -47,14 +43,14 @@ const KEY_CNT: usize = EventType::Key.bits_count(); const READ_AHEAD_CAP: usize = 256; struct Inner { - device: AxInputDevice, + device: ErasedInputDevice, read_ahead: VecDeque<(Duration, Event)>, key_state: Bitmap, } impl Inner { /// Drain everything the driver currently has buffered into `read_ahead`, /// updating cached key state along the way. Stops at the first - /// `DevError::Again` (driver queue empty) or after a hard ceiling of + /// `InputError::Again` (driver queue empty) or after a hard ceiling of /// `READ_AHEAD_CAP` pulls per call to bound a single pass. /// /// Returns `true` if at least one event is now queued for userspace. @@ -78,7 +74,7 @@ impl Inner { } self.read_ahead.push_back((wall_time(), event)); } - Err(DevError::Again) => break, + Err(InputError::Again) => break, Err(err) => { warn!("Failed to read event: {err:?}"); break; @@ -142,7 +138,7 @@ pub struct EventDev { } impl EventDev { - pub fn new(mut device: AxInputDevice) -> Self { + pub fn new(mut device: ErasedInputDevice) -> Self { let mut ev_bits = Bitmap::new(); for i in 0..EventType::COUNT { let Some(ty) = EventType::from_repr(i) else { @@ -229,15 +225,15 @@ fn return_str(arg: usize, size: usize, s: &str) -> AxResult { Ok(copy_bytes(s.as_bytes(), slice)) } -fn dev_error_to_ax_error(err: DevError) -> AxError { +fn input_error_to_ax_error(err: InputError) -> AxError { match err { - DevError::AlreadyExists => AxError::AlreadyExists, - DevError::Again => AxError::WouldBlock, - DevError::BadState => AxError::BadState, - DevError::InvalidParam | DevError::Unsupported => AxError::InvalidInput, - DevError::Io => AxError::Io, - DevError::NoMemory => AxError::NoMemory, - DevError::ResourceBusy => AxError::ResourceBusy, + InputError::AlreadyExists => AxError::AlreadyExists, + InputError::Again => AxError::WouldBlock, + InputError::BadState => AxError::BadState, + InputError::InvalidInput | InputError::Unsupported => AxError::InvalidInput, + InputError::Io => AxError::Io, + InputError::NoMemory => AxError::NoMemory, + InputError::ResourceBusy => AxError::ResourceBusy, } } @@ -359,11 +355,7 @@ impl DeviceOps for EventDev { match nr { // EVIOCGNAME 0x06 => { - return return_str( - arg, - size, - self.inner.lock().device.device_name(), - ); + return return_str(arg, size, self.inner.lock().device.name()); } // EVIOCGPHYS 0x07 => { @@ -432,15 +424,15 @@ impl DeviceOps for EventDev { } let info = match self.inner.lock().device.get_abs_info(axis) { Ok(info) => info, - Err(err) => return Err(dev_error_to_ax_error(err)), + Err(err) => return Err(input_error_to_ax_error(err)), }; let abs = InputAbsInfo { value: 0, - minimum: info.min as i32, - maximum: info.max as i32, - fuzz: info.fuzz as i32, - flat: info.flat as i32, - resolution: info.res as i32, + minimum: info.min, + maximum: info.max, + fuzz: info.fuzz, + flat: info.flat, + resolution: info.res, }; let bytes = abs.as_bytes(); let slice = UserPtr::::from(arg).get_as_mut_slice(size)?; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/fb.rs b/os/StarryOS/kernel/src/pseudofs/dev/fb.rs index 3d19a17fe9..0bf8df5502 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/fb.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/fb.rs @@ -1,7 +1,5 @@ use core::{any::Any, slice}; -#[allow(unused_imports)] -use ax_driver::prelude::DisplayDriverOps; use ax_errno::AxError; use ax_hal::mem::virt_to_phys; use ax_memory_addr::{PhysAddrRange, VirtAddr}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/loop.rs b/os/StarryOS/kernel/src/pseudofs/dev/loop.rs index b6d7172f27..a1f9e5a1cf 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/loop.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/loop.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "ext4")] -use alloc::{boxed::Box, sync::Arc}; use core::{ any::Any, sync::atomic::{AtomicBool, AtomicU32, Ordering}, @@ -7,8 +5,6 @@ use core::{ use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::FileBackend; -#[cfg(feature = "ext4")] -use ax_kspin::SpinNoIrq; use ax_sync::Mutex; use axfs_ng_vfs::{DeviceId, NodeFlags, VfsResult}; use linux_raw_sys::{ @@ -24,6 +20,8 @@ use linux_raw_sys::{ }; use starry_vm::{VmMutPtr, VmPtr}; +#[cfg(feature = "ext4")] +use super::loop_block::BlockCache; use crate::{ file::{FileLike, get_file_like}, pseudofs::{DeviceMmap, DeviceOps}, @@ -33,214 +31,6 @@ use crate::{ /// Not defined in linux-raw-sys, so we use the standard value directly. const HDIO_GETGEO: u32 = 0x0301; -#[cfg(feature = "ext4")] -const CACHE_BLK: usize = 4096; - -#[cfg(feature = "ext4")] -fn writeback_buffer(file: &FileBackend, cd: &CacheData) -> bool { - let total = cd.total_len; - let nchunks = cd.blocks.lock().len(); - let mut offset: usize = 0; - for i in 0..nchunks { - let mut buf = [0u8; CACHE_BLK]; - let to_write = { - let guard = cd.blocks.lock(); - let Some(chunk) = guard.get(i) else { break }; - let n = chunk.len().min(total.saturating_sub(offset)); - buf[..n].copy_from_slice(&chunk[..n]); - n - }; - if to_write == 0 { - break; - } - let mut written = 0usize; - while written < to_write { - match file.write_at(&buf[written..to_write], (offset + written) as u64) { - Ok(0) => { - warn!("LoopDevice: writeback stalled at {}", offset + written); - return false; - } - Ok(w) => written += w, - Err(e) => { - warn!("LoopDevice: writeback err at {}: {e:?}", offset + written); - return false; - } - } - } - offset += to_write; - } - if let Err(e) = file.sync(true) { - warn!("LoopDevice: writeback sync failed: {e:?}"); - return false; - } - true -} - -/// Shared cache data backing a `LoopBlockDevice`. -/// -/// Owning an `Arc` keeps the buffer alive even if the -/// `LoopDevice` replaces its cache slot (e.g. on re-mount). -/// -/// The buffer is protected by `SpinNoIrq` because ext4 may call -/// `read_block`/`write_block` under filesystem locks that already disable IRQs. -/// Write-back copies each chunk out under this short guard, then performs VFS -/// I/O after dropping it. -#[cfg(feature = "ext4")] -struct CacheData { - blocks: SpinNoIrq>>, - total_len: usize, - dirty: AtomicBool, - /// `true` while a `LoopBlockDevice` referencing this cache is alive. - mounted: AtomicBool, -} - -#[cfg(feature = "ext4")] -impl CacheData { - fn new(blocks: alloc::vec::Vec>, total_len: usize) -> Self { - Self { - blocks: SpinNoIrq::new(blocks), - total_len, - dirty: AtomicBool::new(false), - mounted: AtomicBool::new(false), - } - } -} - -/// Adapter that wraps a LoopDevice's file backend as a block device. -/// -/// This allows ext4 (or other filesystem drivers) to use a loop device as -/// backing storage by converting offset-based I/O to block-based I/O. -/// -/// The adapter holds an `Arc` so the buffer remains valid even if -/// the `LoopDevice`'s cache slot is later replaced (e.g. on re-mount while -/// old filesystem references still exist). The old adapter keeps its buffer -/// alive through the Arc — no use-after-free. -/// -/// Write-back happens in three places: -/// - `LOOP_CLR_FD` (losetup -d): writes back and clears the cache -/// - `as_dyn_block_device()` (re-mount): writes back before re-reading -/// - `BLKFLSBUF` ioctl: explicit flush -/// -/// All three run in normal syscall context where VFS I/O is safe. -/// The `flush()` callback is intentionally a no-op because filesystem block -/// I/O paths must not re-enter backing-file VFS writeback. -#[cfg(feature = "ext4")] -pub struct LoopBlockDevice { - cache: Arc, - block_size: usize, - ro: bool, -} - -#[cfg(feature = "ext4")] -impl LoopBlockDevice { - /// Create a new block device adapter backed by the given `CacheData`. - fn new(cache: Arc, ro: bool) -> VfsResult { - cache.mounted.store(true, Ordering::Release); - Ok(Self { - cache, - block_size: 512, - ro, - }) - } -} - -#[cfg(feature = "ext4")] -impl Drop for LoopBlockDevice { - fn drop(&mut self) { - self.cache.mounted.store(false, Ordering::Release); - } -} - -#[cfg(feature = "ext4")] -impl ax_driver::prelude::BaseDriverOps for LoopBlockDevice { - fn device_name(&self) -> &str { - "loop" - } - - fn device_type(&self) -> ax_driver::prelude::DeviceType { - ax_driver::prelude::DeviceType::Block - } -} - -#[cfg(feature = "ext4")] -impl ax_driver::prelude::BlockDriverOps for LoopBlockDevice { - fn num_blocks(&self) -> u64 { - self.cache.total_len as u64 / self.block_size as u64 - } - - fn block_size(&self) -> usize { - self.block_size - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> ax_driver::prelude::DevResult { - let byte_off = block_id as usize * self.block_size; - if byte_off - .checked_add(buf.len()) - .is_none_or(|end| end > self.cache.total_len) - { - return Err(ax_driver::prelude::DevError::Io); - } - let blocks = self.cache.blocks.lock(); - let mut pos = 0; - let mut cur = byte_off; - while pos < buf.len() { - let idx = cur / CACHE_BLK; - let off = cur % CACHE_BLK; - let Some(chunk) = blocks.get(idx) else { - return Err(ax_driver::prelude::DevError::Io); - }; - let to_copy = (buf.len() - pos).min(CACHE_BLK - off); - buf[pos..pos + to_copy].copy_from_slice(&chunk[off..off + to_copy]); - pos += to_copy; - cur += to_copy; - } - Ok(()) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> ax_driver::prelude::DevResult { - if self.ro { - return Err(ax_driver::prelude::DevError::Io); - } - let byte_off = block_id as usize * self.block_size; - if byte_off - .checked_add(buf.len()) - .is_none_or(|end| end > self.cache.total_len) - { - return Err(ax_driver::prelude::DevError::Io); - } - let mut blocks = self.cache.blocks.lock(); - let mut pos = 0; - let mut cur = byte_off; - while pos < buf.len() { - let idx = cur / CACHE_BLK; - let off = cur % CACHE_BLK; - let Some(chunk) = blocks.get_mut(idx) else { - return Err(ax_driver::prelude::DevError::Io); - }; - let to_copy = (buf.len() - pos).min(CACHE_BLK - off); - chunk[off..off + to_copy].copy_from_slice(&buf[pos..pos + to_copy]); - pos += to_copy; - cur += to_copy; - } - self.cache.dirty.store(true, Ordering::Release); - Ok(()) - } - - fn flush(&mut self) -> ax_driver::prelude::DevResult { - // Intentionally a no-op. Filesystem block I/O paths must not re-enter - // backing-file VFS writeback. Dirty data is written back in LOOP_CLR_FD - // (losetup -d), BLKFLSBUF, or after umount, all in normal syscall - // context. - Ok(()) - } -} - -/// Block-device data cache owned by the LoopDevice. -#[cfg(feature = "ext4")] -struct BlockCache { - data: Option>, -} - /// /dev/loopX devices pub struct LoopDevice { number: u32, @@ -260,7 +50,7 @@ pub struct LoopDevice { /// Block-device data cache. Populated by `as_dyn_block_device()`, /// written back and cleared by `LOOP_CLR_FD`. #[cfg(feature = "ext4")] - block_cache: Mutex, + pub(super) block_cache: Mutex, } impl LoopDevice { @@ -275,7 +65,7 @@ impl LoopDevice { flags: AtomicU32::new(0), exclusive: AtomicBool::new(false), #[cfg(feature = "ext4")] - block_cache: Mutex::new(BlockCache { data: None }), + block_cache: Mutex::new(BlockCache::new()), } } @@ -330,107 +120,6 @@ impl LoopDevice { let file = self.file.lock().clone(); file.ok_or(AxError::from(LinuxError::ENXIO)) } - - /// Create a boxed block device adapter from this loop device. - /// - /// Returns `Err` if no file is attached to the loop device. - /// - /// If a previous `CacheData` still has outstanding `Arc` references from - /// an old (unmounted) filesystem, those references keep the old buffer - /// alive — no use-after-free. The new adapter gets a fresh `CacheData` - /// re-read from the backing file. - #[cfg(feature = "ext4")] - pub fn as_dyn_block_device(&self) -> VfsResult> { - let file = self.file.lock().clone(); - let file = file.ok_or(AxError::from(LinuxError::ENXIO))?; - let len = file.location().len().unwrap_or(0) as usize; - - // Reject re-mount if an existing CacheData is still actively mounted. - { - let mut cache = self.block_cache.lock(); - if let Some(ref cd) = cache.data { - if cd.mounted.load(Ordering::Acquire) { - return Err(AxError::from(LinuxError::EBUSY)); - } - if cd.dirty.swap(false, Ordering::AcqRel) { - if self.ro.load(Ordering::Relaxed) { - cd.dirty.store(true, Ordering::Release); - return Err(AxError::Io); - } - if !writeback_buffer(&file, cd) { - cd.dirty.store(true, Ordering::Release); - return Err(AxError::Io); - } - } - } - cache.data = None; - } - - // Read the backing file in CACHE_BLK-sized chunks. Each individual - // allocation is only 4 KiB, small enough to satisfy even when physical - // memory is fragmented after many mount/umount cycles. - let num_chunks = if len == 0 { - 0 - } else { - (len - 1) / CACHE_BLK + 1 - }; - let mut chunks = alloc::vec::Vec::with_capacity(num_chunks); - let mut offset: usize = 0; - for _ in 0..num_chunks { - let to_read = CACHE_BLK.min(len - offset); - let mut chunk = alloc::vec![0u8; CACHE_BLK]; - let n = file.read_at(&mut chunk[..to_read], offset as u64)?; - if n != to_read { - warn!("LoopDevice: short read {n}/{to_read} at offset {offset}"); - return Err(AxError::Io); - } - chunks.push(chunk); - offset += to_read; - } - - let mut cache = self.block_cache.lock(); - let cd = Arc::new(CacheData::new(chunks, len)); - cache.data = Some(cd.clone()); - drop(cache); - - Ok(Box::new(LoopBlockDevice::new( - cd, - self.ro.load(Ordering::Relaxed), - )?)) - } - - /// Write back dirty block-cache data to the backing file. - /// - /// Called after `unmount` in normal syscall context where VFS I/O is - /// safe, so that data is persisted without requiring an explicit - /// `losetup -d` or `BLKFLSBUF`. - /// - /// Returns `Err(AxError::Io)` if writeback fails, so that callers - /// (`sys_umount2`) can propagate the error to userspace. On failure - /// the dirty flag is preserved so that subsequent flush attempts - /// (BLKFLSBUF, LOOP_CLR_FD) can retry. - #[cfg(feature = "ext4")] - pub fn flush_cache_to_file(&self) -> AxResult<()> { - let file = self.file.lock().clone(); - let cache = self.block_cache.lock(); - if let Some(ref cd) = cache.data { - let mut wb_err = false; - if cd.dirty.swap(false, Ordering::AcqRel) - && let Some(ref file) = file - { - let writeback_ok = !self.ro.load(Ordering::Relaxed) && writeback_buffer(file, cd); - if !writeback_ok { - cd.dirty.store(true, Ordering::Release); - wb_err = true; - } - } - cd.mounted.store(false, Ordering::Release); - if wb_err { - return Err(AxError::Io); - } - } - Ok(()) - } } impl DeviceOps for LoopDevice { @@ -502,35 +191,7 @@ impl DeviceOps for LoopDevice { // (page cache updates) is safe. The cache lock ensures no // concurrent write_block() can race. #[cfg(feature = "ext4")] - { - let cache = self.block_cache.lock(); - // If a LoopBlockDevice is still alive (mounted), refuse to - // detach — the old Arc would keep receiving - // write_block calls but the backing file would be gone, - // causing silent data loss on umount. - if let Some(ref cd) = cache.data - && cd.mounted.load(Ordering::Acquire) - { - return Err(AxError::from(LinuxError::EBUSY)); - } - - if let Some(ref cd) = cache.data - && cd.dirty.load(Ordering::Acquire) - { - if self.ro.load(Ordering::Relaxed) { - return Err(AxError::Io); - } - if let Some(ref file) = *guard - && !writeback_buffer(file, cd) - { - warn!("LoopDevice: writeback failed on LOOP_CLR_FD, data may be lost"); - return Err(AxError::Io); - } - cd.dirty.store(false, Ordering::Release); - } - drop(cache); - self.block_cache.lock().data = None; - } + self.detach_block_cache(guard.as_ref())?; *guard = None; *self.file_name.lock() = [0u8; 64]; @@ -540,7 +201,7 @@ impl DeviceOps for LoopDevice { (arg as *mut loop_info).vm_write(self.get_info()?)?; } LOOP_SET_STATUS => { - // FIXME: AnyBitPattern + // `loop_info` is a C ioctl payload copied from the guest ABI. let info = unsafe { (arg as *const loop_info).vm_read_uninit()?.assume_init() }; self.set_info(info)?; let mut name = self.file_name.lock(); @@ -558,13 +219,13 @@ impl DeviceOps for LoopDevice { (arg as *mut loop_info64).vm_write(self.get_info64()?)?; } LOOP_SET_STATUS64 => { - // FIXME: AnyBitPattern + // `loop_info64` is a C ioctl payload copied from the guest ABI. let info = unsafe { (arg as *const loop_info64).vm_read_uninit()?.assume_init() }; *self.file_name.lock() = info.lo_file_name; self.set_lo_flags(info.lo_flags); } LOOP_CONFIGURE => { - // FIXME: AnyBitPattern + // `loop_config` is a C ioctl payload copied from the guest ABI. let cfg = unsafe { (arg as *const loop_config).vm_read_uninit()?.assume_init() }; let fd = cfg.fd as i32; if fd < 0 { @@ -587,7 +248,6 @@ impl DeviceOps for LoopDevice { } self.set_lo_flags(flags); } - // TODO: the following should apply to any block devices BLKGETSIZE | BLKGETSIZE64 => { let sectors = if let Ok(f) = self.clone_file() { f.location().len()? / 512 @@ -646,24 +306,7 @@ impl DeviceOps for LoopDevice { // write_block() will re-set dirty=true after our snapshot, // guaranteeing its data is flushed on the next attempt. #[cfg(feature = "ext4")] - { - let file = self.file.lock().clone(); - let cache = self.block_cache.lock(); - if let Some(ref cd) = cache.data - && let Some(ref file) = file - { - if self.ro.load(Ordering::Relaxed) { - return Err(AxError::Io); - } - if !cd.dirty.swap(false, Ordering::AcqRel) { - return Ok(0); - } - if !writeback_buffer(file, cd) { - cd.dirty.store(true, Ordering::Release); - return Err(AxError::Io); - } - } - } + self.flush_block_cache_ioctl()?; } BLKIOMIN => { // minimum I/O size diff --git a/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs b/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs new file mode 100644 index 0000000000..76d14f429b --- /dev/null +++ b/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs @@ -0,0 +1,306 @@ +use alloc::{boxed::Box, sync::Arc}; +use core::sync::atomic::{AtomicBool, Ordering}; + +use ax_errno::{AxError, AxResult, LinuxError}; +use ax_fs::FileBackend; +use ax_kspin::SpinNoIrq; +use axfs_ng_vfs::VfsResult; + +use super::r#loop::LoopDevice; + +const CACHE_BLK: usize = 4096; + +pub(super) struct BlockCache { + data: Option>, +} + +impl BlockCache { + pub(super) const fn new() -> Self { + Self { data: None } + } +} + +struct CacheData { + blocks: SpinNoIrq>>, + total_len: usize, + dirty: AtomicBool, + mounted: AtomicBool, +} + +impl CacheData { + fn new(blocks: alloc::vec::Vec>, total_len: usize) -> Self { + Self { + blocks: SpinNoIrq::new(blocks), + total_len, + dirty: AtomicBool::new(false), + mounted: AtomicBool::new(false), + } + } +} + +struct LoopBlockDevice { + cache: Arc, + block_size: usize, + ro: bool, +} + +impl LoopBlockDevice { + fn new(cache: Arc, ro: bool) -> VfsResult { + cache.mounted.store(true, Ordering::Release); + Ok(Self { + cache, + block_size: 512, + ro, + }) + } +} + +impl Drop for LoopBlockDevice { + fn drop(&mut self) { + self.cache.mounted.store(false, Ordering::Release); + } +} + +impl ax_fs::FsBlockDevice for LoopBlockDevice { + fn name(&self) -> &str { + "loop" + } + + fn num_blocks(&self) -> u64 { + self.cache.total_len as u64 / self.block_size as u64 + } + + fn block_size(&self) -> usize { + self.block_size + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { + let byte_off = block_id as usize * self.block_size; + if byte_off + .checked_add(buf.len()) + .is_none_or(|end| end > self.cache.total_len) + { + return Err(AxError::Io); + } + let blocks = self.cache.blocks.lock(); + let mut pos = 0; + let mut cur = byte_off; + while pos < buf.len() { + let idx = cur / CACHE_BLK; + let off = cur % CACHE_BLK; + let Some(chunk) = blocks.get(idx) else { + return Err(AxError::Io); + }; + let to_copy = (buf.len() - pos).min(CACHE_BLK - off); + buf[pos..pos + to_copy].copy_from_slice(&chunk[off..off + to_copy]); + pos += to_copy; + cur += to_copy; + } + Ok(()) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { + if self.ro { + return Err(AxError::Io); + } + let byte_off = block_id as usize * self.block_size; + if byte_off + .checked_add(buf.len()) + .is_none_or(|end| end > self.cache.total_len) + { + return Err(AxError::Io); + } + let mut blocks = self.cache.blocks.lock(); + let mut pos = 0; + let mut cur = byte_off; + while pos < buf.len() { + let idx = cur / CACHE_BLK; + let off = cur % CACHE_BLK; + let Some(chunk) = blocks.get_mut(idx) else { + return Err(AxError::Io); + }; + let to_copy = (buf.len() - pos).min(CACHE_BLK - off); + chunk[off..off + to_copy].copy_from_slice(&buf[pos..pos + to_copy]); + pos += to_copy; + cur += to_copy; + } + self.cache.dirty.store(true, Ordering::Release); + Ok(()) + } + + fn flush(&mut self) -> AxResult { + Ok(()) + } +} + +impl LoopDevice { + pub fn as_dyn_block_device(&self) -> VfsResult> { + let file = self.file.lock().clone(); + let file = file.ok_or(AxError::from(LinuxError::ENXIO))?; + let len = file.location().len().unwrap_or(0) as usize; + + { + let mut cache = self.block_cache.lock(); + if let Some(ref cd) = cache.data { + if cd.mounted.load(Ordering::Acquire) { + return Err(AxError::from(LinuxError::EBUSY)); + } + if cd.dirty.swap(false, Ordering::AcqRel) { + if self.ro.load(Ordering::Relaxed) { + cd.dirty.store(true, Ordering::Release); + return Err(AxError::Io); + } + if !writeback_buffer(&file, cd) { + cd.dirty.store(true, Ordering::Release); + return Err(AxError::Io); + } + } + } + cache.data = None; + } + + let chunks = read_file_chunks(&file, len)?; + let mut cache = self.block_cache.lock(); + let cd = Arc::new(CacheData::new(chunks, len)); + cache.data = Some(cd.clone()); + drop(cache); + + Ok(Box::new(LoopBlockDevice::new( + cd, + self.ro.load(Ordering::Relaxed), + )?)) + } + + pub fn flush_cache_to_file(&self) -> AxResult<()> { + let file = self.file.lock().clone(); + let cache = self.block_cache.lock(); + if let Some(ref cd) = cache.data { + let mut wb_err = false; + if cd.dirty.swap(false, Ordering::AcqRel) + && let Some(ref file) = file + { + let writeback_ok = !self.ro.load(Ordering::Relaxed) && writeback_buffer(file, cd); + if !writeback_ok { + cd.dirty.store(true, Ordering::Release); + wb_err = true; + } + } + cd.mounted.store(false, Ordering::Release); + if wb_err { + return Err(AxError::Io); + } + } + Ok(()) + } + + pub(super) fn detach_block_cache(&self, file: Option<&FileBackend>) -> AxResult<()> { + let cache = self.block_cache.lock(); + if let Some(ref cd) = cache.data + && cd.mounted.load(Ordering::Acquire) + { + return Err(AxError::from(LinuxError::EBUSY)); + } + + if let Some(ref cd) = cache.data + && cd.dirty.load(Ordering::Acquire) + { + if self.ro.load(Ordering::Relaxed) { + return Err(AxError::Io); + } + if let Some(file) = file + && !writeback_buffer(file, cd) + { + warn!("LoopDevice: writeback failed on LOOP_CLR_FD, data may be lost"); + return Err(AxError::Io); + } + cd.dirty.store(false, Ordering::Release); + } + drop(cache); + self.block_cache.lock().data = None; + Ok(()) + } + + pub(super) fn flush_block_cache_ioctl(&self) -> AxResult<()> { + let file = self.file.lock().clone(); + let cache = self.block_cache.lock(); + if let Some(ref cd) = cache.data + && let Some(ref file) = file + { + if self.ro.load(Ordering::Relaxed) { + return Err(AxError::Io); + } + if !cd.dirty.swap(false, Ordering::AcqRel) { + return Ok(()); + } + if !writeback_buffer(file, cd) { + cd.dirty.store(true, Ordering::Release); + return Err(AxError::Io); + } + } + Ok(()) + } +} + +fn read_file_chunks( + file: &FileBackend, + len: usize, +) -> VfsResult>> { + let num_chunks = if len == 0 { + 0 + } else { + (len - 1) / CACHE_BLK + 1 + }; + let mut chunks = alloc::vec::Vec::with_capacity(num_chunks); + let mut offset: usize = 0; + for _ in 0..num_chunks { + let to_read = CACHE_BLK.min(len - offset); + let mut chunk = alloc::vec![0u8; CACHE_BLK]; + let n = file.read_at(&mut chunk[..to_read], offset as u64)?; + if n != to_read { + warn!("LoopDevice: short read {n}/{to_read} at offset {offset}"); + return Err(AxError::Io); + } + chunks.push(chunk); + offset += to_read; + } + Ok(chunks) +} + +fn writeback_buffer(file: &FileBackend, cd: &CacheData) -> bool { + let total = cd.total_len; + let nchunks = cd.blocks.lock().len(); + let mut offset: usize = 0; + for i in 0..nchunks { + let mut buf = [0u8; CACHE_BLK]; + let to_write = { + let guard = cd.blocks.lock(); + let Some(chunk) = guard.get(i) else { break }; + let n = chunk.len().min(total.saturating_sub(offset)); + buf[..n].copy_from_slice(&chunk[..n]); + n + }; + if to_write == 0 { + break; + } + let mut written = 0usize; + while written < to_write { + match file.write_at(&buf[written..to_write], (offset + written) as u64) { + Ok(0) => { + warn!("LoopDevice: writeback stalled at {}", offset + written); + return false; + } + Ok(w) => written += w, + Err(e) => { + warn!("LoopDevice: writeback err at {}: {e:?}", offset + written); + return false; + } + } + } + offset += to_write; + } + if let Err(e) = file.sync(true) { + warn!("LoopDevice: writeback sync failed: {e:?}"); + return false; + } + true +} diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index d776f828c9..8c2ab6448e 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -13,6 +13,8 @@ mod fb; mod log; mod r#loop; #[cfg(feature = "ext4")] +mod loop_block; +#[cfg(feature = "ext4")] pub use r#loop::LoopDevice; #[cfg(feature = "sg2002")] pub mod ion; diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs index e183e752ab..a783ed9e56 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs @@ -416,8 +416,7 @@ impl UsbFsManager { }; for (device_id, bus_num) in pending_hosts { - let host = match rdrive::get::(device_id) - { + let host = match rdrive::get::(device_id) { Ok(host) => host, Err(err) => { warn!( @@ -702,7 +701,7 @@ impl UsbFsManager { #[cfg(target_os = "none")] fn open_device(&self, host_device_id: RDriveDeviceId, info: &DeviceInfo) -> AxResult { - let host = rdrive::get::(host_device_id) + let host = rdrive::get::(host_device_id) .map_err(|_| AxError::NoSuchDevice)?; let mut guard = host.lock().map_err(|_| AxError::ResourceBusy)?; ax_task::future::block_on(guard.host_mut().open_device(info)).map_err(|err| { @@ -724,7 +723,7 @@ impl UsbFsManager { #[cfg(target_os = "none")] fn refresh_host(&self, host_device_id: RDriveDeviceId, bus_num: u8) -> AxResult<()> { - let host = rdrive::get::(host_device_id) + let host = rdrive::get::(host_device_id) .map_err(|_| AxError::NoSuchDevice)?; let mut guard = host.lock().map_err(|_| AxError::ResourceBusy)?; let devices = @@ -1176,7 +1175,7 @@ pub(super) fn initialize_hosts(manager: &UsbFsManager) -> usize { let mut failed_device_ids = Vec::new(); for (device_id, bus_num, irq_num) in hosts { - let host = match rdrive::get::(device_id) { + let host = match rdrive::get::(device_id) { Ok(host) => host, Err(err) => { warn!( @@ -1320,7 +1319,7 @@ pub(super) fn discover_hosts() -> (Vec, Vec) { } #[cfg(target_os = "none")] { - let hosts = rdrive::get_list::(); + let hosts = rdrive::get_list::(); let mut initialized_hosts = Vec::new(); let mut irq_slots = Vec::new(); diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs index 39302026c9..c14cd77dc6 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs @@ -98,7 +98,7 @@ pub(crate) fn open_usbfs_file( lease: BlockingMutex::new(None), lifecycle_lock: BlockingMutex::new(()), claimed_interfaces: Mutex::new(Default::default()), - submitted_urbs: Arc::new(Mutex::new(VecDeque::new())), + submitted_urbs: Arc::new(BlockingMutex::new(VecDeque::new())), pending_urbs: Arc::new(Mutex::new(VecDeque::new())), poll_urbs: Arc::new(PollSet::new()), urb_worker: Arc::new(UrbWorker::new()), @@ -117,7 +117,7 @@ struct UsbDeviceFile { lease: BlockingMutex>>, lifecycle_lock: BlockingMutex<()>, claimed_interfaces: Mutex>, - submitted_urbs: Arc>>, + submitted_urbs: Arc>>, pending_urbs: Arc>>, poll_urbs: Arc, urb_worker: Arc, diff --git a/os/StarryOS/kernel/src/syscall/fs/mount.rs b/os/StarryOS/kernel/src/syscall/fs/mount.rs index 889784b1a9..4c1432b90d 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mount.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mount.rs @@ -153,8 +153,6 @@ pub fn sys_mount( fn mount_ext4(source: &str, target: &str, readonly: bool) -> AxResult<()> { use alloc::{boxed::Box, sync::Arc}; - use ax_driver::prelude::BlockDriverOps; - let ctx = FS_CONTEXT.lock(); // Resolve source device path (e.g., "/dev/loop0") to a block device @@ -182,7 +180,7 @@ fn mount_ext4(source: &str, target: &str, readonly: bool) -> AxResult<()> { })?; let num_blocks = block_dev.num_blocks(); - let region = ax_driver::PartitionRegion::from_num_blocks(num_blocks); + let region = ax_fs::BlockRegion::from_num_blocks(num_blocks); // Create ext4 filesystem from the dynamic block device let fs = ax_fs::new_filesystem_from_dyn(block_dev, region).map_err(|e| { diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index 73384c1cee..1171a16c9f 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -16,21 +16,22 @@ version = "0.5.12" [features] default = [] kcov = ["starry-kernel/kcov"] +myplat = ["ax-feat/myplat"] qemu = [ "ax-feat/defplat", "ax-feat/irq", "ax-feat/rtc", - "ax-feat/bus-pci", "ax-feat/display", "starry-kernel/input", "starry-kernel/vsock", # auxilary features - "axplat-dyn?/rtc", - "axplat-dyn?/intel-net", - "axplat-dyn?/virtio-net-pci", ] smp = ["ax-feat/smp", "axplat-dyn?/smp", "axplat-riscv64-visionfive2?/smp"] -sg2002 = ["ax-plat-riscv64-sg2002", "ax-feat/driver-cvsd", "starry-kernel/sg2002"] +sg2002 = [ + "dep:ax-plat-riscv64-sg2002", + "starry-kernel/sg2002", +] + # Enable the GICv3 distributor + redistributor + system-register # CPU interface on aarch64. Only meaningful when the target is the # QEMU aarch64 virt platform; on other targets it is a no-op @@ -48,8 +49,8 @@ cntv-timer = ["ax-feat/aarch64-cntv-timer"] # matching axconfig override is `devices.timer-irq=27`. aarch64-hvf = ["gic-v3", "cntv-timer"] -rknpu = ["dep:axplat-dyn", "starry-kernel/rknpu", "axplat-dyn/rknpu"] -vf2 = ["dep:axplat-riscv64-visionfive2", "ax-feat/driver-sdmmc"] +rknpu = ["ax-driver/rknpu", "starry-kernel/rknpu"] +vf2 = ["dep:axplat-riscv64-visionfive2"] [[bin]] name = "starryos" @@ -61,13 +62,14 @@ path = "xtask/main.rs" [dependencies] ax-feat = { workspace = true, features = ["fs-ng-times", "irq"] } +ax-driver.workspace = true +ax-hal.workspace = true axplat-dyn = { workspace = true, optional = true } starry-kernel = { workspace = true, features = ["dev-log", "ext4"] } -[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies.ax-plat-riscv64-sg2002] -workspace = true -features = ["fp-simd", "irq", "rtc"] -optional = true +[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] +ax-plat-riscv64-sg2002 = { workspace = true, features = ["fp-simd", "irq", "rtc"], optional = true } +axplat-riscv64-visionfive2 = { workspace = true, features = ["fp-simd", "irq", "rtc"], optional = true } [target.'cfg(any(windows,unix))'.dependencies] anyhow = "1.0" @@ -75,11 +77,6 @@ axbuild = { workspace = true } clap = { version = "4.6", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } -[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies.axplat-riscv64-visionfive2] -features = ["fp-simd", "irq", "rtc"] -optional = true -workspace = true - [package.metadata.vendor-filter] all-features = true platforms = ["riscv64gc-unknown-none-elf", "loongarch64-unknown-none-softfloat"] diff --git a/os/StarryOS/starryos/src/main.rs b/os/StarryOS/starryos/src/main.rs index b1661d4b25..531d66c44c 100644 --- a/os/StarryOS/starryos/src/main.rs +++ b/os/StarryOS/starryos/src/main.rs @@ -25,3 +25,6 @@ fn main() { any(target_arch = "riscv32", target_arch = "riscv64") ))] extern crate ax_plat_riscv64_sg2002; + +#[cfg(all(feature = "vf2", target_arch = "riscv64"))] +extern crate axplat_riscv64_visionfive2; diff --git a/os/arceos/README.md b/os/arceos/README.md index 7595dd2d26..60aab66a23 100644 --- a/os/arceos/README.md +++ b/os/arceos/README.md @@ -207,9 +207,9 @@ You may also need to select the corrsponding device drivers by setting the `FEAT ```bash # Build the shell app for raspi4, and use the SD card driver -make MYPLAT=axplat-aarch64-raspi SMP=4 A=examples/shell FEATURES=page-alloc-4g,driver-bcm2835-sdhci BUS=mmio +make MYPLAT=axplat-aarch64-raspi SMP=4 A=examples/shell FEATURES=page-alloc-4g,ax-driver/bcm2835-sdhci # Build httpserver for the bare-metal x86_64 platform, and use the ixgbe and ramdisk driver -make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml A=examples/httpserver FEATURES=page-alloc-4g,driver-ixgbe,driver-ramdisk SMP=4 +make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml A=examples/httpserver FEATURES=page-alloc-4g,ax-driver/pci,ax-driver/ixgbe,ax-driver/ramdisk SMP=4 ``` ## How to reuse ArceOS modules in your own project diff --git a/os/arceos/api/arceos_api/Cargo.toml b/os/arceos/api/arceos_api/Cargo.toml index 64e43ff0dd..8ff1536b04 100644 --- a/os/arceos/api/arceos_api/Cargo.toml +++ b/os/arceos/api/arceos_api/Cargo.toml @@ -17,9 +17,9 @@ paging = ["dep:ax-mm", "ax-feat/paging"] dma = ["dep:ax-dma", "ax-feat/dma"] multitask = ["ax-task/multitask", "ax-sync/multitask", "ax-feat/multitask"] stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] -fs = ["dep:ax-fs", "dep:ax-driver", "ax-feat/fs"] -net = ["dep:ax-net", "dep:ax-driver", "ax-feat/net"] -display = ["dep:ax-display", "dep:ax-driver", "ax-feat/display"] +fs = ["dep:ax-fs", "ax-feat/fs"] +net = ["dep:ax-net", "ax-feat/net"] +display = ["dep:ax-display", "ax-feat/display"] # Use dummy functions if the feature is not enabled @@ -30,7 +30,6 @@ ax-alloc = { workspace = true, optional = true, features = ["default"] } ax-config.workspace = true ax-display = { workspace = true, optional = true } ax-dma = { workspace = true, optional = true } -ax-driver = { workspace = true, optional = true } ax-errno.workspace = true ax-feat.workspace = true ax-fs = { workspace = true, optional = true } diff --git a/os/arceos/api/arceos_api/src/lib.rs b/os/arceos/api/arceos_api/src/lib.rs index ef5ad1d1d2..cb29e11556 100644 --- a/os/arceos/api/arceos_api/src/lib.rs +++ b/os/arceos/api/arceos_api/src/lib.rs @@ -398,8 +398,6 @@ pub mod modules { pub use ax_display; #[cfg(feature = "dma")] pub use ax_dma; - #[cfg(any(feature = "fs", feature = "net", feature = "display"))] - pub use ax_driver; #[cfg(feature = "fs")] pub use ax_fs; pub use ax_hal; diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index 0275ebb36f..835eea21f7 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -78,25 +78,30 @@ stack-guard-page = [ fs = [ "alloc", "paging", - "ax-driver/virtio-blk", "dep:ax-fs", "ax-runtime/fs", ] # TODO: try to remove "paging" -fs-ng = ["fs", "ax-runtime/fs-ng"] +fs-ng = ["alloc", "paging", "dep:ax-fs-ng", "ax-runtime/fs-ng"] fs-ng-ext4 = ["fs-ng", "ax-fs-ng/ext4"] fs-ng-fat = ["fs-ng", "ax-fs-ng/fat"] fs-ng-times = ["fs-ng", "ax-fs-ng/times"] # Networking -net = ["alloc", "paging", "ax-driver/virtio-net", "dep:ax-net", "ax-runtime/net"] -net-ng = ["net", "irq", "multitask", "ax-runtime/net-ng"] +net = [ + "alloc", + "paging", + "irq", + "multitask", + "dep:ax-net", + "ax-runtime/net", +] +net-ng = ["alloc", "paging", "irq", "multitask", "dep:ax-net-ng", "ax-runtime/net-ng"] vsock = ["ax-runtime/vsock"] # Display display = [ "alloc", "paging", - "ax-driver/virtio-gpu", "dep:ax-display", "ax-runtime/display", ] @@ -105,7 +110,6 @@ display = [ input = [ "alloc", "paging", - "ax-driver/virtio-input", "dep:ax-input", "ax-runtime/input", ] @@ -113,17 +117,6 @@ input = [ # Real Time Clock (RTC) Driver. rtc = ["ax-hal/rtc", "ax-runtime/rtc"] -# Device drivers -bus-mmio = ["ax-driver?/bus-mmio"] -bus-pci = ["ax-driver?/bus-pci"] -driver-ramdisk = ["ax-driver?/ramdisk", "ax-fs?/use-ramdisk"] -driver-sdmmc = ["ax-driver?/sdmmc"] -driver-cvsd = ["ax-driver?/cvsd"] -driver-ixgbe = ["ax-driver?/ixgbe"] -driver-fxmac = ["ax-driver?/fxmac"] # fxmac ethernet driver for PhytiumPi -driver-bcm2835-sdhci = ["ax-driver?/bcm2835-sdhci"] -driver-ahci = ["ax-driver?/ahci"] - # Backtrace backtrace = ["alloc", "axbacktrace/alloc"] dwarf = ["alloc", "axbacktrace/dwarf"] @@ -132,8 +125,8 @@ dwarf = ["alloc", "axbacktrace/dwarf"] ax-alloc = { workspace = true, optional = true, features = ["default"] } axbacktrace.workspace = true ax-config = { workspace = true, optional = true } -ax-display = { workspace = true, optional = true } ax-driver = { workspace = true, optional = true } +ax-display = { workspace = true, optional = true } ax-fs = { workspace = true, optional = true } ax-fs-ng = { workspace = true, optional = true } ax-hal.workspace = true @@ -141,6 +134,7 @@ ax-input = { workspace = true, optional = true } ax-ipi = { workspace = true, optional = true } ax-log.workspace = true ax-net = { workspace = true, optional = true } +ax-net-ng = { workspace = true, optional = true } ax-runtime.workspace = true ax-sync = { workspace = true, optional = true } ax-task = { workspace = true, optional = true } diff --git a/os/arceos/api/axfeat/src/lib.rs b/os/arceos/api/axfeat/src/lib.rs index 9f078e7226..17bc22c7f6 100644 --- a/os/arceos/api/axfeat/src/lib.rs +++ b/os/arceos/api/axfeat/src/lib.rs @@ -24,12 +24,8 @@ //! - `fs`: Enable file system support. //! - `net`: Enable networking support. //! - `display`: Enable graphics support. -//! - Device drivers -//! - `bus-mmio`: Use device tree to probe all MMIO devices. -//! - `bus-pci`: Use PCI bus to probe all PCI devices. -//! - `driver-ramdisk`: Use the RAM disk to emulate the block device. -//! - `driver-ixgbe`: Enable the Intel 82599 10Gbit NIC driver. -//! - `driver-bcm2835-sdhci`: Enable the BCM2835 SDHCI driver (Raspberry Pi SD card). +//! - Device drivers are selected directly through `ax-driver/*` features by +//! board configurations. //! //! [ArceOS]: https://github.com/arceos-org/arceos diff --git a/os/arceos/configs/dummy.toml b/os/arceos/configs/dummy.toml index 767eed39d2..bc0b556917 100644 --- a/os/arceos/configs/dummy.toml +++ b/os/arceos/configs/dummy.toml @@ -59,3 +59,7 @@ ipi-irq = 0 # uint # SDMMC controller physical address. sdmmc-paddr = 0 # uint +# SG2002 CV SD/MMC controller physical address. +cvsd-paddr = 0 # uint +# SG2002 system controller physical address. +syscon-paddr = 0 # uint diff --git a/os/arceos/doc/ixgbe.md b/os/arceos/doc/ixgbe.md index 50b0c5bb6b..23f1a8597f 100644 --- a/os/arceos/doc/ixgbe.md +++ b/os/arceos/doc/ixgbe.md @@ -6,7 +6,7 @@ You can use the following command to compile an 'httpserver' app application: ```shell make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml defconfig -make A=examples/httpserver PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=driver-ixgbe +make A=examples/httpserver PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=ax-driver/pci,ax-driver/ixgbe ``` You can also use the following command to start the iperf application: @@ -14,7 +14,7 @@ You can also use the following command to start the iperf application: ```shell git clone https://github.com/arceos-org/arceos-apps.git make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml defconfig -make A=arceos-apps/c/iperf PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=driver-ixgbe,driver-ramdisk +make A=arceos-apps/c/iperf PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=ax-driver/pci,ax-driver/ixgbe,ax-driver/ramdisk ``` ## Use ixgbe NIC in QEMU with PCI passthrough @@ -38,7 +38,7 @@ make A=arceos-apps/c/iperf PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.tom 3. Build and run ArceOS: ```shell - make A=examples/httpserver FEATURES=driver-ixgbe VFIO_PCI=02:00.0 IP=x.x.x.x GW=x.x.x.x run + make A=examples/httpserver FEATURES=ax-driver/pci,ax-driver/ixgbe VFIO_PCI=02:00.0 IP=x.x.x.x GW=x.x.x.x run ``` 4. If no longer in use, bind the NIC back to the `ixgbe` driver: diff --git a/os/arceos/examples/helloworld-myplat/Cargo.toml b/os/arceos/examples/helloworld-myplat/Cargo.toml index 6c19dadbff..fc8c525786 100644 --- a/os/arceos/examples/helloworld-myplat/Cargo.toml +++ b/os/arceos/examples/helloworld-myplat/Cargo.toml @@ -17,20 +17,22 @@ riscv64-qemu-virt = ["dep:ax-plat-riscv64-qemu-virt"] loongarch64-qemu-virt = ["dep:ax-plat-loongarch64-qemu-virt"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true cfg-if.workspace = true ax-std = { workspace = true, features = ["myplat"], optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] -ax-plat-x86-pc = { workspace = true, features = ["smp", "irq"], optional = true } +ax-plat-x86-pc = { workspace = true, optional = true } [target.'cfg(target_arch = "aarch64")'.dependencies] -ax-plat-aarch64-qemu-virt = { workspace = true, features = ["smp", "irq"], optional = true } -ax-plat-aarch64-raspi = { workspace = true, features = ["smp", "irq"], optional = true } -ax-plat-aarch64-bsta1000b = { workspace = true, features = ["smp", "irq"], optional = true } -ax-plat-aarch64-phytium-pi = { workspace = true, features = ["smp", "irq"], optional = true } +ax-plat-aarch64-qemu-virt = { workspace = true, optional = true } +ax-plat-aarch64-raspi = { workspace = true, optional = true } +ax-plat-aarch64-bsta1000b = { workspace = true, optional = true } +ax-plat-aarch64-phytium-pi = { workspace = true, optional = true } [target.'cfg(target_arch = "riscv64")'.dependencies] -ax-plat-riscv64-qemu-virt = { workspace = true, features = ["smp", "irq"], optional = true } +ax-plat-riscv64-qemu-virt = { workspace = true, optional = true } [target.'cfg(target_arch = "loongarch64")'.dependencies] -ax-plat-loongarch64-qemu-virt = { workspace = true, features = ["smp", "irq"], optional = true } +ax-plat-loongarch64-qemu-virt = { workspace = true, optional = true } diff --git a/os/arceos/examples/helloworld/Cargo.toml b/os/arceos/examples/helloworld/Cargo.toml index a350d950f3..afa13fb951 100644 --- a/os/arceos/examples/helloworld/Cargo.toml +++ b/os/arceos/examples/helloworld/Cargo.toml @@ -10,4 +10,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, optional = true } diff --git a/os/arceos/examples/httpclient/Cargo.toml b/os/arceos/examples/httpclient/Cargo.toml index 1bd23158bc..06a95c1511 100644 --- a/os/arceos/examples/httpclient/Cargo.toml +++ b/os/arceos/examples/httpclient/Cargo.toml @@ -10,6 +10,8 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["net"], optional = true } [features] diff --git a/os/arceos/examples/httpserver/Cargo.toml b/os/arceos/examples/httpserver/Cargo.toml index 083766a3a9..79635c7d8e 100644 --- a/os/arceos/examples/httpserver/Cargo.toml +++ b/os/arceos/examples/httpserver/Cargo.toml @@ -10,4 +10,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask", "net"], optional = true } diff --git a/os/arceos/examples/shell/Cargo.toml b/os/arceos/examples/shell/Cargo.toml index 8aa5ae2558..94bfa9e968 100644 --- a/os/arceos/examples/shell/Cargo.toml +++ b/os/arceos/examples/shell/Cargo.toml @@ -13,4 +13,6 @@ publish = false default = [] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "fs"], optional = true } diff --git a/os/arceos/modules/axconfig/dummy.toml b/os/arceos/modules/axconfig/dummy.toml index 767eed39d2..bc0b556917 100644 --- a/os/arceos/modules/axconfig/dummy.toml +++ b/os/arceos/modules/axconfig/dummy.toml @@ -59,3 +59,7 @@ ipi-irq = 0 # uint # SDMMC controller physical address. sdmmc-paddr = 0 # uint +# SG2002 CV SD/MMC controller physical address. +cvsd-paddr = 0 # uint +# SG2002 system controller physical address. +syscon-paddr = 0 # uint diff --git a/os/arceos/modules/axconfig/src/driver_dyn_config.rs b/os/arceos/modules/axconfig/src/driver_dyn_config.rs index 21ec301dcb..3cc8bab13a 100644 --- a/os/arceos/modules/axconfig/src/driver_dyn_config.rs +++ b/os/arceos/modules/axconfig/src/driver_dyn_config.rs @@ -35,6 +35,10 @@ pub mod devices { pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[]; #[doc = " SDMMC controller physical address."] pub const SDMMC_PADDR: usize = 0; + #[doc = " SG2002 CV SD/MMC controller physical address."] + pub const CVSD_PADDR: usize = 0; + #[doc = " SG2002 system controller physical address."] + pub const SYSCON_PADDR: usize = 0; } #[doc = ""] #[doc = " Platform configs"] diff --git a/os/arceos/modules/axdisplay/Cargo.toml b/os/arceos/modules/axdisplay/Cargo.toml index d7f6b40bf2..70d47ba27f 100644 --- a/os/arceos/modules/axdisplay/Cargo.toml +++ b/os/arceos/modules/axdisplay/Cargo.toml @@ -8,7 +8,7 @@ description = "ArceOS graphics module" license.workspace = true [dependencies] -ax-driver = { workspace = true, features = ["display"] } ax-sync.workspace = true ax-lazyinit.workspace = true log.workspace = true +rdif-display.workspace = true diff --git a/os/arceos/modules/axdisplay/src/device.rs b/os/arceos/modules/axdisplay/src/device.rs new file mode 100644 index 0000000000..15261818a5 --- /dev/null +++ b/os/arceos/modules/axdisplay/src/device.rs @@ -0,0 +1,55 @@ +use alloc::{boxed::Box, string::String}; + +use crate::DisplayInfo; + +pub type DisplayResult = Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DisplayError { + NotSupported, + NotAvailable, + InvalidFramebuffer, + BadState, +} + +/// Domain boundary consumed by graphics modules and device files. +pub trait DisplayDevice: Send { + fn name(&self) -> &str; + + fn info(&self) -> DisplayInfo; + + fn flush(&mut self) -> DisplayResult; +} + +pub struct ErasedDisplayDevice { + name: String, + inner: Box, +} + +impl ErasedDisplayDevice { + pub fn new(device: impl DisplayDevice + 'static) -> Self { + let name = device.name().into(); + Self { + name, + inner: Box::new(device), + } + } + + pub fn name(&self) -> &str { + &self.name + } +} + +impl DisplayDevice for ErasedDisplayDevice { + fn name(&self) -> &str { + &self.name + } + + fn info(&self) -> DisplayInfo { + self.inner.info() + } + + fn flush(&mut self) -> DisplayResult { + self.inner.flush() + } +} diff --git a/os/arceos/modules/axdisplay/src/lib.rs b/os/arceos/modules/axdisplay/src/lib.rs index e74ed5cdbe..e01ff83d6d 100644 --- a/os/arceos/modules/axdisplay/src/lib.rs +++ b/os/arceos/modules/axdisplay/src/lib.rs @@ -4,26 +4,28 @@ #![no_std] -#[macro_use] -extern crate log; +extern crate alloc; + +mod device; +pub mod rdif; +mod types; -#[doc(no_inline)] -pub use ax_driver::prelude::DisplayInfo; -use ax_driver::{AxDeviceContainer, prelude::*}; use ax_lazyinit::LazyInit; use ax_sync::Mutex; +pub use device::{DisplayDevice, DisplayError, DisplayResult, ErasedDisplayDevice}; +pub use types::{DisplayInfo, PixelFormat}; -static MAIN_DISPLAY: LazyInit> = LazyInit::new(); +static MAIN_DISPLAY: LazyInit> = LazyInit::new(); /// Initializes the display subsystem by underlayer devices. -pub fn init_display(mut display_devs: AxDeviceContainer) { - info!("Initialize display subsystem..."); +pub fn init_display(display_devs: impl IntoIterator) { + log::info!("Initialize display subsystem..."); - if let Some(dev) = display_devs.take_one() { - info!(" use display device 0: {:?}", dev.device_name()); + if let Some(dev) = display_devs.into_iter().next() { + log::info!(" use display device 0: {}", dev.name()); MAIN_DISPLAY.init_once(Mutex::new(dev)); } else { - warn!(" No display device found!"); + log::warn!(" No display device found!"); } } diff --git a/os/arceos/modules/axdisplay/src/rdif.rs b/os/arceos/modules/axdisplay/src/rdif.rs new file mode 100644 index 0000000000..c3950ae783 --- /dev/null +++ b/os/arceos/modules/axdisplay/src/rdif.rs @@ -0,0 +1,81 @@ +use alloc::{boxed::Box, string::String}; +use core::ptr::NonNull; + +use rdif_display::{DisplayError as RdifDisplayError, Interface}; + +use crate::{DisplayDevice, DisplayError, DisplayInfo, PixelFormat}; + +pub struct RdifDisplayDevice { + name: String, + device: Box, + fb_base_vaddr: NonNull, +} + +unsafe impl Send for RdifDisplayDevice {} + +impl RdifDisplayDevice { + pub fn new(mut device: Box) -> Result { + let name = device.name().into(); + let fb_base_vaddr = { + let mut framebuffer = device.framebuffer().map_err(map_display_error)?; + NonNull::new(framebuffer.as_mut_slice().as_mut_ptr()) + .ok_or(DisplayError::InvalidFramebuffer)? + }; + Ok(Self { + name, + device, + fb_base_vaddr, + }) + } + + pub fn from_interface(device: impl Interface + 'static) -> Result { + Self::new(Box::new(device)) + } +} + +impl DisplayDevice for RdifDisplayDevice { + fn name(&self) -> &str { + &self.name + } + + fn info(&self) -> DisplayInfo { + let info = self.device.info(); + DisplayInfo { + width: info.width, + height: info.height, + fb_base_vaddr: self.fb_base_vaddr.as_ptr() as usize, + fb_size: info.fb_size, + stride: info.stride, + format: info.format.into(), + } + } + + fn flush(&mut self) -> Result<(), DisplayError> { + if self.device.need_flush() { + self.device.flush().map_err(map_display_error)?; + } + Ok(()) + } +} + +impl From for PixelFormat { + fn from(value: rdif_display::PixelFormat) -> Self { + match value { + rdif_display::PixelFormat::Rgb565 => Self::Rgb565, + rdif_display::PixelFormat::Rgb888 => Self::Rgb888, + rdif_display::PixelFormat::Xrgb8888 => Self::Xrgb8888, + rdif_display::PixelFormat::Argb8888 => Self::Argb8888, + rdif_display::PixelFormat::Bgr888 => Self::Bgr888, + rdif_display::PixelFormat::Xbgr8888 => Self::Xbgr8888, + } + } +} + +fn map_display_error(error: RdifDisplayError) -> DisplayError { + match error { + RdifDisplayError::NotSupported => DisplayError::NotSupported, + RdifDisplayError::NotAvailable => DisplayError::NotAvailable, + RdifDisplayError::InvalidFramebuffer => DisplayError::InvalidFramebuffer, + RdifDisplayError::Other(_) => DisplayError::BadState, + } +} diff --git a/os/arceos/modules/axdisplay/src/types.rs b/os/arceos/modules/axdisplay/src/types.rs new file mode 100644 index 0000000000..4e1f8bacc8 --- /dev/null +++ b/os/arceos/modules/axdisplay/src/types.rs @@ -0,0 +1,41 @@ +/// The information of the graphics device. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DisplayInfo { + /// The visible width. + pub width: u32, + /// The visible height. + pub height: u32, + /// The base virtual address of the framebuffer. + pub fb_base_vaddr: usize, + /// The size of the framebuffer in bytes. + pub fb_size: usize, + /// The number of framebuffer bytes per scanline. + pub stride: usize, + /// The framebuffer pixel layout. + pub format: PixelFormat, +} + +/// Pixel layouts used by framebuffer-backed display devices. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PixelFormat { + Rgb565, + Rgb888, + Xrgb8888, + Argb8888, + Bgr888, + Xbgr8888, + Unknown, +} + +impl DisplayInfo { + /// Compatibility helper for callers that still infer pitch from size. + pub fn line_length(&self) -> usize { + if self.stride != 0 { + self.stride + } else if self.height == 0 { + 0 + } else { + self.fb_size / self.height as usize + } + } +} diff --git a/os/arceos/modules/axdriver/Cargo.toml b/os/arceos/modules/axdriver/Cargo.toml deleted file mode 100644 index 654250875f..0000000000 --- a/os/arceos/modules/axdriver/Cargo.toml +++ /dev/null @@ -1,63 +0,0 @@ -[package] -name = "ax-driver" -version = "0.5.16" -repository = "https://github.com/rcore-os/tgoskits" -edition.workspace = true -authors = [ - "Yuekai Jia ", - "ChengXiang Qi ", -] -description = "ArceOS device drivers" -license.workspace = true - -[features] -bus-mmio = [] -bus-pci = ["dep:ax-driver-pci", "dep:ax-hal", "dep:ax-config"] -net = ["dep:ax-driver-net", "axplat-dyn?/net"] -block = ["dep:ax-driver-block"] -display = ["dep:ax-driver-display"] -input = ["dep:ax-driver-input"] -vsock = ["ax-driver-vsock"] - -# Enabled by features `virtio-*` -virtio = ["dep:ax-driver-virtio", "dep:ax-alloc", "dep:ax-hal", "dep:ax-config"] - -# various types of drivers -virtio-blk = ["block", "virtio", "ax-driver-virtio/block"] -virtio-net = ["net", "virtio", "ax-driver-virtio/net", "axplat-dyn?/virtio-net-pci"] -virtio-gpu = ["display", "virtio", "ax-driver-virtio/gpu"] -virtio-input = ["input", "virtio", "ax-driver-virtio/input"] -virtio-socket = ["vsock", "virtio", "ax-driver-virtio/socket"] -ramdisk = ["block", "ax-driver-block/ramdisk"] -bcm2835-sdhci = ["block", "ax-driver-block/bcm2835-sdhci"] -sdmmc = ["block", "ax-driver-block/sdmmc", "dep:ax-hal", "dep:ax-config"] -cvsd = ["block", "ax-driver-block/cvsd", "dep:ax-hal", "dep:ax-config"] -ahci = ["block", "ax-driver-block/ahci", "dep:ax-hal", "dep:ax-config"] -ixgbe = ["net", "ax-driver-net/ixgbe", "dep:ax-alloc", "dep:ax-hal", "dep:ax-dma"] -fxmac = ["net", "ax-driver-net/fxmac", "dep:ax-alloc", "dep:ax-hal", "dep:ax-dma"] -# more devices example: e1000 = ["net", "ax-driver-net/e1000"] - -irq = [] -dyn = ["dep:ax-errno", "dep:axplat-dyn"] - -default = ["bus-pci"] - -[dependencies] -ax-alloc = { workspace = true, optional = true, features = ["default"] } -ax-config = { workspace = true, optional = true } -ax-dma = { workspace = true, optional = true } -ax-driver-base.workspace = true -ax-driver-block = { workspace = true, optional = true } -ax-driver-display = { workspace = true, optional = true } -ax-driver-input = { workspace = true, optional = true } -ax-driver-net = { workspace = true, optional = true } -ax-driver-pci = { workspace = true, optional = true } -ax-driver-virtio = { workspace = true, optional = true } -ax-driver-vsock = { workspace = true, optional = true } -ax-errno = { workspace = true, optional = true } -ax-hal = { workspace = true, optional = true } -cfg-if.workspace = true -ax-crate-interface.workspace = true -log.workspace = true -smallvec = { version = "1.15", features = ["const_generics", "union"] } -axplat-dyn = { workspace = true, optional = true } diff --git a/os/arceos/modules/axdriver/build.rs b/os/arceos/modules/axdriver/build.rs deleted file mode 100644 index cd875ac39c..0000000000 --- a/os/arceos/modules/axdriver/build.rs +++ /dev/null @@ -1,109 +0,0 @@ -const NET_DEV_FEATURES: &[&str] = &["fxmac", "ixgbe", "virtio-net"]; -const BLOCK_DEV_FEATURES: &[&str] = &["ramdisk", "sdmmc", "cvsd", "bcm2835-sdhci", "virtio-blk"]; -const DISPLAY_DEV_FEATURES: &[&str] = &["virtio-gpu"]; -const INPUT_DEV_FEATURES: &[&str] = &["virtio-input"]; -const VSOCK_DEV_FEATURES: &[&str] = &["virtio-socket"]; -const VIRTIO_DEV_FEATURES: &[&str] = &[ - "virtio-blk", - "virtio-gpu", - "virtio-input", - "virtio-net", - "virtio-socket", -]; - -fn make_cfg_values(str_list: &[&str]) -> String { - str_list - .iter() - .map(|s| format!("{s:?}")) - .collect::>() - .join(", ") -} - -fn has_feature(feature: &str) -> bool { - std::env::var(format!( - "CARGO_FEATURE_{}", - feature.to_uppercase().replace('-', "_") - )) - .is_ok() -} - -fn has_any_feature(features: &[&str]) -> bool { - features.iter().any(|feature| has_feature(feature)) -} - -fn enable_cfg(key: &str, value: &str) { - println!("cargo:rustc-cfg={key}=\"{value}\""); -} - -fn enable_cfg_flag(key: &str) { - println!("cargo:rustc-cfg={key}"); -} - -fn main() { - let has_virtio_dev = has_any_feature(VIRTIO_DEV_FEATURES); - if has_feature("bus-mmio") { - enable_cfg("bus", "mmio"); - } else if has_feature("bus-pci") { - enable_cfg("bus", "pci"); - } else if has_virtio_dev { - enable_cfg("bus", "mmio"); - } - if has_virtio_dev { - enable_cfg_flag("virtio_dev"); - } - - // Generate cfgs like `net_dev="virtio-net"`. if `dyn` is not enabled, only one device is - // selected for each device category. If no device is selected, `dummy` is selected. - let is_dyn = has_feature("dyn"); - for (dev_kind, feat_list) in [ - ("net", NET_DEV_FEATURES), - ("block", BLOCK_DEV_FEATURES), - ("display", DISPLAY_DEV_FEATURES), - ("input", INPUT_DEV_FEATURES), - ("vsock", VSOCK_DEV_FEATURES), - ] { - if !has_feature(dev_kind) { - continue; - } - - let mut selected = false; - for feat in feat_list { - if has_feature(feat) { - enable_cfg(&format!("{dev_kind}_dev"), feat); - selected = true; - if !is_dyn { - break; - } - } - } - if !is_dyn && !selected { - enable_cfg(&format!("{dev_kind}_dev"), "dummy"); - } - } - - println!( - "cargo::rustc-check-cfg=cfg(bus, values({}))", - make_cfg_values(&["pci", "mmio"]) - ); - println!("cargo::rustc-check-cfg=cfg(virtio_dev)"); - println!( - "cargo::rustc-check-cfg=cfg(net_dev, values({}, \"dummy\"))", - make_cfg_values(NET_DEV_FEATURES) - ); - println!( - "cargo::rustc-check-cfg=cfg(block_dev, values({}, \"dummy\"))", - make_cfg_values(BLOCK_DEV_FEATURES) - ); - println!( - "cargo::rustc-check-cfg=cfg(display_dev, values({}, \"dummy\"))", - make_cfg_values(DISPLAY_DEV_FEATURES) - ); - println!( - "cargo::rustc-check-cfg=cfg(input_dev, values({}, \"dummy\"))", - make_cfg_values(INPUT_DEV_FEATURES) - ); - println!( - "cargo::rustc-check-cfg=cfg(vsock_dev, values({}, \"dummy\"))", - make_cfg_values(VSOCK_DEV_FEATURES) - ); -} diff --git a/os/arceos/modules/axdriver/src/bus/mmio.rs b/os/arceos/modules/axdriver/src/bus/mmio.rs deleted file mode 100644 index ab97839b9b..0000000000 --- a/os/arceos/modules/axdriver/src/bus/mmio.rs +++ /dev/null @@ -1,23 +0,0 @@ -#[allow(unused_imports)] -use crate::{AllDevices, prelude::*}; - -impl AllDevices { - pub(crate) fn probe_bus_devices(&mut self) { - // TODO: parse device tree - #[cfg(virtio_dev)] - for reg in ax_config::devices::VIRTIO_MMIO_RANGES { - for_each_drivers!(type Driver, { - if let Some(dev) = Driver::probe_mmio(reg.0, reg.1) { - info!( - "registered a new {:?} device at [PA:{:#x}, PA:{:#x}): {:?}", - dev.device_type(), - reg.0, reg.0 + reg.1, - dev.device_name(), - ); - self.add_device(dev); - continue; // skip to the next device - } - }); - } - } -} diff --git a/os/arceos/modules/axdriver/src/bus/mod.rs b/os/arceos/modules/axdriver/src/bus/mod.rs deleted file mode 100644 index 0173096f71..0000000000 --- a/os/arceos/modules/axdriver/src/bus/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[cfg(all(bus = "mmio", feature = "bus-mmio"))] -mod mmio; -#[cfg(all(bus = "pci", feature = "bus-pci"))] -mod pci; diff --git a/os/arceos/modules/axdriver/src/bus/pci.rs b/os/arceos/modules/axdriver/src/bus/pci.rs deleted file mode 100644 index d3cccd02bd..0000000000 --- a/os/arceos/modules/axdriver/src/bus/pci.rs +++ /dev/null @@ -1,126 +0,0 @@ -use ax_driver_pci::{ - BarInfo, Cam, Command, ConfigurationAccess, DeviceFunction, HeaderType, MemoryBarType, MmioCam, - PciRangeAllocator, PciRoot, -}; -use ax_hal::mem::phys_to_virt; - -use crate::{AllDevices, prelude::*}; - -const PCI_BAR_NUM: u8 = 6; - -fn config_pci_device( - root: &mut PciRoot, - bdf: DeviceFunction, - allocator: &mut Option, -) -> DevResult { - let mut bar = 0; - while bar < PCI_BAR_NUM { - let info = root.bar_info(bdf, bar).map_err(|err| { - warn!("failed to read PCI BAR {bar} info for {bdf}: {err:?}"); - DevError::Io - })?; - if let Some(BarInfo::Memory { - address_type, - address, - size, - .. - }) = info - { - // if the BAR address is not assigned, call the allocator and assign it. - if size > 0 && address == 0 { - let new_addr = allocator - .as_mut() - .expect("No memory ranges available for PCI BARs!") - .alloc(size as _) - .ok_or(DevError::NoMemory)?; - if address_type == MemoryBarType::Width32 { - root.set_bar_32(bdf, bar, new_addr as _); - } else if address_type == MemoryBarType::Width64 { - root.set_bar_64(bdf, bar, new_addr); - } - } - } - - // read the BAR info again after assignment. - let info = root.bar_info(bdf, bar).map_err(|err| { - warn!("failed to re-read PCI BAR {bar} info for {bdf}: {err:?}"); - DevError::Io - })?; - match info { - Some(BarInfo::IO { address, size }) if address > 0 && size > 0 => { - debug!(" BAR {}: IO [{:#x}, {:#x})", bar, address, address + size); - } - Some(BarInfo::Memory { - address_type, - prefetchable, - address, - size, - }) if address > 0 && size > 0 => { - debug!( - " BAR {}: MEM [{:#x}, {:#x}){}{}", - bar, - address, - address + size, - if address_type == MemoryBarType::Width64 { - " 64bit" - } else { - "" - }, - if prefetchable { " pref" } else { "" }, - ); - } - None => {} - Some(_) => {} - } - - bar += 1; - if info.as_ref().is_some_and(BarInfo::takes_two_entries) { - bar += 1; - } - } - - // Enable the device. - let (_status, cmd) = root.get_status_command(bdf); - root.set_command( - bdf, - (cmd | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER) - & !Command::INTERRUPT_DISABLE, - ); - Ok(()) -} - -impl AllDevices { - pub(crate) fn probe_bus_devices(&mut self) { - let base_vaddr = phys_to_virt(ax_config::devices::PCI_ECAM_BASE.into()); - let mut root = PciRoot::new(unsafe { MmioCam::new(base_vaddr.as_mut_ptr(), Cam::Ecam) }); - - // PCI 32-bit MMIO space - let mut allocator = ax_config::devices::PCI_RANGES - .get(1) - .map(|range| PciRangeAllocator::new(range.0 as u64, range.1 as u64)); - - for bus in 0..=ax_config::devices::PCI_BUS_END as u8 { - for (bdf, dev_info) in root.enumerate_bus(bus) { - debug!("PCI {bdf}: {dev_info}"); - if dev_info.header_type != HeaderType::Standard { - continue; - } - match config_pci_device(&mut root, bdf, &mut allocator) { - Ok(_) => for_each_drivers!(type Driver, { - if let Some(dev) = Driver::probe_pci(&mut root, bdf, &dev_info) { - info!( - "registered a new {:?} device at {}: {:?}", - dev.device_type(), - bdf, - dev.device_name(), - ); - self.add_device(dev); - continue; // skip to the next device - } - }), - Err(e) => warn!("failed to enable PCI device at {bdf}({dev_info}): {e:?}"), - } - } - } - } -} diff --git a/os/arceos/modules/axdriver/src/drivers.rs b/os/arceos/modules/axdriver/src/drivers.rs deleted file mode 100644 index 2518c2e182..0000000000 --- a/os/arceos/modules/axdriver/src/drivers.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! Defines types and probe methods of all supported devices. - -#![allow(unused_imports, dead_code)] - -use ax_driver_base::DeviceType; -#[cfg(feature = "bus-pci")] -use ax_driver_pci::{ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, PciRoot}; - -pub use super::dummy::*; -use crate::AxDeviceEnum; -#[cfg(virtio_dev)] -use crate::virtio::{self, VirtIoDevMeta}; - -pub trait DriverProbe { - fn probe_global() -> Option { - None - } - - #[cfg(bus = "mmio")] - fn probe_mmio(_mmio_base: usize, _mmio_size: usize) -> Option { - None - } - - #[cfg(all(bus = "pci", feature = "bus-pci"))] - fn probe_pci( - _root: &mut PciRoot, - _bdf: DeviceFunction, - _dev_info: &DeviceFunctionInfo, - ) -> Option { - None - } -} - -#[cfg(net_dev = "virtio-net")] -register_net_driver!( - ::Driver, - ::Device -); - -#[cfg(block_dev = "virtio-blk")] -register_block_driver!( - ::Driver, - ::Device -); - -#[cfg(display_dev = "virtio-gpu")] -register_display_driver!( - ::Driver, - ::Device -); - -#[cfg(input_dev = "virtio-input")] -register_input_driver!( - ::Driver, - ::Device -); - -#[cfg(vsock_dev = "virtio-socket")] -register_vsock_driver!( - ::Driver, - ::Device -); - -cfg_if::cfg_if! { - if #[cfg(block_dev = "ramdisk")] { - pub struct RamDiskDriver; - register_block_driver!(RamDiskDriver, ax_driver_block::ramdisk::RamDisk); - - impl DriverProbe for RamDiskDriver { - fn probe_global() -> Option { - // TODO: format RAM disk - Some(AxDeviceEnum::from_block( - ax_driver_block::ramdisk::RamDisk::new(0x100_0000), // 16 MiB - )) - } - } - } -} - -cfg_if::cfg_if! { - if #[cfg(block_dev = "sdmmc")] { - pub struct SdMmcDriver; - register_block_driver!(SdMmcDriver, ax_driver_block::sdmmc::SdMmcDriver); - - impl DriverProbe for SdMmcDriver { - fn probe_global() -> Option { - let sdmmc = unsafe { - ax_driver_block::sdmmc::SdMmcDriver::new( - ax_hal::mem::phys_to_virt(ax_config::devices::SDMMC_PADDR.into()).into(), - ) - }; - Some(AxDeviceEnum::from_block(sdmmc)) - } - } - } -} - -cfg_if::cfg_if! { - if #[cfg(block_dev = "cvsd")] { - use ax_driver_block::cvsd::CvsdDriver; - #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] - use ax_hal::mem::phys_to_virt; - - pub struct CvsdMmc; - register_block_driver!(CvsdMmc, CvsdDriver); - - impl DriverProbe for CvsdMmc { - #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] - fn probe_global() -> Option { - info!("Probe CV SD Bootable Part @ {:#x}", ax_config::devices::CVSD_PADDR); - - let sdmmc = CvsdDriver::new( - phys_to_virt(ax_config::devices::CVSD_PADDR.into()).into(), - phys_to_virt(ax_config::devices::SYSCON_PADDR.into()).into(), - ).expect("CVSD init failed"); - Some(AxDeviceEnum::from_block(sdmmc)) - } - } - } -} - -cfg_if::cfg_if! { - if #[cfg(block_dev = "bcm2835-sdhci")]{ - pub struct BcmSdhciDriver; - register_block_driver!(BcmSdhciDriver, ax_driver_block::bcm2835sdhci::SDHCIDriver); - - impl DriverProbe for BcmSdhciDriver { - fn probe_global() -> Option { - debug!("mmc probe"); - ax_driver_block::bcm2835sdhci::SDHCIDriver::try_new() - .ok() - .map(AxDeviceEnum::from_block) - } - } - } -} - -cfg_if::cfg_if! { - if #[cfg(net_dev = "ixgbe")] { - use crate::ixgbe::IxgbeHalImpl; - pub struct IxgbeDriver; - register_net_driver!(IxgbeDriver, ax_driver_net::ixgbe::IxgbeNic); - impl DriverProbe for IxgbeDriver { - #[cfg(all(bus = "pci", feature = "bus-pci"))] - fn probe_pci( - root: &mut ax_driver_pci::PciRoot, - bdf: ax_driver_pci::DeviceFunction, - dev_info: &ax_driver_pci::DeviceFunctionInfo, - ) -> Option { - use ax_driver_net::ixgbe::{INTEL_82599, INTEL_VEND, IxgbeNic}; - if dev_info.vendor_id == INTEL_VEND && dev_info.device_id == INTEL_82599 { - // Intel 10Gb Network - info!("ixgbe PCI device found at {:?}", bdf); - - // Initialize the device - // These can be changed according to the requirments specified in the ixgbe init - // function. - const QN: u16 = 1; - const QS: usize = 1024; - let bar_info = root.bar_info(bdf, 0).unwrap(); - match bar_info { - ax_driver_pci::BarInfo::Memory { address, size, .. } => { - let ixgbe_nic = IxgbeNic::::init( - ax_hal::mem::phys_to_virt((address as usize).into()).into(), - size as usize, - ) - .expect("failed to initialize ixgbe device"); - return Some(AxDeviceEnum::from_net(ixgbe_nic)); - } - ax_driver_pci::BarInfo::IO { .. } => { - error!("ixgbe: BAR0 is of I/O type"); - return None; - } - } - } - None - } - } - } -} - -cfg_if::cfg_if! { - if #[cfg(net_dev = "fxmac")]{ - use ax_alloc::{UsageKind, global_allocator}; - use ax_hal::mem::PAGE_SIZE_4K; - - #[ax_crate_interface::impl_interface] - impl ax_driver_net::fxmac::KernelFunc for FXmacDriver { - fn virt_to_phys(addr: usize) -> usize { - ax_hal::mem::virt_to_phys(addr.into()).into() - } - - fn phys_to_virt(addr: usize) -> usize { - ax_hal::mem::phys_to_virt(addr.into()).into() - } - - fn dma_alloc_coherent(pages: usize) -> (usize, usize) { - let Ok(vaddr) = global_allocator().alloc_pages(pages, PAGE_SIZE_4K, UsageKind::Dma) - else { - error!("failed to alloc pages"); - return (0, 0); - }; - let paddr = ax_hal::mem::virt_to_phys((vaddr).into()); - debug!("alloc pages @ vaddr={:#x}, paddr={:#x}", vaddr, paddr); - (vaddr, paddr.as_usize()) - } - - fn dma_free_coherent(vaddr: usize, pages: usize) { - global_allocator().dealloc_pages(vaddr, pages, UsageKind::Dma); - } - - fn dma_request_irq(_irq: usize, _handler: fn(usize)) { - warn!("unimplemented dma_request_irq for fxmax"); - } - } - - register_net_driver!(FXmacDriver, ax_driver_net::fxmac::FXmacNic); - - pub struct FXmacDriver; - impl DriverProbe for FXmacDriver { - fn probe_global() -> Option { - info!("fxmac for phytiumpi probe global"); - ax_driver_net::fxmac::FXmacNic::init(0) - .ok() - .map(AxDeviceEnum::from_net) - } - } - } -} diff --git a/os/arceos/modules/axdriver/src/dummy.rs b/os/arceos/modules/axdriver/src/dummy.rs deleted file mode 100644 index 172043b95e..0000000000 --- a/os/arceos/modules/axdriver/src/dummy.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! Dummy types used if no device of a certain category is selected. - -#![allow(unused_imports)] -#![allow(dead_code)] - -use cfg_if::cfg_if; - -use super::prelude::*; - -cfg_if! { - if #[cfg(net_dev = "dummy")] { - use ax_driver_net::{EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr}; - - pub struct DummyNetDev; - pub struct DummyNetDrvier; - register_net_driver!(DummyNetDriver, DummyNetDev); - - impl BaseDriverOps for DummyNetDev { - fn device_type(&self) -> DeviceType { DeviceType::Net } - fn device_name(&self) -> &str { "dummy-net" } - } - - impl NetDriverOps for DummyNetDev { - fn mac_address(&self) -> EthernetAddress { unreachable!() } - fn can_transmit(&self) -> bool { false } - fn can_receive(&self) -> bool { false } - fn rx_queue_size(&self) -> usize { 0 } - fn tx_queue_size(&self) -> usize { 0 } - fn recycle_rx_buffer(&mut self, _: NetBufPtr) -> DevResult { Err(DevError::Unsupported) } - fn recycle_tx_buffers(&mut self) -> DevResult { Err(DevError::Unsupported) } - fn transmit(&mut self, _: NetBufPtr) -> DevResult { Err(DevError::Unsupported) } - fn receive(&mut self) -> DevResult { Err(DevError::Unsupported) } - fn alloc_tx_buffer(&mut self, _: usize) -> DevResult { Err(DevError::Unsupported) } - } - } -} - -cfg_if! { - if #[cfg(block_dev = "dummy")] { - pub struct DummyBlockDev; - pub struct DummyBlockDriver; - register_block_driver!(DummyBlockDriver, DummyBlockDev); - - impl BaseDriverOps for DummyBlockDev { - fn device_type(&self) -> DeviceType { - DeviceType::Block - } - fn device_name(&self) -> &str { - "dummy-block" - } - } - - impl BlockDriverOps for DummyBlockDev { - fn num_blocks(&self) -> u64 { - 0 - } - fn block_size(&self) -> usize { - 0 - } - fn read_block(&mut self, _: u64, _: &mut [u8]) -> DevResult { - Err(DevError::Unsupported) - } - fn write_block(&mut self, _: u64, _: &[u8]) -> DevResult { - Err(DevError::Unsupported) - } - fn flush(&mut self) -> DevResult { - Err(DevError::Unsupported) - } - } - } -} - -cfg_if! { - if #[cfg(display_dev = "dummy")] { - pub struct DummyDisplayDev; - pub struct DummyDisplayDriver; - register_display_driver!(DummyDisplayDriver, DummyDisplayDev); - - impl BaseDriverOps for DummyDisplayDev { - fn device_type(&self) -> DeviceType { - DeviceType::Display - } - fn device_name(&self) -> &str { - "dummy-display" - } - } - - impl DisplayDriverOps for DummyDisplayDev { - fn info(&self) -> ax_driver_display::DisplayInfo { - unreachable!() - } - fn fb(&self) -> ax_driver_display::FrameBuffer<'_> { - unreachable!() - } - fn need_flush(&self) -> bool { - false - } - fn flush(&mut self) -> DevResult { - Err(DevError::Unsupported) - } - } - } -} - -cfg_if! { - if #[cfg(input_dev = "dummy")] { - pub struct DummyInputDev; - pub struct DummyInputDriver; - register_input_driver!(DummyInputDriver, DummyInputDev); - - impl BaseDriverOps for DummyInputDev { - fn device_type(&self) -> DeviceType { - DeviceType::Input - } - fn device_name(&self) -> &str { - "dummy-input" - } - } - - impl InputDriverOps for DummyInputDev { - fn device_id(&self) -> InputDeviceId { - InputDeviceId { bus_type: 0, vendor: 0, product: 0, version: 0 } - } - fn physical_location(&self) -> &str { - "dummy" - } - fn unique_id(&self) -> &str { - "dummy" - } - fn get_event_bits(&mut self, _ty: EventType, _out: &mut [u8]) -> DevResult { - Err(DevError::Unsupported) - } - fn read_event(&mut self) -> DevResult { - Err(DevError::Unsupported) - } - } - } -} - -cfg_if! { - if #[cfg(vsock_dev = "dummy")] { - pub struct DummyVsockDev; - pub struct DummyVsockDriver; - register_vsock_driver!(DummyVsockDriver, DummyVsockDev); - - impl BaseDriverOps for DummyVsockDev { - fn device_type(&self) -> DeviceType { - DeviceType::Vsock - } - fn device_name(&self) -> &str { - "dummy-vsock" - } - } - - impl VsockDriverOps for DummyVsockDev { - fn guest_cid(&self) -> u64 { - unimplemented!() - } - fn listen(&mut self, _src_port: u32) { - unimplemented!() - } - fn connect(&mut self, _cid: VsockConnId) -> DevResult<()> { - Err(DevError::Unsupported) - } - fn send(&mut self, _cid: VsockConnId, _buf: &[u8]) -> DevResult { - Err(DevError::Unsupported) - } - fn recv(&mut self, _cid: VsockConnId, _buf: &mut [u8]) -> DevResult { - Err(DevError::Unsupported) - } - fn recv_avail(&mut self, _cid: VsockConnId) -> DevResult { - Err(DevError::Unsupported) - } - fn disconnect(&mut self, _cid: VsockConnId) -> DevResult<()> { - Err(DevError::Unsupported) - } - fn abort(&mut self, _cid: VsockConnId) -> DevResult<()> { - Err(DevError::Unsupported) - } - fn poll_event(&mut self) -> DevResult> { - Err(DevError::Unsupported) - } - } - } -} diff --git a/os/arceos/modules/axdriver/src/dyn_drivers/mod.rs b/os/arceos/modules/axdriver/src/dyn_drivers/mod.rs deleted file mode 100644 index acaad2d750..0000000000 --- a/os/arceos/modules/axdriver/src/dyn_drivers/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -use alloc::vec::Vec; - -pub fn probe_all_devices() -> Vec { - #[cfg(target_os = "none")] - { - if let Err(err) = axplat_dyn::drivers::probe_all_devices() { - error!("failed to probe dynamic platform devices: {err:?}"); - return Vec::new(); - } - - #[allow(unused_mut)] - let mut devices = Vec::new(); - - #[cfg(feature = "block")] - for dev in axplat_dyn::drivers::take_block_devices() { - devices.push(super::AxDeviceEnum::Block(dev)); - } - - #[cfg(feature = "net")] - for dev in axplat_dyn::drivers::take_net_devices() { - devices.push(super::AxDeviceEnum::Net(dev)); - } - - devices - } - #[cfg(not(target_os = "none"))] - Vec::new() -} diff --git a/os/arceos/modules/axdriver/src/ixgbe.rs b/os/arceos/modules/axdriver/src/ixgbe.rs deleted file mode 100644 index 3d34f76e5b..0000000000 --- a/os/arceos/modules/axdriver/src/ixgbe.rs +++ /dev/null @@ -1,40 +0,0 @@ -use core::{alloc::Layout, ptr::NonNull}; - -use ax_dma::{BusAddr, DMAInfo, alloc_coherent, dealloc_coherent}; -use ax_driver_net::ixgbe::{IxgbeHal, PhysAddr as IxgbePhysAddr}; -use ax_hal::mem::{phys_to_virt, virt_to_phys}; - -pub struct IxgbeHalImpl; - -unsafe impl IxgbeHal for IxgbeHalImpl { - fn dma_alloc(size: usize) -> (IxgbePhysAddr, NonNull) { - let layout = Layout::from_size_align(size, 8).unwrap(); - match unsafe { alloc_coherent(layout) } { - Ok(dma_info) => (dma_info.bus_addr.as_u64() as usize, dma_info.cpu_addr), - Err(_) => (0, NonNull::dangling()), - } - } - - unsafe fn dma_dealloc(paddr: IxgbePhysAddr, vaddr: NonNull, size: usize) -> i32 { - let layout = Layout::from_size_align(size, 8).unwrap(); - let dma_info = DMAInfo { - cpu_addr: vaddr, - bus_addr: BusAddr::from(paddr as u64), - }; - unsafe { dealloc_coherent(dma_info, layout) }; - 0 - } - - unsafe fn mmio_phys_to_virt(paddr: IxgbePhysAddr, _size: usize) -> NonNull { - NonNull::new(phys_to_virt(paddr.into()).as_mut_ptr()).unwrap() - } - - unsafe fn mmio_virt_to_phys(vaddr: NonNull, _size: usize) -> IxgbePhysAddr { - virt_to_phys((vaddr.as_ptr() as usize).into()).into() - } - - fn wait_until(duration: core::time::Duration) -> Result<(), &'static str> { - ax_hal::time::busy_wait_until(duration); - Ok(()) - } -} diff --git a/os/arceos/modules/axdriver/src/lib.rs b/os/arceos/modules/axdriver/src/lib.rs deleted file mode 100644 index 7dd34bca74..0000000000 --- a/os/arceos/modules/axdriver/src/lib.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! [ArceOS](https://github.com/arceos-org/arceos) device drivers. -//! -//! # Usage -//! -//! All detected devices are composed into a large struct [`AllDevices`] -//! and returned by the [`init_drivers`] function. The upperlayer subsystems -//! (e.g., the network stack) may unpack the struct to get the specified device -//! driver they want. -//! -//! For each device category (i.e., net, block, display, etc.), an unified type -//! is used to represent all devices in that category. Currently, there are 3 -//! categories: [`AxNetDevice`], [`AxBlockDevice`], and [`AxDisplayDevice`]. -//! -//! # Concepts -//! -//! This crate supports two device models depending on the `dyn` feature: -//! -//! - **Static**: The type of all devices is static, it is determined at compile -//! time by corresponding cargo features. For example, [`AxNetDevice`] will be -//! an alias of [`VirtioNetDev`] if the `virtio-net` feature is enabled. This -//! model provides the best performance as it avoids dynamic dispatch. But on -//! limitation, only one device instance is supported for each device category. -//! - **Dynamic**: All device instance is using [trait objects] and wrapped in a -//! `Box`. For example, [`AxNetDevice`] will be [`Box`]. -//! When call a method provided by the device, it uses [dynamic dispatch][dyn] -//! that may introduce a little overhead. But on the other hand, it is more -//! flexible, multiple instances of each device category are supported. -//! -//! # Supported Devices -//! -//! | Device Category | Cargo Feature | Description | -//! |-|-|-| -//! | Block | `ramdisk` | A RAM disk that stores data in a vector | -//! | Block | `virtio-blk` | VirtIO block device | -//! | Network | `virtio-net` | VirtIO network device | -//! | Display | `virtio-gpu` | VirtIO graphics device | -//! -//! # Other Cargo Features -//! -//! - `dyn`: use the dynamic device model (see above). -//! - `bus-mmio`: use device tree to probe all MMIO devices. -//! - `bus-pci`: use PCI bus to probe all PCI devices. This feature is -//! enabled by default. -//! - `virtio`: use VirtIO devices. This is enabled if any of `virtio-blk`, -//! `virtio-net` or `virtio-gpu` is enabled. -//! - `net`: use network devices. This is enabled if any feature of network -//! devices is selected. If this feature is enabled without any network device -//! features, a dummy struct is used for [`AxNetDevice`]. -//! - `block`: use block storage devices. Similar to the `net` feature. -//! - `display`: use graphics display devices. Similar to the `net` feature. -//! -//! [`VirtioNetDev`]: ax_driver_virtio::VirtIoNetDev -//! [`Box`]: ax_driver_net::NetDriverOps -//! [trait objects]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html -//! [dyn]: https://doc.rust-lang.org/std/keyword.dyn.html - -#![no_std] -#![cfg_attr(virtio_dev, feature(associated_type_defaults))] - -#[macro_use] -extern crate log; - -#[cfg(feature = "dyn")] -extern crate alloc; - -#[macro_use] -mod macros; - -#[cfg(not(feature = "dyn"))] -mod bus; -mod drivers; -mod dummy; -mod structs; - -#[cfg(virtio_dev)] -mod virtio; - -#[cfg(feature = "ixgbe")] -mod ixgbe; - -#[cfg(feature = "dyn")] -mod dyn_drivers; - -pub mod prelude; - -#[cfg(feature = "block")] -pub use ax_driver_block::partition::{ - PartitionBlockDevice, PartitionInfo, PartitionRegion, PartitionTable, PartitionTableKind, - scan_partitions, -}; - -#[allow(unused_imports)] -use self::prelude::*; -#[cfg(feature = "block")] -pub use self::structs::AxBlockDevice; -#[cfg(feature = "display")] -pub use self::structs::AxDisplayDevice; -#[cfg(feature = "net")] -pub use self::structs::AxNetDevice; -pub use self::structs::{AxDeviceContainer, AxDeviceEnum}; - -/// A structure that contains all device drivers, organized by their category. -#[derive(Default)] -pub struct AllDevices { - /// All network device drivers. - #[cfg(feature = "net")] - pub net: AxDeviceContainer, - /// All block device drivers. - #[cfg(feature = "block")] - pub block: AxDeviceContainer, - /// All graphics device drivers. - #[cfg(feature = "display")] - pub display: AxDeviceContainer, - /// All input device drivers. - #[cfg(feature = "input")] - pub input: AxDeviceContainer, - /// All vsock device drivers. - #[cfg(feature = "vsock")] - pub vsock: AxDeviceContainer, -} - -impl AllDevices { - /// Returns the device model used, either `dyn` or `static`. - /// - /// See the [crate-level documentation](crate) for more details. - pub const fn device_model() -> &'static str { - if cfg!(feature = "dyn") { - "dyn" - } else { - "static" - } - } - - /// Probes all supported devices. - fn probe(&mut self) { - #[cfg(feature = "dyn")] - for dev in dyn_drivers::probe_all_devices() { - self.add_device(dev); - } - #[cfg(not(feature = "dyn"))] - { - for_each_drivers!(type Driver, { - if let Some(dev) = Driver::probe_global() { - info!( - "registered a new {:?} device: {:?}", - dev.device_type(), - dev.device_name(), - ); - self.add_device(dev); - } - }); - - #[cfg(any( - all(bus = "mmio", feature = "bus-mmio"), - all(bus = "pci", feature = "bus-pci") - ))] - self.probe_bus_devices(); - } - } - - /// Adds one device into the corresponding container, according to its device category. - #[allow(dead_code)] - fn add_device(&mut self, dev: AxDeviceEnum) { - match dev { - #[cfg(feature = "net")] - AxDeviceEnum::Net(dev) => self.net.push(dev), - #[cfg(feature = "block")] - AxDeviceEnum::Block(dev) => self.block.push(dev), - #[cfg(feature = "display")] - AxDeviceEnum::Display(dev) => self.display.push(dev), - #[cfg(feature = "input")] - AxDeviceEnum::Input(dev) => self.input.push(dev), - #[cfg(feature = "vsock")] - AxDeviceEnum::Vsock(dev) => self.vsock.push(dev), - } - } -} - -/// Probes and initializes all device drivers, returns the [`AllDevices`] struct. -pub fn init_drivers() -> AllDevices { - info!("Initialize device drivers..."); - info!(" device model: {}", AllDevices::device_model()); - - let mut all_devs = AllDevices::default(); - all_devs.probe(); - - #[cfg(feature = "net")] - { - debug!("number of NICs: {}", all_devs.net.len()); - for (i, dev) in all_devs.net.iter().enumerate() { - assert_eq!(dev.device_type(), DeviceType::Net); - debug!(" NIC {}: {:?}", i, dev.device_name()); - } - } - #[cfg(feature = "block")] - { - debug!("number of block devices: {}", all_devs.block.len()); - for (i, dev) in all_devs.block.iter().enumerate() { - assert_eq!(dev.device_type(), DeviceType::Block); - debug!(" block device {}: {:?}", i, dev.device_name()); - } - } - #[cfg(feature = "display")] - { - debug!("number of graphics devices: {}", all_devs.display.len()); - for (i, dev) in all_devs.display.iter().enumerate() { - assert_eq!(dev.device_type(), DeviceType::Display); - debug!(" graphics device {}: {:?}", i, dev.device_name()); - } - } - #[cfg(feature = "input")] - { - debug!("number of input devices: {}", all_devs.input.len()); - for (i, dev) in all_devs.input.iter().enumerate() { - assert_eq!(dev.device_type(), DeviceType::Input); - debug!(" input device {}: {:?}", i, dev.device_name()); - } - } - #[cfg(feature = "vsock")] - { - debug!("number of vsock devices: {}", all_devs.vsock.len()); - for (i, dev) in all_devs.vsock.iter().enumerate() { - assert_eq!(dev.device_type(), DeviceType::Vsock); - debug!(" vsock device {}: {:?}", i, dev.device_name()); - } - } - - all_devs -} diff --git a/os/arceos/modules/axdriver/src/macros.rs b/os/arceos/modules/axdriver/src/macros.rs deleted file mode 100644 index 520f5b1391..0000000000 --- a/os/arceos/modules/axdriver/src/macros.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! TODO: generate registered drivers in `for_each_drivers!` automatically. - -#![allow(unused_macros)] - -macro_rules! register_net_driver { - ($driver_type:ty, $device_type:ty) => { - /// The unified type of the NIC devices. - #[cfg(not(feature = "dyn"))] - pub type AxNetDevice = $device_type; - }; -} - -macro_rules! register_block_driver { - ($driver_type:ty, $device_type:ty) => { - /// The unified type of the NIC devices. - #[cfg(not(feature = "dyn"))] - pub type AxBlockDevice = $device_type; - }; -} - -macro_rules! register_display_driver { - ($driver_type:ty, $device_type:ty) => { - /// The unified type of the NIC devices. - #[cfg(not(feature = "dyn"))] - pub type AxDisplayDevice = $device_type; - }; -} - -macro_rules! register_input_driver { - ($driver_type:ty, $device_type:ty) => { - /// The unified type of the NIC devices. - #[cfg(not(feature = "dyn"))] - pub type AxInputDevice = $device_type; - }; -} - -macro_rules! register_vsock_driver { - ($driver_type:ty, $device_type:ty) => { - /// The unified type of the NIC devices. - #[cfg(not(feature = "dyn"))] - pub type AxVsockDevice = $device_type; - }; -} - -macro_rules! for_each_drivers { - (type $drv_type:ident, $code:block) => {{ - #[allow(unused_imports)] - use crate::drivers::DriverProbe; - #[cfg(virtio_dev)] - #[allow(unused_imports)] - use crate::virtio::{self, VirtIoDevMeta}; - - #[cfg(net_dev = "virtio-net")] - { - type $drv_type = ::Driver; - $code - } - #[cfg(block_dev = "virtio-blk")] - { - type $drv_type = ::Driver; - $code - } - #[cfg(display_dev = "virtio-gpu")] - { - type $drv_type = ::Driver; - $code - } - #[cfg(input_dev = "virtio-input")] - { - type $drv_type = ::Driver; - $code - } - #[cfg(vsock_dev = "virtio-socket")] - { - type $drv_type = ::Driver; - $code - } - #[cfg(block_dev = "ramdisk")] - { - type $drv_type = crate::drivers::RamDiskDriver; - $code - } - #[cfg(block_dev = "sdmmc")] - { - type $drv_type = crate::drivers::SdMmcDriver; - $code - } - #[cfg(block_dev = "cvsd")] - { - type $drv_type = crate::drivers::CvsdMmc; - $code - } - #[cfg(block_dev = "bcm2835-sdhci")] - { - type $drv_type = crate::drivers::BcmSdhciDriver; - $code - } - #[cfg(net_dev = "ixgbe")] - { - type $drv_type = crate::drivers::IxgbeDriver; - $code - } - #[cfg(net_dev = "fxmac")] - { - type $drv_type = crate::drivers::FXmacDriver; - $code - } - }}; -} diff --git a/os/arceos/modules/axdriver/src/prelude.rs b/os/arceos/modules/axdriver/src/prelude.rs deleted file mode 100644 index f48f4fdd4f..0000000000 --- a/os/arceos/modules/axdriver/src/prelude.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Device driver prelude that includes some traits and types. - -pub use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -#[cfg(feature = "block")] -pub use { - crate::structs::AxBlockDevice, - ax_driver_block::{ - BlockDriverOps, - partition::{ - PartitionBlockDevice, PartitionInfo, PartitionRegion, PartitionTable, - PartitionTableKind, scan_partitions, - }, - }, -}; -#[cfg(feature = "display")] -pub use { - crate::structs::AxDisplayDevice, - ax_driver_display::{DisplayDriverOps, DisplayInfo}, -}; -#[cfg(feature = "input")] -pub use { - crate::structs::AxInputDevice, - ax_driver_input::{AbsInfo, Event, EventType, InputDeviceId, InputDriverOps}, -}; -#[cfg(feature = "net")] -pub use { - crate::structs::AxNetDevice, - ax_driver_net::{NetBufPtr, NetDriverOps, NetIrqEvent}, -}; -#[cfg(feature = "vsock")] -pub use { - crate::structs::AxVsockDevice, - ax_driver_vsock::{VsockAddr, VsockConnId, VsockDriverEvent, VsockDriverOps}, -}; diff --git a/os/arceos/modules/axdriver/src/structs/dyn.rs b/os/arceos/modules/axdriver/src/structs/dyn.rs deleted file mode 100644 index 3acba71584..0000000000 --- a/os/arceos/modules/axdriver/src/structs/dyn.rs +++ /dev/null @@ -1,51 +0,0 @@ -use alloc::boxed::Box; - -use crate::prelude::*; - -/// The unified type of the NIC devices. -#[cfg(feature = "net")] -pub type AxNetDevice = Box; -/// The unified type of the block storage devices. -#[cfg(feature = "block")] -pub type AxBlockDevice = Box; -/// The unified type of the graphics display devices. -#[cfg(feature = "display")] -pub type AxDisplayDevice = Box; -/// The unified type of the input devices. -#[cfg(feature = "input")] -pub type AxInputDevice = Box; -/// The unified type of the vsock devices. -#[cfg(feature = "vsock")] -pub type AxVsockDevice = Box; - -impl super::AxDeviceEnum { - /// Constructs a network device. - #[cfg(feature = "net")] - pub fn from_net(dev: impl NetDriverOps + 'static) -> Self { - Self::Net(Box::new(dev)) - } - - /// Constructs a block device. - #[cfg(feature = "block")] - pub fn from_block(dev: impl BlockDriverOps + 'static) -> Self { - Self::Block(Box::new(dev)) - } - - /// Constructs a display device. - #[cfg(feature = "display")] - pub fn from_display(dev: impl DisplayDriverOps + 'static) -> Self { - Self::Display(Box::new(dev)) - } - - /// Constructs an input device. - #[cfg(feature = "input")] - pub fn from_input(dev: impl InputDriverOps + 'static) -> Self { - Self::Input(Box::new(dev)) - } - - /// Constructs a vsock device. - #[cfg(feature = "vsock")] - pub fn from_vsock(dev: impl VsockDriverOps + 'static) -> Self { - Self::Vsock(Box::new(dev)) - } -} diff --git a/os/arceos/modules/axdriver/src/structs/mod.rs b/os/arceos/modules/axdriver/src/structs/mod.rs deleted file mode 100644 index 793df5fed3..0000000000 --- a/os/arceos/modules/axdriver/src/structs/mod.rs +++ /dev/null @@ -1,106 +0,0 @@ -#![allow(unused_imports)] - -use core::ops::{Deref, DerefMut}; - -use ax_driver_base::{BaseDriverOps, DeviceType}; -use smallvec::SmallVec; - -#[cfg_attr(feature = "dyn", path = "dyn.rs")] -#[cfg_attr(not(feature = "dyn"), path = "static.rs")] -mod imp; - -pub use imp::*; - -/// A unified enum that represents different categories of devices. -#[allow(clippy::large_enum_variant)] -pub enum AxDeviceEnum { - /// Network card device. - #[cfg(feature = "net")] - Net(AxNetDevice), - /// Block storage device. - #[cfg(feature = "block")] - Block(AxBlockDevice), - /// Graphic display device. - #[cfg(feature = "display")] - Display(AxDisplayDevice), - /// Graphic input device. - #[cfg(feature = "input")] - Input(AxInputDevice), - /// Vsock device. - #[cfg(feature = "vsock")] - Vsock(AxVsockDevice), -} - -impl BaseDriverOps for AxDeviceEnum { - #[inline] - #[allow(unreachable_patterns)] - fn device_type(&self) -> DeviceType { - match self { - #[cfg(feature = "net")] - Self::Net(_) => DeviceType::Net, - #[cfg(feature = "block")] - Self::Block(_) => DeviceType::Block, - #[cfg(feature = "display")] - Self::Display(_) => DeviceType::Display, - #[cfg(feature = "input")] - Self::Input(_) => DeviceType::Input, - #[cfg(feature = "vsock")] - Self::Vsock(_) => DeviceType::Vsock, - _ => unreachable!(), - } - } - - #[inline] - #[allow(unreachable_patterns)] - fn device_name(&self) -> &str { - match self { - #[cfg(feature = "net")] - Self::Net(dev) => dev.device_name(), - #[cfg(feature = "block")] - Self::Block(dev) => dev.device_name(), - #[cfg(feature = "display")] - Self::Display(dev) => dev.device_name(), - #[cfg(feature = "input")] - Self::Input(dev) => dev.device_name(), - #[cfg(feature = "vsock")] - Self::Vsock(dev) => dev.device_name(), - _ => unreachable!(), - } - } -} - -/// A structure that contains all device drivers of a certain category. -pub struct AxDeviceContainer(SmallVec<[D; 1]>); - -impl AxDeviceContainer { - /// Constructs the container from one device. - pub fn from_one(dev: D) -> Self { - Self(SmallVec::from_buf([dev])) - } - - /// Takes one device out of the container (will remove it from the - /// container). - pub fn take_one(&mut self) -> Option { - self.0.pop() - } -} - -impl Deref for AxDeviceContainer { - type Target = SmallVec<[D; 1]>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for AxDeviceContainer { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for AxDeviceContainer { - fn default() -> Self { - Self(Default::default()) - } -} diff --git a/os/arceos/modules/axdriver/src/structs/static.rs b/os/arceos/modules/axdriver/src/structs/static.rs deleted file mode 100644 index c4e87066b1..0000000000 --- a/os/arceos/modules/axdriver/src/structs/static.rs +++ /dev/null @@ -1,42 +0,0 @@ -#[cfg(feature = "block")] -pub use crate::drivers::AxBlockDevice; -#[cfg(feature = "display")] -pub use crate::drivers::AxDisplayDevice; -#[cfg(feature = "input")] -pub use crate::drivers::AxInputDevice; -#[cfg(feature = "net")] -pub use crate::drivers::AxNetDevice; -#[cfg(feature = "vsock")] -pub use crate::drivers::AxVsockDevice; - -impl super::AxDeviceEnum { - /// Constructs a network device. - #[cfg(feature = "net")] - pub const fn from_net(dev: AxNetDevice) -> Self { - Self::Net(dev) - } - - /// Constructs a block device. - #[cfg(feature = "block")] - pub const fn from_block(dev: AxBlockDevice) -> Self { - Self::Block(dev) - } - - /// Constructs a display device. - #[cfg(feature = "display")] - pub const fn from_display(dev: AxDisplayDevice) -> Self { - Self::Display(dev) - } - - /// Constructs a display device. - #[cfg(feature = "input")] - pub const fn from_input(dev: AxInputDevice) -> Self { - Self::Input(dev) - } - - /// Constructs a vsock device. - #[cfg(feature = "vsock")] - pub const fn from_vsock(dev: AxVsockDevice) -> Self { - Self::Vsock(dev) - } -} diff --git a/os/arceos/modules/axdriver/src/virtio.rs b/os/arceos/modules/axdriver/src/virtio.rs deleted file mode 100644 index 89f6b1543d..0000000000 --- a/os/arceos/modules/axdriver/src/virtio.rs +++ /dev/null @@ -1,305 +0,0 @@ -use core::marker::PhantomData; -#[cfg(virtio_dev)] -use core::ptr::NonNull; - -#[cfg(virtio_dev)] -use ax_alloc::{UsageKind, global_allocator}; -use ax_driver_base::{BaseDriverOps, DevResult, DeviceType}; -#[cfg(virtio_dev)] -use ax_driver_virtio::{BufferDirection, PhysAddr, VirtIoHal}; -#[cfg(virtio_dev)] -use ax_hal::mem::{phys_to_virt, virt_to_phys}; -use cfg_if::cfg_if; - -use crate::{AxDeviceEnum, drivers::DriverProbe}; - -cfg_if! { - if #[cfg(bus = "pci")] { - #[cfg(feature = "bus-pci")] - use ax_driver_virtio::pci::{ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, PciRoot}; - type VirtIoTransport = ax_driver_virtio::PciTransport; - } else if #[cfg(bus = "mmio")] { - type VirtIoTransport = ax_driver_virtio::MmioTransport; - } else { - type VirtIoTransport = ax_driver_virtio::MmioTransport; - } -} - -/// A trait for VirtIO device meta information. -pub trait VirtIoDevMeta { - const DEVICE_TYPE: DeviceType; - - type Device: BaseDriverOps; - type Driver = VirtIoDriver; - - fn try_new(transport: VirtIoTransport, irq: Option) -> DevResult; -} - -cfg_if! { - if #[cfg(net_dev = "virtio-net")] { - #[allow(dead_code)] - pub struct VirtIoNet; - - impl VirtIoDevMeta for VirtIoNet { - const DEVICE_TYPE: DeviceType = DeviceType::Net; - type Device = ax_driver_virtio::VirtIoNetDev; - - fn try_new(transport: VirtIoTransport, irq: Option) -> DevResult { - Ok(AxDeviceEnum::from_net(Self::Device::try_new(transport, irq)?)) - } - } - } -} - -cfg_if! { - if #[cfg(block_dev = "virtio-blk")] { - #[allow(dead_code)] - pub struct VirtIoBlk; - - impl VirtIoDevMeta for VirtIoBlk { - const DEVICE_TYPE: DeviceType = DeviceType::Block; - type Device = ax_driver_virtio::VirtIoBlkDev; - - fn try_new(transport: VirtIoTransport, _irq: Option) -> DevResult { - Ok(AxDeviceEnum::from_block(Self::Device::try_new(transport)?)) - } - } - } -} - -cfg_if! { - if #[cfg(display_dev = "virtio-gpu")] { - pub struct VirtIoGpu; - - impl VirtIoDevMeta for VirtIoGpu { - const DEVICE_TYPE: DeviceType = DeviceType::Display; - type Device = ax_driver_virtio::VirtIoGpuDev; - - fn try_new(transport: VirtIoTransport, _irq: Option) -> DevResult { - Ok(AxDeviceEnum::from_display(Self::Device::try_new(transport)?)) - } - } - } -} - -cfg_if! { - if #[cfg(input_dev = "virtio-input")] { - pub struct VirtIoInput; - - impl VirtIoDevMeta for VirtIoInput { - const DEVICE_TYPE: DeviceType = DeviceType::Input; - type Device = ax_driver_virtio::VirtIoInputDev; - - fn try_new(transport: VirtIoTransport, irq: Option) -> DevResult { - Ok(AxDeviceEnum::from_input(Self::Device::try_new(transport, irq)?)) - } - } - } -} - -cfg_if! { - if #[cfg(vsock_dev = "virtio-socket")] { - pub struct VirtIoSocket; - - impl VirtIoDevMeta for VirtIoSocket { - const DEVICE_TYPE: DeviceType = DeviceType::Vsock; - type Device = ax_driver_virtio::VirtIoSocketDev; - - fn try_new(transport: VirtIoTransport, _irq: Option) -> DevResult { - Ok(AxDeviceEnum::from_vsock(Self::Device::try_new(transport)?)) - } - } - } -} - -/// A common driver for all VirtIO devices that implements [`DriverProbe`]. -pub struct VirtIoDriver(PhantomData); - -impl DriverProbe for VirtIoDriver { - #[cfg(bus = "mmio")] - fn probe_mmio(mmio_base: usize, mmio_size: usize) -> Option { - let base_vaddr = phys_to_virt(mmio_base.into()); - if let Some((ty, transport)) = - ax_driver_virtio::probe_mmio_device(base_vaddr.as_mut_ptr(), mmio_size) - && ty == D::DEVICE_TYPE - { - match D::try_new(transport, None) { - Ok(dev) => return Some(dev), - Err(e) => { - warn!( - "failed to initialize MMIO device at [PA:{:#x}, PA:{:#x}): {:?}", - mmio_base, - mmio_base + mmio_size, - e - ); - return None; - } - } - } - None - } - - #[cfg(all(bus = "pci", feature = "bus-pci"))] - fn probe_pci( - root: &mut PciRoot, - bdf: DeviceFunction, - dev_info: &DeviceFunctionInfo, - ) -> Option { - if dev_info.vendor_id != 0x1af4 { - return None; - } - match (D::DEVICE_TYPE, dev_info.device_id) { - (DeviceType::Net, 0x1000) | (DeviceType::Net, 0x1041) => {} - (DeviceType::Block, 0x1001) | (DeviceType::Block, 0x1042) => {} - (DeviceType::Input, 0x1052) => {} - (DeviceType::Display, 0x1050) => {} - (DeviceType::Vsock, 0x1053) => {} - _ => return None, - } - - if let Some((ty, transport)) = - ax_driver_virtio::probe_pci_device::(root, bdf, dev_info) - && ty == D::DEVICE_TYPE - { - let irq = pci_irq_vector(bdf); - match D::try_new(transport, Some(irq)) { - Ok(dev) => return Some(dev), - Err(e) => { - warn!("failed to initialize PCI device at {bdf}({dev_info}): {e:?}"); - return None; - } - } - } - None - } -} - -#[cfg(all(bus = "pci", feature = "bus-pci", target_arch = "x86_64"))] -fn pci_irq_vector(bdf: DeviceFunction) -> usize { - const PCI_INTERRUPT_REG: usize = 0x3c; - const IO_APIC_VECTOR_OFFSET: usize = 0x20; - - let fallback = pci_legacy_irq_fallback(bdf); - let config_offset = ((bdf.bus as usize) << 20) - | ((bdf.device as usize) << 15) - | ((bdf.function as usize) << 12) - | PCI_INTERRUPT_REG; - let config_addr = ax_config::devices::PCI_ECAM_BASE + config_offset; - let int_reg = - unsafe { (phys_to_virt(config_addr.into()).as_usize() as *const u32).read_volatile() }; - let line = (int_reg & 0xff) as usize; - let pin = ((int_reg >> 8) & 0xff) as usize; - - if (1..0x20).contains(&line) && pin != 0 { - let vector = IO_APIC_VECTOR_OFFSET + line; - debug!("PCI {bdf} INTx line {line}, pin {pin} -> vector {vector:#x}"); - vector - } else { - debug!("PCI {bdf} has INTx line {line}, pin {pin}; using fallback vector {fallback:#x}"); - fallback - } -} - -#[cfg(all(bus = "pci", feature = "bus-pci", not(target_arch = "x86_64")))] -fn pci_irq_vector(bdf: DeviceFunction) -> usize { - pci_legacy_irq_fallback(bdf) -} - -#[cfg(all(bus = "pci", feature = "bus-pci"))] -const fn pci_irq_base() -> usize { - #[cfg(target_arch = "x86_64")] - { - 0x20 - } - #[cfg(target_arch = "riscv64")] - { - 0x20 - } - #[cfg(target_arch = "loongarch64")] - { - 0x10 - } - #[cfg(target_arch = "aarch64")] - { - 0x23 - } -} - -#[cfg(all(bus = "pci", feature = "bus-pci"))] -const fn pci_legacy_irq_fallback(bdf: DeviceFunction) -> usize { - pci_irq_base() + (bdf.device & 3) as usize -} - -#[cfg(virtio_dev)] -pub struct VirtIoHalImpl; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct HalAddress { - addr: usize, -} - -impl HalAddress { - const fn new(addr: usize) -> Self { - Self { addr } - } - - const fn addr(self) -> usize { - self.addr - } -} - -#[inline] -fn nonnull_from_addr(addr: usize, context: &str) -> NonNull { - assert_ne!(addr, 0, "{context} returned a null address"); - // SAFETY: The assertion above guarantees the pointer is non-null. - unsafe { NonNull::new_unchecked(addr as *mut u8) } -} - -#[inline] -fn nonnull_from_hal_address(addr: HalAddress, context: &str) -> NonNull { - nonnull_from_addr(addr.addr(), context) -} - -#[inline] -fn hal_phys_to_virt_addr(paddr: PhysAddr) -> HalAddress { - HalAddress::new(phys_to_virt((paddr as usize).into()).as_mut_ptr() as usize) -} - -#[inline] -fn hal_virt_to_phys_addr(vaddr: usize) -> PhysAddr { - virt_to_phys(vaddr.into()).as_usize() as PhysAddr -} - -unsafe impl VirtIoHal for VirtIoHalImpl { - fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { - let vaddr = if let Ok(vaddr) = global_allocator().alloc_pages(pages, 0x1000, UsageKind::Dma) - { - vaddr - } else { - return (0, NonNull::dangling()); - }; - let paddr = hal_virt_to_phys_addr(vaddr); - let ptr = nonnull_from_hal_address(HalAddress::new(vaddr), "dma_alloc"); - (paddr, ptr) - } - - unsafe fn dma_dealloc(_paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32 { - global_allocator().dealloc_pages(vaddr.as_ptr() as usize, pages, UsageKind::Dma); - 0 - } - - #[inline] - unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { - let vaddr = hal_phys_to_virt_addr(paddr); - nonnull_from_hal_address(vaddr, "mmio_phys_to_virt") - } - - #[inline] - unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { - let vaddr = buffer.as_ptr() as *mut u8 as usize; - hal_virt_to_phys_addr(vaddr) - } - - #[inline] - unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) {} -} diff --git a/os/arceos/modules/axfs-ng/Cargo.toml b/os/arceos/modules/axfs-ng/Cargo.toml index f361fd6ae3..868e4c9695 100644 --- a/os/arceos/modules/axfs-ng/Cargo.toml +++ b/os/arceos/modules/axfs-ng/Cargo.toml @@ -17,7 +17,6 @@ std = ["lwext4_rust?/std"] [dependencies] ax-alloc = { workspace = true, features = ["default"] } -ax-driver = { workspace = true, features = ["block"] } ax-errno = { workspace = true } axfs-ng-vfs = { workspace = true } ax-hal = { workspace = true } @@ -31,6 +30,7 @@ intrusive-collections = "0.9" ax-kspin = { workspace = true } log = { workspace = true } lru = "0.16" +rd-block-volume = { workspace = true } scope-local = { workspace = true } slab = { version = "0.4", default-features = false } spin = { workspace = true } diff --git a/os/arceos/modules/axfs-ng/src/block.rs b/os/arceos/modules/axfs-ng/src/block.rs new file mode 100644 index 0000000000..3cd8124563 --- /dev/null +++ b/os/arceos/modules/axfs-ng/src/block.rs @@ -0,0 +1,160 @@ +use alloc::boxed::Box; + +#[cfg(any(feature = "ext4", feature = "fat"))] +use ax_errno::AxError; +use ax_errno::AxResult; +use rd_block_volume::{BlockReader, Error as VolumeError}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct BlockRegion { + pub start_lba: u64, + pub end_lba: u64, +} + +impl BlockRegion { + pub const fn from_num_blocks(num_blocks: u64) -> Self { + Self { + start_lba: 0, + end_lba: num_blocks, + } + } + + pub const fn new(start_lba: u64, num_blocks: u64) -> Self { + Self { + start_lba, + end_lba: start_lba.saturating_add(num_blocks), + } + } + + pub const fn num_blocks(self) -> u64 { + self.end_lba.saturating_sub(self.start_lba) + } +} + +pub trait FsBlockDevice: Send { + fn name(&self) -> &str; + fn num_blocks(&self) -> u64; + fn block_size(&self) -> usize; + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult; + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult; + fn flush(&mut self) -> AxResult; +} + +impl FsBlockDevice for Box { + fn name(&self) -> &str { + (**self).name() + } + + fn num_blocks(&self) -> u64 { + (**self).num_blocks() + } + + fn block_size(&self) -> usize { + (**self).block_size() + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { + (**self).read_block(block_id, buf) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { + (**self).write_block(block_id, buf) + } + + fn flush(&mut self) -> AxResult { + (**self).flush() + } +} + +#[cfg(any(feature = "ext4", feature = "fat"))] +pub struct RegionBlockDevice { + inner: T, + region: BlockRegion, +} + +#[cfg(any(feature = "ext4", feature = "fat"))] +impl RegionBlockDevice { + pub const fn new(inner: T, region: BlockRegion) -> Self { + Self { inner, region } + } + + fn check_io_bounds(&self, block_id: u64, buf_len: usize) -> AxResult { + let block_size = self.inner.block_size(); + if block_size == 0 || !buf_len.is_multiple_of(block_size) { + return Err(AxError::InvalidInput); + } + + let blocks = u64::try_from(buf_len / block_size).map_err(|_| AxError::BadState)?; + let end_block = block_id.checked_add(blocks).ok_or(AxError::BadState)?; + if end_block > self.num_blocks() { + return Err(AxError::InvalidInput); + } + + Ok(()) + } +} + +#[cfg(any(feature = "ext4", feature = "fat"))] +impl FsBlockDevice for RegionBlockDevice { + fn name(&self) -> &str { + self.inner.name() + } + + fn num_blocks(&self) -> u64 { + self.region.num_blocks() + } + + fn block_size(&self) -> usize { + self.inner.block_size() + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { + self.check_io_bounds(block_id, buf.len())?; + let physical = self + .region + .start_lba + .checked_add(block_id) + .ok_or(AxError::BadState)?; + self.inner.read_block(physical, buf) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { + self.check_io_bounds(block_id, buf.len())?; + let physical = self + .region + .start_lba + .checked_add(block_id) + .ok_or(AxError::BadState)?; + self.inner.write_block(physical, buf) + } + + fn flush(&mut self) -> AxResult { + self.inner.flush() + } +} + +pub struct VolumeReader<'a, T: FsBlockDevice + ?Sized> { + inner: &'a mut T, +} + +impl<'a, T: FsBlockDevice + ?Sized> VolumeReader<'a, T> { + pub const fn new(inner: &'a mut T) -> Self { + Self { inner } + } +} + +impl BlockReader for VolumeReader<'_, T> { + fn block_size(&self) -> usize { + self.inner.block_size() + } + + fn num_blocks(&self) -> u64 { + self.inner.num_blocks() + } + + fn read_block(&mut self, block: u64, buf: &mut [u8]) -> rd_block_volume::Result<()> { + self.inner + .read_block(block, buf) + .map_err(|_| VolumeError::Reader) + } +} diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs b/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs index c54d2d6def..f9318efaf6 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs +++ b/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs @@ -1,7 +1,6 @@ -use alloc::sync::Arc; +use alloc::{boxed::Box, sync::Arc}; use core::cell::OnceCell; -use ax_driver::{AxBlockDevice, PartitionRegion}; use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; use axfs_ng_vfs::{ DirEntry, DirNode, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, @@ -12,6 +11,7 @@ use super::{ Ext4Disk, Inode, util::{LwExt4Filesystem, into_vfs_err}, }; +use crate::block::{BlockRegion, FsBlockDevice}; const EXT4_CONFIG: FsConfig = FsConfig { bcache_size: 256 }; @@ -21,7 +21,7 @@ pub struct Ext4Filesystem { } impl Ext4Filesystem { - pub fn new(dev: AxBlockDevice, region: PartitionRegion) -> VfsResult { + pub fn new(dev: Box, region: BlockRegion) -> VfsResult { let ext4 = lwext4_rust::Ext4Filesystem::new(Ext4Disk::new(dev, region), EXT4_CONFIG) .map_err(into_vfs_err)?; diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs b/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs index baf3e1d929..bf4c048456 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs +++ b/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs @@ -1,17 +1,20 @@ +use alloc::boxed::Box; + mod fs; mod inode; mod util; -use ax_driver::{AxBlockDevice, PartitionBlockDevice, PartitionRegion, prelude::BlockDriverOps}; pub use fs::*; pub use inode::*; use lwext4_rust::{BlockDevice, Ext4Error, Ext4Result, ffi::EIO}; -pub(crate) struct Ext4Disk(PartitionBlockDevice); +use crate::block::{BlockRegion, FsBlockDevice, RegionBlockDevice}; + +pub(crate) struct Ext4Disk(RegionBlockDevice>); impl Ext4Disk { - pub(crate) const fn new(dev: AxBlockDevice, region: PartitionRegion) -> Self { - Self(PartitionBlockDevice::new(dev, region)) + pub(crate) const fn new(dev: Box, region: BlockRegion) -> Self { + Self(RegionBlockDevice::new(dev, region)) } fn check_buffer_len(&self, buf_len: usize) -> Ext4Result<()> { diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs b/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs index 0c14a1ca89..2639600503 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs +++ b/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs @@ -1,7 +1,6 @@ use alloc::{boxed::Box, sync::Arc}; use core::cell::OnceCell; -use ax_driver::{AxBlockDevice, PartitionRegion, prelude::BlockDriverOps}; use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; use axfs_ng_vfs::{ DirEntry, DirNode, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, @@ -9,6 +8,7 @@ use axfs_ng_vfs::{ use rsext4::{Jbd2Dev, bmalloc::InodeNumber, superblock::Ext4Superblock}; use super::{Ext4Disk, Inode, util::into_vfs_err}; +use crate::block::{BlockRegion, FsBlockDevice}; const EXT4_ROOT_INO: u32 = 2; @@ -31,15 +31,14 @@ pub struct Ext4Filesystem { } impl Ext4Filesystem { - /// Create from a compile-time block device (e.g. VirtIO root device). - pub fn new(dev: AxBlockDevice, region: PartitionRegion) -> VfsResult { - Self::new_from_boxed(Box::new(dev) as Box, region) + pub fn new(dev: Box, region: BlockRegion) -> VfsResult { + Self::new_from_boxed(dev, region) } /// Create from a dynamic (boxed) block device (e.g. loop device). pub fn new_from_boxed( - dev: Box, - region: PartitionRegion, + dev: Box, + region: BlockRegion, ) -> VfsResult { let mut dev = Jbd2Dev::initial_jbd2dev(0, Ext4Disk::new(dev, region), true); let fs = rsext4::mount(&mut dev).map_err(into_vfs_err)?; diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/mod.rs b/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/mod.rs index 9049e289f9..a699bacbfe 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/mod.rs +++ b/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/mod.rs @@ -4,7 +4,6 @@ mod util; use alloc::boxed::Box; -use ax_driver::{PartitionBlockDevice, PartitionRegion, prelude::BlockDriverOps}; pub use fs::*; pub use inode::*; use rsext4::{ @@ -15,11 +14,13 @@ use rsext4::{ error::{Ext4Error, Ext4Result}, }; -pub(crate) struct Ext4Disk(PartitionBlockDevice>); +use crate::block::{BlockRegion, FsBlockDevice, RegionBlockDevice}; + +pub(crate) struct Ext4Disk(RegionBlockDevice>); impl Ext4Disk { - pub fn new(dev: Box, region: PartitionRegion) -> Self { - Self(PartitionBlockDevice::new(dev, region)) + pub fn new(dev: Box, region: BlockRegion) -> Self { + Self(RegionBlockDevice::new(dev, region)) } } diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/disk.rs b/os/arceos/modules/axfs-ng/src/fs/fat/disk.rs index 797a7bc39f..72f093a6f9 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/disk.rs +++ b/os/arceos/modules/axfs-ng/src/fs/fat/disk.rs @@ -1,7 +1,9 @@ use alloc::{boxed::Box, vec}; use core::mem; -use ax_driver::{AxBlockDevice, PartitionBlockDevice, PartitionRegion, prelude::*}; +use ax_errno::{AxError as FsBlockError, AxResult as FsBlockResult}; + +use crate::block::{BlockRegion, FsBlockDevice, RegionBlockDevice}; fn take<'a>(buf: &mut &'a [u8], cnt: usize) -> &'a [u8] { let (first, rem) = buf.split_at(cnt); @@ -18,7 +20,7 @@ fn take_mut<'a>(buf: &mut &'a mut [u8], cnt: usize) -> &'a mut [u8] { /// A disk device with a cursor. pub struct SeekableDisk { - dev: PartitionBlockDevice, + dev: RegionBlockDevice>, block_id: u64, offset: usize, @@ -33,14 +35,14 @@ pub struct SeekableDisk { } impl SeekableDisk { - pub fn new(dev: AxBlockDevice, region: PartitionRegion) -> Self { + pub fn new(dev: Box, region: BlockRegion) -> Self { assert!(dev.block_size().is_power_of_two()); let block_size = dev.block_size(); let block_size_log2 = block_size.trailing_zeros() as u8; let read_buffer = vec![0u8; block_size].into_boxed_slice(); let write_buffer = vec![0u8; block_size].into_boxed_slice(); Self { - dev: PartitionBlockDevice::new(dev, region), + dev: RegionBlockDevice::new(dev, region), block_id: 0, offset: 0, block_size_log2, @@ -66,7 +68,7 @@ impl SeekableDisk { } /// Set the position of the cursor. - pub fn set_position(&mut self, pos: u64) -> DevResult<()> { + pub fn set_position(&mut self, pos: u64) -> FsBlockResult<()> { self.flush()?; self.block_id = pos >> self.block_size_log2; self.offset = pos as usize & (self.block_size() - 1); @@ -74,7 +76,7 @@ impl SeekableDisk { } /// Write all pending changes to the disk. - pub fn flush(&mut self) -> DevResult<()> { + pub fn flush(&mut self) -> FsBlockResult<()> { if self.write_buffer_dirty { self.dev.write_block(self.block_id, &self.write_buffer)?; self.write_buffer_dirty = false; @@ -82,7 +84,7 @@ impl SeekableDisk { Ok(()) } - fn read_partial(&mut self, buf: &mut &mut [u8]) -> DevResult { + fn read_partial(&mut self, buf: &mut &mut [u8]) -> FsBlockResult { self.flush()?; self.dev.read_block(self.block_id, &mut self.read_buffer)?; @@ -100,7 +102,7 @@ impl SeekableDisk { } /// Read from the disk, returns the number of bytes read. - pub fn read(&mut self, mut buf: &mut [u8]) -> DevResult { + pub fn read(&mut self, mut buf: &mut [u8]) -> FsBlockResult { let mut read = 0; if self.offset != 0 { read += self.read_partial(&mut buf)?; @@ -115,7 +117,7 @@ impl SeekableDisk { self.block_id = self .block_id .checked_add(blocks as u64) - .ok_or(DevError::BadState)?; + .ok_or(FsBlockError::BadState)?; } if !buf.is_empty() { read += self.read_partial(&mut buf)?; @@ -124,7 +126,7 @@ impl SeekableDisk { Ok(read) } - fn write_partial(&mut self, buf: &mut &[u8]) -> DevResult { + fn write_partial(&mut self, buf: &mut &[u8]) -> FsBlockResult { if !self.write_buffer_dirty { self.dev.read_block(self.block_id, &mut self.write_buffer)?; self.write_buffer_dirty = true; @@ -145,7 +147,7 @@ impl SeekableDisk { } /// Write to the disk, returns the number of bytes written. - pub fn write(&mut self, mut buf: &[u8]) -> DevResult { + pub fn write(&mut self, mut buf: &[u8]) -> FsBlockResult { let mut written = 0; if self.offset != 0 { written += self.write_partial(&mut buf)?; @@ -160,7 +162,7 @@ impl SeekableDisk { self.block_id = self .block_id .checked_add(blocks as u64) - .ok_or(DevError::BadState)?; + .ok_or(FsBlockError::BadState)?; } if !buf.is_empty() { written += self.write_partial(&mut buf)?; diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/fs.rs b/os/arceos/modules/axfs-ng/src/fs/fat/fs.rs index 6c05965237..70f3daa3b2 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/fs.rs +++ b/os/arceos/modules/axfs-ng/src/fs/fat/fs.rs @@ -1,7 +1,6 @@ -use alloc::sync::Arc; +use alloc::{boxed::Box, sync::Arc}; use core::marker::PhantomPinned; -use ax_driver::{AxBlockDevice, PartitionRegion}; use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; use axfs_ng_vfs::{ DirEntry, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, @@ -9,6 +8,7 @@ use axfs_ng_vfs::{ use slab::Slab; use super::{dir::FatDirNode, disk::SeekableDisk, ff, util::into_vfs_err}; +use crate::block::{BlockRegion, FsBlockDevice}; pub struct FatFilesystemInner { pub inner: ff::FileSystem, @@ -32,7 +32,7 @@ pub struct FatFilesystem { } impl FatFilesystem { - pub fn new(dev: AxBlockDevice, region: PartitionRegion) -> VfsResult { + pub fn new(dev: Box, region: BlockRegion) -> VfsResult { let disk = SeekableDisk::new(dev, region); let mut inner = FatFilesystemInner { inner: ff::FileSystem::new(disk, fatfs::FsOptions::new()) diff --git a/os/arceos/modules/axfs-ng/src/fs/mod.rs b/os/arceos/modules/axfs-ng/src/fs/mod.rs index ddddd69039..12b725666c 100644 --- a/os/arceos/modules/axfs-ng/src/fs/mod.rs +++ b/os/arceos/modules/axfs-ng/src/fs/mod.rs @@ -1,11 +1,9 @@ -#[cfg(feature = "ext4")] use alloc::boxed::Box; -#[cfg(feature = "ext4")] -use ax_driver::prelude::BlockDriverOps; -use ax_driver::{AxBlockDevice, PartitionRegion}; use axfs_ng_vfs::{Filesystem, VfsResult}; +use crate::block::{BlockRegion, FsBlockDevice}; + cfg_if::cfg_if! { if #[cfg(feature = "ext4")] { mod ext4; @@ -16,7 +14,7 @@ cfg_if::cfg_if! { } else { struct DefaultFilesystem; impl DefaultFilesystem { - pub fn new(_dev: AxBlockDevice, _region: PartitionRegion) -> VfsResult { + pub fn new(_dev: Box, _region: BlockRegion) -> VfsResult { panic!("No filesystem feature enabled"); } } @@ -24,18 +22,15 @@ cfg_if::cfg_if! { } /// Create a filesystem instance from a block device. -pub fn new_default(dev: AxBlockDevice, region: PartitionRegion) -> VfsResult { +pub fn new_default(dev: Box, region: BlockRegion) -> VfsResult { DefaultFilesystem::new(dev, region) } -/// Create a filesystem instance from a dynamic (boxed) block device. +/// Create a filesystem instance from a boxed block device. /// -/// Use this for loop devices and other block backends that don't match -/// the compile-time `AxBlockDevice` type alias. +/// Use this for loop devices and other block backends created outside the +/// platform probe path. #[cfg(feature = "ext4")] -pub fn new_from_dyn( - dev: Box, - region: PartitionRegion, -) -> VfsResult { +pub fn new_from_dyn(dev: Box, region: BlockRegion) -> VfsResult { ext4::Ext4Filesystem::new_from_boxed(dev, region) } diff --git a/os/arceos/modules/axfs-ng/src/lib.rs b/os/arceos/modules/axfs-ng/src/lib.rs index 41fb8f7d02..0b9344f4df 100644 --- a/os/arceos/modules/axfs-ng/src/lib.rs +++ b/os/arceos/modules/axfs-ng/src/lib.rs @@ -12,93 +12,32 @@ extern crate alloc; #[macro_use] extern crate log; -use alloc::{ - format, - string::{String, ToString}, - vec::Vec, -}; - -use ax_driver::{ - AxBlockDevice, AxDeviceContainer, PartitionInfo, PartitionRegion, PartitionTableKind, - prelude::{BaseDriverOps, BlockDriverOps}, - scan_partitions, -}; +use alloc::{boxed::Box, vec::Vec}; +mod block; mod fs; - mod highlevel; +mod root; +pub use block::{BlockRegion, FsBlockDevice}; /// Create a filesystem from a dynamic (boxed) block device. #[cfg(feature = "ext4")] pub use fs::new_from_dyn as new_filesystem_from_dyn; pub use highlevel::*; - -#[derive(Debug, Default)] -struct RootSpec { - disk_index: Option, - partition_index: Option, - partuuid: Option, - partlabel: Option, -} - -struct RootCandidate { - disk_index: usize, - partition: Option, -} - -struct DiscoveredDisk { - disk_index: usize, - dev: AxBlockDevice, - partitions: Vec, -} - -#[derive(Clone)] -struct DetectedPartition { - info: PartitionInfo, - filesystem: Option, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum FilesystemKind { - #[cfg(feature = "ext4")] - Ext4, - #[cfg(feature = "fat")] - Fat, -} - -impl RootCandidate { - fn description(&self) -> String { - if let Some(partition) = &self.partition { - let name = partition.info.name.as_deref().unwrap_or(""); - let fs = partition - .filesystem - .map(filesystem_name) - .unwrap_or("unknown"); - format!( - "disk{} partition {} ({}, fs={}, lba {}..{})", - self.disk_index, - partition.info.index + 1, - name, - fs, - partition.info.region.start_lba, - partition.info.region.end_lba - ) - } else { - format!("disk{} raw device", self.disk_index) - } - } -} +use root::FilesystemKind; /// Initializes the filesystem subsystem by selecting a root device from the /// available block devices and optional boot arguments. -pub fn init_filesystems(mut block_devs: AxDeviceContainer, bootargs: Option<&str>) { +pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { info!("Initialize filesystem subsystem..."); - let root_spec = parse_root_spec(bootargs); - let mut disks = collect_disks(&mut block_devs); - let candidates = collect_root_candidates(&disks); - let (selected_disk_index, selected_partition) = select_root_candidate(&candidates, &root_spec) - .unwrap_or_else(|| panic!("failed to determine root device from available block devices")); + let root_spec = root::parse_root_spec(bootargs); + let mut disks = root::collect_disks(block_devs); + let candidates = root::collect_root_candidates(&disks); + let (selected_disk_index, selected_partition) = + root::select_root_candidate(&candidates, &root_spec).unwrap_or_else(|| { + panic!("failed to determine root device from available block devices") + }); let selected_disk_pos = disks .iter() .position(|disk| disk.disk_index == selected_disk_index) @@ -112,7 +51,7 @@ pub fn init_filesystems(mut block_devs: AxDeviceContainer, bootar .find(|partition| partition.info.index == part_index) }); ( - describe_selection(selected.disk_index, selected_partition_info), + root::describe_selection(selected.disk_index, selected_partition_info), selected_partition_info .map_or_else(|| full_region(&selected.dev), |part| part.info.region), ) @@ -131,371 +70,7 @@ pub fn init_filesystems(mut block_devs: AxDeviceContainer, bootar ROOT_FS_CONTEXT.call_once(|| FsContext::new(mp.root_location())); } -fn collect_disks(block_devs: &mut AxDeviceContainer) -> Vec { - let mut disks = Vec::new(); - - for (disk_index, mut dev) in block_devs.drain(..).enumerate() { - let device_name = dev.device_name().to_string(); - match scan_partitions(&mut dev) { - Ok(table) if !table.partitions.is_empty() => { - let partitions: Vec = table - .partitions - .into_iter() - .map(|partition| { - let filesystem = detect_filesystem(&mut dev, partition.region); - info!( - " partition {} name={:?} fs={:?} lba {}..{}", - partition.index + 1, - partition.name, - filesystem, - partition.region.start_lba, - partition.region.end_lba - ); - DetectedPartition { - info: partition, - filesystem, - } - }) - .collect(); - info!( - " block device {} ({}) has {:?} partition table with {} partitions", - disk_index, - device_name, - table.kind, - partitions.len() - ); - disks.push(DiscoveredDisk { - disk_index, - dev, - partitions, - }); - } - Ok(table) => { - if table.kind == PartitionTableKind::None { - info!( - " block device {} ({}) has no partition table", - disk_index, device_name - ); - let raw_region = full_region(&dev); - let raw_fs = detect_filesystem(&mut dev, raw_region); - info!(" raw device fs={:?}", raw_fs); - disks.push(DiscoveredDisk { - disk_index, - dev, - partitions: Vec::new(), - }); - } else { - warn!( - " block device {} ({}) has a {:?} partition table but no usable \ - partitions; treating the whole disk as a candidate", - disk_index, device_name, table.kind - ); - let raw_region = full_region(&dev); - let raw_fs = detect_filesystem(&mut dev, raw_region); - info!(" raw device fs={:?}", raw_fs); - disks.push(DiscoveredDisk { - disk_index, - dev, - partitions: Vec::new(), - }); - } - } - Err(err) => { - warn!( - " failed to scan partitions on block device {} ({}): {err:?}", - disk_index, device_name - ); - } - } - } - - disks -} - -fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { - let mut candidates = Vec::new(); - - for disk in disks { - if disk.partitions.is_empty() { - candidates.push(RootCandidate { - disk_index: disk.disk_index, - partition: None, - }); - continue; - } - - for partition in &disk.partitions { - candidates.push(RootCandidate { - disk_index: disk.disk_index, - partition: Some(partition.clone()), - }); - } - } - - candidates -} - -fn select_root_candidate( - candidates: &[RootCandidate], - spec: &RootSpec, -) -> Option<(usize, Option)> { - if let Some(index) = select_explicit_root(candidates, spec) { - return Some(index); - } - - select_default_root(candidates) -} - -fn select_explicit_root( - candidates: &[RootCandidate], - spec: &RootSpec, -) -> Option<(usize, Option)> { - for candidate in candidates { - if let Some(partition) = candidate.partition.as_ref() { - if let Some(partuuid) = &spec.partuuid - && partition - .info - .part_uuid - .as_ref() - .is_some_and(|candidate_uuid| candidate_uuid.eq_ignore_ascii_case(partuuid)) - { - info!(" matched root by PARTUUID on {}", candidate.description()); - return Some((candidate.disk_index, Some(partition.info.index))); - } - - if let Some(partlabel) = &spec.partlabel - && partition.info.name.as_deref() == Some(partlabel.as_str()) - { - info!(" matched root by PARTLABEL on {}", candidate.description()); - return Some((candidate.disk_index, Some(partition.info.index))); - } - } - - if let Some(disk_index) = spec.disk_index - && candidate.disk_index == disk_index - { - match (spec.partition_index, &candidate.partition) { - (Some(partition_index), Some(partition)) - if partition.info.index == partition_index => - { - info!( - " matched root by device path on {}", - candidate.description() - ); - return Some((candidate.disk_index, Some(partition.info.index))); - } - (None, None) => { - info!( - " matched root by raw device path on {}", - candidate.description() - ); - return Some((candidate.disk_index, None)); - } - _ => {} - } - } - } - - if spec.disk_index.is_some() || spec.partuuid.is_some() || spec.partlabel.is_some() { - panic!("configured root device was not found in discovered block devices"); - } - - None -} - -fn select_default_root(candidates: &[RootCandidate]) -> Option<(usize, Option)> { - let rootfs_matches: Vec<_> = candidates - .iter() - .filter(|candidate| { - candidate - .partition - .as_ref() - .and_then(|part| part.info.name.as_deref()) - == Some("rootfs") - }) - .map(|candidate| { - ( - candidate.disk_index, - candidate.partition.as_ref().map(|part| part.info.index), - ) - }) - .collect(); - if rootfs_matches.len() == 1 { - info!(" falling back to PARTLABEL=rootfs"); - return rootfs_matches.into_iter().next(); - } - if rootfs_matches.len() > 1 { - panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); - } - - let partition_matches: Vec<_> = candidates - .iter() - .filter(|candidate| { - candidate.partition.as_ref().is_some_and(|partition| { - if !partition.filesystem.is_some() { - return false; - } - match partition.info.table_kind { - PartitionTableKind::Mbr => { - #[cfg(feature = "ext4")] - { - partition.info.bootable - && partition.filesystem == Some(FilesystemKind::Ext4) - } - #[cfg(all(not(feature = "ext4"), feature = "fat"))] - { - partition.filesystem == Some(FilesystemKind::Fat) - } - #[cfg(all(not(feature = "ext4"), not(feature = "fat")))] - { - false - } - } - PartitionTableKind::Gpt | PartitionTableKind::None => { - #[cfg(feature = "ext4")] - { - partition.filesystem == Some(FilesystemKind::Ext4) - } - #[cfg(all(not(feature = "ext4"), feature = "fat"))] - { - partition.filesystem == Some(FilesystemKind::Fat) - } - #[cfg(all(not(feature = "ext4"), not(feature = "fat")))] - { - false - } - } - } - }) - }) - .map(|candidate| { - ( - candidate.disk_index, - candidate.partition.as_ref().map(|part| part.info.index), - ) - }) - .collect(); - if partition_matches.len() == 1 { - info!(" only one supported filesystem partition is available; using it as root"); - return partition_matches.into_iter().next(); - } - - let raw_matches: Vec<_> = candidates - .iter() - .filter(|candidate| candidate.partition.is_none()) - .map(|candidate| (candidate.disk_index, None)) - .collect(); - if partition_matches.is_empty() && raw_matches.len() == 1 { - info!(" only one raw block device is available; using it as root"); - return raw_matches.into_iter().next(); - } - - None -} - -fn describe_selection(disk_index: usize, partition: Option<&DetectedPartition>) -> String { - if let Some(partition) = partition { - let name = partition.info.name.as_deref().unwrap_or(""); - let fs = partition - .filesystem - .map(filesystem_name) - .unwrap_or("unknown"); - format!( - "disk{} partition {} ({}, fs={}, lba {}..{})", - disk_index, - partition.info.index + 1, - name, - fs, - partition.info.region.start_lba, - partition.info.region.end_lba - ) - } else { - format!("disk{} raw device", disk_index) - } -} - -fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { - let mut spec = RootSpec::default(); - - if let Some(bootargs) = bootargs - && let Some(root_arg) = bootargs - .split_whitespace() - .find(|arg| arg.starts_with("root=")) - { - let root_value = root_arg.strip_prefix("root=").unwrap_or(""); - spec = match root_value { - value if value.starts_with("/dev/mmcblk") => parse_mmcblk_path(value), - value if value.starts_with("/dev/sd") => parse_sd_path(value), - value if value.starts_with("PARTUUID=") => RootSpec { - partuuid: Some(value.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase()), - ..RootSpec::default() - }, - value if value.starts_with("PARTLABEL=") => RootSpec { - partlabel: Some(value.strip_prefix("PARTLABEL=").unwrap_or("").to_string()), - ..RootSpec::default() - }, - _ => RootSpec::default(), - }; - } - - spec -} - -fn parse_mmcblk_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/mmcblk") { - if let Some(p_pos) = remaining.find('p') { - let disk = remaining[..p_pos].parse::().ok(); - let part = remaining[p_pos + 1..] - .parse::() - .ok() - .and_then(|part| part.checked_sub(1)); - return RootSpec { - disk_index: disk, - partition_index: part, - ..RootSpec::default() - }; - } - - if let Ok(disk) = remaining.parse::() { - return RootSpec { - disk_index: Some(disk), - ..RootSpec::default() - }; - } - } - - RootSpec::default() -} - -fn parse_sd_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/sd") { - let bytes = remaining.as_bytes(); - if bytes.is_empty() { - return RootSpec::default(); - } - - let disk_index = bytes[0] - .is_ascii_lowercase() - .then(|| usize::from(bytes[0] - b'a')); - let partition_index = if bytes.len() > 1 { - core::str::from_utf8(&bytes[1..]) - .ok() - .and_then(|part| part.parse::().ok()) - .and_then(|part| part.checked_sub(1)) - } else { - None - }; - return RootSpec { - disk_index, - partition_index, - ..RootSpec::default() - }; - } - - RootSpec::default() -} - -fn detect_filesystem(dev: &mut AxBlockDevice, region: PartitionRegion) -> Option { +fn detect_filesystem(dev: &mut dyn FsBlockDevice, region: BlockRegion) -> Option { #[cfg(not(any(feature = "ext4", feature = "fat")))] let _ = (&mut *dev, region); @@ -513,7 +88,7 @@ fn detect_filesystem(dev: &mut AxBlockDevice, region: PartitionRegion) -> Option } #[cfg(feature = "ext4")] -fn region_has_ext4(dev: &mut AxBlockDevice, region: PartitionRegion) -> bool { +fn region_has_ext4(dev: &mut dyn FsBlockDevice, region: BlockRegion) -> bool { const EXT4_SUPERBLOCK_OFFSET: usize = 1024; const EXT4_MAGIC_OFFSET: usize = 0x38; const EXT4_MAGIC: u16 = 0xEF53; @@ -526,7 +101,7 @@ fn region_has_ext4(dev: &mut AxBlockDevice, region: PartitionRegion) -> bool { } #[cfg(feature = "fat")] -fn region_has_fat(dev: &mut AxBlockDevice, region: PartitionRegion) -> bool { +fn region_has_fat(dev: &mut dyn FsBlockDevice, region: BlockRegion) -> bool { const FAT16_MAGIC: &[u8; 5] = b"FAT16"; const FAT32_MAGIC: &[u8; 5] = b"FAT32"; let start_lba = region.start_lba; @@ -552,8 +127,8 @@ fn region_has_fat(dev: &mut AxBlockDevice, region: PartitionRegion) -> bool { #[cfg(feature = "ext4")] fn region_has_magic_u16( - dev: &mut AxBlockDevice, - region: PartitionRegion, + dev: &mut dyn FsBlockDevice, + region: BlockRegion, byte_offset: usize, magic: u16, ) -> bool { @@ -589,15 +164,6 @@ fn region_has_magic_u16( u16::from_le_bytes([buf[within_block], buf[within_block + 1]]) == magic } -fn full_region(dev: &AxBlockDevice) -> PartitionRegion { - PartitionRegion::from_num_blocks(dev.num_blocks()) -} - -const fn filesystem_name(fs: FilesystemKind) -> &'static str { - match fs { - #[cfg(feature = "ext4")] - FilesystemKind::Ext4 => "ext4", - #[cfg(feature = "fat")] - FilesystemKind::Fat => "fat", - } +fn full_region(dev: &dyn FsBlockDevice) -> BlockRegion { + BlockRegion::from_num_blocks(dev.num_blocks()) } diff --git a/os/arceos/modules/axfs-ng/src/root.rs b/os/arceos/modules/axfs-ng/src/root.rs new file mode 100644 index 0000000000..cf64221ea9 --- /dev/null +++ b/os/arceos/modules/axfs-ng/src/root.rs @@ -0,0 +1,544 @@ +use alloc::{ + boxed::Box, + format, + string::{String, ToString}, + vec::Vec, +}; + +use rd_block_volume::{BlockVolume, DiskId, PartitionTableKind as VolumeTableKind, scan_volumes}; + +use crate::block::{BlockRegion, FsBlockDevice, VolumeReader}; + +#[derive(Debug, Default)] +pub(crate) struct RootSpec { + disk_index: Option, + partition_index: Option, + partuuid: Option, + partlabel: Option, +} + +pub(crate) struct RootCandidate { + pub(crate) disk_index: usize, + pub(crate) partition: Option, +} + +pub(crate) struct DiscoveredDisk { + pub(crate) disk_index: usize, + pub(crate) dev: Box, + pub(crate) partitions: Vec, +} + +#[derive(Clone)] +pub(crate) struct DetectedPartition { + pub(crate) info: PartitionInfo, + pub(crate) filesystem: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct PartitionInfo { + pub(crate) index: usize, + pub(crate) table_kind: PartitionTableKind, + pub(crate) region: BlockRegion, + pub(crate) name: Option, + pub(crate) part_uuid: Option, + pub(crate) bootable: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum PartitionTableKind { + Raw, + Gpt, + Mbr, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum FilesystemKind { + #[cfg(feature = "ext4")] + Ext4, + #[cfg(feature = "fat")] + Fat, +} + +impl RootCandidate { + pub(crate) fn description(&self) -> String { + if let Some(partition) = &self.partition { + describe_partition(self.disk_index, partition) + } else { + format!("disk{} raw device", self.disk_index) + } + } +} + +pub(crate) fn collect_disks( + block_devs: impl IntoIterator>, +) -> Vec { + let mut disks = Vec::new(); + + for (disk_index, mut dev) in block_devs.into_iter().enumerate() { + let device_name = dev.name().to_string(); + let mut reader = VolumeReader::new(&mut *dev); + match scan_volumes(&mut reader, DiskId(disk_index as u64)) { + Ok(volumes) => { + let partitions = collect_partitions(&mut *dev, volumes); + log_disk(disk_index, &device_name, &partitions); + disks.push(DiscoveredDisk { + disk_index, + dev, + partitions, + }); + } + Err(err) => { + warn!( + " failed to scan partitions on block device {} ({}): {err:?}", + disk_index, device_name + ); + } + } + } + + disks +} + +fn collect_partitions( + dev: &mut dyn FsBlockDevice, + volumes: Vec, +) -> Vec { + let mut partitions = Vec::new(); + for volume in volumes { + if volume.table_kind == VolumeTableKind::Raw { + let region = region_from_volume(&volume); + let raw_fs = super::detect_filesystem(dev, region); + info!(" raw device fs={:?}", raw_fs); + continue; + } + + let info = partition_info_from_volume(&volume); + let filesystem = super::detect_filesystem(dev, info.region); + info!( + " partition {} name={:?} fs={:?} lba {}..{}", + info.index + 1, + info.name, + filesystem, + info.region.start_lba, + info.region.end_lba + ); + partitions.push(DetectedPartition { info, filesystem }); + } + + partitions +} + +fn log_disk(disk_index: usize, device_name: &str, partitions: &[DetectedPartition]) { + if let Some(first) = partitions.first() { + info!( + " block device {} ({}) has {:?} partition table with {} partitions", + disk_index, + device_name, + first.info.table_kind, + partitions.len() + ); + } else { + info!( + " block device {} ({}) has no usable partition table; treating the whole disk as a \ + candidate", + disk_index, device_name + ); + } +} + +fn partition_info_from_volume(volume: &BlockVolume) -> PartitionInfo { + PartitionInfo { + index: volume + .partition_id + .0 + .checked_sub(1) + .map(|index| index as usize) + .unwrap_or(0), + table_kind: table_kind_from_volume(volume.table_kind), + region: region_from_volume(volume), + name: volume.partlabel.as_ref().map(|label| label.0.clone()), + part_uuid: volume.partuuid.as_ref().map(|uuid| uuid.0.clone()), + bootable: volume.bootable, + } +} + +fn region_from_volume(volume: &BlockVolume) -> BlockRegion { + BlockRegion::new(volume.region.start_block, volume.region.num_blocks) +} + +fn table_kind_from_volume(kind: VolumeTableKind) -> PartitionTableKind { + match kind { + VolumeTableKind::Raw => PartitionTableKind::Raw, + VolumeTableKind::Gpt => PartitionTableKind::Gpt, + VolumeTableKind::Mbr => PartitionTableKind::Mbr, + } +} + +pub(crate) fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { + let mut candidates = Vec::new(); + + for disk in disks { + if disk.partitions.is_empty() { + candidates.push(RootCandidate { + disk_index: disk.disk_index, + partition: None, + }); + continue; + } + + for partition in &disk.partitions { + candidates.push(RootCandidate { + disk_index: disk.disk_index, + partition: Some(partition.clone()), + }); + } + } + + candidates +} + +pub(crate) fn select_root_candidate( + candidates: &[RootCandidate], + spec: &RootSpec, +) -> Option<(usize, Option)> { + if let Some(index) = select_explicit_root(candidates, spec) { + return Some(index); + } + + select_default_root(candidates) +} + +fn select_explicit_root( + candidates: &[RootCandidate], + spec: &RootSpec, +) -> Option<(usize, Option)> { + for candidate in candidates { + if let Some(partition) = candidate.partition.as_ref() { + if let Some(partuuid) = &spec.partuuid + && partition + .info + .part_uuid + .as_ref() + .is_some_and(|candidate_uuid| candidate_uuid.eq_ignore_ascii_case(partuuid)) + { + info!(" matched root by PARTUUID on {}", candidate.description()); + return Some((candidate.disk_index, Some(partition.info.index))); + } + + if let Some(partlabel) = &spec.partlabel + && partition.info.name.as_deref() == Some(partlabel.as_str()) + { + info!(" matched root by PARTLABEL on {}", candidate.description()); + return Some((candidate.disk_index, Some(partition.info.index))); + } + } + + if let Some(disk_index) = spec.disk_index + && candidate.disk_index == disk_index + { + match (spec.partition_index, &candidate.partition) { + (Some(partition_index), Some(partition)) + if partition.info.index == partition_index => + { + info!( + " matched root by device path on {}", + candidate.description() + ); + return Some((candidate.disk_index, Some(partition.info.index))); + } + (None, None) => { + info!( + " matched root by raw device path on {}", + candidate.description() + ); + return Some((candidate.disk_index, None)); + } + _ => {} + } + } + } + + if spec.disk_index.is_some() || spec.partuuid.is_some() || spec.partlabel.is_some() { + panic!("configured root device was not found in discovered block devices"); + } + + None +} + +fn select_default_root(candidates: &[RootCandidate]) -> Option<(usize, Option)> { + let rootfs_matches: Vec<_> = candidates + .iter() + .filter(|candidate| { + candidate + .partition + .as_ref() + .and_then(|part| part.info.name.as_deref()) + == Some("rootfs") + }) + .map(|candidate| { + ( + candidate.disk_index, + candidate.partition.as_ref().map(|part| part.info.index), + ) + }) + .collect(); + if rootfs_matches.len() == 1 { + info!(" falling back to PARTLABEL=rootfs"); + return rootfs_matches.into_iter().next(); + } + if rootfs_matches.len() > 1 { + panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); + } + + let partition_matches = supported_filesystem_partition_matches(candidates); + let bootable_mbr_partition_matches: Vec<_> = partition_matches + .iter() + .copied() + .filter(|(_, partition)| { + partition.info.table_kind == PartitionTableKind::Mbr && partition.info.bootable + }) + .map(|(disk_index, partition)| (disk_index, Some(partition.info.index))) + .collect(); + if bootable_mbr_partition_matches.len() == 1 { + info!(" only one bootable MBR filesystem partition is available; using it as root"); + return bootable_mbr_partition_matches.into_iter().next(); + } + + let partition_matches: Vec<_> = partition_matches + .into_iter() + .map(|(disk_index, partition)| (disk_index, Some(partition.info.index))) + .collect(); + if partition_matches.len() == 1 { + info!(" only one supported filesystem partition is available; using it as root"); + return partition_matches.into_iter().next(); + } + + let raw_matches: Vec<_> = candidates + .iter() + .filter(|candidate| candidate.partition.is_none()) + .map(|candidate| (candidate.disk_index, None)) + .collect(); + if partition_matches.is_empty() && raw_matches.len() == 1 { + info!(" only one raw block device is available; using it as root"); + return raw_matches.into_iter().next(); + } + + None +} + +fn supported_filesystem_partition_matches( + candidates: &[RootCandidate], +) -> Vec<(usize, &DetectedPartition)> { + candidates + .iter() + .filter_map(|candidate| { + let partition = candidate.partition.as_ref()?; + if !supported_default_root_partition(partition) { + return None; + } + Some((candidate.disk_index, partition)) + }) + .collect() +} + +fn supported_default_root_partition(partition: &DetectedPartition) -> bool { + if !partition.filesystem.is_some() { + return false; + } + match partition.info.table_kind { + PartitionTableKind::Mbr => { + #[cfg(feature = "ext4")] + { + partition.filesystem == Some(FilesystemKind::Ext4) + } + #[cfg(all(not(feature = "ext4"), feature = "fat"))] + { + partition.filesystem == Some(FilesystemKind::Fat) + } + #[cfg(all(not(feature = "ext4"), not(feature = "fat")))] + { + false + } + } + PartitionTableKind::Gpt | PartitionTableKind::Raw => { + #[cfg(feature = "ext4")] + { + partition.filesystem == Some(FilesystemKind::Ext4) + } + #[cfg(all(not(feature = "ext4"), feature = "fat"))] + { + partition.filesystem == Some(FilesystemKind::Fat) + } + #[cfg(all(not(feature = "ext4"), not(feature = "fat")))] + { + false + } + } + } +} + +pub(crate) fn describe_selection( + disk_index: usize, + partition: Option<&DetectedPartition>, +) -> String { + if let Some(partition) = partition { + describe_partition(disk_index, partition) + } else { + format!("disk{} raw device", disk_index) + } +} + +fn describe_partition(disk_index: usize, partition: &DetectedPartition) -> String { + let name = partition.info.name.as_deref().unwrap_or(""); + let fs = partition + .filesystem + .map(filesystem_name) + .unwrap_or("unknown"); + format!( + "disk{} partition {} ({}, fs={}, lba {}..{})", + disk_index, + partition.info.index + 1, + name, + fs, + partition.info.region.start_lba, + partition.info.region.end_lba + ) +} + +pub(crate) fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { + let mut spec = RootSpec::default(); + + if let Some(bootargs) = bootargs + && let Some(root_arg) = bootargs + .split_whitespace() + .find(|arg| arg.starts_with("root=")) + { + let root_value = root_arg.strip_prefix("root=").unwrap_or(""); + spec = match root_value { + value if value.starts_with("/dev/mmcblk") => parse_mmcblk_path(value), + value if value.starts_with("/dev/sd") => parse_sd_path(value), + value if value.starts_with("PARTUUID=") => RootSpec { + partuuid: Some(value.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase()), + ..RootSpec::default() + }, + value if value.starts_with("PARTLABEL=") => RootSpec { + partlabel: Some(value.strip_prefix("PARTLABEL=").unwrap_or("").to_string()), + ..RootSpec::default() + }, + _ => RootSpec::default(), + }; + } + + spec +} + +fn parse_mmcblk_path(path: &str) -> RootSpec { + if let Some(remaining) = path.strip_prefix("/dev/mmcblk") { + if let Some(p_pos) = remaining.find('p') { + let disk = remaining[..p_pos].parse::().ok(); + let part = remaining[p_pos + 1..] + .parse::() + .ok() + .and_then(|part| part.checked_sub(1)); + return RootSpec { + disk_index: disk, + partition_index: part, + ..RootSpec::default() + }; + } + + if let Ok(disk) = remaining.parse::() { + return RootSpec { + disk_index: Some(disk), + ..RootSpec::default() + }; + } + } + + RootSpec::default() +} + +fn parse_sd_path(path: &str) -> RootSpec { + if let Some(remaining) = path.strip_prefix("/dev/sd") { + let bytes = remaining.as_bytes(); + if bytes.is_empty() { + return RootSpec::default(); + } + + let disk_index = bytes[0] + .is_ascii_lowercase() + .then(|| usize::from(bytes[0] - b'a')); + let partition_index = if bytes.len() > 1 { + core::str::from_utf8(&bytes[1..]) + .ok() + .and_then(|part| part.parse::().ok()) + .and_then(|part| part.checked_sub(1)) + } else { + None + }; + return RootSpec { + disk_index, + partition_index, + ..RootSpec::default() + }; + } + + RootSpec::default() +} + +const fn filesystem_name(fs: FilesystemKind) -> &'static str { + match fs { + #[cfg(feature = "ext4")] + FilesystemKind::Ext4 => "ext4", + #[cfg(feature = "fat")] + FilesystemKind::Fat => "fat", + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn mbr_partition( + index: usize, + filesystem: Option, + bootable: bool, + ) -> RootCandidate { + RootCandidate { + disk_index: 0, + partition: Some(DetectedPartition { + info: PartitionInfo { + index, + table_kind: PartitionTableKind::Mbr, + region: BlockRegion::new(index as u64 * 100, 100), + name: None, + part_uuid: None, + bootable, + }, + filesystem, + }), + } + } + + #[test] + #[cfg(feature = "ext4")] + fn default_root_uses_only_supported_mbr_filesystem_partition_without_boot_flag() { + let candidates = [ + mbr_partition(0, None, false), + mbr_partition(1, Some(FilesystemKind::Ext4), false), + ]; + + assert_eq!(select_default_root(&candidates), Some((0, Some(1)))); + } + + #[test] + #[cfg(feature = "ext4")] + fn default_root_prefers_only_bootable_mbr_filesystem_partition() { + let candidates = [ + mbr_partition(0, Some(FilesystemKind::Ext4), false), + mbr_partition(1, Some(FilesystemKind::Ext4), true), + ]; + + assert_eq!(select_default_root(&candidates), Some((0, Some(1)))); + } +} diff --git a/os/arceos/modules/axfs/Cargo.toml b/os/arceos/modules/axfs/Cargo.toml index 5ff4cf82ce..7ce0dee20e 100644 --- a/os/arceos/modules/axfs/Cargo.toml +++ b/os/arceos/modules/axfs/Cargo.toml @@ -10,10 +10,9 @@ license.workspace = true [features] default = [] times = [] -use-ramdisk = [] # TODO: init ramdisk +use-ramdisk = [] [dependencies] -ax-driver = { workspace = true, features = ["block"] } ax-errno = { workspace = true } ax-hal = { workspace = true } ax-fs-devfs = { workspace = true } diff --git a/os/arceos/modules/axfs/src/dev.rs b/os/arceos/modules/axfs/src/dev.rs index 9b97e49b8f..4c6d11341b 100644 --- a/os/arceos/modules/axfs/src/dev.rs +++ b/os/arceos/modules/axfs/src/dev.rs @@ -16,22 +16,57 @@ use alloc::sync::Arc; -use ax_driver::prelude::*; +use ax_errno::AxResult; use ax_kspin::SpinNoIrq as Mutex; const BLOCK_SIZE: usize = 512; +pub trait FsBlockDevice: Send { + fn name(&self) -> &str; + fn num_blocks(&self) -> u64; + fn block_size(&self) -> usize; + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult; + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult; + fn flush(&mut self) -> AxResult; +} + +impl FsBlockDevice for alloc::boxed::Box { + fn name(&self) -> &str { + (**self).name() + } + + fn num_blocks(&self) -> u64 { + (**self).num_blocks() + } + + fn block_size(&self) -> usize { + (**self).block_size() + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { + (**self).read_block(block_id, buf) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { + (**self).write_block(block_id, buf) + } + + fn flush(&mut self) -> AxResult { + (**self).flush() + } +} + /// A disk device with a cursor. #[derive(Clone)] pub struct Disk { block_id: u64, offset: usize, - dev: Arc>, + dev: Arc>>, } impl Disk { /// Create a new disk. - pub fn new(dev: AxBlockDevice) -> Self { + pub fn new(dev: alloc::boxed::Box) -> Self { assert_eq!(BLOCK_SIZE, dev.block_size()); Self { block_id: 0, @@ -58,7 +93,7 @@ impl Disk { } /// Read within one block, returns the number of bytes read. - pub fn read_one(&mut self, buf: &mut [u8]) -> DevResult { + pub fn read_one(&mut self, buf: &mut [u8]) -> AxResult { let read_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { // whole block let mut dev = self.dev.lock(); @@ -87,7 +122,7 @@ impl Disk { } /// Write within one block, returns the number of bytes written. - pub fn write_one(&mut self, buf: &[u8]) -> DevResult { + pub fn write_one(&mut self, buf: &[u8]) -> AxResult { let write_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { // whole block let mut dev = self.dev.lock(); @@ -151,7 +186,7 @@ impl Partition { } /// Read within one block, returns the number of bytes read. - pub fn read_one(&mut self, buf: &mut [u8]) -> DevResult { + pub fn read_one(&mut self, buf: &mut [u8]) -> AxResult { if self.position >= self.size() { return Ok(0); } @@ -175,7 +210,7 @@ impl Partition { } /// Write within one block, returns the number of bytes written. - pub fn write_one(&mut self, buf: &[u8]) -> DevResult { + pub fn write_one(&mut self, buf: &[u8]) -> AxResult { if self.position >= self.size() { return Ok(0); } diff --git a/os/arceos/modules/axfs/src/lib.rs b/os/arceos/modules/axfs/src/lib.rs index ada32207a6..79c059dc0e 100644 --- a/os/arceos/modules/axfs/src/lib.rs +++ b/os/arceos/modules/axfs/src/lib.rs @@ -28,22 +28,22 @@ pub mod api; pub mod fops; use alloc::{ + boxed::Box, string::{String, ToString}, sync::Arc, vec::Vec, }; -use ax_driver::{AxBlockDevice, AxDeviceContainer, prelude::*}; - -use crate::partition::PartitionInfo; +pub use crate::dev::FsBlockDevice; +use crate::{dev::Disk, partition::PartitionInfo}; /// Initializes filesystems by block devices. -pub fn init_filesystems(mut blk_devs: AxDeviceContainer, bootargs: Option<&str>) { +pub fn init_filesystems(mut block_devs: Vec>, bootargs: Option<&str>) { info!("Initialize filesystems..."); - let dev = blk_devs.take_one().expect("No block device found!"); - info!(" use block device 0: {:?}", dev.device_name()); - let mut disk = self::dev::Disk::new(dev); + let dev = block_devs.pop().expect("No block device found!"); + info!(" use block device 0: {:?}", dev.name()); + let mut disk = Disk::new(dev); // Parse root parameter from bootargs let root_spec = parse_root_spec(bootargs); @@ -64,11 +64,7 @@ pub fn init_filesystems(mut blk_devs: AxDeviceContainer, bootargs } /// Initialize filesystems with detected partitions -fn initialize_with_partitions( - disk: self::dev::Disk, - partitions: Vec, - root_spec: &RootSpec, -) { +fn initialize_with_partitions(disk: Disk, partitions: Vec, root_spec: &RootSpec) { info!( "Found {} partitions, initializing with dynamic filesystem detection", partitions.len() diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 91a2fc644a..c18a6a8f31 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -11,16 +11,28 @@ license.workspace = true smp = [ "ax-plat-x86-pc?/smp", "ax-plat-aarch64-qemu-virt?/smp", + "ax-plat-aarch64-raspi?/smp", + "ax-plat-aarch64-bsta1000b?/smp", + "ax-plat-aarch64-phytium-pi?/smp", "ax-plat-riscv64-qemu-virt?/smp", + "ax-plat-riscv64-sg2002?/smp", + "axplat-riscv64-visionfive2?/smp", "ax-plat-loongarch64-qemu-virt?/smp", + "axplat-x86-qemu-q35?/smp", "axplat-dyn?/smp", "ax-plat/smp", ] irq = [ "ax-plat-x86-pc?/irq", "ax-plat-aarch64-qemu-virt?/irq", + "ax-plat-aarch64-raspi?/irq", + "ax-plat-aarch64-bsta1000b?/irq", + "ax-plat-aarch64-phytium-pi?/irq", "ax-plat-riscv64-qemu-virt?/irq", + "ax-plat-riscv64-sg2002?/irq", + "axplat-riscv64-visionfive2?/irq", "ax-plat-loongarch64-qemu-virt?/irq", + "axplat-x86-qemu-q35?/irq", "ax-plat/irq", "axplat-dyn?/irq", ] @@ -28,15 +40,27 @@ fp-simd = [ "ax-cpu/fp-simd", "ax-plat-x86-pc?/fp-simd", "ax-plat-aarch64-qemu-virt?/fp-simd", + "ax-plat-aarch64-raspi?/fp-simd", + "ax-plat-aarch64-bsta1000b?/fp-simd", + "ax-plat-aarch64-phytium-pi?/fp-simd", "ax-plat-riscv64-qemu-virt?/fp-simd", + "ax-plat-riscv64-sg2002?/fp-simd", + "axplat-riscv64-visionfive2?/fp-simd", "ax-plat-loongarch64-qemu-virt?/fp-simd", + "axplat-x86-qemu-q35?/fp-simd", "axplat-dyn?/fp-simd", ] rtc = [ "ax-plat-x86-pc?/rtc", "ax-plat-aarch64-qemu-virt?/rtc", + "ax-plat-aarch64-raspi?/rtc", + "ax-plat-aarch64-bsta1000b?/rtc", + "ax-plat-aarch64-phytium-pi?/rtc", "ax-plat-riscv64-qemu-virt?/rtc", + "ax-plat-riscv64-sg2002?/rtc", + "axplat-riscv64-visionfive2?/rtc", "ax-plat-loongarch64-qemu-virt?/rtc", + "axplat-x86-qemu-q35?/rtc", "axplat-dyn?/rtc", ] # Forward the GICv3 toggle to the aarch64 qemu-virt platform. @@ -69,6 +93,20 @@ starry-kcov = [] # Custom or default platforms myplat = [] plat-dyn = ["axplat-dyn"] +x86-pc = ["dep:ax-plat-x86-pc"] +aarch64-qemu-virt = ["dep:ax-plat-aarch64-qemu-virt"] +aarch64-raspi = ["dep:ax-plat-aarch64-raspi"] +aarch64-bsta1000b = ["dep:ax-plat-aarch64-bsta1000b"] +aarch64-phytium-pi = ["dep:ax-plat-aarch64-phytium-pi"] +riscv64-qemu-virt = ["dep:ax-plat-riscv64-qemu-virt"] +riscv64-sg2002 = ["dep:ax-plat-riscv64-sg2002"] +riscv64-visionfive2 = ["dep:axplat-riscv64-visionfive2"] +riscv64-qemu-virt-hv = [ + "dep:ax-plat-riscv64-qemu-virt", + "ax-plat-riscv64-qemu-virt/hypervisor", +] +loongarch64-qemu-virt = ["dep:ax-plat-loongarch64-qemu-virt"] +x86-qemu-q35 = ["dep:axplat-x86-qemu-q35", "axplat-x86-qemu-q35/reboot-on-system-off"] defplat = [ "dep:ax-plat-x86-pc", "dep:ax-plat-aarch64-qemu-virt", @@ -92,19 +130,26 @@ log.workspace = true ax-memory-addr.workspace = true ax-page-table-multiarch = { workspace = true, optional = true } ax-percpu = { workspace = true, features = ["non-zero-vma"] } +rdrive.workspace = true spin.workspace = true [target.'cfg(target_arch = "x86_64")'.dependencies] ax-plat-x86-pc = { workspace = true, optional = true } +axplat-x86-qemu-q35 = { workspace = true, optional = true } [target.'cfg(target_os = "none")'.dependencies] axplat-dyn = { workspace = true, default-features = false, optional = true } [target.'cfg(target_arch = "aarch64")'.dependencies] ax-plat-aarch64-qemu-virt = { workspace = true, optional = true } +ax-plat-aarch64-raspi = { workspace = true, optional = true } +ax-plat-aarch64-bsta1000b = { workspace = true, optional = true } +ax-plat-aarch64-phytium-pi = { workspace = true, optional = true } [target.'cfg(target_arch = "riscv64")'.dependencies] ax-plat-riscv64-qemu-virt = { workspace = true, optional = true } +ax-plat-riscv64-sg2002 = { workspace = true, optional = true } +axplat-riscv64-visionfive2 = { workspace = true, optional = true } [target.'cfg(target_arch = "loongarch64")'.dependencies] ax-plat-loongarch64-qemu-virt = { workspace = true, optional = true } diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index ee13d29de4..a86e7ba5c2 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -6,26 +6,210 @@ use std::{ const LINKER_SCRIPT_NAME: &str = "linker.x"; const LINKER_TEMPLATE_NAME: &str = "linker.lds.S"; +const SELECTED_PLATFORM_NAME: &str = "selected_platform.rs"; + +struct PlatformFeature { + feature: &'static str, + target_arch: Option<&'static str>, + crate_name: &'static str, +} + +const PLATFORM_FEATURES: &[PlatformFeature] = &[ + PlatformFeature { + feature: "plat-dyn", + target_arch: None, + crate_name: "axplat_dyn", + }, + PlatformFeature { + feature: "x86-pc", + target_arch: Some("x86_64"), + crate_name: "ax_plat_x86_pc", + }, + PlatformFeature { + feature: "x86-qemu-q35", + target_arch: Some("x86_64"), + crate_name: "axplat_x86_qemu_q35", + }, + PlatformFeature { + feature: "aarch64-qemu-virt", + target_arch: Some("aarch64"), + crate_name: "ax_plat_aarch64_qemu_virt", + }, + PlatformFeature { + feature: "aarch64-raspi", + target_arch: Some("aarch64"), + crate_name: "ax_plat_aarch64_raspi", + }, + PlatformFeature { + feature: "aarch64-bsta1000b", + target_arch: Some("aarch64"), + crate_name: "ax_plat_aarch64_bsta1000b", + }, + PlatformFeature { + feature: "aarch64-phytium-pi", + target_arch: Some("aarch64"), + crate_name: "ax_plat_aarch64_phytium_pi", + }, + PlatformFeature { + feature: "riscv64-qemu-virt", + target_arch: Some("riscv64"), + crate_name: "ax_plat_riscv64_qemu_virt", + }, + PlatformFeature { + feature: "riscv64-sg2002", + target_arch: Some("riscv64"), + crate_name: "ax_plat_riscv64_sg2002", + }, + PlatformFeature { + feature: "riscv64-visionfive2", + target_arch: Some("riscv64"), + crate_name: "axplat_riscv64_visionfive2", + }, + PlatformFeature { + feature: "riscv64-qemu-virt-hv", + target_arch: Some("riscv64"), + crate_name: "ax_plat_riscv64_qemu_virt", + }, + PlatformFeature { + feature: "loongarch64-qemu-virt", + target_arch: Some("loongarch64"), + crate_name: "ax_plat_loongarch64_qemu_virt", + }, +]; + +const DEFAULT_PLATFORMS: &[(&str, &str)] = &[ + ("aarch64", "ax_plat_aarch64_qemu_virt"), + ("loongarch64", "ax_plat_loongarch64_qemu_virt"), + ("riscv64", "ax_plat_riscv64_qemu_virt"), + ("x86_64", "ax_plat_x86_pc"), +]; fn main() { println!("cargo:rustc-check-cfg=cfg(plat_dyn)"); + println!("cargo:rustc-check-cfg=cfg(ax_hal_any_platform_feature)"); println!("cargo:rerun-if-changed={LINKER_TEMPLATE_NAME}"); println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); + println!("cargo:rerun-if-env-changed={}", feature_env("myplat")); + println!("cargo:rerun-if-env-changed={}", feature_env("defplat")); + for platform in PLATFORM_FEATURES { + println!( + "cargo:rerun-if-env-changed={}", + feature_env(platform.feature) + ); + } let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - let has_plat_dyn = std::env::var_os("CARGO_FEATURE_PLAT_DYN").is_some(); - let config = load_linker_config().unwrap(); + let selected_platform = check_platform_features(&arch, &target_os); + gen_selected_platform(&arch, &target_os, selected_platform).unwrap(); - if has_plat_dyn && target_os == "none" { - println!("cargo:rustc-cfg=plat_dyn"); - } + let config = load_linker_config().unwrap(); if config.platform != "dummy" { gen_linker_script(&arch, &config).unwrap(); } } +fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static PlatformFeature> { + let has_myplat = feature_enabled("myplat"); + let enabled_platforms = PLATFORM_FEATURES + .iter() + .filter(|platform| feature_enabled(platform.feature)) + .collect::>(); + + if has_myplat || !enabled_platforms.is_empty() { + println!("cargo:rustc-cfg=ax_hal_any_platform_feature"); + } + + if has_myplat && !enabled_platforms.is_empty() { + panic!("ax-hal/myplat must not be combined with a built-in ax-hal platform feature"); + } + + if feature_enabled("plat-dyn") && enabled_platforms.len() > 1 { + panic!("ax-hal/plat-dyn must not be combined with a built-in ax-hal platform feature"); + } + + for platform in &enabled_platforms { + if let Some(target_arch) = platform.target_arch { + let conflicting_features = enabled_platforms + .iter() + .filter(|other| other.target_arch == Some(target_arch)) + .map(|platform| platform.feature) + .collect::>(); + if conflicting_features.len() > 1 { + panic!( + "multiple ax-hal platform features are enabled for target_arch = \"{}\": {}", + target_arch, + conflicting_features.join(", ") + ); + } + } + } + + if target_os == "none" { + for platform in &enabled_platforms { + if let Some(target_arch) = platform.target_arch + && arch != target_arch + { + panic!( + "ax-hal/{} requires target_arch = \"{}\"", + platform.feature, target_arch + ); + } + } + } + + enabled_platforms.into_iter().find(|platform| { + platform + .target_arch + .is_none_or(|target_arch| target_arch == arch) + }) +} + +fn gen_selected_platform( + arch: &str, + target_os: &str, + platform: Option<&PlatformFeature>, +) -> Result<()> { + let crate_name = if let Some(platform) = platform { + if platform.feature == "plat-dyn" { + (target_os == "none").then_some(platform.crate_name) + } else { + platform + .target_arch + .is_some_and(|target_arch| target_arch == arch) + .then_some(platform.crate_name) + } + } else if target_os == "none" && feature_enabled("defplat") && !feature_enabled("myplat") { + DEFAULT_PLATFORMS + .iter() + .find_map(|(target_arch, crate_name)| (*target_arch == arch).then_some(*crate_name)) + } else { + None + }; + + if crate_name == Some("axplat_dyn") { + println!("cargo:rustc-cfg=plat_dyn"); + } + + let content = crate_name + .map(|crate_name| format!("extern crate {crate_name} as _;\n")) + .unwrap_or_default(); + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + fs::write(out_dir.join(SELECTED_PLATFORM_NAME), content) +} + +fn feature_enabled(feature: &str) -> bool { + std::env::var_os(feature_env(feature)).is_some() +} + +fn feature_env(feature: &str) -> String { + format!( + "CARGO_FEATURE_{}", + feature.replace('-', "_").to_ascii_uppercase() + ) +} + #[derive(Debug)] struct LinkerConfig { platform: String, diff --git a/os/arceos/modules/axhal/linker.lds.S b/os/arceos/modules/axhal/linker.lds.S index f7298924ba..70718d830a 100644 --- a/os/arceos/modules/axhal/linker.lds.S +++ b/os/arceos/modules/axhal/linker.lds.S @@ -46,9 +46,9 @@ SECTIONS *(.got .got.*) . = ALIGN(0x10); - _sdriver = .; + __sdriver_register = .; KEEP(*(.driver.register*)) - _edriver = .; + __edriver_register = .; . = ALIGN(0x10); _ex_table_start = .; diff --git a/os/arceos/modules/axhal/src/dummy.rs b/os/arceos/modules/axhal/src/dummy.rs index 1c95c5fb63..b8246deead 100644 --- a/os/arceos/modules/axhal/src/dummy.rs +++ b/os/arceos/modules/axhal/src/dummy.rs @@ -4,6 +4,7 @@ use ax_plat::irq::{IpiTarget, IrqHandler, IrqIf}; use ax_plat::{ console::ConsoleIf, + drivers::DriversIf, impl_plat_interface, init::InitIf, mem::{MemIf, RawRange}, @@ -16,6 +17,7 @@ struct DummyConsole; struct DummyMem; struct DummyTime; struct DummyPower; +struct DummyDrivers; #[cfg(feature = "irq")] struct DummyIrq; @@ -124,6 +126,13 @@ impl PowerIf for DummyPower { } } +#[impl_plat_interface] +impl DriversIf for DummyDrivers { + fn static_devices_fn() -> &'static [rdrive::probe::static_::StaticDeviceDesc] { + &[] + } +} + #[cfg(feature = "irq")] #[impl_plat_interface] impl IrqIf for DummyIrq { diff --git a/os/arceos/modules/axhal/src/lib.rs b/os/arceos/modules/axhal/src/lib.rs index 729a97fbc9..f32a8860ab 100644 --- a/os/arceos/modules/axhal/src/lib.rs +++ b/os/arceos/modules/axhal/src/lib.rs @@ -37,33 +37,28 @@ extern crate log; #[macro_use] extern crate ax_memory_addr; -cfg_if::cfg_if! { - if #[cfg(feature = "myplat")] { - // link the custom platform crate in your application. - } - else if #[cfg(plat_dyn)] { - extern crate axplat_dyn; - } - else if #[cfg(all(target_os = "none", feature = "defplat"))] { - #[cfg(target_arch = "x86_64")] - extern crate ax_plat_x86_pc; - #[cfg(target_arch = "aarch64")] - extern crate ax_plat_aarch64_qemu_virt; - #[cfg(target_arch = "riscv64")] - extern crate ax_plat_riscv64_qemu_virt; - #[cfg(target_arch = "loongarch64")] - extern crate ax_plat_loongarch64_qemu_virt; - } else { - // Link the dummy platform implementation to pass cargo test. - mod dummy; - } -} +#[path = "platform.rs"] +mod platform_select; +pub use platform_select::selected as platform; pub mod dtb; pub mod mem; pub mod percpu; pub mod time; +/// Static driver resources exported by the selected platform. +pub mod drivers { + #[cfg(not(feature = "plat-dyn"))] + pub fn static_devices() -> &'static [rdrive::probe::static_::StaticDeviceDesc] { + ax_plat::drivers::static_devices() + } + + #[cfg(feature = "plat-dyn")] + pub const fn static_devices() -> &'static [rdrive::probe::static_::StaticDeviceDesc] { + &[] + } +} + #[cfg(feature = "tls")] pub mod tls; diff --git a/os/arceos/modules/axhal/src/platform.rs b/os/arceos/modules/axhal/src/platform.rs new file mode 100644 index 0000000000..5361ec8acf --- /dev/null +++ b/os/arceos/modules/axhal/src/platform.rs @@ -0,0 +1,26 @@ +#[cfg(all(not(test), not(feature = "myplat")))] +include!(concat!(env!("OUT_DIR"), "/selected_platform.rs")); + +#[cfg(all( + target_os = "none", + not(test), + not(feature = "myplat"), + not(feature = "defplat"), + not(ax_hal_any_platform_feature) +))] +compile_error!("select an ax-hal platform feature or enable ax-hal/myplat"); + +#[cfg(test)] +#[path = "dummy.rs"] +mod dummy; + +#[cfg(all( + not(test), + not(target_os = "none"), + not(feature = "defplat"), + not(ax_hal_any_platform_feature) +))] +#[path = "dummy.rs"] +mod dummy; + +pub mod selected {} diff --git a/os/arceos/modules/axhal/src/time.rs b/os/arceos/modules/axhal/src/time.rs index 2da28a0ce5..76f305f7d1 100644 --- a/os/arceos/modules/axhal/src/time.rs +++ b/os/arceos/modules/axhal/src/time.rs @@ -7,3 +7,15 @@ pub use ax_plat::time::{ }; #[cfg(feature = "irq")] pub use ax_plat::time::{irq_num, set_oneshot_timer}; + +pub fn try_init_epoch_offset(epoch_time_nanos: u64) -> bool { + #[cfg(plat_dyn)] + { + axplat_dyn::try_init_epoch_offset(epoch_time_nanos) + } + #[cfg(not(plat_dyn))] + { + let _ = epoch_time_nanos; + false + } +} diff --git a/os/arceos/modules/axinput/Cargo.toml b/os/arceos/modules/axinput/Cargo.toml index 6effce3297..79f97d4db4 100644 --- a/os/arceos/modules/axinput/Cargo.toml +++ b/os/arceos/modules/axinput/Cargo.toml @@ -10,8 +10,8 @@ categories.workspace = true license.workspace = true [dependencies] -ax-driver = { workspace = true, features = ["input"] } ax-sync = { workspace = true } ax-lazyinit = { workspace = true } log = { workspace = true } +rdif-input.workspace = true diff --git a/os/arceos/modules/axinput/src/device.rs b/os/arceos/modules/axinput/src/device.rs new file mode 100644 index 0000000000..672a9d26ac --- /dev/null +++ b/os/arceos/modules/axinput/src/device.rs @@ -0,0 +1,101 @@ +use alloc::{boxed::Box, string::String}; + +use crate::{AbsInfo, Event, EventType, InputDeviceId}; + +pub type InputResult = Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InputError { + AlreadyExists, + Again, + BadState, + InvalidInput, + Io, + NoMemory, + ResourceBusy, + Unsupported, +} + +/// Domain boundary consumed by evdev and upper input services. +pub trait InputDevice: Send { + fn name(&self) -> &str; + + fn device_id(&self) -> InputDeviceId; + + fn physical_location(&self) -> &str; + + fn unique_id(&self) -> &str; + + fn irq_num(&self) -> Option { + None + } + + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> InputResult; + + fn read_event(&mut self) -> InputResult; + + fn get_prop_bits(&mut self, _out: &mut [u8]) -> InputResult { + Ok(0) + } + + fn get_abs_info(&mut self, _axis: u8) -> InputResult { + Err(InputError::Unsupported) + } +} + +pub struct ErasedInputDevice { + name: String, + inner: Box, +} + +impl ErasedInputDevice { + pub fn new(device: impl InputDevice + 'static) -> Self { + let name = device.name().into(); + Self { + name, + inner: Box::new(device), + } + } + + pub fn name(&self) -> &str { + &self.name + } +} + +impl InputDevice for ErasedInputDevice { + fn name(&self) -> &str { + &self.name + } + + fn device_id(&self) -> InputDeviceId { + self.inner.device_id() + } + + fn physical_location(&self) -> &str { + self.inner.physical_location() + } + + fn unique_id(&self) -> &str { + self.inner.unique_id() + } + + fn irq_num(&self) -> Option { + self.inner.irq_num() + } + + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> InputResult { + self.inner.get_event_bits(ty, out) + } + + fn read_event(&mut self) -> InputResult { + self.inner.read_event() + } + + fn get_prop_bits(&mut self, out: &mut [u8]) -> InputResult { + self.inner.get_prop_bits(out) + } + + fn get_abs_info(&mut self, axis: u8) -> InputResult { + self.inner.get_abs_info(axis) + } +} diff --git a/os/arceos/modules/axinput/src/event.rs b/os/arceos/modules/axinput/src/event.rs new file mode 100644 index 0000000000..2c71790fc1 --- /dev/null +++ b/os/arceos/modules/axinput/src/event.rs @@ -0,0 +1,71 @@ +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum EventType { + Synchronization = 0x00, + Key = 0x01, + Relative = 0x02, + Absolute = 0x03, + Misc = 0x04, + Switch = 0x05, + Led = 0x11, + Sound = 0x12, + ForceFeedback = 0x15, +} + +impl EventType { + pub const MAX: u8 = 0x1f; + pub const COUNT: u8 = Self::MAX + 1; + + pub const fn from_repr(value: u8) -> Option { + match value { + 0x00 => Some(Self::Synchronization), + 0x01 => Some(Self::Key), + 0x02 => Some(Self::Relative), + 0x03 => Some(Self::Absolute), + 0x04 => Some(Self::Misc), + 0x05 => Some(Self::Switch), + 0x11 => Some(Self::Led), + 0x12 => Some(Self::Sound), + 0x15 => Some(Self::ForceFeedback), + _ => None, + } + } + + pub const fn bits_count(self) -> usize { + match self { + Self::Synchronization => 0x10, + Self::Key => 0x300, + Self::Relative => 0x10, + Self::Absolute => 0x40, + Self::Misc => 0x08, + Self::Switch => 0x12, + Self::Led => 0x10, + Self::Sound => 0x08, + Self::ForceFeedback => 0x80, + } + } +} + +/// An input event, as defined by the Linux input subsystem. +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct Event { + pub event_type: u16, + pub code: u16, + pub value: i32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct AbsInfo { + /// The minimum value for the axis. + pub min: i32, + /// The maximum value for the axis. + pub max: i32, + /// The fuzz value used to filter noise from the event stream. + pub fuzz: i32, + /// The size of the dead zone; values less than this will be reported as 0. + pub flat: i32, + /// The resolution for values reported for the axis. + pub res: i32, +} diff --git a/os/arceos/modules/axinput/src/id.rs b/os/arceos/modules/axinput/src/id.rs new file mode 100644 index 0000000000..2b9f2098fa --- /dev/null +++ b/os/arceos/modules/axinput/src/id.rs @@ -0,0 +1,12 @@ +#[repr(C)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct InputDeviceId { + /// The bustype identifier. + pub bus_type: u16, + /// The vendor identifier. + pub vendor: u16, + /// The product identifier. + pub product: u16, + /// The version identifier. + pub version: u16, +} diff --git a/os/arceos/modules/axinput/src/lib.rs b/os/arceos/modules/axinput/src/lib.rs index 4a93552805..ee5893db7d 100644 --- a/os/arceos/modules/axinput/src/lib.rs +++ b/os/arceos/modules/axinput/src/lib.rs @@ -2,36 +2,37 @@ #![no_std] -#[macro_use] -extern crate log; extern crate alloc; +mod device; +mod event; +mod id; +pub mod rdif; + use alloc::vec::Vec; use core::mem; -use ax_driver::{AxDeviceContainer, prelude::*}; use ax_lazyinit::LazyInit; use ax_sync::Mutex; +pub use device::{ErasedInputDevice, InputDevice, InputError, InputResult}; +pub use event::{AbsInfo, Event, EventType}; +pub use id::InputDeviceId; -static DEVICES: LazyInit>> = LazyInit::new(); +static DEVICES: LazyInit>> = LazyInit::new(); -/// Initializes the graphics subsystem by underlayer devices. -pub fn init_input(mut input_devs: AxDeviceContainer) { - info!("Initialize input subsystem..."); +/// Initializes the input subsystem by underlayer devices. +pub fn init_input(input_devs: impl IntoIterator) { + log::info!("Initialize input subsystem..."); let mut devices = Vec::new(); - while let Some(dev) = input_devs.take_one() { - info!( - " registered a new {:?} input device: {}", - dev.device_type(), - dev.device_name(), - ); + for dev in input_devs { + log::info!(" registered a new input device: {}", dev.name()); devices.push(dev); } DEVICES.init_once(Mutex::new(devices)); } /// Takes the initialized input devices. -pub fn take_inputs() -> Vec { +pub fn take_inputs() -> Vec { mem::take(&mut DEVICES.lock()) } diff --git a/os/arceos/modules/axinput/src/rdif.rs b/os/arceos/modules/axinput/src/rdif.rs new file mode 100644 index 0000000000..7dbd8414f8 --- /dev/null +++ b/os/arceos/modules/axinput/src/rdif.rs @@ -0,0 +1,122 @@ +use alloc::{boxed::Box, string::String}; + +use rdif_input::{InputError as RdifInputError, Interface}; + +use crate::{AbsInfo, Event, EventType, InputDevice, InputDeviceId, InputError, InputResult}; + +pub struct RdifInputDevice { + name: String, + device: Box, +} + +impl RdifInputDevice { + pub fn new(device: Box) -> Self { + let name = device.name().into(); + Self { name, device } + } + + pub fn from_interface(device: impl Interface + 'static) -> Self { + Self::new(Box::new(device)) + } +} + +impl InputDevice for RdifInputDevice { + fn name(&self) -> &str { + &self.name + } + + fn device_id(&self) -> InputDeviceId { + self.device.device_id().into() + } + + fn physical_location(&self) -> &str { + self.device.physical_location() + } + + fn unique_id(&self) -> &str { + self.device.unique_id() + } + + fn get_event_bits(&mut self, ty: EventType, out: &mut [u8]) -> InputResult { + self.device + .get_event_bits(ty.into(), out) + .map_err(map_input_error) + } + + fn read_event(&mut self) -> InputResult { + self.device + .read_event() + .map(Into::into) + .map_err(map_input_error) + } + + fn get_prop_bits(&mut self, out: &mut [u8]) -> InputResult { + self.device.get_prop_bits(out).map_err(map_input_error) + } + + fn get_abs_info(&mut self, axis: u8) -> InputResult { + self.device + .get_abs_info(axis) + .map(Into::into) + .map_err(map_input_error) + } +} + +impl From for InputDeviceId { + fn from(value: rdif_input::InputDeviceId) -> Self { + Self { + bus_type: value.bus_type, + vendor: value.vendor, + product: value.product, + version: value.version, + } + } +} + +impl From for rdif_input::EventType { + fn from(value: EventType) -> Self { + match value { + EventType::Synchronization => Self::Synchronization, + EventType::Key => Self::Key, + EventType::Relative => Self::Relative, + EventType::Absolute => Self::Absolute, + EventType::Misc => Self::Misc, + EventType::Switch => Self::Switch, + EventType::Led => Self::Led, + EventType::Sound => Self::Sound, + EventType::ForceFeedback => Self::ForceFeedback, + } + } +} + +impl From for Event { + fn from(value: rdif_input::InputEvent) -> Self { + Self { + event_type: value.event_type, + code: value.code, + value: value.value, + } + } +} + +impl From for AbsInfo { + fn from(value: rdif_input::AbsInfo) -> Self { + Self { + min: value.min, + max: value.max, + fuzz: value.fuzz, + flat: value.flat, + res: value.res, + } + } +} + +fn map_input_error(error: RdifInputError) -> InputError { + match error { + RdifInputError::NotSupported => InputError::Unsupported, + RdifInputError::Again => InputError::Again, + RdifInputError::NotAvailable => InputError::ResourceBusy, + RdifInputError::InvalidEvent => InputError::InvalidInput, + RdifInputError::Other(_) => InputError::Io, + } +} diff --git a/os/arceos/modules/axnet-ng/Cargo.toml b/os/arceos/modules/axnet-ng/Cargo.toml index bd511a472a..fbec6699e5 100644 --- a/os/arceos/modules/axnet-ng/Cargo.toml +++ b/os/arceos/modules/axnet-ng/Cargo.toml @@ -8,13 +8,12 @@ description = "ArceOS network module" license.workspace = true [features] -vsock = ["ax-driver/vsock"] +vsock = ["dep:rdif-vsock"] [dependencies] async-channel = { version = "2.5", default-features = false } async-trait = "0.1" ax-config = { workspace = true } -ax-driver = { workspace = true, features = ["net"] } ax-errno = { workspace = true } ax-fs-ng = { workspace = true } axfs-ng-vfs = { workspace = true } @@ -30,6 +29,8 @@ enum_dispatch = "0.3" event-listener = { version = "5.4", default-features = false } hashbrown = "0.16" log = { workspace = true } +rd-net = { workspace = true } +rdif-vsock = { workspace = true, optional = true } ringbuf = { version = "0.4", default-features = false, features = ["alloc"] } spin = { workspace = true } diff --git a/os/arceos/modules/axnet-ng/src/device/driver.rs b/os/arceos/modules/axnet-ng/src/device/driver.rs new file mode 100644 index 0000000000..fceb39234c --- /dev/null +++ b/os/arceos/modules/axnet-ng/src/device/driver.rs @@ -0,0 +1,260 @@ +use alloc::{boxed::Box, collections::VecDeque, string::String, vec::Vec}; + +use ax_sync::spin::SpinNoIrq; +use rd_net::{Net, NetError, RxQueue, TxQueue}; + +const RX_PREFETCH_TARGET: usize = 1; +const ETH_ZLEN: usize = 60; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum NetDeviceError { + Again, + BadState, + InvalidParam, + Io, + NoMemory, + Unsupported, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct NetIrqEvents(u32); + +impl NetIrqEvents { + pub const RX_READY: Self = Self(1 << 0); + pub const TX_DONE: Self = Self(1 << 1); + pub const RX_ERROR: Self = Self(1 << 2); + pub const SPURIOUS: Self = Self(1 << 31); + + pub const fn empty() -> Self { + Self(0) + } + + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + pub const fn intersects(self, other: Self) -> bool { + (self.0 & other.0) != 0 + } +} + +impl core::ops::BitOr for NetIrqEvents { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } +} + +impl core::ops::BitOrAssign for NetIrqEvents { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } +} + +pub type NetDeviceResult = Result; + +pub trait NetRxBuffer: Send { + fn packet(&self) -> &[u8]; + fn packet_len(&self) -> usize { + self.packet().len() + } +} + +pub trait NetTxBuffer: Send { + fn packet(&self) -> &[u8]; + fn packet_mut(&mut self) -> &mut [u8]; + fn packet_len(&self) -> usize; +} + +pub trait EthernetDriver: Send + Sync { + fn device_name(&self) -> &str; + fn irq_num(&self) -> Option; + fn mac_address(&self) -> [u8; 6]; + fn alloc_tx_buffer(&mut self, size: usize) -> NetDeviceResult>; + fn recycle_tx_buffers(&mut self) -> NetDeviceResult; + fn transmit(&mut self, tx_buf: &mut dyn NetTxBuffer) -> NetDeviceResult; + fn receive(&mut self) -> NetDeviceResult>; + fn recycle_rx_buffer(&mut self, rx_buf: &mut dyn NetRxBuffer) -> NetDeviceResult; + fn handle_irq(&mut self) -> NetIrqEvents; +} + +pub type EthernetDeviceList = Vec>; + +struct VecTxBuffer { + packet: Vec, +} + +impl VecTxBuffer { + fn new(size: usize) -> Self { + Self { + packet: alloc::vec![0; size], + } + } +} + +impl NetTxBuffer for VecTxBuffer { + fn packet(&self) -> &[u8] { + &self.packet + } + + fn packet_mut(&mut self) -> &mut [u8] { + &mut self.packet + } + + fn packet_len(&self) -> usize { + self.packet.len() + } +} + +struct VecRxBuffer { + packet: Vec, +} + +impl NetRxBuffer for VecRxBuffer { + fn packet(&self) -> &[u8] { + &self.packet + } +} + +struct RdNetState { + tx_queue: TxQueue, + rx_queue: RxQueue, + pending_rx: VecDeque, +} + +pub struct RdNetDriver { + name: String, + mac: [u8; 6], + irq_num: Option, + irq_handler: Option, + state: SpinNoIrq, +} + +impl RdNetDriver { + pub fn new( + name: impl Into, + mut net: Net, + irq_num: Option, + ) -> NetDeviceResult { + let mac = net.mac_address(); + let tx_queue = net.create_tx_queue().map_err(map_net_error)?; + let rx_queue = net.create_rx_queue().map_err(map_net_error)?; + let irq_handler = irq_num.map(|_| net.irq_handler()); + + Ok(Self { + name: name.into(), + mac, + irq_num, + irq_handler, + state: SpinNoIrq::new(RdNetState { + tx_queue, + rx_queue, + pending_rx: VecDeque::with_capacity(RX_PREFETCH_TARGET), + }), + }) + } + + fn prefetch_rx_packets(&self, state: &mut RdNetState, target: usize) -> NetDeviceResult { + while state.pending_rx.len() < target { + let Some(packet) = state.rx_queue.receive(|packet| VecRxBuffer { + packet: packet.to_vec(), + }) else { + break; + }; + state.pending_rx.push_back(packet); + } + Ok(()) + } +} + +impl EthernetDriver for RdNetDriver { + fn device_name(&self) -> &str { + &self.name + } + + fn irq_num(&self) -> Option { + self.irq_num + } + + fn mac_address(&self) -> [u8; 6] { + self.mac + } + + fn alloc_tx_buffer(&mut self, size: usize) -> NetDeviceResult> { + let capacity = self.state.lock().tx_queue.buf_size(); + if size > capacity { + return Err(NetDeviceError::InvalidParam); + } + Ok(Box::new(VecTxBuffer::new(size))) + } + + fn recycle_tx_buffers(&mut self) -> NetDeviceResult { + Ok(()) + } + + fn transmit(&mut self, tx_buf: &mut dyn NetTxBuffer) -> NetDeviceResult { + let packet_len = tx_buf.packet_len(); + let tx_len = packet_len.max(ETH_ZLEN); + let mut state = self.state.lock(); + let (_ret, mut pending) = state + .tx_queue + .prepare_send(tx_len, |buffer| { + let packet = tx_buf.packet_mut(); + buffer[..packet_len].copy_from_slice(packet); + buffer[packet_len..tx_len].fill(0); + }) + .map_err(map_net_error)?; + pending.try_submit().map_err(map_net_error) + } + + fn receive(&mut self) -> NetDeviceResult> { + let mut state = self.state.lock(); + self.prefetch_rx_packets(&mut state, RX_PREFETCH_TARGET)?; + state + .pending_rx + .pop_front() + .map(|packet| Box::new(packet) as Box) + .ok_or(NetDeviceError::Again) + } + + fn recycle_rx_buffer(&mut self, _rx_buf: &mut dyn NetRxBuffer) -> NetDeviceResult { + Ok(()) + } + + fn handle_irq(&mut self) -> NetIrqEvents { + let Some(handler) = &self.irq_handler else { + return NetIrqEvents::SPURIOUS; + }; + + handler.handle(); + + let mut events = NetIrqEvents::empty(); + let mut state = self.state.lock(); + if let Err(err) = self.prefetch_rx_packets(&mut state, RX_PREFETCH_TARGET) { + warn!( + "failed to prefetch rx packets for {} during irq: {err:?}", + self.name + ); + events |= NetIrqEvents::RX_ERROR; + } + if !state.pending_rx.is_empty() { + events |= NetIrqEvents::RX_READY; + } + + if events.is_empty() { + NetIrqEvents::SPURIOUS + } else { + events + } + } +} + +fn map_net_error(err: NetError) -> NetDeviceError { + match err { + NetError::Retry => NetDeviceError::Again, + NetError::NoMemory => NetDeviceError::NoMemory, + NetError::NotSupported => NetDeviceError::Unsupported, + NetError::LinkDown | NetError::Other(_) => NetDeviceError::Io, + } +} diff --git a/os/arceos/modules/axnet-ng/src/device/ethernet.rs b/os/arceos/modules/axnet-ng/src/device/ethernet.rs index 1d95257aa1..002adeeb98 100644 --- a/os/arceos/modules/axnet-ng/src/device/ethernet.rs +++ b/os/arceos/modules/axnet-ng/src/device/ethernet.rs @@ -1,4 +1,5 @@ use alloc::{ + boxed::Box, string::String, sync::{Arc, Weak}, vec, @@ -6,7 +7,6 @@ use alloc::{ }; use core::task::Waker; -use ax_driver::prelude::*; use ax_sync::spin::SpinNoIrq; use axpoll::PollSet; use hashbrown::HashMap; @@ -21,7 +21,7 @@ use smoltcp::{ use crate::{ consts::{ETHERNET_MAX_PENDING_PACKETS, STANDARD_MTU}, - device::{ArpEntry, Device}, + device::{ArpEntry, Device, EthernetDriver, NetDeviceError, NetIrqEvents}, }; const EMPTY_MAC: EthernetAddress = EthernetAddress([0; 6]); @@ -40,12 +40,12 @@ struct PendingNeighbor { struct EthernetIrqState { irq_num: Option, - driver: SpinNoIrq, + driver: SpinNoIrq>, poll_ready: PollSet, } impl EthernetIrqState { - fn handle_irq(&self) -> NetIrqEvent { + fn handle_irq(&self) -> NetIrqEvents { self.driver.lock().handle_irq() } } @@ -66,7 +66,7 @@ fn handle_ethernet_irq(slot: usize) { }; let events = state.handle_irq(); - if events.intersects(NetIrqEvent::RX_READY | NetIrqEvent::RX_ERROR | NetIrqEvent::TX_DONE) { + if events.intersects(NetIrqEvents::RX_READY | NetIrqEvents::RX_ERROR | NetIrqEvents::TX_DONE) { state.poll_ready.wake(); } } @@ -121,7 +121,7 @@ impl EthernetDevice { const NEIGHBOR_TTL: Duration = Duration::from_secs(60); const ARP_REQUEST_RETRY: Duration = Duration::from_secs(1); - pub fn new(name: String, inner: AxNetDevice, ip: Option) -> Self { + pub fn new(name: String, inner: Box, ip: Option) -> Self { let irq_num = inner.irq_num(); let inner = Arc::new(EthernetIrqState { irq_num, @@ -168,11 +168,11 @@ impl EthernetDevice { #[inline] fn hardware_address(&self) -> EthernetAddress { - EthernetAddress(self.inner.driver.lock().mac_address().0) + EthernetAddress(self.inner.driver.lock().mac_address()) } fn send_to( - inner: &mut AxNetDevice, + inner: &mut dyn EthernetDriver, dst: EthernetAddress, size: usize, f: F, @@ -186,7 +186,7 @@ impl EthernetDevice { } let repr = EthernetRepr { - src_addr: EthernetAddress(inner.mac_address().0), + src_addr: EthernetAddress(inner.mac_address()), dst_addr: dst, ethertype: proto, }; @@ -206,7 +206,7 @@ impl EthernetDevice { tx_buf.packet_len(), tx_buf.packet() ); - if let Err(err) = inner.transmit(tx_buf) { + if let Err(err) = inner.transmit(&mut *tx_buf) { warn!("transmit failed: {:?}", err); } } @@ -268,7 +268,7 @@ impl EthernetDevice { let mut inner = self.inner.driver.lock(); Self::send_to( - &mut inner, + &mut **inner, EthernetAddress::BROADCAST, arp_repr.buffer_len(), |buf| arp_repr.emit(&mut ArpPacket::new_unchecked(buf)), @@ -349,7 +349,7 @@ impl EthernetDevice { let mut inner = self.inner.driver.lock(); Self::send_to( - &mut inner, + &mut **inner, source_hardware_addr, response.buffer_len(), |buf| response.emit(&mut ArpPacket::new_unchecked(buf)), @@ -382,7 +382,7 @@ impl EthernetDevice { self.name, next_hop, neighbor.hardware_address ); Self::send_to( - &mut inner, + &mut **inner, neighbor.hardware_address, buf.len(), |b| b.copy_from_slice(buf), @@ -407,12 +407,12 @@ impl Device for EthernetDevice { snoop: &mut dyn FnMut(&[u8]), ) -> bool { loop { - let rx_buf = { + let mut rx_buf = { let mut inner = self.inner.driver.lock(); match inner.receive() { Ok(buf) => buf, Err(err) => { - if !matches!(err, DevError::Again) { + if !matches!(err, NetDeviceError::Again) { warn!("receive failed: {:?}", err); } return false; @@ -426,7 +426,9 @@ impl Device for EthernetDevice { ); let result = self.handle_frame(rx_buf.packet(), buffer, timestamp, snoop); - self.inner.driver.lock().recycle_rx_buffer(rx_buf).unwrap(); + if let Err(err) = self.inner.driver.lock().recycle_rx_buffer(&mut *rx_buf) { + warn!("recycle_rx_buffer failed: {:?}", err); + } if result { return true; } @@ -439,7 +441,7 @@ impl Device for EthernetDevice { if next_hop.is_broadcast() || is_subnet_broadcast { let mut inner = self.inner.driver.lock(); Self::send_to( - &mut inner, + &mut **inner, EthernetAddress::BROADCAST, packet.len(), |buf| buf.copy_from_slice(packet), @@ -452,7 +454,7 @@ impl Device for EthernetDevice { Some(neighbor) if neighbor.expires_at > timestamp => { let mut inner = self.inner.driver.lock(); Self::send_to( - &mut inner, + &mut **inner, neighbor.hardware_address, packet.len(), |buf| buf.copy_from_slice(packet), diff --git a/os/arceos/modules/axnet-ng/src/device/mod.rs b/os/arceos/modules/axnet-ng/src/device/mod.rs index 71654995e5..2a67c3a0e1 100644 --- a/os/arceos/modules/axnet-ng/src/device/mod.rs +++ b/os/arceos/modules/axnet-ng/src/device/mod.rs @@ -7,11 +7,13 @@ use smoltcp::{ wire::{IpAddress, Ipv4Cidr}, }; +mod driver; mod ethernet; mod loopback; #[cfg(feature = "vsock")] mod vsock; +pub use driver::*; pub use ethernet::*; pub use loopback::*; #[cfg(feature = "vsock")] diff --git a/os/arceos/modules/axnet-ng/src/device/vsock.rs b/os/arceos/modules/axnet-ng/src/device/vsock.rs index 1b2698d6cf..186c9f99ce 100644 --- a/os/arceos/modules/axnet-ng/src/device/vsock.rs +++ b/os/arceos/modules/axnet-ng/src/device/vsock.rs @@ -4,21 +4,24 @@ use core::{ time::Duration, }; -use ax_driver::prelude::*; use ax_errno::{AxError, AxResult, ax_bail}; use ax_sync::Mutex; use ax_task::future::{block_on, interruptible}; +use rdif_vsock::{Interface, VsockAddr, VsockConnId, VsockError, VsockEvent}; use crate::vsock::connection_manager::VSOCK_CONN_MANAGER; +pub type VsockDevice = alloc::boxed::Box; +pub type VsockDeviceList = alloc::vec::Vec; + // we need a global and static only one vsock device -static VSOCK_DEVICE: Mutex> = Mutex::new(None); -static PENDING_EVENTS: Mutex> = Mutex::new(VecDeque::new()); +static VSOCK_DEVICE: Mutex> = Mutex::new(None); +static PENDING_EVENTS: Mutex> = Mutex::new(VecDeque::new()); const VSOCK_RX_TMPBUF_SIZE: usize = 0x1000; // 4KiB buffer for vsock receive /// Registers a vsock device. Only one vsock device can be registered. -pub fn register_vsock_device(dev: AxVsockDevice) -> AxResult { +pub fn register_vsock_device(dev: VsockDevice) -> AxResult { let mut guard = VSOCK_DEVICE.lock(); if guard.is_some() { ax_bail!(AlreadyExists, "vsock device already registered"); @@ -157,18 +160,18 @@ fn poll_vsock_interfaces() -> AxResult { Ok(event_count > 0) } -fn handle_vsock_event(event: VsockDriverEvent, dev: &mut AxVsockDevice, buf: &mut [u8]) { +fn handle_vsock_event(event: VsockEvent, dev: &mut VsockDevice, buf: &mut [u8]) { let mut manager = VSOCK_CONN_MANAGER.lock(); debug!("Handling vsock event: {event:?}"); match event { - VsockDriverEvent::ConnectionRequest(conn_id) => { + VsockEvent::ConnectionRequest(conn_id) => { if let Err(e) = manager.on_connection_request(conn_id) { info!("Connection request failed: {conn_id:?}, error={e:?}"); } } - VsockDriverEvent::Received(conn_id, len) => { + VsockEvent::Received(conn_id, len) => { let free_space = if let Some(conn) = manager.get_connection(conn_id) { conn.lock().rx_buffer_free() } else { @@ -179,7 +182,7 @@ fn handle_vsock_event(event: VsockDriverEvent, dev: &mut AxVsockDevice, buf: &mu if free_space == 0 { PENDING_EVENTS .lock() - .push_back(VsockDriverEvent::Received(conn_id, len)); + .push_back(VsockEvent::Received(conn_id, len)); return; } @@ -196,19 +199,19 @@ fn handle_vsock_event(event: VsockDriverEvent, dev: &mut AxVsockDevice, buf: &mu } } - VsockDriverEvent::Disconnected(conn_id) => { + VsockEvent::Disconnected(conn_id) => { if let Err(e) = manager.on_disconnected(conn_id) { info!("Failed to handle disconnection: {conn_id:?}, error={e:?}",); } } - VsockDriverEvent::Connected(conn_id) => { + VsockEvent::Connected(conn_id) => { if let Err(e) = manager.on_connected(conn_id) { info!("Failed to handle connection established: {conn_id:?}, error={e:?}",); } } - VsockDriverEvent::CreditUpdate(conn_id) => { + VsockEvent::CreditUpdate(conn_id) => { if let Err(e) = manager.on_credit_update(conn_id) { warn!( "Failed to handle credit update: {:?}, error={:?}", @@ -217,31 +220,31 @@ fn handle_vsock_event(event: VsockDriverEvent, dev: &mut AxVsockDevice, buf: &mu } } - VsockDriverEvent::Unknown => warn!("Received unknown vsock event"), + VsockEvent::Unknown => warn!("Received unknown vsock event"), } } pub fn vsock_listen(addr: VsockAddr) -> AxResult<()> { let mut guard = VSOCK_DEVICE.lock(); let dev = guard.as_mut().ok_or(AxError::NotFound)?; - dev.listen(addr.port); - Ok(()) + dev.listen(addr.port).map_err(map_vsock_error) } -fn map_dev_err(e: DevError) -> AxError { +fn map_vsock_error(e: VsockError) -> AxError { match e { - DevError::AlreadyExists => AxError::AlreadyExists, - DevError::Again => AxError::WouldBlock, - DevError::InvalidParam => AxError::InvalidInput, - DevError::Io => AxError::Io, - _ => AxError::BadState, + VsockError::AlreadyExists => AxError::AlreadyExists, + VsockError::Retry => AxError::WouldBlock, + VsockError::NotConnected => AxError::NotConnected, + VsockError::NotAvailable => AxError::NotFound, + VsockError::NotSupported => AxError::Unsupported, + VsockError::Other(_) => AxError::BadState, } } pub fn vsock_connect(conn_id: VsockConnId) -> AxResult<()> { let mut guard = VSOCK_DEVICE.lock(); let dev = guard.as_mut().ok_or(AxError::NotFound)?; - dev.connect(conn_id).map_err(map_dev_err) + dev.connect(conn_id).map_err(map_vsock_error) } pub fn vsock_send(conn_id: VsockConnId, buf: &[u8]) -> AxResult { @@ -254,23 +257,23 @@ pub fn vsock_send(conn_id: VsockConnId, buf: &[u8]) -> AxResult { }; match result { Ok(len) => return Ok(len), - Err(DevError::Again) => { + Err(VsockError::Retry) => { let manager = VSOCK_CONN_MANAGER.lock(); if let Some(conn) = manager.get_connection(conn_id) { drop(manager); conn.lock().wait_for_tx(); }; } - Err(e) => return Err(map_dev_err(e)), + Err(e) => return Err(map_vsock_error(e)), } } - Err(map_dev_err(DevError::Again)) + Err(map_vsock_error(VsockError::Retry)) } pub fn vsock_disconnect(conn_id: VsockConnId) -> AxResult<()> { let mut guard = VSOCK_DEVICE.lock(); let dev = guard.as_mut().ok_or(AxError::NotFound)?; - dev.disconnect(conn_id).map_err(map_dev_err) + dev.disconnect(conn_id).map_err(map_vsock_error) } pub fn vsock_guest_cid() -> AxResult { diff --git a/os/arceos/modules/axnet-ng/src/lib.rs b/os/arceos/modules/axnet-ng/src/lib.rs index 9e7c284b8f..170fc5c26d 100644 --- a/os/arceos/modules/axnet-ng/src/lib.rs +++ b/os/arceos/modules/axnet-ng/src/lib.rs @@ -48,11 +48,12 @@ use core::{ time::Duration, }; -use ax_driver::{AxDeviceContainer, prelude::*}; use ax_sync::Mutex; use smoltcp::wire::{EthernetAddress, Ipv4Address, Ipv4Cidr}; use spin::{Lazy, Once}; +#[cfg(feature = "vsock")] +pub use self::device::{VsockDevice, VsockDeviceList}; use self::{ consts::{GATEWAY, IP, IP_PREFIX}, device::{EthernetDevice, LoopbackDevice}, @@ -61,7 +62,13 @@ use self::{ service::Service, wrapper::SocketSetWrapper, }; -pub use self::{device::ArpEntry, socket::*}; +pub use self::{ + device::{ + ArpEntry, EthernetDeviceList, EthernetDriver, NetDeviceError, NetDeviceResult, + NetIrqEvents, NetRxBuffer, NetTxBuffer, RdNetDriver, + }, + socket::*, +}; static LISTEN_TABLE: Lazy = Lazy::new(ListenTable::new); static SOCKET_SET: Lazy = Lazy::new(SocketSetWrapper::new); @@ -81,7 +88,7 @@ fn get_service() -> ax_sync::MutexGuard<'static, Service> { } /// Initializes the network subsystem by NIC devices. -pub fn init_network(mut net_devs: AxDeviceContainer) { +pub fn init_network(mut net_devs: EthernetDeviceList) { info!("Initialize network subsystem..."); let mut router = Router::new(); @@ -99,10 +106,11 @@ pub fn init_network(mut net_devs: AxDeviceContainer) { let mut dhcp_dev = None; let mut dhcp_mac = None; - let eth0_ip = if let Some(dev) = net_devs.take_one() { + let eth0_ip = if !net_devs.is_empty() { + let dev = net_devs.remove(0); info!(" use NIC 0: {:?}", dev.device_name()); - let eth0_address = EthernetAddress(dev.mac_address().0); + let eth0_address = EthernetAddress(dev.mac_address()); let eth0_ip = static_network .then(|| Ipv4Cidr::new(IP.parse().expect("Invalid IPv4 address"), IP_PREFIX)); @@ -160,11 +168,11 @@ pub fn init_network(mut net_devs: AxDeviceContainer) { /// Init vsock subsystem by vsock devices. #[cfg(feature = "vsock")] -pub fn init_vsock(mut vsock_devs: AxDeviceContainer) { +pub fn init_vsock(mut vsock_devs: device::VsockDeviceList) { use self::device::register_vsock_device; info!("Initialize vsock subsystem..."); - if let Some(dev) = vsock_devs.take_one() { - info!(" use vsock 0: {:?}", dev.device_name()); + if let Some(dev) = vsock_devs.pop() { + info!(" use vsock 0: {:?}", dev.name()); if let Err(e) = register_vsock_device(dev) { warn!("Failed to initialize vsock device: {:?}", e); } diff --git a/os/arceos/modules/axnet-ng/src/socket.rs b/os/arceos/modules/axnet-ng/src/socket.rs index caa7de61cb..8bfbdc1bfc 100644 --- a/os/arceos/modules/axnet-ng/src/socket.rs +++ b/os/arceos/modules/axnet-ng/src/socket.rs @@ -6,15 +6,13 @@ use core::{ task::Context, }; -#[cfg(feature = "vsock")] -use ax_driver::prelude::VsockAddr; use ax_errno::{AxError, AxResult, LinuxError}; use ax_io::prelude::*; use axpoll::{IoEvents, Pollable}; use bitflags::bitflags; #[cfg(feature = "vsock")] -use crate::vsock::VsockSocket; +use crate::vsock::{VsockAddr, VsockSocket}; use crate::{ options::{Configurable, GetSocketOption, SetSocketOption}, raw::RawSocket, diff --git a/os/arceos/modules/axnet-ng/src/vsock/mod.rs b/os/arceos/modules/axnet-ng/src/vsock/mod.rs index 28a420def3..aad26a686e 100644 --- a/os/arceos/modules/axnet-ng/src/vsock/mod.rs +++ b/os/arceos/modules/axnet-ng/src/vsock/mod.rs @@ -5,11 +5,11 @@ pub(crate) mod stream; use core::task::Context; -pub use ax_driver::prelude::{VsockAddr, VsockConnId}; use ax_errno::{AxError, AxResult}; use ax_io::{IoBuf, IoBufMut, Read, Write}; use axpoll::{IoEvents, Pollable}; use enum_dispatch::enum_dispatch; +pub use rdif_vsock::{VsockAddr, VsockConnId}; pub use self::stream::VsockStreamTransport; use crate::{ diff --git a/os/arceos/modules/axnet/Cargo.toml b/os/arceos/modules/axnet/Cargo.toml index 266d6bb460..88ed2f052b 100644 --- a/os/arceos/modules/axnet/Cargo.toml +++ b/os/arceos/modules/axnet/Cargo.toml @@ -13,15 +13,15 @@ license.workspace = true [features] smoltcp = ["dep:smoltcp"] default = ["smoltcp"] -vsock = ["ax-driver/vsock"] +vsock = [] [dependencies] -ax-driver = { workspace = true, features = ["net"] } ax-errno.workspace = true ax-hal.workspace = true ax-io.workspace = true ax-sync.workspace = true ax-task.workspace = true +ax-net-ng.workspace = true cfg-if.workspace = true ax-lazyinit.workspace = true log.workspace = true diff --git a/os/arceos/modules/axnet/src/lib.rs b/os/arceos/modules/axnet/src/lib.rs index 86dd638d85..c6a5ce8705 100644 --- a/os/arceos/modules/axnet/src/lib.rs +++ b/os/arceos/modules/axnet/src/lib.rs @@ -24,6 +24,9 @@ extern crate alloc; #[macro_use] extern crate log; +#[cfg(feature = "smoltcp")] +use alloc::{boxed::Box, vec::Vec}; + cfg_if::cfg_if! { if #[cfg(feature = "smoltcp")] { mod smoltcp_impl; @@ -32,7 +35,10 @@ cfg_if::cfg_if! { } #[cfg(feature = "smoltcp")] -use ax_driver::{AxDeviceContainer, AxNetDevice}; +pub use ax_net_ng::{ + EthernetDriver, NetDeviceError, NetDeviceResult, NetIrqEvents, NetRxBuffer, NetTxBuffer, + RdNetDriver, +}; #[cfg(feature = "smoltcp")] pub use self::net_impl::{ @@ -41,12 +47,10 @@ pub use self::net_impl::{ /// Initializes the network subsystem by NIC devices. #[cfg(feature = "smoltcp")] -pub fn init_network(mut net_devs: AxDeviceContainer) { - use ax_driver::prelude::*; - +pub fn init_network(mut net_devs: Vec>) { info!("Initialize network subsystem..."); - if let Some(dev) = net_devs.take_one() { + if let Some(dev) = net_devs.pop() { info!(" use NIC 0: {:?}", dev.device_name()); net_impl::init(dev); } else { diff --git a/os/arceos/modules/axnet/src/smoltcp_impl/mod.rs b/os/arceos/modules/axnet/src/smoltcp_impl/mod.rs index 90f1bfca74..dd13ed7287 100644 --- a/os/arceos/modules/axnet/src/smoltcp_impl/mod.rs +++ b/os/arceos/modules/axnet/src/smoltcp_impl/mod.rs @@ -5,12 +5,12 @@ mod listen_table; mod tcp; mod udp; -use alloc::vec; +use alloc::{boxed::Box, vec}; use core::{cell::RefCell, ops::DerefMut}; -use ax_driver::prelude::*; use ax_hal::time::{NANOS_PER_MICROS, wall_time_nanos}; use ax_lazyinit::LazyInit; +use ax_net_ng::{EthernetDriver, NetDeviceError, NetRxBuffer}; use ax_sync::Mutex; use smoltcp::{ iface::{Config, Interface, SocketHandle, SocketSet}, @@ -54,7 +54,7 @@ static ETH0: LazyInit = LazyInit::new(); struct SocketSetWrapper<'a>(Mutex>); struct DeviceWrapper { - inner: RefCell, /* use `RefCell` is enough since it's wrapped in `Mutex` in `InterfaceWrapper`. */ + inner: RefCell>, sockets_for_preprocess: Option, } @@ -128,7 +128,7 @@ impl<'a> SocketSetWrapper<'a> { } impl InterfaceWrapper { - fn new(name: &'static str, dev: AxNetDevice, ether_addr: EthernetAddress) -> Self { + fn new(name: &'static str, dev: Box, ether_addr: EthernetAddress) -> Self { let mut config = Config::new(HardwareAddress::Ethernet(ether_addr)); config.random_seed = RANDOM_SEED; @@ -181,7 +181,7 @@ impl InterfaceWrapper { } impl DeviceWrapper { - fn new(inner: AxNetDevice) -> Self { + fn new(inner: Box) -> Self { Self { inner: RefCell::new(inner), sockets_for_preprocess: None, @@ -210,13 +210,10 @@ impl Device for DeviceWrapper { return None; } - if !dev.can_transmit() { - return None; - } let rx_buf = match dev.receive() { Ok(buf) => buf, Err(err) => { - if !matches!(err, DevError::Again) { + if !matches!(err, NetDeviceError::Again) { warn!("receive failed: {err:?}"); } return None; @@ -234,11 +231,7 @@ impl Device for DeviceWrapper { warn!("recycle_tx_buffers failed: {e:?}"); return None; } - if dev.can_transmit() { - Some(AxNetTxToken(&self.inner)) - } else { - None - } + Some(AxNetTxToken(&self.inner)) } fn capabilities(&self) -> DeviceCapabilities { @@ -250,15 +243,19 @@ impl Device for DeviceWrapper { } } -struct AxNetRxToken<'a>(&'a RefCell, NetBufPtr, Option); -struct AxNetTxToken<'a>(&'a RefCell); +struct AxNetRxToken<'a>( + &'a RefCell>, + Box, + Option, +); +struct AxNetTxToken<'a>(&'a RefCell>); impl RxToken for AxNetRxToken<'_> { fn consume(self, f: F) -> R where F: FnOnce(&[u8]) -> R, { - let rx_buf = self.1; + let mut rx_buf = self.1; trace!( "RECV {} bytes: {:02X?}", rx_buf.packet_len(), @@ -273,7 +270,7 @@ impl RxToken for AxNetRxToken<'_> { } } let result = f(rx_buf.packet()); - self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap(); + self.0.borrow_mut().recycle_rx_buffer(&mut *rx_buf).unwrap(); result } } @@ -287,7 +284,7 @@ impl TxToken for AxNetTxToken<'_> { let mut tx_buf = dev.alloc_tx_buffer(len).unwrap(); let ret = f(tx_buf.packet_mut()); trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); - dev.transmit(tx_buf).unwrap(); + dev.transmit(&mut *tx_buf).unwrap(); ret } } @@ -329,8 +326,8 @@ pub fn bench_receive() { ETH0.dev.lock().bench_receive_bandwidth(); } -pub(crate) fn init(net_dev: AxNetDevice) { - let ether_addr = EthernetAddress(net_dev.mac_address().0); +pub(crate) fn init(net_dev: Box) { + let ether_addr = EthernetAddress(net_dev.mac_address()); let eth0 = InterfaceWrapper::new("eth0", net_dev, ether_addr); let ip = IP.parse().expect("invalid IP address"); diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 92bee18867..88da7cad43 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -16,29 +16,43 @@ buddy-slab = ["alloc", "ax-alloc/buddy-slab"] ipi = ["dep:ax-ipi"] irq = ["ax-hal/irq", "ax-task?/irq", "dep:ax-percpu"] multitask = ["ax-task/multitask"] -paging = ["ax-hal/paging", "dep:ax-mm", "dep:axklib"] +paging = ["alloc", "ax-hal/paging", "dep:ax-mm", "dep:axklib"] rtc = [] smp = ["alloc", "ax-hal/smp", "ax-task?/smp"] stack-guard-page = ["multitask", "paging", "ax-task/stack-guard-page"] tls = ["ax-hal/tls", "ax-task?/tls"] -plat-dyn = ["ax-hal/plat-dyn", "ax-driver/dyn", "paging", "buddy-slab"] +plat-dyn = ["ax-hal/plat-dyn", "paging", "buddy-slab"] -ax-driver = ["dep:ax-driver"] -display = ["ax-driver", "dep:ax-display"] -fs = ["ax-driver", "dep:ax-fs"] -fs-ng = ["ax-driver", "dep:ax-fs-ng"] -input = ["ax-driver", "dep:ax-input"] -net = ["ax-driver", "dep:ax-net"] -net-ng = ["ax-driver", "dep:ax-net-ng"] -vsock = ["net-ng", "ax-net-ng/vsock"] +display = ["dep:ax-display", "ax-driver/display"] +fs = [ + "dep:ax-errno", + "dep:ax-fs", + "dep:rd-block", + "dep:spin", + "dep:axklib", + "ax-driver/block", +] +fs-ng = [ + "dep:ax-errno", + "dep:ax-fs-ng", + "dep:rd-block", + "dep:spin", + "dep:axklib", + "ax-driver/block", +] +input = ["dep:ax-input", "ax-driver/input"] +net = ["dep:ax-net", "ax-driver/net"] +net-ng = ["dep:ax-net-ng", "dep:rd-net", "dep:spin", "dep:axklib", "ax-driver/net"] +vsock = ["net-ng", "ax-net-ng/vsock", "ax-driver/vsock"] [dependencies] ax-alloc = { workspace = true, optional = true, features = ["default"] } axbacktrace = { workspace = true } ax-config = { workspace = true } ax-display = { workspace = true, optional = true } -ax-driver = { workspace = true, optional = true } +ax-driver = { workspace = true } +ax-errno = { workspace = true, optional = true } ax-fs = { workspace = true, optional = true } ax-fs-ng = { workspace = true, optional = true } ax-hal = { workspace = true } @@ -60,3 +74,7 @@ ax-crate-interface = { workspace = true } ax-ctor-bare = { workspace = true } indoc = "2" ax-percpu = { workspace = true, optional = true } +rd-block = { workspace = true, optional = true } +rd-net = { workspace = true, optional = true } +rdrive.workspace = true +spin = { workspace = true, optional = true } diff --git a/os/arceos/modules/axruntime/src/devices.rs b/os/arceos/modules/axruntime/src/devices.rs new file mode 100644 index 0000000000..f7bfe6643f --- /dev/null +++ b/os/arceos/modules/axruntime/src/devices.rs @@ -0,0 +1,293 @@ +pub(crate) fn probe_all_devices() { + info!("Probe platform devices..."); + rdrive::probe_all(false) + .unwrap_or_else(|err| panic!("failed to probe platform devices: {err:?}")); +} + +#[cfg(all(feature = "fs", not(feature = "fs-ng"), feature = "plat-dyn"))] +pub(crate) fn take_dyn_fs_block_devices() +-> alloc::vec::Vec> { + #[cfg(target_os = "none")] + { + ax_driver::block::take_block_devices() + .into_iter() + .map(|dev| { + alloc::boxed::Box::new(FsBlockDevice(dev)) + as alloc::boxed::Box + }) + .collect() + } + + #[cfg(not(target_os = "none"))] + alloc::vec::Vec::new() +} + +#[cfg(all(feature = "fs", not(feature = "fs-ng"), not(feature = "plat-dyn")))] +pub(crate) fn take_static_fs_block_devices() +-> alloc::vec::Vec> { + ax_driver::block::take_block_devices() + .into_iter() + .map(|dev| { + alloc::boxed::Box::new(FsBlockDevice(dev)) + as alloc::boxed::Box + }) + .collect() +} + +#[cfg(all(feature = "fs-ng", feature = "plat-dyn"))] +pub(crate) fn take_dyn_fs_ng_block_devices() +-> alloc::vec::Vec> { + #[cfg(target_os = "none")] + { + ax_driver::block::take_block_devices() + .into_iter() + .map(|dev| { + alloc::boxed::Box::new(FsBlockDevice(dev)) + as alloc::boxed::Box + }) + .collect() + } + + #[cfg(not(target_os = "none"))] + alloc::vec::Vec::new() +} + +#[cfg(all(feature = "fs-ng", not(feature = "plat-dyn")))] +pub(crate) fn take_static_fs_ng_block_devices() +-> alloc::vec::Vec> { + ax_driver::block::take_block_devices() + .into_iter() + .map(|dev| { + alloc::boxed::Box::new(FsBlockDevice(dev)) + as alloc::boxed::Box + }) + .collect() +} + +#[cfg(all(feature = "display", feature = "plat-dyn"))] +pub(crate) fn init_dyn_display() { + #[cfg(target_os = "none")] + { + let devices = ax_driver::display::take_display_devices() + .unwrap_or_else(|err| panic!("failed to open display devices: {err:?}")) + .into_iter() + .map(|dev| { + let display = ax_display::rdif::RdifDisplayDevice::new(dev) + .unwrap_or_else(|err| panic!("failed to adapt display device: {err:?}")); + ax_display::ErasedDisplayDevice::new(display) + }); + ax_display::init_display(devices); + } + + #[cfg(not(target_os = "none"))] + ax_display::init_display(core::iter::empty::()); +} + +#[cfg(all(feature = "display", not(feature = "plat-dyn")))] +pub(crate) fn init_static_display() { + let devices = ax_driver::display::take_display_devices() + .unwrap_or_else(|err| panic!("failed to open static display devices: {err:?}")) + .into_iter() + .map(|dev| { + let display = ax_display::rdif::RdifDisplayDevice::new(dev) + .unwrap_or_else(|err| panic!("failed to adapt static display device: {err:?}")); + ax_display::ErasedDisplayDevice::new(display) + }); + ax_display::init_display(devices); +} + +#[cfg(all(feature = "input", feature = "plat-dyn"))] +pub(crate) fn init_dyn_input() { + #[cfg(target_os = "none")] + { + let devices = ax_driver::input::take_input_devices() + .unwrap_or_else(|err| panic!("failed to open input devices: {err:?}")) + .into_iter() + .map(|dev| ax_input::ErasedInputDevice::new(ax_input::rdif::RdifInputDevice::new(dev))); + ax_input::init_input(devices); + } + + #[cfg(not(target_os = "none"))] + ax_input::init_input(core::iter::empty::()); +} + +#[cfg(all(feature = "input", not(feature = "plat-dyn")))] +pub(crate) fn init_static_input() { + let devices = ax_driver::input::take_input_devices() + .unwrap_or_else(|err| panic!("failed to open static input devices: {err:?}")) + .into_iter() + .map(|dev| ax_input::ErasedInputDevice::new(ax_input::rdif::RdifInputDevice::new(dev))); + ax_input::init_input(devices); +} + +#[cfg(all(feature = "net", not(feature = "net-ng"), feature = "plat-dyn"))] +pub(crate) fn init_dyn_net() { + ax_net::init_network(take_dyn_net_drivers()); +} + +#[cfg(all(feature = "net", not(feature = "net-ng"), not(feature = "plat-dyn")))] +pub(crate) fn init_static_net() { + ax_net::init_network(take_static_net_drivers()); +} + +#[cfg(all(feature = "net-ng", feature = "plat-dyn"))] +pub(crate) fn init_dyn_net_ng() { + ax_net_ng::init_network(take_dyn_net_ng_drivers()); +} + +#[cfg(all(feature = "net", not(feature = "net-ng"), not(feature = "plat-dyn")))] +pub(crate) fn take_static_net_drivers() +-> alloc::vec::Vec> { + let mut devices = alloc::vec::Vec::new(); + for dev in rdrive::get_list::() { + let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) + .unwrap_or_else(|err| panic!("failed to open static net device: {err:?}")); + let driver = ax_net::RdNetDriver::new(name, net, irq_num) + .unwrap_or_else(|err| panic!("failed to adapt static net device: {err:?}")); + devices + .push(alloc::boxed::Box::new(driver) as alloc::boxed::Box); + } + devices +} + +#[cfg(all(feature = "net-ng", not(feature = "plat-dyn")))] +pub(crate) fn take_static_net_ng_drivers() +-> alloc::vec::Vec> { + let mut devices = alloc::vec::Vec::new(); + for dev in rdrive::get_list::() { + let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) + .unwrap_or_else(|err| panic!("failed to open static net device: {err:?}")); + let driver = ax_net_ng::RdNetDriver::new(name, net, irq_num) + .unwrap_or_else(|err| panic!("failed to adapt static net device: {err:?}")); + devices.push( + alloc::boxed::Box::new(driver) as alloc::boxed::Box + ); + } + devices +} + +#[cfg(all(feature = "vsock", feature = "plat-dyn"))] +pub(crate) fn init_dyn_vsock() { + #[cfg(target_os = "none")] + { + let devices = ax_driver::vsock::take_vsock_devices() + .unwrap_or_else(|err| panic!("failed to open vsock devices: {err:?}")); + ax_net_ng::init_vsock(devices); + } + + #[cfg(not(target_os = "none"))] + ax_net_ng::init_vsock(alloc::vec::Vec::new()); +} + +#[cfg(all(feature = "vsock", not(feature = "plat-dyn")))] +pub(crate) fn init_static_vsock() { + let devices = ax_driver::vsock::take_vsock_devices() + .unwrap_or_else(|err| panic!("failed to open static vsock devices: {err:?}")); + ax_net_ng::init_vsock(devices); +} + +#[cfg(all(feature = "net", not(feature = "net-ng"), feature = "plat-dyn"))] +fn take_dyn_net_drivers() -> alloc::vec::Vec> { + #[cfg(target_os = "none")] + { + let mut devices = alloc::vec::Vec::new(); + for dev in rdrive::get_list::() { + let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) + .unwrap_or_else(|err| panic!("failed to open net device: {err:?}")); + let driver = ax_net::RdNetDriver::new(name, net, irq_num) + .unwrap_or_else(|err| panic!("failed to adapt net device: {err:?}")); + devices.push( + alloc::boxed::Box::new(driver) as alloc::boxed::Box + ); + } + devices + } + + #[cfg(not(target_os = "none"))] + alloc::vec::Vec::new() +} + +#[cfg(all(feature = "net-ng", feature = "plat-dyn"))] +fn take_dyn_net_ng_drivers() -> alloc::vec::Vec> { + #[cfg(target_os = "none")] + { + let mut devices = alloc::vec::Vec::new(); + for dev in rdrive::get_list::() { + let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) + .unwrap_or_else(|err| panic!("failed to open net device: {err:?}")); + let driver = ax_net_ng::RdNetDriver::new(name, net, irq_num) + .unwrap_or_else(|err| panic!("failed to adapt net device: {err:?}")); + devices + .push(alloc::boxed::Box::new(driver) + as alloc::boxed::Box); + } + devices + } + + #[cfg(not(target_os = "none"))] + alloc::vec::Vec::new() +} + +#[cfg(all( + any(feature = "fs", feature = "fs-ng"), + any(not(feature = "plat-dyn"), target_os = "none") +))] +struct FsBlockDevice(ax_driver::block::Block); + +#[cfg(all( + feature = "fs", + not(feature = "fs-ng"), + any(not(feature = "plat-dyn"), target_os = "none") +))] +impl ax_fs::FsBlockDevice for FsBlockDevice { + fn name(&self) -> &str { + self.0.name() + } + + fn num_blocks(&self) -> u64 { + self.0.num_blocks() + } + + fn block_size(&self) -> usize { + self.0.block_size() + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> ax_errno::AxResult { + self.0.read_block(block_id, buf) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> ax_errno::AxResult { + self.0.write_block(block_id, buf) + } + + fn flush(&mut self) -> ax_errno::AxResult { + self.0.flush() + } +} + +#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +impl ax_fs_ng::FsBlockDevice for FsBlockDevice { + fn name(&self) -> &str { + self.0.name() + } + + fn num_blocks(&self) -> u64 { + self.0.num_blocks() + } + + fn block_size(&self) -> usize { + self.0.block_size() + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> ax_errno::AxResult { + self.0.read_block(block_id, buf) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> ax_errno::AxResult { + self.0.write_block(block_id, buf) + } + + fn flush(&mut self) -> ax_errno::AxResult { + self.0.flush() + } +} diff --git a/os/arceos/modules/axruntime/src/klib.rs b/os/arceos/modules/axruntime/src/klib.rs index 15677338e3..9247770599 100644 --- a/os/arceos/modules/axruntime/src/klib.rs +++ b/os/arceos/modules/axruntime/src/klib.rs @@ -77,6 +77,10 @@ impl_trait! { ax_mm::iomap(addr, size) } + fn mem_virt_to_phys(addr: VirtAddr) -> PhysAddr { + ax_hal::mem::virt_to_phys(addr) + } + fn mem_make_dma_coherent_uncached(addr: VirtAddr, size: usize) -> AxResult { let Some((start, size)) = dma_coherent_range(addr, size) else { return Ok(()); @@ -114,6 +118,31 @@ impl_trait! { Ok(()) } + fn dma_alloc_pages(dma_mask: u64, num_pages: usize, align: usize) -> AxResult { + let addr = if dma_mask <= u32::MAX as u64 { + ax_alloc::global_allocator().alloc_dma32_pages( + num_pages, + align, + ax_alloc::UsageKind::Dma, + ) + } else { + ax_alloc::global_allocator().alloc_pages( + num_pages, + align, + ax_alloc::UsageKind::Dma, + ) + }?; + Ok(VirtAddr::from(addr)) + } + + fn dma_dealloc_pages(addr: VirtAddr, num_pages: usize) { + ax_alloc::global_allocator().dealloc_pages( + addr.as_usize(), + num_pages, + ax_alloc::UsageKind::Dma, + ); + } + /// Busy-wait for the given duration by calling into `ax-hal`. /// /// Short delays are serviced by the hardware abstraction layer's @@ -123,26 +152,30 @@ impl_trait! { ax_hal::time::busy_wait(dur); } + fn time_monotonic_nanos() -> u64 { + ax_hal::time::monotonic_time_nanos() + } + + fn time_try_init_epoch_offset(epoch_time_nanos: u64) -> bool { + ax_hal::time::try_init_epoch_offset(epoch_time_nanos) + } + /// Enable or disable the specified IRQ line. /// /// When the `irq` feature is enabled this forwards to - /// `ax_hal::irq::set_enable`. If the feature is not enabled the - /// function currently panics via `unimplemented!()`; callers should - /// avoid relying on IRQ operations when the platform omits IRQ - /// support. + /// `ax_hal::irq::set_enable`. Platforms built without IRQ support + /// ignore this request because there is no interrupt controller + /// service to program. fn irq_set_enable(_irq: usize, _enabled: bool) { #[cfg(feature = "irq")] ax_hal::irq::set_enable(_irq, _enabled); - #[cfg(not(feature = "irq"))] - unimplemented!(); } /// Register an IRQ handler for the given IRQ number. /// /// Returns `true` when registration succeeds. With the `irq` - /// feature enabled this delegates to `ax_hal::irq::register`. - /// When IRQs are not enabled the function is currently unimplemented - /// and will panic if called. + /// feature enabled this delegates to `ax_hal::irq::register`. Without + /// IRQ support registration fails explicitly by returning `false`. fn irq_register(_irq: usize, _handler: IrqHandler) -> bool { #[cfg(feature = "irq")] { @@ -150,7 +183,7 @@ impl_trait! { } #[cfg(not(feature = "irq"))] { - unimplemented!() + false } } } diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 74a97ade06..1ffb5b211b 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -37,6 +37,8 @@ #[macro_use] extern crate ax_log; +extern crate ax_driver as _; + #[cfg(all(target_os = "none", not(test)))] mod lang_items; @@ -46,9 +48,23 @@ mod mp; #[cfg(feature = "paging")] mod klib; +mod devices; +mod registers; + #[cfg(feature = "smp")] pub use self::mp::rust_main_secondary; +#[cfg(any( + all(any(feature = "fs", feature = "fs-ng"), not(feature = "plat-dyn")), + all(any(feature = "fs", feature = "fs-ng"), feature = "plat-dyn"), + feature = "net", + feature = "net-ng", + feature = "display", + feature = "input", + feature = "vsock" +))] +extern crate alloc; + const LOGO: &str = r#" d8888 .d88888b. .d8888b. d88888 d88P" "Y88b d88P Y88b @@ -233,11 +249,15 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { ax_hal::trap::set_page_fault_handler(runtime_page_fault_handler); } - // #[cfg(feature = "plat-dyn")] - // ax_driver::setup(arg); - info!("Initialize platform devices..."); ax_hal::init_later(cpu_id, arg); + if !rdrive::is_initialized() { + rdrive::init(rdrive::Platform::Static(ax_hal::drivers::static_devices())) + .unwrap_or_else(|err| panic!("failed to initialize static rdrive source: {err:?}")); + } + registers::append_linker_registers(); + rdrive::probe_pre_kernel() + .unwrap_or_else(|err| panic!("failed to run pre-kernel driver probes: {err:?}")); #[cfg(feature = "multitask")] ax_task::init_scheduler(); @@ -245,47 +265,65 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { #[cfg(feature = "ipi")] ax_ipi::init(); - #[cfg(feature = "ax-driver")] + #[cfg(feature = "irq")] { - #[allow(unused_variables)] - let all_devices = ax_driver::init_drivers(); - - cfg_if::cfg_if! { - if #[cfg(feature = "fs-ng")] { - ax_fs_ng::init_filesystems(all_devices.block, ax_hal::dtb::get_chosen_bootargs()); - } else - if #[cfg(feature = "fs")] { - ax_fs::init_filesystems(all_devices.block, ax_hal::dtb::get_chosen_bootargs()); - } + info!("Initialize interrupt handlers..."); + init_interrupt(); + } + + devices::probe_all_devices(); + + cfg_if::cfg_if! { + if #[cfg(all(feature = "fs-ng", feature = "plat-dyn"))] { + ax_fs_ng::init_filesystems( + devices::take_dyn_fs_ng_block_devices(), + ax_hal::dtb::get_chosen_bootargs(), + ); + } else if #[cfg(all(feature = "fs-ng", not(feature = "plat-dyn")))] { + ax_fs_ng::init_filesystems(devices::take_static_fs_ng_block_devices(), None); + } else if #[cfg(all(feature = "fs", feature = "plat-dyn"))] { + ax_fs::init_filesystems( + devices::take_dyn_fs_block_devices(), + ax_hal::dtb::get_chosen_bootargs(), + ); + } else if #[cfg(all(feature = "fs", not(feature = "plat-dyn")))] { + ax_fs::init_filesystems(devices::take_static_fs_block_devices(), None); } + } - cfg_if::cfg_if! { - if #[cfg(feature = "net-ng")] { - ax_net_ng::init_network(all_devices.net); + #[cfg(all(feature = "display", feature = "plat-dyn"))] + devices::init_dyn_display(); - #[cfg(feature = "vsock")] - ax_net_ng::init_vsock(all_devices.vsock); - } else if #[cfg(feature = "net")] { - ax_net::init_network(all_devices.net); - } - } + #[cfg(all(feature = "display", not(feature = "plat-dyn")))] + devices::init_static_display(); + + #[cfg(all(feature = "input", feature = "plat-dyn"))] + devices::init_dyn_input(); - #[cfg(feature = "display")] - ax_display::init_display(all_devices.display); + #[cfg(all(feature = "input", not(feature = "plat-dyn")))] + devices::init_static_input(); - #[cfg(feature = "input")] - ax_input::init_input(all_devices.input); + cfg_if::cfg_if! { + if #[cfg(all(feature = "net-ng", feature = "plat-dyn"))] { + devices::init_dyn_net_ng(); + } else if #[cfg(all(feature = "net-ng", not(feature = "plat-dyn")))] { + ax_net_ng::init_network(devices::take_static_net_ng_drivers()); + } else if #[cfg(all(feature = "net", feature = "plat-dyn"))] { + devices::init_dyn_net(); + } else if #[cfg(all(feature = "net", not(feature = "plat-dyn")))] { + devices::init_static_net(); + } } + #[cfg(all(feature = "vsock", feature = "plat-dyn"))] + devices::init_dyn_vsock(); + + #[cfg(all(feature = "vsock", not(feature = "plat-dyn")))] + devices::init_static_vsock(); + #[cfg(feature = "smp")] self::mp::start_secondary_cpus(cpu_id); - #[cfg(feature = "irq")] - { - info!("Initialize interrupt handlers..."); - init_interrupt(); - } - #[cfg(all(feature = "tls", not(feature = "multitask")))] { info!("Initialize thread local storage..."); diff --git a/os/arceos/modules/axruntime/src/registers.rs b/os/arceos/modules/axruntime/src/registers.rs new file mode 100644 index 0000000000..407bc30fce --- /dev/null +++ b/os/arceos/modules/axruntime/src/registers.rs @@ -0,0 +1,24 @@ +use core::ptr::slice_from_raw_parts; + +use rdrive::register::DriverRegisterSlice; + +pub(crate) fn append_linker_registers() { + let registers = DriverRegisterSlice::from_raw(driver_registers()); + if !registers.is_empty() { + rdrive::register_append(®isters); + } +} + +fn driver_registers() -> &'static [u8] { + unsafe extern "C" { + fn __sdriver_register(); + fn __edriver_register(); + } + + unsafe { + &*slice_from_raw_parts( + __sdriver_register as *const () as *const u8, + __edriver_register as *const () as usize - __sdriver_register as *const () as usize, + ) + } +} diff --git a/os/arceos/scripts/make/features.mk b/os/arceos/scripts/make/features.mk index 9eee3cc21a..366ef36052 100644 --- a/os/arceos/scripts/make/features.mk +++ b/os/arceos/scripts/make/features.mk @@ -40,6 +40,7 @@ override FEATURES := $(strip $(FEATURES)) ax_feat := lib_feat := +direct_feat := ifeq ($(PLAT_DYN),y) ax_feat += plat-dyn @@ -55,10 +56,6 @@ ifeq ($(filter $(LOG),off error warn info debug trace),) $(error "LOG" must be one of "off", "error", "warn", "info", "debug", "trace") endif -ifeq ($(BUS),mmio) - ax_feat += bus-mmio -endif - ifeq ($(DWARF),y) ax_feat += dwarf endif @@ -67,9 +64,12 @@ ifeq ($(shell test $(SMP) -gt 1; echo $$?),0) lib_feat += smp endif -ax_feat += $(filter-out $(lib_features),$(FEATURES)) -lib_feat += $(filter $(lib_features),$(FEATURES)) +direct_feat += $(filter %/%,$(FEATURES)) +legacy_feat := $(filter-out %/%,$(FEATURES)) + +ax_feat += $(filter-out $(lib_features),$(legacy_feat)) +lib_feat += $(filter $(lib_features),$(legacy_feat)) -AX_FEAT := $(strip $(addprefix $(ax_feat_prefix),$(ax_feat))) +AX_FEAT := $(strip $(addprefix $(ax_feat_prefix),$(ax_feat)) $(direct_feat)) LIB_FEAT := $(strip $(addprefix $(lib_feat_prefix),$(lib_feat))) APP_FEAT := $(strip $(shell echo $(APP_FEATURES) | tr ',' ' ')) diff --git a/os/arceos/ulib/arceos-rust/Cargo.toml b/os/arceos/ulib/arceos-rust/Cargo.toml index 9a3d88d545..4617ea6f19 100644 --- a/os/arceos/ulib/arceos-rust/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/Cargo.toml @@ -61,14 +61,6 @@ display = [] # Real Time Clock (RTC) Driver. rtc = [] -# Device drivers -bus-mmio = [] -bus-pci = [] -driver-ramdisk = [] -driver-ixgbe = [] -driver-fxmac = [] -driver-bcm2835-sdhci = [] - # Logging log-level-off = [] log-level-error = [] diff --git a/os/arceos/ulib/arceos-rust/build.rs b/os/arceos/ulib/arceos-rust/build.rs index 234fd35c58..b30392e460 100644 --- a/os/arceos/ulib/arceos-rust/build.rs +++ b/os/arceos/ulib/arceos-rust/build.rs @@ -118,7 +118,8 @@ fn compile_project(lib_dir: &PathBuf, out_dir: &PathBuf, config_path: &PathBuf) let arch = get_arch(); let target = get_target(&arch); let features = env::var("CARGO_CFG_FEATURE").unwrap(); - let feature_list = features.replace(",", " "); + let passthrough_features = env::var("ARCEOS_RUST_FEATURES").unwrap_or_default(); + let feature_list = resolve_nested_features(&features, &passthrough_features).join(" "); let mut command = Command::new(cargo()); command.env("AX_TARGET", target); @@ -272,3 +273,71 @@ fn get_log_level(feature_list: &str) -> &str { } level } + +fn map_nested_feature(feature: &str) -> String { + if let Some(rest) = feature.strip_prefix("ax_hal/") { + format!("ax-hal/{rest}") + } else if let Some(rest) = feature.strip_prefix("ax_driver/") { + format!("ax-driver/{rest}") + } else { + feature.to_string() + } +} + +fn resolve_nested_features(features: &str, passthrough_features: &str) -> Vec { + let mut feature_list = features + .split(',') + .chain(passthrough_features.split(',')) + .map(str::trim) + .filter(|feature| !feature.is_empty()) + .map(map_nested_feature) + .collect::>(); + + if !has_platform_feature(&feature_list) { + feature_list.push("defplat".to_string()); + } + feature_list.sort(); + feature_list.dedup(); + feature_list +} + +fn has_platform_feature(features: &[String]) -> bool { + features.iter().any(|feature| { + matches!(feature.as_str(), "default" | "defplat" | "myplat") + || feature.starts_with("ax-hal/") + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn single_feature_builds_with_default_platform() { + assert_eq!( + resolve_nested_features("alloc", ""), + vec!["alloc".to_string(), "defplat".to_string()] + ); + } + + #[test] + fn default_feature_keeps_inner_defaults() { + assert_eq!(resolve_nested_features("default", ""), vec!["default"]); + } + + #[test] + fn explicit_platform_is_preserved_without_defplat() { + assert_eq!( + resolve_nested_features("alloc", "ax_hal/x86-pc"), + vec!["alloc".to_string(), "ax-hal/x86-pc".to_string()] + ); + } + + #[test] + fn explicit_myplat_is_preserved_without_defplat() { + assert_eq!( + resolve_nested_features("alloc,myplat", ""), + vec!["alloc".to_string(), "myplat".to_string()] + ); + } +} diff --git a/os/arceos/ulib/arceos-rust/lib/Cargo.toml b/os/arceos/ulib/arceos-rust/lib/Cargo.toml index 87a92b8c71..be7f862865 100644 --- a/os/arceos/ulib/arceos-rust/lib/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/lib/Cargo.toml @@ -20,6 +20,10 @@ default = [ "tls", "defplat", "fd", + "fs", + "net", + "irq", + "multitask", ] # Multicore @@ -57,11 +61,11 @@ stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] # File system fd = ["ax-posix-api/fd"] -fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs"] +fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs", "ax-driver/pci", "ax-driver/virtio-blk"] # Networking dns = [] -net = ["ax-api/net", "ax-posix-api/net", "ax-posix-api/poll", "ax-feat/net"] +net = ["ax-api/net", "ax-posix-api/net", "ax-posix-api/poll", "ax-feat/net", "ax-driver/pci", "ax-driver/virtio-net"] # Display display = ["ax-api/display", "ax-feat/display"] @@ -69,14 +73,6 @@ display = ["ax-api/display", "ax-feat/display"] # Real Time Clock (RTC) Driver. rtc = ["ax-feat/rtc"] -# Device drivers -bus-mmio = ["ax-feat/bus-mmio"] -bus-pci = ["ax-feat/bus-pci"] -driver-bcm2835-sdhci = ["ax-feat/driver-bcm2835-sdhci"] -driver-fxmac = ["ax-feat/driver-fxmac"] -driver-ixgbe = ["ax-feat/driver-ixgbe"] -driver-ramdisk = ["ax-feat/driver-ramdisk"] - # Logging log-level-debug = [] log-level-error = [] @@ -87,8 +83,10 @@ log-level-warn = [] [dependencies] ax-api = {workspace = true, features = ["alloc"]} +ax-driver = {workspace = true} ax-errno = {workspace = true} ax-feat = {workspace = true} +ax-hal = {workspace = true} ax-io = {workspace = true} ax-kspin = {workspace = true} ax-posix-api = {workspace = true, features = ["use-hermit-types"]} diff --git a/os/arceos/ulib/arceos-rust/lib/src/lib.rs b/os/arceos/ulib/arceos-rust/lib/src/lib.rs index 0f016deceb..17e00f28e5 100644 --- a/os/arceos/ulib/arceos-rust/lib/src/lib.rs +++ b/os/arceos/ulib/arceos-rust/lib/src/lib.rs @@ -4,6 +4,7 @@ #![feature(integer_cast_extras)] extern crate alloc; +extern crate ax_driver as _; mod interface; diff --git a/os/arceos/ulib/axlibc/Cargo.toml b/os/arceos/ulib/axlibc/Cargo.toml index 14d87609b6..0eef51b331 100644 --- a/os/arceos/ulib/axlibc/Cargo.toml +++ b/os/arceos/ulib/axlibc/Cargo.toml @@ -55,6 +55,8 @@ select = ["ax-posix-api/select"] epoll = ["ax-posix-api/epoll"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-posix-api.workspace = true ax-errno.workspace = true ax-feat.workspace = true diff --git a/os/arceos/ulib/axlibc/src/lib.rs b/os/arceos/ulib/axlibc/src/lib.rs index 53c68a3cc5..acb2864927 100644 --- a/os/arceos/ulib/axlibc/src/lib.rs +++ b/os/arceos/ulib/axlibc/src/lib.rs @@ -31,6 +31,7 @@ #[cfg(feature = "alloc")] extern crate alloc; +extern crate ax_driver as _; mod ctypes { #[rustfmt::skip] diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index fae46d511a..ce212c099f 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -76,16 +76,6 @@ input = ["ax-feat/input"] # Real Time Clock (RTC) Driver. rtc = ["ax-feat/rtc"] -# Device drivers -bus-mmio = ["ax-feat/bus-mmio"] -bus-pci = ["ax-feat/bus-pci"] -driver-ramdisk = ["ax-feat/driver-ramdisk"] -driver-sdmmc = ["ax-feat/driver-sdmmc"] -driver-ixgbe = ["ax-feat/driver-ixgbe"] -driver-fxmac = ["ax-feat/driver-fxmac"] -driver-bcm2835-sdhci = ["ax-feat/driver-bcm2835-sdhci"] -driver-ahci = ["ax-feat/driver-ahci"] - # Backtrace backtrace = ["ax-feat/backtrace"] dwarf = ["ax-feat/dwarf"] diff --git a/os/arceos/ulib/axstd/src/lib.rs b/os/arceos/ulib/axstd/src/lib.rs index 3191ef6885..54993ccbb5 100644 --- a/os/arceos/ulib/axstd/src/lib.rs +++ b/os/arceos/ulib/axstd/src/lib.rs @@ -32,12 +32,8 @@ //! - `net`: Enable networking support. //! - `dns`: Enable DNS lookup support. //! - `display`: Enable graphics support. -//! - Device drivers -//! - `bus-mmio`: Use device tree to probe all MMIO devices. -//! - `bus-pci`: Use PCI bus to probe all PCI devices. -//! - `driver-ramdisk`: Use the RAM disk to emulate the block device. -//! - `driver-ixgbe`: Enable the Intel 82599 10Gbit NIC driver. -//! - `driver-bcm2835-sdhci`: Enable the BCM2835 SDHCI driver (Raspberry Pi SD card). +//! - Device drivers are selected directly through `ax-driver/*` features by +//! board configurations. //! //! [ArceOS]: https://github.com/arceos-org/arceos diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 345b078aa3..aeb1ab5756 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -40,15 +40,17 @@ vmx = ["axvm/vmx"] svm = ["axvm/svm"] sstc = ["riscv_vcpu/sstc"] dyn-plat = ["ax-std/plat-dyn", "dep:axplat-dyn"] -rockchip-soc = ["dep:axplat-dyn", "axplat-dyn/rockchip-soc"] +rockchip-soc = ["ax-driver/rockchip-soc"] +rockchip-sdhci = ["ax-driver/rockchip-sdhci", "ax-driver/rockchip-soc"] +rockchip-dwmmc = ["ax-driver/rockchip-dwmmc", "ax-driver/rockchip-soc"] +phytium-mci = ["ax-driver/phytium-mci"] sdmmc = [ - "dep:axplat-dyn", - "axplat-dyn/rockchip-sdhci", - "axplat-dyn/rockchip-soc", - "axplat-dyn/phytium-mci", + "ax-driver/rockchip-sdhci", + "ax-driver/rockchip-soc", + "ax-driver/phytium-mci", ] -rockchip-pm = ["dep:axplat-dyn", "axplat-dyn/rockchip-pm"] -serial = ["dep:axplat-dyn", "axplat-dyn/serial"] +rockchip-pm = ["ax-driver/rockchip-pm"] +serial = ["ax-driver/serial"] [dependencies] spin = { workspace = true } @@ -90,6 +92,7 @@ ax-page-table-multiarch = { workspace = true } ax-percpu = { workspace = true, features = ["arm-el2", "non-zero-vma"] } rdif-intc.workspace = true rdrive.workspace = true +ax-driver.workspace = true axplat-dyn = { workspace = true, optional = true, default-features = false } [target.'cfg(target_arch = "x86_64")'.dependencies] diff --git a/os/axvisor/configs/board/orangepi-5-plus.toml b/os/axvisor/configs/board/orangepi-5-plus.toml index 14639a4a12..e8f449b079 100644 --- a/os/axvisor/configs/board/orangepi-5-plus.toml +++ b/os/axvisor/configs/board/orangepi-5-plus.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", "sdmmc", "rockchip-soc", "fs", diff --git a/os/axvisor/configs/board/phytiumpi.toml b/os/axvisor/configs/board/phytiumpi.toml index f606576d21..e8dca5f660 100644 --- a/os/axvisor/configs/board/phytiumpi.toml +++ b/os/axvisor/configs/board/phytiumpi.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", "fs", "sdmmc", ] diff --git a/os/axvisor/configs/board/qemu-aarch64.toml b/os/axvisor/configs/board/qemu-aarch64.toml index a9020fdcba..317e9aae7c 100644 --- a/os/axvisor/configs/board/qemu-aarch64.toml +++ b/os/axvisor/configs/board/qemu-aarch64.toml @@ -1,7 +1,8 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ + "ax-hal/plat-dyn", "ept-level-4", - "ax-std/bus-mmio", + "ax-driver/virtio-blk", "fs", ] log = "Info" diff --git a/os/axvisor/configs/board/qemu-loongarch64.toml b/os/axvisor/configs/board/qemu-loongarch64.toml index 9ac13dbf46..b7945d66d0 100644 --- a/os/axvisor/configs/board/qemu-loongarch64.toml +++ b/os/axvisor/configs/board/qemu-loongarch64.toml @@ -1,5 +1,8 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ + "ax-hal/loongarch64-qemu-virt", + "ax-driver/virtio-blk", + "ax-driver/pci", "ept-level-4", "fs", ] diff --git a/os/axvisor/configs/board/qemu-riscv64.toml b/os/axvisor/configs/board/qemu-riscv64.toml index b0e3ea0f8f..da0062ef6c 100644 --- a/os/axvisor/configs/board/qemu-riscv64.toml +++ b/os/axvisor/configs/board/qemu-riscv64.toml @@ -1,6 +1,8 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", + "ax-hal/riscv64-qemu-virt-hv", + "ax-driver/fdt", + "ax-driver/virtio-blk", "ept-level-4", "fs", "sstc" diff --git a/os/axvisor/configs/board/qemu-x86_64.toml b/os/axvisor/configs/board/qemu-x86_64.toml index 6f4740549f..64e99f9093 100644 --- a/os/axvisor/configs/board/qemu-x86_64.toml +++ b/os/axvisor/configs/board/qemu-x86_64.toml @@ -1,5 +1,8 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ + "ax-hal/x86-qemu-q35", + "ax-driver/virtio-blk", + "ax-driver/pci", "ept-level-4", "fs", ] diff --git a/os/axvisor/configs/board/rdk-s100.toml b/os/axvisor/configs/board/rdk-s100.toml index 8c8bc5b3f4..252da5797d 100644 --- a/os/axvisor/configs/board/rdk-s100.toml +++ b/os/axvisor/configs/board/rdk-s100.toml @@ -1,6 +1,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", + "ax-hal/plat-dyn", "ept-level-4", ] log = "Info" diff --git a/os/axvisor/configs/board/roc-rk3568-pc.toml b/os/axvisor/configs/board/roc-rk3568-pc.toml index f606576d21..e8dca5f660 100644 --- a/os/axvisor/configs/board/roc-rk3568-pc.toml +++ b/os/axvisor/configs/board/roc-rk3568-pc.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", "fs", "sdmmc", ] diff --git a/os/axvisor/configs/board/tac-e400.toml b/os/axvisor/configs/board/tac-e400.toml index 8c8bc5b3f4..252da5797d 100644 --- a/os/axvisor/configs/board/tac-e400.toml +++ b/os/axvisor/configs/board/tac-e400.toml @@ -1,6 +1,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", + "ax-hal/plat-dyn", "ept-level-4", ] log = "Info" diff --git a/os/axvisor/src/main.rs b/os/axvisor/src/main.rs index 7bc9f9fd7a..cfb10ebd55 100644 --- a/os/axvisor/src/main.rs +++ b/os/axvisor/src/main.rs @@ -28,11 +28,6 @@ extern crate alloc; extern crate ax_std as std; -#[cfg(target_arch = "loongarch64")] -extern crate ax_plat_loongarch64_qemu_virt; -#[cfg(target_arch = "x86_64")] -extern crate axplat_x86_qemu_q35; - mod hal; mod logo; mod shell; diff --git a/platform/axplat-dyn/Cargo.toml b/platform/axplat-dyn/Cargo.toml index 66bbd878ae..78f20be7e8 100644 --- a/platform/axplat-dyn/Cargo.toml +++ b/platform/axplat-dyn/Cargo.toml @@ -13,78 +13,24 @@ repository.workspace = true default = ["smp", "irq" ] smp = ["ax-plat/smp"] irq = ["ax-plat/irq"] -rtc = ["dep:ax-arm-pl031"] +rtc = [] fp-simd = ["ax-cpu/fp-simd"] uspace = ["somehal/uspace"] hv = ["somehal/hv", "ax-cpu/arm-el2"] -rockchip-soc = ["dep:rockchip-soc"] -rockchip-sdhci = [ - "dep:sdhci-host", - "dep:sdmmc-protocol", - "sdmmc-protocol/sdio", -] -rockchip-dwmmc = [ - "dep:dwmmc-host", - "dep:sdmmc-protocol", - "sdmmc-protocol/sdio", -] -phytium-mci = [ - "dep:phytium-mci-host", - "dep:sdmmc-protocol", - "sdmmc-protocol/sdio", -] -rockchip-pm = ["dep:rockchip-pm"] -usb = ["dep:crab-usb"] -rockchip-dwc-xhci = ["usb", "rockchip-soc", "rockchip-pm"] -xhci-mmio = ["usb"] -xhci-pci = ["usb", "dep:pcie"] -serial = ["dep:some-serial"] -net = ["dep:ax-driver-net", "dep:rd-net"] -intel-net = ["net", "dep:eth-intel", "dep:pcie"] -realtek-rtl8125 = ["net", "dep:pcie", "dep:realtek-rtl8125"] -virtio-net-pci = ["net"] -pci-list-devices = [] -rknpu = ["dep:rockchip-npu", "rockchip-pm", "rockchip-soc"] [dependencies] anyhow = { version = "1", default-features = false } ax-alloc = { workspace = true, features = ["buddy-slab"] } ax-config-macros = { workspace = true } ax-cpu.workspace = true -ax-driver-base.workspace = true -ax-driver-block.workspace = true -ax-driver-net = { workspace = true, optional = true } -ax-driver-virtio = { workspace = true, features = ["block", "net"] } -ax-arm-pl031 = { workspace = true, optional = true } -arm-scmi-rs.workspace = true +ax-driver = { workspace = true, features = ["fdt", "pci-fdt"] } ax-errno.workspace = true -ax-kspin.workspace = true axklib.workspace = true ax-plat.workspace = true -dma-api.workspace = true -eth-intel = { workspace = true, optional = true } -fdt-edit = "0.2" heapless = "0.9" log.workspace = true -mmio-api.workspace = true ax-memory-addr.workspace = true ax-percpu = { workspace = true, features = ["custom-base"] } -crab-usb = { workspace = true, optional = true } -pcie = { workspace = true, optional = true } -realtek-rtl8125 = { workspace = true, optional = true } -rd-block.workspace = true -rd-net = { workspace = true, optional = true } -rdif-clk.workspace = true -rdif-pcie.workspace = true rdrive.workspace = true -rk3588-pci.workspace = true -rockchip-soc = { workspace = true, optional = true } -rockchip-npu = { workspace = true, optional = true } -rockchip-pm = { workspace = true, optional = true } -dwmmc-host = { workspace = true, optional = true } -phytium-mci-host = { workspace = true, optional = true } -sdhci-host = { workspace = true, optional = true } -sdmmc-protocol = { workspace = true, default-features = false, optional = true } somehal.workspace = true -some-serial = { workspace = true, optional = true } spin = "0.10" diff --git a/platform/axplat-dyn/src/boot.rs b/platform/axplat-dyn/src/boot.rs index 0266687418..d1b16cc13a 100644 --- a/platform/axplat-dyn/src/boot.rs +++ b/platform/axplat-dyn/src/boot.rs @@ -1,9 +1,8 @@ -use core::ptr::NonNull; - -use ax_errno::AxError; use ax_memory_addr::VirtAddr; -use ax_plat::mem::phys_to_virt; -use somehal::{KernelOp, setup::*}; +use somehal::{ + KernelOp, + setup::{MapError, MmioAddr, MmioOp, MmioRaw}, +}; #[somehal::entry(Kernel)] fn main() -> ! { @@ -34,19 +33,10 @@ impl KernelOp for Kernel {} impl MmioOp for Kernel { fn ioremap(&self, addr: MmioAddr, size: usize) -> Result { - let virt = match axklib::mem::iomap(addr.as_usize().into(), size) { - Ok(v) => v, - Err(AxError::AlreadyExists) => { - // If the region is already mapped, just return the existing mapping. - phys_to_virt(addr.as_usize().into()) - } - Err(e) => { - error!("Failed to map MMIO region at {addr:?} with size {size:#x}: {e:?}"); - return Err(MapError::Invalid); - } - }; - Ok(unsafe { MmioRaw::new(addr, NonNull::new(virt.as_mut_ptr()).unwrap(), size) }) + axklib::mmio::op().ioremap(addr, size) } - fn iounmap(&self, _mmio: &MmioRaw) {} + fn iounmap(&self, mmio: &MmioRaw) { + axklib::mmio::op().iounmap(mmio); + } } diff --git a/platform/axplat-dyn/src/drivers/blk/rockchip/dwmmc.rs b/platform/axplat-dyn/src/drivers/blk/rockchip/dwmmc.rs deleted file mode 100644 index a839c60507..0000000000 --- a/platform/axplat-dyn/src/drivers/blk/rockchip/dwmmc.rs +++ /dev/null @@ -1,765 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use alloc::{format, sync::Arc, vec::Vec}; -use core::{num::NonZeroUsize, ptr::NonNull, time::Duration}; - -use ax_kspin::SpinNoIrq; -use dma_api::DeviceDma; -use dwmmc_host::{BlockRequest, BlockRequestSlot, DwMmc, RequestId}; -use rdif_clk::ClockId; -use rdrive::{ - DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, -}; -use sdmmc_protocol::{ - BlockPoll, BlockTransferMode, DataCommandPoll, Error, OperationPoll, - error::Phase, - sdio::{CardInfo, SdioHost, SdioInitScratch, SdioSdmmc}, -}; - -use crate::drivers::{ - DmaImpl, - blk::{PlatformDeviceBlock, decode_fdt_irq}, - iomap, - soc::scmi, -}; - -const BLOCK_SIZE: usize = 512; -const DWMMC_STABLE_REFERENCE_CLOCK: u32 = 50_000_000; -const ENABLE_SD_SPEED_SELECTION: bool = true; -const RK3588_CRU_BASE: usize = 0xfd7c_0000; -const RK3588_CRU_SIZE: usize = 0x5c000; -const RK3588_SDMMC_CON0: usize = 0x0c30; -const RK3588_SDMMC_CON1: usize = 0x0c34; -const RK3588_SDMMC_PHASE_SHIFT: u32 = 1; -const RK3588_SDMMC_DRV_PHASE_DEG: u32 = 90; -const RK3588_SDMMC_SAMPLE_PHASE_DEG: u32 = 0; -const RK3588_SDMMC_SAMPLE_PHASE_CANDIDATES: [u32; 8] = [0, 45, 90, 135, 180, 225, 270, 315]; - -type RockchipDwMmc = SdioSdmmc; - -module_driver!( - name: "Rockchip SD", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-dw-mshc", "rockchip,rk3288-dw-mshc"], - on_probe: probe - } - ], -); - -fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let base_reg = info - .node - .regs() - .into_iter() - .next() - .ok_or(OnProbeError::other(alloc::format!( - "[{}] has no reg", - info.node.name() - )))?; - - let mmio_size = base_reg.size.unwrap_or(0x1000); - info!( - "rockchip-dwmmc probe: node={}, addr={:#x}, size={:#x}", - info.node.name(), - base_reg.address as usize, - mmio_size - ); - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size as usize)?; - - let mut host = unsafe { DwMmc::new(mmio_base) }; - let reference_clock = dwmmc_reference_clock(&info); - if let Some(reference_clock) = reference_clock { - info!( - "rockchip-dwmmc: using ciu reference clock {} Hz", - reference_clock - ); - host.set_reference_clock(reference_clock); - if is_rk3588_dwmmc(&info) { - init_rk3588_sdmmc_phase(&info, reference_clock)?; - } - } else { - warn!( - "rockchip-dwmmc: ciu clock not found; leaving DWMMC divider bypassed and relying on \ - CRU rate" - ); - } - info!("rockchip-dwmmc: reset controller"); - host.reset_and_init() - .map_err(|e| init_error(base_reg.address, mmio_size, e))?; - host.set_dma(DeviceDma::new(u32::MAX as u64, &DmaImpl)); - - info!("rockchip-dwmmc: initialize card"); - let mut sd = SdioSdmmc::new(host); - sd.set_sd_speed_selection_enabled(ENABLE_SD_SPEED_SELECTION); - let card_info = poll_card_init(&mut sd).map_err(|e| { - warn!("rockchip-dwmmc: card init failed: {:?}", e); - card_init_error(base_reg.address, mmio_size, e) - })?; - info!( - "rockchip-dwmmc card: kind={:?} high_capacity={} rca={} ocr={:#010x} capacity_blocks={:?} \ - cid={} ext_csd={}", - card_info.kind, - card_info.high_capacity, - card_info.rca, - card_info.ocr, - card_info.capacity_blocks, - card_info.cid.is_some(), - card_info.ext_csd.is_some() - ); - - if let Some(reference_clock) = reference_clock - && is_rk3588_dwmmc(&info) - { - tune_rk3588_sdmmc_sample_phase(&mut sd, reference_clock); - } - - let irq_num = decode_fdt_irq(&info.interrupts()); - let raw = Arc::new(SpinNoIrq::new(sd)); - let dev = SdBlockDevice { - raw: Some(raw.clone()), - capacity_blocks: card_info.capacity_blocks.unwrap_or(0), - irq_enabled: false, - queue_created: false, - }; - plat_dev.register_block_with_irq(dev, irq_num); - info!("rockchip-sd block device registered irq={:?}", irq_num); - Ok(()) -} - -fn poll_card_init(sd: &mut RockchipDwMmc) -> Result { - let mut scratch = SdioInitScratch::new(); - let mut request = sd.submit_init(&mut scratch)?; - loop { - match sd.poll_init_request(&mut request)? { - OperationPoll::Pending => { - if request.take_needs_pace() { - axklib::time::busy_wait(Duration::from_millis(10)); - } else { - core::hint::spin_loop(); - } - } - OperationPoll::Complete(info) => return Ok(info), - _ => return Err(Error::UnsupportedCommand), - } - } -} - -fn init_error(address: u64, size: u64, err: Error) -> OnProbeError { - OnProbeError::other(format!( - "failed to initialize DWMMC device at [PA:{:?}, SZ:0x{:x}): {err:?}", - address, size - )) -} - -fn card_init_error(address: u64, size: u64, err: Error) -> OnProbeError { - if is_absent_card_init_error(err) { - warn!( - "rockchip-dwmmc: no responsive card at [PA:{:?}, SZ:0x{:x}); skipping controller: \ - {err:?}", - address, size - ); - return OnProbeError::NotMatch; - } - - init_error(address, size, err) -} - -fn is_absent_card_init_error(err: Error) -> bool { - match err { - Error::NoCard => true, - Error::Timeout(ctx) | Error::Crc(ctx) | Error::BadResponse(ctx) => { - ctx.cmd.is_some() - && matches!( - ctx.phase, - Phase::CommandSend | Phase::ResponseWait | Phase::Init - ) - } - _ => false, - } -} - -fn dwmmc_reference_clock(info: &FdtInfo<'_>) -> Option { - let Some(clk) = info.find_clk_by_name("ciu") else { - return None; - }; - let Some(device_id) = info.phandle_to_device_id(clk.phandle) else { - warn!( - "[{}] ciu clock phandle {} has no device id", - info.node.name(), - clk.phandle - ); - return None; - }; - let clk_dev = match rdrive::get::(device_id) { - Ok(clk_dev) => clk_dev, - Err(_) => { - let clock_id = clk.select().unwrap_or(0); - if scmi::set_clock_rate(clk.phandle, clock_id, DWMMC_STABLE_REFERENCE_CLOCK as u64) - .is_some() - { - return Some(DWMMC_STABLE_REFERENCE_CLOCK); - } - if let Some(rate) = scmi::clock_rate(clk.phandle, clock_id) { - return validate_reference_clock(info, rate); - } - warn!( - "[{}] ciu clock device {:?} is not registered", - info.node.name(), - device_id - ); - return None; - } - }; - let mut clk_guard = match clk_dev.lock() { - Ok(clk_guard) => clk_guard, - Err(_) => { - warn!( - "[{}] ciu clock device {:?} is locked", - info.node.name(), - device_id - ); - return None; - } - }; - let clock_id = ClockId::from(clk.select().unwrap_or(0) as usize); - if let Err(err) = clk_guard.set_rate(clock_id, DWMMC_STABLE_REFERENCE_CLOCK as u64) { - warn!( - "[{}] failed to set ciu clock {:?} to {} Hz: {:?}", - info.node.name(), - clock_id, - DWMMC_STABLE_REFERENCE_CLOCK, - err - ); - } - let rate = match clk_guard.get_rate(clock_id) { - Ok(rate) => rate, - Err(err) => { - warn!( - "[{}] failed to read ciu clock {:?}: {:?}", - info.node.name(), - clock_id, - err - ); - return None; - } - }; - validate_reference_clock(info, rate) -} - -fn is_rk3588_dwmmc(info: &FdtInfo<'_>) -> bool { - info.node - .as_node() - .compatibles() - .any(|compatible| compatible == "rockchip,rk3588-dw-mshc") -} - -fn init_rk3588_sdmmc_phase(info: &FdtInfo<'_>, parent_rate: u32) -> Result<(), OnProbeError> { - let has_drive_clk = info.find_clk_by_name("ciu-drive").is_some(); - let has_sample_clk = info.find_clk_by_name("ciu-sample").is_some(); - if !has_drive_clk || !has_sample_clk { - warn!( - "[{}] RK3588 SDMMC phase clocks missing: ciu-drive={} ciu-sample={}", - info.node.name(), - has_drive_clk, - has_sample_clk - ); - return Ok(()); - } - - let cru = iomap(RK3588_CRU_BASE.into(), RK3588_CRU_SIZE)?; - set_rk3588_mmc_phase( - cru, - RK3588_SDMMC_CON0, - parent_rate, - RK3588_SDMMC_DRV_PHASE_DEG, - ); - set_rk3588_mmc_phase( - cru, - RK3588_SDMMC_CON1, - parent_rate, - RK3588_SDMMC_SAMPLE_PHASE_DEG, - ); - info!( - "rockchip-dwmmc: RK3588 SDMMC phase configured: drive={}deg sample={}deg parent={}Hz", - RK3588_SDMMC_DRV_PHASE_DEG, RK3588_SDMMC_SAMPLE_PHASE_DEG, parent_rate - ); - Ok(()) -} - -fn tune_rk3588_sdmmc_sample_phase(sd: &mut RockchipDwMmc, parent_rate: u32) { - let Ok(cru) = iomap(RK3588_CRU_BASE.into(), RK3588_CRU_SIZE) else { - warn!("rockchip-dwmmc: failed to map RK3588 CRU for sample phase scan"); - return; - }; - - for sample_phase in RK3588_SDMMC_SAMPLE_PHASE_CANDIDATES { - set_rk3588_mmc_phase(cru, RK3588_SDMMC_CON1, parent_rate, sample_phase); - - let mut block0 = [0; BLOCK_SIZE]; - let block0_result = read_block_sync(sd, 0, &mut block0); - let block0_valid = block0_result.is_ok() && has_mbr_signature(&block0); - - let mut block1 = [0; BLOCK_SIZE]; - let block1_result = read_block_sync(sd, 1, &mut block1); - let block1_valid = block1_result.is_ok() && has_gpt_header(&block1); - - info!( - "rockchip-dwmmc: sample phase probe {}deg: block0_ok={} mbr_sig={:02x}{:02x} \ - block0_head={:02x?} block1_ok={} gpt_head={:02x?}", - sample_phase, - block0_result.is_ok(), - block0[511], - block0[510], - &block0[..16], - block1_result.is_ok(), - &block1[..8] - ); - - if block0_valid || block1_valid { - set_rk3588_mmc_phase(cru, RK3588_SDMMC_CON1, parent_rate, sample_phase); - info!( - "rockchip-dwmmc: selected RK3588 SDMMC sample phase {}deg", - sample_phase - ); - return; - } - } - - set_rk3588_mmc_phase( - cru, - RK3588_SDMMC_CON1, - parent_rate, - RK3588_SDMMC_SAMPLE_PHASE_DEG, - ); - warn!( - "rockchip-dwmmc: no valid RK3588 SDMMC sample phase found; restored {}deg", - RK3588_SDMMC_SAMPLE_PHASE_DEG - ); -} - -fn read_block_sync( - sd: &mut RockchipDwMmc, - addr: u32, - buf: &mut [u8; BLOCK_SIZE], -) -> Result<(), Error> { - let mut request = sd.submit_read_blocks_into(addr, buf)?; - loop { - match sd.poll_data_request(&mut request)? { - DataCommandPoll::Pending => core::hint::spin_loop(), - DataCommandPoll::Complete(_) => return Ok(()), - _ => core::hint::spin_loop(), - } - } -} - -fn set_rk3588_mmc_phase(cru: NonNull, offset: usize, parent_rate: u32, degrees: u32) { - let delay_num = rk3588_mmc_delay_num(parent_rate, degrees); - let raw_value = if delay_num != 0 { 1 << 10 } else { 0 } - | ((delay_num & 0xff) << 2) - | ((degrees / 90) & 0x03); - let reg_value = - ((0x07ff_u32 << RK3588_SDMMC_PHASE_SHIFT) << 16) | (raw_value << RK3588_SDMMC_PHASE_SHIFT); - unsafe { - (cru.as_ptr().add(offset) as *mut u32).write_volatile(reg_value); - } -} - -fn rk3588_mmc_delay_num(parent_rate: u32, degrees: u32) -> u32 { - let degree = degrees % 360; - let remainder = degree % 90; - if parent_rate == 0 { - 0 - } else { - div_round_closest( - 10_000_000_u64 * remainder as u64, - (parent_rate as u64 / 1_000) * 36 * 6, - ) - .min(255) as u32 - } -} - -fn div_round_closest(numerator: u64, denominator: u64) -> u64 { - if denominator == 0 { - 0 - } else { - (numerator + denominator / 2) / denominator - } -} - -fn has_mbr_signature(block: &[u8; BLOCK_SIZE]) -> bool { - block[510] == 0x55 && block[511] == 0xaa -} - -fn has_gpt_header(block: &[u8; BLOCK_SIZE]) -> bool { - &block[..8] == b"EFI PART" -} - -fn validate_reference_clock(info: &FdtInfo<'_>, rate: u64) -> Option { - if rate == 0 || rate > u32::MAX as u64 { - warn!("[{}] invalid ciu clock rate {} Hz", info.node.name(), rate); - return None; - } - Some(rate as u32) -} - -struct SdBlockDevice { - raw: Option>>, - capacity_blocks: u64, - irq_enabled: bool, - queue_created: bool, -} - -struct SdBlockQueue { - raw: Arc>, - capacity_blocks: u64, - id: usize, - dma: DeviceDma, - slot: BlockRequestSlot, - pending: Option, - completed: Vec, -} - -impl DriverGeneric for SdBlockDevice { - fn name(&self) -> &str { - "rockchip-sd" - } -} - -impl rd_block::Interface for SdBlockDevice { - fn create_queue(&mut self) -> Option> { - if self.queue_created { - return None; - } - self.raw.as_ref().map(|dev| { - self.queue_created = true; - alloc::boxed::Box::new(SdBlockQueue { - raw: dev.clone(), - capacity_blocks: self.capacity_blocks, - id: 0, - dma: DeviceDma::new(u32::MAX as u64, &DmaImpl), - slot: BlockRequestSlot::default(), - pending: None, - completed: Vec::new(), - }) as _ - }) - } - - fn enable_irq(&mut self) { - if let Some(raw) = &self.raw { - let mut raw = raw.lock(); - if let Err(err) = SdioHost::enable_completion_irq(raw.host_mut()) { - warn!("rockchip-dwmmc: enable completion IRQ failed: {:?}", err); - return; - } - self.irq_enabled = true; - } - } - - fn disable_irq(&mut self) { - if let Some(raw) = &self.raw { - let mut raw = raw.lock(); - if let Err(err) = SdioHost::disable_completion_irq(raw.host_mut()) { - warn!("rockchip-dwmmc: disable completion IRQ failed: {:?}", err); - } - } - self.irq_enabled = false; - } - - fn is_irq_enabled(&self) -> bool { - self.irq_enabled - } - - fn handle_irq(&mut self) -> rd_block::Event { - let Some(raw) = &self.raw else { - return rd_block::Event::none(); - }; - let irq_event = raw.lock().host_mut().handle_irq(); - block_event_from_dwmmc_irq(irq_event) - } -} - -fn block_event_from_dwmmc_irq(irq_event: dwmmc_host::Event) -> rd_block::Event { - match irq_event { - dwmmc_host::Event::None => rd_block::Event::none(), - dwmmc_host::Event::CommandComplete - | dwmmc_host::Event::TransferComplete - | dwmmc_host::Event::ReceiveReady - | dwmmc_host::Event::TransmitReady - | dwmmc_host::Event::Error { .. } - | dwmmc_host::Event::Other { .. } => { - let mut event = rd_block::Event::none(); - event.queue.insert(0); - event - } - } -} - -impl rd_block::IQueue for SdBlockQueue { - fn num_blocks(&self) -> usize { - self.capacity_blocks as usize - } - - fn block_size(&self) -> usize { - BLOCK_SIZE - } - - fn id(&self) -> usize { - self.id - } - - fn buff_config(&self) -> rd_block::BuffConfig { - rd_block::BuffConfig { - dma_mask: self.dma.dma_mask(), - align: BLOCK_SIZE, - size: BLOCK_SIZE, - } - } - - fn submit_request( - &mut self, - request: rd_block::Request<'_>, - ) -> Result { - self.reap_pending_request()?; - let mut raw = self.raw.lock(); - let start_block = block_addr_for_card(request.block_id, raw.is_high_capacity())?; - // Block I/O uses the host crate's submit/poll request API so - // completions can be driven by IRQ wakeups. Protocol data commands - // use the same submit/poll contract through SdioHost. - match request.kind { - rd_block::RequestKind::Read(buffer) => { - if !buffer.len().is_multiple_of(BLOCK_SIZE) { - return Err(rd_block::BlkError::Other( - "read buffer is not block aligned".into(), - )); - } - let ptr = NonNull::new(buffer.virt).ok_or_else(|| { - rd_block::BlkError::Other("read buffer pointer is null".into()) - })?; - let size = NonZeroUsize::new(buffer.len()) - .ok_or_else(|| rd_block::BlkError::Other("read buffer is empty".into()))?; - let id = submit_read_request( - raw.host_mut(), - start_block, - ptr, - size, - &self.dma, - &mut self.slot, - &mut self.pending, - )?; - Ok(rd_block::RequestId::new(usize::from(id))) - } - rd_block::RequestKind::Write(items) => { - if !items.len().is_multiple_of(BLOCK_SIZE) { - return Err(rd_block::BlkError::Other( - "write buffer is not block aligned".into(), - )); - } - let ptr = NonNull::new(items.as_ptr() as *mut u8).ok_or_else(|| { - rd_block::BlkError::Other("write buffer pointer is null".into()) - })?; - let size = NonZeroUsize::new(items.len()) - .ok_or_else(|| rd_block::BlkError::Other("write buffer is empty".into()))?; - let id = submit_write_request( - raw.host_mut(), - start_block, - ptr, - size, - &self.dma, - &mut self.slot, - &mut self.pending, - )?; - Ok(rd_block::RequestId::new(usize::from(id))) - } - } - } - - fn poll_request(&mut self, request: rd_block::RequestId) -> Result<(), rd_block::BlkError> { - if let Some(index) = self.completed.iter().position(|id| *id == request) { - self.completed.swap_remove(index); - return Ok(()); - } - self.poll_active_request(request) - } -} - -impl SdBlockQueue { - fn poll_active_request( - &mut self, - request: rd_block::RequestId, - ) -> Result<(), rd_block::BlkError> { - match self.raw.lock().host_mut().poll_block_request( - &mut self.pending, - RequestId::new(usize::from(request)), - &mut self.slot, - ) { - Ok(BlockPoll::Complete) => Ok(()), - Ok(BlockPoll::Pending) => Err(rd_block::BlkError::Retry), - Ok(_) => Err(rd_block::BlkError::Other( - "DWMMC returned an unknown poll state".into(), - )), - Err(err) => Err(map_dev_err_to_blk_err(err)), - } - } - - fn pending_id(&self) -> Option { - self.pending.as_ref().map(BlockRequest::id) - } - - fn reap_pending_request(&mut self) -> Result<(), rd_block::BlkError> { - let Some(active) = self.pending_id() else { - return Ok(()); - }; - let id = rd_block::RequestId::new(usize::from(active)); - match self.poll_active_request(id) { - Ok(()) => { - self.completed.push(id); - Ok(()) - } - Err(rd_block::BlkError::Retry) => Err(rd_block::BlkError::Retry), - Err(err) => Err(err), - } - } -} - -fn submit_read_request( - host: &mut DwMmc, - start_block: u32, - buffer: NonNull, - size: NonZeroUsize, - dma: &DeviceDma, - slot: &mut BlockRequestSlot, - pending: &mut Option, -) -> Result { - if pending.is_some() { - return Err(rd_block::BlkError::Retry); - } - let request = match host.submit_read_blocks( - start_block, - buffer, - size, - Some(dma), - BlockTransferMode::Dma, - slot, - ) { - Ok(request) => request, - Err(err) if can_fallback_to_fifo(err) => host - .submit_read_blocks( - start_block, - buffer, - size, - None, - BlockTransferMode::Fifo, - slot, - ) - .map_err(map_dev_err_to_blk_err)?, - Err(err) => return Err(map_dev_err_to_blk_err(err)), - }; - let id = request.id(); - *pending = Some(request); - Ok(id) -} - -fn submit_write_request( - host: &mut DwMmc, - start_block: u32, - buffer: NonNull, - size: NonZeroUsize, - dma: &DeviceDma, - slot: &mut BlockRequestSlot, - pending: &mut Option, -) -> Result { - if pending.is_some() { - return Err(rd_block::BlkError::Retry); - } - let request = match host.submit_write_blocks( - start_block, - buffer, - size, - Some(dma), - BlockTransferMode::Dma, - slot, - ) { - Ok(request) => request, - Err(err) if can_fallback_to_fifo(err) => host - .submit_write_blocks( - start_block, - buffer, - size, - None, - BlockTransferMode::Fifo, - slot, - ) - .map_err(map_dev_err_to_blk_err)?, - Err(err) => return Err(map_dev_err_to_blk_err(err)), - }; - let id = request.id(); - *pending = Some(request); - Ok(id) -} - -fn can_fallback_to_fifo(err: Error) -> bool { - matches!( - err, - Error::UnsupportedCommand | Error::InvalidArgument | Error::Misaligned - ) -} - -fn block_addr_for_card(block_id: usize, high_capacity: bool) -> Result { - let block_id = - u32::try_from(block_id).map_err(|_| rd_block::BlkError::InvalidBlockIndex(block_id))?; - if high_capacity { - Ok(block_id) - } else { - block_id - .checked_mul(BLOCK_SIZE as u32) - .ok_or(rd_block::BlkError::InvalidBlockIndex(block_id as usize)) - } -} - -fn map_dev_err_to_blk_err(err: Error) -> rd_block::BlkError { - match err { - Error::NoCard | Error::UnsupportedCommand | Error::CardLocked => { - rd_block::BlkError::NotSupported - } - Error::Misaligned | Error::InvalidArgument => { - rd_block::BlkError::Other("SD/MMC request is not block aligned".into()) - } - _ => rd_block::BlkError::Other("DWMMC I/O error".into()), - } -} - -#[cfg(test)] -mod tests { - use sdmmc_protocol::error::ErrorContext; - - use super::*; - - #[test] - fn command_timeout_during_card_init_is_absent_card() { - let err = Error::Timeout(ErrorContext::for_cmd(Phase::ResponseWait, 1)); - - assert!(is_absent_card_init_error(err)); - } - - #[test] - fn data_timeout_after_card_init_is_not_absent_card() { - let err = Error::Timeout(ErrorContext::for_cmd(Phase::DataRead, 17)); - - assert!(!is_absent_card_init_error(err)); - } -} diff --git a/platform/axplat-dyn/src/drivers/blk/rockchip/mod.rs b/platform/axplat-dyn/src/drivers/blk/rockchip/mod.rs deleted file mode 100644 index ffda0aeef2..0000000000 --- a/platform/axplat-dyn/src/drivers/blk/rockchip/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(feature = "rockchip-dwmmc")] -mod dwmmc; -#[cfg(feature = "rockchip-sdhci")] -mod sdhci_rk3568; -#[cfg(feature = "rockchip-sdhci")] -mod sdhci_rk3588; diff --git a/platform/axplat-dyn/src/drivers/blk/virtio.rs b/platform/axplat-dyn/src/drivers/blk/virtio.rs deleted file mode 100644 index 47067135c5..0000000000 --- a/platform/axplat-dyn/src/drivers/blk/virtio.rs +++ /dev/null @@ -1,173 +0,0 @@ -extern crate alloc; - -use alloc::format; - -use ax_driver_base::DeviceType; -use ax_driver_block::BlockDriverOps; -use ax_driver_virtio::Transport; -use ax_plat::mem::PhysAddr; -use rdrive::{ - DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, -}; - -use super::PlatformDeviceBlock; -use crate::drivers::{iomap, virtio::VirtIoHalImpl}; - -pub(super) type VirtIoBlkDevice = ax_driver_virtio::VirtIoBlkDev; - -module_driver!( - name: "Virtio Block", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["virtio,mmio"], - on_probe: probe - } - ], -); - -fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let base_reg = info - .node - .regs() - .into_iter() - .next() - .ok_or(OnProbeError::other(alloc::format!( - "[{}] has no reg", - info.node.name() - )))?; - - let mmio_size = base_reg.size.unwrap_or(0x1000) as usize; - let mmio_base = PhysAddr::from_usize(base_reg.address as usize); - - let mmio_base = iomap(mmio_base, mmio_size)?.as_ptr(); - - let (ty, transport) = - ax_driver_virtio::probe_mmio_device(mmio_base, mmio_size).ok_or(OnProbeError::NotMatch)?; - - if ty != DeviceType::Block { - return Err(OnProbeError::NotMatch); - } - - let dev = VirtIoBlkDevice::try_new(transport).map_err(|e| { - OnProbeError::other(format!( - "failed to initialize Virtio Block device at [PA:{mmio_base:?},): {e:?}" - )) - })?; - - register_virtio_block(plat_dev, dev); - debug!("virtio block device registered successfully"); - Ok(()) -} - -pub(super) fn register_virtio_block( - plat_dev: PlatformDevice, - dev: VirtIoBlkDevice, -) { - plat_dev.register_block(BlockDevice { dev: Some(dev) }); -} - -struct BlockDevice { - dev: Option>, -} - -struct BlockQueue { - raw: VirtIoBlkDevice, -} - -impl DriverGeneric for BlockDevice { - fn name(&self) -> &str { - "virtio-blk" - } -} - -impl rd_block::Interface for BlockDevice { - fn create_queue(&mut self) -> Option> { - self.dev - .take() - .map(|dev| alloc::boxed::Box::new(BlockQueue { raw: dev }) as _) - } - - fn enable_irq(&mut self) { - todo!() - } - - fn disable_irq(&mut self) { - todo!() - } - - fn is_irq_enabled(&self) -> bool { - false - } - - fn handle_irq(&mut self) -> rd_block::Event { - rd_block::Event::none() - } -} - -impl rd_block::IQueue for BlockQueue { - fn num_blocks(&self) -> usize { - self.raw.num_blocks() as _ - } - - fn block_size(&self) -> usize { - self.raw.block_size() - } - - fn id(&self) -> usize { - 0 - } - - fn buff_config(&self) -> rd_block::BuffConfig { - rd_block::BuffConfig { - dma_mask: u64::MAX, - align: 0x1000, - size: self.block_size(), - } - } - - fn submit_request( - &mut self, - request: rd_block::Request<'_>, - ) -> Result { - let id = request.block_id; - match request.kind { - rd_block::RequestKind::Read(mut buffer) => { - self.raw - .read_block(id as _, &mut buffer) - .map_err(map_dev_err_to_blk_err)?; - Ok(rd_block::RequestId::new(0)) - } - rd_block::RequestKind::Write(items) => { - self.raw - .write_block(id as _, items) - .map_err(map_dev_err_to_blk_err)?; - Ok(rd_block::RequestId::new(0)) - } - } - } - - fn poll_request(&mut self, _request: rd_block::RequestId) -> Result<(), rd_block::BlkError> { - Ok(()) - } -} - -fn map_dev_err_to_blk_err(err: ax_driver_base::DevError) -> rd_block::BlkError { - match err { - ax_driver_base::DevError::Again => rd_block::BlkError::Retry, - ax_driver_base::DevError::AlreadyExists => { - rd_block::BlkError::Other("Already exists".into()) - } - ax_driver_base::DevError::BadState => { - rd_block::BlkError::Other("Bad internal state".into()) - } - ax_driver_base::DevError::InvalidParam => { - rd_block::BlkError::Other("Invalid parameter".into()) - } - ax_driver_base::DevError::Io => rd_block::BlkError::Other("I/O error".into()), - ax_driver_base::DevError::NoMemory => rd_block::BlkError::NoMemory, - ax_driver_base::DevError::ResourceBusy => rd_block::BlkError::Other("Resource busy".into()), - ax_driver_base::DevError::Unsupported => rd_block::BlkError::NotSupported, - } -} diff --git a/platform/axplat-dyn/src/drivers/blk/virtio_pci.rs b/platform/axplat-dyn/src/drivers/blk/virtio_pci.rs deleted file mode 100644 index d2661642f3..0000000000 --- a/platform/axplat-dyn/src/drivers/blk/virtio_pci.rs +++ /dev/null @@ -1,114 +0,0 @@ -extern crate alloc; - -use alloc::{format, sync::Arc}; - -use ax_driver_base::DeviceType; -use ax_driver_virtio::pci::{ - ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, HeaderType, PciRoot, -}; -use ax_kspin::SpinNoIrq as Mutex; -use rdrive::{ - PlatformDevice, module_driver, - probe::{ - OnProbeError, - pci::{Endpoint, EndpointRc}, - }, -}; - -use super::virtio::{VirtIoBlkDevice, register_virtio_block}; -use crate::drivers::virtio::VirtIoHalImpl; - -module_driver!( - name: "Virtio PCI Block", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ProbeKind::Pci { on_probe: probe }], -); - -fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - match (endpoint.vendor_id(), endpoint.device_id()) { - (0x1af4, 0x1001 | 0x1042) => {} - _ => return Err(OnProbeError::NotMatch), - } - - let bdf = as_device_function(endpoint.address()); - let dev_info = as_device_function_info(endpoint); - let mut root = PciRoot::new(EndpointConfigAccess::new(bdf, endpoint.take())); - - let (ty, transport) = - ax_driver_virtio::probe_pci_device::(&mut root, bdf, &dev_info) - .ok_or(OnProbeError::NotMatch)?; - - if ty != DeviceType::Block { - return Err(OnProbeError::NotMatch); - } - - let dev = VirtIoBlkDevice::try_new(transport).map_err(|err| { - OnProbeError::other(format!( - "failed to initialize Virtio PCI block device at {bdf}: {err:?}" - )) - })?; - - register_virtio_block(plat_dev, dev); - debug!("virtio PCI block device registered successfully at {bdf}"); - Ok(()) -} - -fn as_device_function(address: rdrive::probe::pci::PciAddress) -> DeviceFunction { - DeviceFunction { - bus: address.bus(), - device: address.device(), - function: address.function(), - } -} - -fn as_device_function_info(endpoint: &Endpoint) -> DeviceFunctionInfo { - let class_info = endpoint.revision_and_class(); - let header_type = HeaderType::from(((endpoint.read(0x0c) >> 16) as u8) & 0x7f); - DeviceFunctionInfo { - vendor_id: endpoint.vendor_id(), - device_id: endpoint.device_id(), - class: class_info.base_class, - subclass: class_info.sub_class, - prog_if: class_info.interface, - revision: class_info.revision_id, - header_type, - } -} - -struct EndpointConfigAccess { - bdf: DeviceFunction, - endpoint: Arc>, -} - -impl EndpointConfigAccess { - fn new(bdf: DeviceFunction, endpoint: Endpoint) -> Self { - Self { - bdf, - endpoint: Arc::new(Mutex::new(endpoint)), - } - } - - fn assert_same_function(&self, device_function: DeviceFunction) { - assert_eq!(device_function, self.bdf); - } -} - -impl ConfigurationAccess for EndpointConfigAccess { - fn read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 { - self.assert_same_function(device_function); - self.endpoint.lock().read(register_offset.into()) - } - - fn write_word(&mut self, device_function: DeviceFunction, register_offset: u8, data: u32) { - self.assert_same_function(device_function); - self.endpoint.lock().write(register_offset.into(), data); - } - - unsafe fn unsafe_clone(&self) -> Self { - Self { - bdf: self.bdf, - endpoint: Arc::clone(&self.endpoint), - } - } -} diff --git a/platform/axplat-dyn/src/drivers/mod.rs b/platform/axplat-dyn/src/drivers/mod.rs index 7c45c2c803..c2d12a2374 100644 --- a/platform/axplat-dyn/src/drivers/mod.rs +++ b/platform/axplat-dyn/src/drivers/mod.rs @@ -1,390 +1,5 @@ -extern crate alloc; - -use alloc::boxed::Box; -use core::{alloc::Layout, ptr::NonNull}; - -use ax_driver_block::BlockDriverOps; -#[cfg(feature = "net")] -use ax_driver_net::NetDriverOps; use ax_errno::AxError; -use ax_kspin::SpinNoIrq as Mutex; -use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; -use ax_plat::mem::PhysAddr; -use heapless::Vec; -use rdrive::probe::OnProbeError; - -mod pci; -#[cfg(feature = "rknpu")] -mod rknpu; -#[cfg(feature = "serial")] -mod serial; -mod soc; -#[cfg(feature = "rtc")] -mod time; -mod virtio; - -pub mod blk; -#[cfg(feature = "net")] -pub mod net; -#[cfg(feature = "usb")] -pub mod usb; - -const MAX_BLOCK_DEVICES: usize = 16; -#[cfg(feature = "net")] -const MAX_NET_DEVICES: usize = 16; - -pub type DynBlockDevice = Box; -#[cfg(feature = "net")] -pub type DynNetDevice = Box; - -static BLOCK_DEVICES: Mutex> = Mutex::new(Vec::new()); -#[cfg(feature = "net")] -static NET_DEVICES: Mutex> = Mutex::new(Vec::new()); - -pub fn clear_block_devices() { - BLOCK_DEVICES.lock().clear(); -} - -pub fn register_block_device(device: DynBlockDevice) -> Result<(), DynBlockDevice> { - BLOCK_DEVICES.lock().push(device) -} - -pub fn take_block_devices() -> Vec { - let mut devices = BLOCK_DEVICES.lock(); - core::mem::take(&mut *devices) -} - -#[cfg(feature = "net")] -pub fn clear_net_devices() { - NET_DEVICES.lock().clear(); -} - -#[cfg(feature = "net")] -pub fn register_net_device(device: DynNetDevice) -> Result<(), DynNetDevice> { - NET_DEVICES.lock().push(device) -} - -#[cfg(feature = "net")] -pub fn take_net_devices() -> Vec { - let mut devices = NET_DEVICES.lock(); - core::mem::take(&mut *devices) -} - -/// maps a mmio physical address to a virtual address. -pub(crate) fn iomap(addr: PhysAddr, size: usize) -> Result, OnProbeError> { - axklib::mem::iomap(addr, size) - .map_err(|e| match e { - AxError::NoMemory => OnProbeError::KError(rdrive::KError::NoMem), - _ => OnProbeError::Other(alloc::format!("{e:?}").into()), - }) - .map(|v| unsafe { NonNull::new_unchecked(v.as_mut_ptr()) }) -} pub fn probe_all_devices() -> Result<(), AxError> { - clear_block_devices(); - #[cfg(feature = "net")] - clear_net_devices(); - rdrive::probe_all(false).map_err(|_| AxError::BadState)?; - - for dev in rdrive::get_list::() { - let block = Box::new(blk::Block::try_from(dev).map_err(|err| match err { - ax_driver_base::DevError::NoMemory => AxError::NoMemory, - _ => AxError::BadState, - })?); - if register_block_device(block).is_err() { - return Err(AxError::NoMemory); - } - } - - #[cfg(feature = "net")] - for dev in rdrive::get_list::() { - let net = net::Net::try_from(dev).map_err(|err| match err { - ax_driver_base::DevError::NoMemory => AxError::NoMemory, - _ => AxError::BadState, - })?; - if register_net_device(Box::new(net)).is_err() { - return Err(AxError::NoMemory); - } - } - - #[cfg(feature = "net")] - for dev in rdrive::get_list::() { - let net = net::take_net_driver(dev).map_err(|err| match err { - ax_driver_base::DevError::NoMemory => AxError::NoMemory, - _ => AxError::BadState, - })?; - if register_net_device(net).is_err() { - return Err(AxError::NoMemory); - } - } - - Ok(()) -} - -pub(crate) struct DmaImpl; - -struct DmaPages { - cpu_addr: NonNull, - dma_addr: u64, - num_pages: usize, -} - -impl DmaPages { - fn layout_pages(layout: Layout) -> usize { - layout.size().div_ceil(PAGE_SIZE_4K) - } - - fn layout_align(layout: Layout) -> usize { - layout.align().max(PAGE_SIZE_4K) - } - - unsafe fn alloc_for_layout(dma_mask: u64, layout: Layout) -> Result { - if layout.size() == 0 { - return Ok(Self { - cpu_addr: NonNull::dangling(), - dma_addr: 0, - num_pages: 0, - }); - } - - let num_pages = Self::layout_pages(layout); - let align = Self::layout_align(layout); - let cpu_vaddr = if dma_mask <= u32::MAX as u64 { - ax_alloc::global_allocator().alloc_dma32_pages( - num_pages, - align, - ax_alloc::UsageKind::Dma, - ) - } else { - ax_alloc::global_allocator().alloc_pages(num_pages, align, ax_alloc::UsageKind::Dma) - } - .map_err(|_| dma_api::DmaError::NoMemory)?; - - let cpu_addr = NonNull::new(cpu_vaddr as *mut u8).ok_or(dma_api::DmaError::NoMemory)?; - let dma_addr = dma_addr_from_ptr(cpu_addr); - if !dma_range_fits_mask(dma_addr, layout.size(), dma_mask) { - unsafe { Self::dealloc_pages(cpu_addr, num_pages) }; - return Err(dma_api::DmaError::DmaMaskNotMatch { - addr: dma_addr.into(), - mask: dma_mask, - }); - } - if !dma_addr_is_aligned(dma_addr, layout.align()) { - unsafe { Self::dealloc_pages(cpu_addr, num_pages) }; - return Err(dma_api::DmaError::AlignMismatch { - required: layout.align(), - address: dma_addr.into(), - }); - } - - Ok(Self { - cpu_addr, - dma_addr, - num_pages, - }) - } - - unsafe fn dealloc_pages(cpu_addr: NonNull, num_pages: usize) { - if num_pages == 0 { - return; - } - ax_alloc::global_allocator().dealloc_pages( - cpu_addr.as_ptr() as usize, - num_pages, - ax_alloc::UsageKind::Dma, - ); - } -} - -struct CoherentDmaPolicy; - -impl CoherentDmaPolicy { - /// Converts freshly allocated `alloc_coherent` pages to uncached DMA pages. - /// - /// Streaming mappings created by `map_single` remain cacheable and must use - /// `confirm_write`/`prepare_read` for cache maintenance. The kernel helper - /// owns the cache/TLB/barrier sequence for this mapping transition. - fn make_uncached(pages: &DmaPages, layout: Layout) -> Result<(), dma_api::DmaError> { - if pages.num_pages == 0 { - return Ok(()); - } - - let range_size = pages.num_pages * PAGE_SIZE_4K; - let start = VirtAddr::from_usize(pages.cpu_addr.as_ptr() as usize).align_down_4k(); - axklib::mem::make_dma_coherent_uncached(start, range_size) - .map_err(|_| dma_api::DmaError::NoMemory)?; - unsafe { - pages.cpu_addr.as_ptr().write_bytes(0, layout.size()); - } - Ok(()) - } - - /// Restores pages before returning them to the general DMA page allocator. - /// - /// The caller must ensure the device no longer owns or accesses the buffer. - fn restore_cached(pages: NonNull, num_pages: usize) -> Result<(), dma_api::DmaError> { - if num_pages == 0 { - return Ok(()); - } - - let start = VirtAddr::from_usize(pages.as_ptr() as usize).align_down_4k(); - axklib::mem::restore_dma_cached(start, num_pages * PAGE_SIZE_4K) - .map_err(|_| dma_api::DmaError::NoMemory) - } -} - -#[inline] -fn dma_addr_from_ptr(ptr: NonNull) -> u64 { - somehal::mem::virt_to_phys(ptr.as_ptr()) as u64 -} - -#[inline] -fn dma_range_fits_mask(dma_addr: u64, size: usize, dma_mask: u64) -> bool { - if size == 0 { - dma_addr <= dma_mask - } else { - dma_addr - .checked_add(size.saturating_sub(1) as u64) - .map(|end| end <= dma_mask) - .unwrap_or(false) - } -} - -#[inline] -fn dma_addr_is_aligned(dma_addr: u64, align: usize) -> bool { - dma_addr.is_multiple_of(align as u64) -} - -impl dma_api::DmaOp for DmaImpl { - fn page_size(&self) -> usize { - PAGE_SIZE_4K - } - - unsafe fn map_single( - &self, - dma_mask: u64, - addr: NonNull, - size: core::num::NonZeroUsize, - align: usize, - direction: dma_api::DmaDirection, - ) -> Result { - let layout = Layout::from_size_align(size.get(), align)?; - let dma_addr = dma_addr_from_ptr(addr); - - if dma_range_fits_mask(dma_addr, size.get(), dma_mask) - && dma_addr_is_aligned(dma_addr, align) - { - return Ok(unsafe { dma_api::DmaMapHandle::new(addr, dma_addr.into(), layout, None) }); - } - - let map_pages = unsafe { DmaPages::alloc_for_layout(dma_mask, layout)? }; - let map_virt = map_pages.cpu_addr; - - if matches!( - direction, - dma_api::DmaDirection::ToDevice | dma_api::DmaDirection::Bidirectional - ) { - unsafe { - map_virt - .as_ptr() - .copy_from_nonoverlapping(addr.as_ptr(), size.get()); - } - } - - Ok(unsafe { - dma_api::DmaMapHandle::new(addr, map_pages.dma_addr.into(), layout, Some(map_virt)) - }) - } - - unsafe fn unmap_single(&self, handle: dma_api::DmaMapHandle) { - if let Some(map_virt) = handle.alloc_virt() { - let num_pages = DmaPages::layout_pages(handle.layout()); - unsafe { DmaPages::dealloc_pages(map_virt, num_pages) }; - } - } - - fn prepare_read( - &self, - handle: &dma_api::DmaMapHandle, - offset: usize, - size: usize, - direction: dma_api::DmaDirection, - ) { - if !matches!( - direction, - dma_api::DmaDirection::FromDevice | dma_api::DmaDirection::Bidirectional - ) { - return; - } - - let target = unsafe { handle.as_ptr().add(offset) }; - if let Some(map_virt) = handle.alloc_virt() - && map_virt != handle.as_ptr() - { - let source = unsafe { map_virt.add(offset) }; - self.invalidate(source, size); - unsafe { - target - .as_ptr() - .copy_from_nonoverlapping(source.as_ptr(), size); - } - return; - } - - self.invalidate(target, size); - } - - fn confirm_write( - &self, - handle: &dma_api::DmaMapHandle, - offset: usize, - size: usize, - direction: dma_api::DmaDirection, - ) { - if !matches!( - direction, - dma_api::DmaDirection::ToDevice | dma_api::DmaDirection::Bidirectional - ) { - return; - } - - let source = unsafe { handle.as_ptr().add(offset) }; - if let Some(map_virt) = handle.alloc_virt() - && map_virt != handle.as_ptr() - { - let target = unsafe { map_virt.add(offset) }; - unsafe { - target - .as_ptr() - .copy_from_nonoverlapping(source.as_ptr(), size); - } - self.flush(target, size); - return; - } - - self.flush(source, size); - } - - unsafe fn alloc_coherent(&self, dma_mask: u64, layout: Layout) -> Option { - let pages = unsafe { DmaPages::alloc_for_layout(dma_mask, layout).ok()? }; - if CoherentDmaPolicy::make_uncached(&pages, layout).is_err() { - unsafe { DmaPages::dealloc_pages(pages.cpu_addr, pages.num_pages) }; - return None; - } - - Some(unsafe { dma_api::DmaHandle::new(pages.cpu_addr, pages.dma_addr.into(), layout) }) - } - - unsafe fn dealloc_coherent(&self, handle: dma_api::DmaHandle) { - let num_pages = DmaPages::layout_pages(handle.layout()); - if CoherentDmaPolicy::restore_cached(handle.as_ptr(), num_pages).is_err() { - error!( - "failed to restore coherent DMA pages to cached mapping; leaking {} pages at {:p}", - num_pages, - handle.as_ptr().as_ptr(), - ); - return; - } - unsafe { DmaPages::dealloc_pages(handle.as_ptr(), num_pages) }; - } + rdrive::probe_all(false).map_err(|_| AxError::BadState) } diff --git a/platform/axplat-dyn/src/drivers/net/mod.rs b/platform/axplat-dyn/src/drivers/net/mod.rs deleted file mode 100644 index 2df4cdcc72..0000000000 --- a/platform/axplat-dyn/src/drivers/net/mod.rs +++ /dev/null @@ -1,329 +0,0 @@ -extern crate alloc; - -use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; - -use ax_driver_base::{BaseDriverOps, DevError, DevResult, DeviceType}; -use ax_driver_net::{ - EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr, NetDriverOps, NetIrqEvent, -}; -use ax_kspin::SpinNoIrq; -use rd_net::{Interface, NetError}; -use rdrive::{Device, DriverGeneric}; - -use super::DmaImpl; - -#[cfg(feature = "intel-net")] -mod intel; -#[cfg(feature = "realtek-rtl8125")] -mod realtek; -#[cfg(feature = "virtio-net-pci")] -mod virtio_pci; - -const NET_BUF_LEN: usize = 2048; -const NET_BUF_POOL_CAPACITY: usize = 512; -const NET_QUEUE_SIZE: usize = 256; -const RX_PREFETCH_TARGET: usize = 1; -const ETH_ZLEN: usize = 60; - -pub struct PlatformNetDevice { - name: &'static str, - net: rd_net::Net, - irq_num: Option, -} - -impl PlatformNetDevice { - fn new(name: &'static str, net: rd_net::Net, irq_num: Option) -> Self { - Self { name, net, irq_num } - } -} - -impl DriverGeneric for PlatformNetDevice { - fn name(&self) -> &str { - self.name - } -} - -pub struct PlatformNetDriver { - name: &'static str, - dev: Option>, -} - -impl DriverGeneric for PlatformNetDriver { - fn name(&self) -> &str { - self.name - } -} - -struct NetState { - tx_queue: rd_net::TxQueue, - rx_queue: rd_net::RxQueue, - pending_rx: VecDeque, -} - -pub struct Net { - name: &'static str, - mac: [u8; 6], - irq_num: Option, - buf_pool: Arc, - irq_handler: Option, - state: SpinNoIrq, -} - -impl TryFrom> for Net { - type Error = DevError; - - fn try_from(device: Device) -> Result { - let mut dev = device.lock().map_err(map_device_err_to_dev_err)?; - let name = dev.name; - let mac = dev.net.mac_address(); - let irq_num = dev.irq_num; - let tx_queue = dev.net.create_tx_queue().map_err(map_net_err_to_dev_err)?; - let rx_queue = dev.net.create_rx_queue().map_err(map_net_err_to_dev_err)?; - let irq_handler = irq_num.map(|_| dev.net.irq_handler()); - drop(dev); - - Ok(Self { - name, - mac, - irq_num, - buf_pool: NetBufPool::new(NET_BUF_POOL_CAPACITY, NET_BUF_LEN)?, - irq_handler, - state: SpinNoIrq::new(NetState { - tx_queue, - rx_queue, - pending_rx: VecDeque::with_capacity(RX_PREFETCH_TARGET), - }), - }) - } -} - -impl Net { - fn prefetch_rx_packets(&self, state: &mut NetState, target: usize) -> DevResult { - while state.pending_rx.len() < target { - let Some(result) = state.rx_queue.receive(|packet| { - let Some(mut net_buf) = self.buf_pool.alloc_boxed() else { - return Err(DevError::NoMemory); - }; - - if packet.len() > net_buf.capacity() { - warn!( - "dropping oversized rx packet for {}: {} bytes", - self.name, - packet.len() - ); - return Err(DevError::InvalidParam); - } - - net_buf.set_header_len(0); - net_buf.set_packet_len(packet.len()); - net_buf.packet_mut().copy_from_slice(packet); - Ok(net_buf) - }) else { - break; - }; - - match result { - Ok(net_buf) => state.pending_rx.push_back(net_buf), - Err(DevError::InvalidParam) => continue, - Err(err) => return Err(err), - } - } - - Ok(()) - } -} - -pub(super) fn pci_legacy_irq_for_address(address: rdrive::probe::pci::PciAddress) -> usize { - if let Some(irq) = super::pci::legacy_irq_for_address(address) { - return irq; - } - - const PCI_IRQ_BASE: usize = if cfg!(target_arch = "x86_64") || cfg!(target_arch = "riscv64") { - 0x20 - } else if cfg!(target_arch = "loongarch64") { - 0x10 - } else if cfg!(target_arch = "aarch64") { - 0x23 - } else { - 0 - }; - - PCI_IRQ_BASE + (usize::from(address.device()) & 3) -} - -impl BaseDriverOps for Net { - fn device_name(&self) -> &str { - self.name - } - - fn device_type(&self) -> DeviceType { - DeviceType::Net - } - - fn irq_num(&self) -> Option { - self.irq_num - } -} - -impl NetDriverOps for Net { - fn mac_address(&self) -> EthernetAddress { - EthernetAddress(self.mac) - } - - fn can_transmit(&self) -> bool { - let mut state = self.state.lock(); - match state.tx_queue.prepare_send(0, |_| ()) { - Ok((_ret, _pending)) => true, - Err(NetError::Retry) => false, - Err(err) => { - warn!("failed to test tx readiness for {}: {err:?}", self.name); - false - } - } - } - - fn can_receive(&self) -> bool { - let mut state = self.state.lock(); - if let Err(err) = self.prefetch_rx_packets(&mut state, RX_PREFETCH_TARGET) { - warn!("failed to prefetch rx packets for {}: {err:?}", self.name); - } - !state.pending_rx.is_empty() - } - - fn rx_queue_size(&self) -> usize { - NET_QUEUE_SIZE - } - - fn tx_queue_size(&self) -> usize { - NET_QUEUE_SIZE - } - - fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { - drop(unsafe { NetBuf::from_buf_ptr(rx_buf) }); - Ok(()) - } - - fn recycle_tx_buffers(&mut self) -> DevResult { - Ok(()) - } - - fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { - let tx_buf = unsafe { NetBuf::from_buf_ptr(tx_buf) }; - let packet = tx_buf.packet(); - let tx_len = packet.len().max(ETH_ZLEN); - - let mut state = self.state.lock(); - let (_ret, mut pending) = state - .tx_queue - .prepare_send(tx_len, |buffer| { - buffer[..packet.len()].copy_from_slice(packet); - buffer[packet.len()..tx_len].fill(0); - }) - .map_err(map_net_err_to_dev_err)?; - pending.try_submit().map_err(map_net_err_to_dev_err)?; - drop(tx_buf); - Ok(()) - } - - fn receive(&mut self) -> DevResult { - let mut state = self.state.lock(); - self.prefetch_rx_packets(&mut state, RX_PREFETCH_TARGET)?; - state - .pending_rx - .pop_front() - .map(NetBuf::into_buf_ptr) - .ok_or(DevError::Again) - } - - fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { - let mut net_buf = self.buf_pool.alloc_boxed().ok_or(DevError::NoMemory)?; - if size > net_buf.capacity() { - return Err(DevError::InvalidParam); - } - - net_buf.set_header_len(0); - net_buf.set_packet_len(size); - Ok(net_buf.into_buf_ptr()) - } - - fn handle_irq(&mut self) -> NetIrqEvent { - let Some(handler) = &self.irq_handler else { - return NetIrqEvent::SPURIOUS; - }; - - handler.handle(); - - let mut events = NetIrqEvent::empty(); - let mut state = self.state.lock(); - if let Err(err) = self.prefetch_rx_packets(&mut state, RX_PREFETCH_TARGET) { - warn!( - "failed to prefetch rx packets for {} during irq: {err:?}", - self.name - ); - events |= NetIrqEvent::RX_ERROR; - } - if !state.pending_rx.is_empty() { - events |= NetIrqEvent::RX_READY; - } - - if events.is_empty() { - NetIrqEvent::SPURIOUS - } else { - events - } - } -} - -pub trait PlatformDeviceNet { - fn register_net(self, name: &'static str, dev: T, irq_num: Option) - where - T: Interface + 'static; -} - -impl PlatformDeviceNet for rdrive::PlatformDevice { - fn register_net(self, name: &'static str, dev: T, irq_num: Option) - where - T: Interface + 'static, - { - let net = rd_net::Net::new(dev, &DmaImpl); - self.register(PlatformNetDevice::new(name, net, irq_num)); - } -} - -pub trait PlatformDeviceNetDriver { - fn register_net_driver(self, name: &'static str, dev: T) - where - T: NetDriverOps + 'static; -} - -impl PlatformDeviceNetDriver for rdrive::PlatformDevice { - fn register_net_driver(self, name: &'static str, dev: T) - where - T: NetDriverOps + 'static, - { - self.register(PlatformNetDriver { - name, - dev: Some(Box::new(dev)), - }); - } -} - -pub(super) fn take_net_driver( - device: Device, -) -> Result, DevError> { - let mut dev = device.lock().map_err(map_device_err_to_dev_err)?; - dev.dev.take().ok_or(DevError::BadState) -} - -fn map_net_err_to_dev_err(err: NetError) -> DevError { - match err { - NetError::Retry => DevError::Again, - NetError::NoMemory => DevError::NoMemory, - NetError::NotSupported => DevError::Unsupported, - NetError::LinkDown | NetError::Other(_) => DevError::Io, - } -} - -fn map_device_err_to_dev_err(_err: rdrive::GetDeviceError) -> DevError { - DevError::BadState -} diff --git a/platform/axplat-dyn/src/drivers/net/virtio_pci.rs b/platform/axplat-dyn/src/drivers/net/virtio_pci.rs deleted file mode 100644 index fe3a0061b7..0000000000 --- a/platform/axplat-dyn/src/drivers/net/virtio_pci.rs +++ /dev/null @@ -1,119 +0,0 @@ -extern crate alloc; - -use alloc::{format, sync::Arc}; - -use ax_driver_base::DeviceType; -use ax_driver_virtio::pci::{ - ConfigurationAccess, DeviceFunction, DeviceFunctionInfo, HeaderType, PciRoot, -}; -use ax_kspin::SpinNoIrq as Mutex; -use rdrive::{ - PlatformDevice, module_driver, - probe::{ - OnProbeError, - pci::{Endpoint, EndpointRc}, - }, -}; - -use super::PlatformDeviceNetDriver; -use crate::drivers::virtio::VirtIoHalImpl; - -const DRIVER_NAME: &str = "virtio-net-pci"; -type VirtIoNetDevice = ax_driver_virtio::VirtIoNetDev; - -module_driver!( - name: "Virtio PCI Network", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ProbeKind::Pci { on_probe: probe }], -); - -fn probe(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - match (endpoint.vendor_id(), endpoint.device_id()) { - (0x1af4, 0x1000 | 0x1041) => {} - _ => return Err(OnProbeError::NotMatch), - } - - let address = endpoint.address(); - let bdf = as_device_function(address); - let dev_info = as_device_function_info(endpoint); - let mut root = PciRoot::new(EndpointConfigAccess::new(bdf, endpoint.take())); - - let (ty, transport) = - ax_driver_virtio::probe_pci_device::(&mut root, bdf, &dev_info) - .ok_or(OnProbeError::NotMatch)?; - let irq = super::pci_legacy_irq_for_address(address); - - if ty != DeviceType::Net { - return Err(OnProbeError::NotMatch); - } - - let dev = VirtIoNetDevice::try_new(transport, Some(irq)).map_err(|err| { - OnProbeError::other(format!( - "failed to initialize Virtio PCI network device at {bdf}: {err:?}" - )) - })?; - - plat_dev.register_net_driver(DRIVER_NAME, dev); - debug!("virtio PCI network device registered successfully at {bdf} with irq {irq:#x}"); - Ok(()) -} - -fn as_device_function(address: rdrive::probe::pci::PciAddress) -> DeviceFunction { - DeviceFunction { - bus: address.bus(), - device: address.device(), - function: address.function(), - } -} - -fn as_device_function_info(endpoint: &Endpoint) -> DeviceFunctionInfo { - let class_info = endpoint.revision_and_class(); - let header_type = HeaderType::from(((endpoint.read(0x0c) >> 16) as u8) & 0x7f); - DeviceFunctionInfo { - vendor_id: endpoint.vendor_id(), - device_id: endpoint.device_id(), - class: class_info.base_class, - subclass: class_info.sub_class, - prog_if: class_info.interface, - revision: class_info.revision_id, - header_type, - } -} - -struct EndpointConfigAccess { - bdf: DeviceFunction, - endpoint: Arc>, -} - -impl EndpointConfigAccess { - fn new(bdf: DeviceFunction, endpoint: Endpoint) -> Self { - Self { - bdf, - endpoint: Arc::new(Mutex::new(endpoint)), - } - } - - fn assert_same_function(&self, device_function: DeviceFunction) { - assert_eq!(device_function, self.bdf); - } -} - -impl ConfigurationAccess for EndpointConfigAccess { - fn read_word(&self, device_function: DeviceFunction, register_offset: u8) -> u32 { - self.assert_same_function(device_function); - self.endpoint.lock().read(register_offset.into()) - } - - fn write_word(&mut self, device_function: DeviceFunction, register_offset: u8, data: u32) { - self.assert_same_function(device_function); - self.endpoint.lock().write(register_offset.into(), data); - } - - unsafe fn unsafe_clone(&self) -> Self { - Self { - bdf: self.bdf, - endpoint: Arc::clone(&self.endpoint), - } - } -} diff --git a/platform/axplat-dyn/src/drivers/pci.rs b/platform/axplat-dyn/src/drivers/pci.rs deleted file mode 100644 index 2f08d35d04..0000000000 --- a/platform/axplat-dyn/src/drivers/pci.rs +++ /dev/null @@ -1,162 +0,0 @@ -extern crate alloc; - -use alloc::format; - -use ax_kspin::SpinNoIrq as Mutex; -use fdt_edit::{PciRange, PciSpace}; -use heapless::Vec as ArrayVec; -use rdrive::{ - PlatformDevice, module_driver, - probe::{OnProbeError, fdt::NodeType, pci::*}, - register::FdtInfo, -}; - -mod rk3588; - -const MAX_PCIE_LEGACY_IRQS: usize = 8; - -module_driver!( - name: "Generic PCIe Controller Driver", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["pci-host-ecam-generic"], - on_probe: probe_generic_ecam - } - ], -); - -#[derive(Clone, Copy)] -struct LegacyIrqRoute { - bus_start: u8, - bus_end: u8, - irq: usize, -} - -static LEGACY_IRQ_ROUTES: Mutex> = - Mutex::new(ArrayVec::new()); - -pub(crate) fn legacy_irq_for_address(address: PciAddress) -> Option { - let bus = address.bus(); - LEGACY_IRQ_ROUTES - .lock() - .iter() - .find(|route| bus >= route.bus_start && bus <= route.bus_end) - .map(|route| route.irq) -} - -fn probe_generic_ecam(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let NodeType::Pci(node) = info.node else { - return Err(OnProbeError::NotMatch); - }; - - let regs = node.regs(); - for reg in ®s { - trace!( - "pcie reg: {:#x}, bus: {:#x}", - reg.address, reg.child_bus_address - ); - } - - let reg = regs - .first() - .ok_or_else(|| OnProbeError::other("PCIe controller has no regs"))?; - let mmio_base = reg.address as usize; - let mmio_size = reg.size.unwrap_or(0x1000) as usize; - let mut drv = new_driver_generic(mmio_base, mmio_size, &crate::boot::Kernel) - .map_err(|e| OnProbeError::other(format!("failed to create PCIe controller: {e:?}")))?; - - for range in node.ranges().unwrap_or_default() { - debug!("pcie range {range:?}"); - set_pcie_mem_range(&mut drv, &range); - } - - plat_dev.register_pcie(drv); - - Ok(()) -} - -pub(super) fn set_pcie_mem_range(drv: &mut PcieController, range: &PciRange) { - match range.space { - PciSpace::Memory32 => { - drv.set_mem32( - PciMem32 { - address: range.cpu_address as _, - size: range.size as _, - }, - range.prefetchable, - ); - } - PciSpace::Memory64 => { - drv.set_mem64( - PciMem64 { - address: range.cpu_address, - size: range.size, - }, - range.prefetchable, - ); - } - PciSpace::IO => {} - } -} - -pub(super) fn register_legacy_irq(info: &FdtInfo<'_>, logical_bus_end: u8) { - let Some(interrupt) = info - .interrupts() - .into_iter() - .find(|interrupt| interrupt.name.as_deref() == Some("legacy")) - else { - return; - }; - let Some(parent) = info.phandle_to_device_id(interrupt.interrupt_parent) else { - warn!( - "failed to resolve PCIe legacy IRQ parent phandle {}", - interrupt.interrupt_parent - ); - return; - }; - - let irq = somehal::irq::irq_setup_by_fdt(parent, &interrupt.specifier).raw(); - let mut routes = LEGACY_IRQ_ROUTES.lock(); - if routes - .iter() - .any(|route| route.bus_start == 0 && route.bus_end == logical_bus_end && route.irq == irq) - { - return; - } - if routes - .push(LegacyIrqRoute { - bus_start: 0, - bus_end: logical_bus_end, - irq, - }) - .is_err() - { - warn!("too many PCIe legacy IRQ routes; dropping IRQ {}", irq); - } else { - info!( - "PCIe legacy IRQ route: logical bus 0..={} -> IRQ {}", - logical_bus_end, irq - ); - } -} - -#[cfg(feature = "pci-list-devices")] -mod pci_list_devices { - use super::*; - - module_driver!( - name: "PCI Device Lister", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ProbeKind::Pci { - on_probe: probe as FnOnProbe - }], - ); - - fn probe(endpoint: &mut EndpointRc, _plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - info!("PCIe endpoint: {} bars={:?}", &**endpoint, endpoint.bars()); - Err(OnProbeError::NotMatch) - } -} diff --git a/platform/axplat-dyn/src/drivers/pci/rk3588.rs b/platform/axplat-dyn/src/drivers/pci/rk3588.rs deleted file mode 100644 index 139211842f..0000000000 --- a/platform/axplat-dyn/src/drivers/pci/rk3588.rs +++ /dev/null @@ -1,1289 +0,0 @@ -extern crate alloc; - -use alloc::{ - format, - string::{String, ToString}, - vec::Vec, -}; -use core::{ - ptr::NonNull, - sync::atomic::{AtomicU32, Ordering}, - time::Duration, -}; - -use fdt_edit::{ClockRef, Fdt, Node, PciRange, PciSpace, Phandle, RegFixed}; -use mmio_api::{MmioAddr, MmioRaw}; -use rdif_pcie::{PciMem64, PcieController}; -use rdrive::{ - PlatformDevice, module_driver, - probe::{OnProbeError, fdt::NodeType}, - register::FdtInfo, -}; -use rk3588_pci::{ - Delay, HostConfig, IatuMode, MEM_ATU_FIRST_REGION, OutboundWindow, ResetControl, Rk3588PcieHost, -}; - -use crate::drivers::soc::{ - RockchipPinCtrl, rk3588_enable_clock, rk3588_enable_power_domain, rk3588_reset_assert, - rk3588_reset_deassert, rk3588_set_clock_rate, -}; - -const RK3588_GPIO_BASES: [u64; 5] = [ - 0xfd8a_0000, - 0xfec2_0000, - 0xfec3_0000, - 0xfec4_0000, - 0xfec5_0000, -]; -const RK3588_GPIO_SIZE: usize = 0x110; -const RK3588_GPIO_SWPORT_DR_L: usize = 0x00; -const RK3588_GPIO_SWPORT_DR_H: usize = 0x04; -const RK3588_GPIO_SWPORT_DDR_L: usize = 0x08; -const RK3588_GPIO_SWPORT_DDR_H: usize = 0x0c; -const RK3588_PCIE_PERST_INACTIVE_MS: u64 = 200; -const DEFAULT_CFG_SIZE: u64 = 0x10_0000; -const PHY_TYPE_PCIE: u32 = 2; -const RK3588_PCIE3PHY_DEFAULT_MODE: u32 = 4; -const RK3588_PCIE3PHY_CMN_CON0: usize = 0x000; -const RK3588_PCIE3PHY_PHY0_STATUS1: usize = 0x904; -const RK3588_PCIE3PHY_PHY1_STATUS1: usize = 0xa04; -const PHP_GRF_PCIESEL_CON: usize = 0x100; -const PCIE3PHY_SRAM_INIT_DONE: u32 = 1; -const BIT_WRITEABLE_SHIFT: u32 = 16; -const RK3588_PCIE_MAX_HOSTS: u32 = 8; -static PROBED_HOST_MASK: AtomicU32 = AtomicU32::new(0); - -struct AxDelay; - -impl Delay for AxDelay { - fn delay_us(&self, us: u64) { - axklib::time::busy_wait(Duration::from_micros(us)); - } - - fn delay_ms(&self, ms: u64) { - axklib::time::busy_wait(Duration::from_millis(ms)); - } -} - -struct Rk3588GpioReset { - apb_phys: u64, - bank: u8, - pin: u8, - active_high: bool, - gpio: MmioRaw, -} - -impl Rk3588GpioReset { - fn map(apb_phys: u64, bank: u8, pin: u8, active_high: bool) -> Result { - let phys = *RK3588_GPIO_BASES - .get(usize::from(bank)) - .ok_or_else(|| OnProbeError::other(format!("invalid RK3588 GPIO bank {}", bank)))?; - Ok(Self { - apb_phys, - bank, - pin, - active_high, - gpio: map_mmio(phys, RK3588_GPIO_SIZE)?, - }) - } - - fn set_logical(&self, value: bool) { - let physical = if self.active_high { value } else { !value }; - self.write_masked_pair(RK3588_GPIO_SWPORT_DR_L, RK3588_GPIO_SWPORT_DR_H, physical); - self.write_masked_pair(RK3588_GPIO_SWPORT_DDR_L, RK3588_GPIO_SWPORT_DDR_H, true); - } - - fn write_masked_pair(&self, low_offset: usize, high_offset: usize, value: bool) { - let pin = u32::from(self.pin); - let (offset, shift) = if pin < 16 { - (low_offset, pin) - } else { - (high_offset, pin - 16) - }; - let mask = 1_u32 << (shift + 16); - let data = u32::from(value) << shift; - self.gpio.write::(offset, mask | data); - } -} - -impl ResetControl for Rk3588GpioReset { - fn assert_perst(&mut self) { - self.set_logical(false); - info!( - "Rockchip RK3588 PCIe host {:#x}: assert PERST via GPIO{} pin {}", - self.apb_phys, self.bank, self.pin - ); - } - - fn deassert_perst(&mut self) { - self.set_logical(true); - info!( - "Rockchip RK3588 PCIe host {:#x}: release PERST after {}ms", - self.apb_phys, RK3588_PCIE_PERST_INACTIVE_MS - ); - } -} - -struct RegMmio { - mmio: MmioRaw, - size: usize, -} - -impl RegMmio { - fn map_phandle(phandle: Phandle, context: &str) -> Result { - let fdt = live_fdt()?; - let node = fdt.get_by_phandle(phandle).ok_or_else(|| { - OnProbeError::other(format!("{context} phandle {phandle:?} not found")) - })?; - let reg = node.regs().into_iter().next().ok_or_else(|| { - OnProbeError::other(format!("[{}] has no reg for {context}", node.name())) - })?; - Self::map_reg(reg) - } - - fn map_reg(reg: RegFixed) -> Result { - let size = align_up_4k((reg.size.unwrap_or(0x1000) as usize).max(1)); - let mmio = map_mmio(reg.address, size)?; - Ok(Self { mmio, size }) - } - - fn read32(&self, offset: usize) -> u32 { - debug_assert!(offset + core::mem::size_of::() <= self.size); - self.mmio.read::(offset) - } - - fn write32(&self, offset: usize, value: u32) { - debug_assert!(offset + core::mem::size_of::() <= self.size); - self.mmio.write::(offset, value); - } - - fn update32(&self, offset: usize, mask: u32, value: u32) { - let current = self.read32(offset); - self.write32(offset, (current & !mask) | value); - } -} - -#[derive(Clone)] -struct ClockSpec { - name: Option, - id: u32, - assigned_rate: Option, -} - -#[derive(Clone)] -struct ResetSpec { - name: Option, - id: u64, -} - -#[derive(Clone, Copy)] -struct GpioSpec { - bank: u8, - pin: u8, - active_high: bool, -} - -struct HostResources<'a> { - name: String, - node: NodeType<'a>, - apb: RegFixed, - dbi: RegFixed, - cfg_phys: u64, - cfg_size: u64, - ranges: Vec, - bus_base: u8, - logical_bus_end: u8, - power_domains: Vec, - clocks: Vec, - resets: Vec, - pipe_grf: Option, - reset_gpio: Option, - supply: Option, - phys: Vec, -} - -#[derive(Clone)] -struct PhyRef { - phandle: Phandle, - specifier: Vec, - name: Option, -} - -struct Pcie3PhyResources { - name: String, - reg: RegFixed, - phy_grf: Phandle, - pipe_grf: Option, - pcie30_phymode: u32, - clocks: Vec, - resets: Vec, -} - -struct CombphyResources { - name: String, - reg: RegFixed, - pipe_grf: Phandle, - pipe_phy_grf: Phandle, - pcie1ln_sel_bits: Option<[u32; 4]>, - refclk_rate: u32, - clocks: Vec, - resets: Vec, -} - -fn probe_rk3588(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let NodeType::Pci(node) = info.node else { - return Err(OnProbeError::NotMatch); - }; - - let resources = parse_host_resources(&info, NodeType::Pci(node))?; - if !claim_host_probe(resources.apb.address) { - return Err(OnProbeError::NotMatch); - } - let mut reset = resources - .reset_gpio - .map(|gpio| { - Rk3588GpioReset::map(resources.apb.address, gpio.bank, gpio.pin, gpio.active_high) - }) - .transpose()?; - prepare_controller_resources(&resources)?; - - let apb_size = resources.apb.size.unwrap_or(0x10000) as usize; - let dbi_size = resources.dbi.size.unwrap_or(0x400000) as usize; - let apb = map_mmio(resources.apb.address, apb_size)?; - let dbi = map_mmio(resources.dbi.address, dbi_size)?; - let cfg = map_mmio(resources.cfg_phys, resources.cfg_size as usize)?; - - let mut host = Rk3588PcieHost::new( - apb, - dbi, - cfg, - HostConfig { - apb_phys: resources.apb.address, - cfg_phys: resources.cfg_phys, - cfg_size: resources.cfg_size as usize, - bus_base: resources.bus_base, - logical_bus_end: resources.logical_bus_end, - iatu_mode: IatuMode::Unroll, - }, - ); - - let delay = AxDelay; - match reset.as_mut() { - Some(reset) => { - host.init(&delay, Some(reset)); - } - None => { - host.init(&delay, None); - } - } - - program_memory_windows( - &host, - &resources.ranges, - resources.cfg_phys, - resources.cfg_size, - ); - host.unmask_legacy_intx_all(); - info!( - "Rockchip RK3588 PCIe host {:#x}: legacy INTx unmasked", - host.apb_phys() - ); - log_direct_endpoint(&host); - super::register_legacy_irq(&info, resources.logical_bus_end); - - let mut drv = PcieController::new(host); - for range in &resources.ranges { - if is_config_range(range, resources.cfg_phys, resources.cfg_size) { - continue; - } - set_rk3588_bar_range(&mut drv, range); - } - - info!( - "Rockchip RK3588 PCIe host {:#x}: registering config window {:#x}/{} bytes, DT buses \ - {:#x}..={:#x}, logical buses 0..={}", - resources.apb.address, - resources.cfg_phys, - resources.cfg_size, - resources.bus_base, - resources.bus_base.saturating_add(resources.logical_bus_end), - resources.logical_bus_end - ); - plat_dev.register_pcie(drv); - Ok(()) -} - -fn claim_host_probe(apb_base: u64) -> bool { - let Some(index) = apb_base - .checked_sub(0xfe15_0000) - .filter(|offset| offset % 0x1_0000 == 0) - .map(|offset| (offset / 0x1_0000) as u32) - .filter(|index| *index < RK3588_PCIE_MAX_HOSTS) - else { - return true; - }; - let bit = 1_u32 << index; - PROBED_HOST_MASK.fetch_or(bit, Ordering::AcqRel) & bit == 0 -} - -fn map_mmio(phys: u64, size: usize) -> Result { - let virt = crate::drivers::iomap((phys as usize).into(), size)?; - Ok(unsafe { MmioRaw::new(MmioAddr::from(phys), virt, size) }) -} - -fn prepare_controller_resources(resources: &HostResources<'_>) -> Result<(), OnProbeError> { - let delay = AxDelay; - if let Some(gpio) = resources.reset_gpio { - let mut reset = - Rk3588GpioReset::map(resources.apb.address, gpio.bank, gpio.pin, gpio.active_high)?; - reset.assert_perst(); - } else { - warn!( - "Rockchip RK3588 PCIe host {:#x}: no PERST GPIO discovered", - resources.apb.address - ); - } - - enable_vpcie3v3_supply(resources.supply)?; - enable_power_domains(&resources.power_domains)?; - init_phys(resources.node, &resources.phys)?; - assert_resets(&resources.resets)?; - delay.delay_us(1); - deassert_resets(&resources.resets)?; - enable_clocks(&resources.clocks)?; - axklib::time::busy_wait(Duration::from_millis(1)); - log_resource_summary(resources); - Ok(()) -} - -fn parse_power_domains(node: &Node) -> Result, OnProbeError> { - let Some(prop) = node.get_property("power-domains") else { - return Ok(Vec::new()); - }; - let cells = prop.get_u32_iter().collect::>(); - if cells.len() % 2 != 0 { - return Err(OnProbeError::other(format!( - "[{}] has malformed power-domains", - node.name() - ))); - } - Ok(cells.chunks(2).map(|chunk| chunk[1] as usize).collect()) -} - -fn enable_power_domains(domains: &[usize]) -> Result<(), OnProbeError> { - if domains.is_empty() { - return Ok(()); - } - - for &domain in domains { - rk3588_enable_power_domain(domain).map_err(|err| { - OnProbeError::other(format!( - "failed to enable RK3588 PCIe power domain {domain}: {err}" - )) - })?; - } - Ok(()) -} - -fn enable_vpcie3v3_supply(supply: Option) -> Result<(), OnProbeError> { - let Some(supply) = supply else { - return Ok(()); - }; - let pinctrl = rdrive::get_one::() - .ok_or_else(|| OnProbeError::other("RockchipPinCtrl not found for PCIe regulator"))?; - let mut pinctrl = pinctrl - .lock() - .map_err(|err| OnProbeError::other(format!("failed to lock RockchipPinCtrl: {err}")))?; - pinctrl.enable_fixed_regulator(supply) -} - -fn parse_host_resources<'a>( - info: &FdtInfo<'a>, - node_type: NodeType<'a>, -) -> Result, OnProbeError> { - let NodeType::Pci(node) = node_type else { - return Err(OnProbeError::NotMatch); - }; - let raw_node = node_type.as_node(); - let node_name = raw_node.name().to_string(); - let regs = node.regs(); - let apb = *regs - .first() - .ok_or_else(|| OnProbeError::other(format!("{node_name} has no APB register")))?; - let dbi = *regs - .get(1) - .ok_or_else(|| OnProbeError::other(format!("{node_name} has no DBI register")))?; - let ranges = node.ranges().unwrap_or_default(); - let (cfg_phys, cfg_size) = config_window(®s, &ranges)?; - let (bus_base, logical_bus_end) = bus_range_info(node.bus_range()); - - Ok(HostResources { - name: node_name, - node: node_type, - apb, - dbi, - cfg_phys, - cfg_size, - ranges, - bus_base, - logical_bus_end, - power_domains: parse_power_domains(raw_node)?, - clocks: clock_specs(node.clocks()), - resets: parse_resets(node_type)?, - pipe_grf: prop_phandle(raw_node, "rockchip,pipe-grf"), - reset_gpio: parse_reset_gpio(info, apb.address)?, - supply: prop_phandle(raw_node, "vpcie3v3-supply"), - phys: parse_phys(node_type)?, - }) -} - -fn clock_specs_for_node(node: NodeType<'_>) -> Vec { - let assigned_clocks = node - .as_node() - .get_property("assigned-clocks") - .map(|prop| { - let vals = prop.get_u32_iter().collect::>(); - let mut ids = Vec::new(); - for cells in vals.chunks(2) { - if let [_, id] = cells { - ids.push(*id); - } - } - ids - }) - .unwrap_or_default(); - let assigned_rates = node - .as_node() - .get_property("assigned-clock-rates") - .map(|prop| prop.get_u32_iter().collect::>()) - .unwrap_or_default(); - - node.clocks() - .into_iter() - .filter_map(|clock| { - let assigned_rate = clock.specifier.first().and_then(|id| { - assigned_clocks - .iter() - .position(|assigned| assigned == id) - .and_then(|index| assigned_rates.get(index).copied()) - .filter(|rate| *rate != 0) - }); - let id = *clock.specifier.first()?; - Some(ClockSpec { - name: clock.name, - id, - assigned_rate, - }) - }) - .collect() -} - -fn clock_specs(clocks: Vec) -> Vec { - clocks - .into_iter() - .filter_map(|clock| { - let id = *clock.specifier.first()?; - Some(ClockSpec { - name: clock.name, - id, - assigned_rate: None, - }) - }) - .collect() -} - -fn enable_clocks(clocks: &[ClockSpec]) -> Result<(), OnProbeError> { - for clock in clocks { - let id = clock.id; - if id == 0 { - continue; - } - if let Some(rate) = clock.assigned_rate { - rk3588_set_clock_rate(id, u64::from(rate)).map_err(|err| { - OnProbeError::other(format!( - "failed to set RK3588 PCIe clock {:?} ({id:#x}) rate to {rate}: {err}", - clock.name - )) - })?; - } - rk3588_enable_clock(id).map_err(|err| { - OnProbeError::other(format!( - "failed to enable RK3588 PCIe clock {:?} ({id:#x}): {err}", - clock.name - )) - })?; - } - Ok(()) -} - -fn parse_resets(node: NodeType<'_>) -> Result, OnProbeError> { - let Some(prop) = node.as_node().get_property("resets") else { - return Ok(Vec::new()); - }; - let cells = prop.get_u32_iter().collect::>(); - if cells.len() % 2 != 0 { - return Err(OnProbeError::other(format!( - "[{}] has malformed resets", - node.name() - ))); - } - let reset_names = prop_str_list(node.as_node(), "reset-names"); - Ok(cells - .chunks(2) - .enumerate() - .map(|(idx, chunk)| ResetSpec { - name: reset_names.get(idx).cloned(), - id: u64::from(chunk[1]), - }) - .collect()) -} - -fn assert_resets(resets: &[ResetSpec]) -> Result<(), OnProbeError> { - for reset in resets { - rk3588_reset_assert(reset.id).map_err(|err| { - OnProbeError::other(format!( - "failed to assert RK3588 PCIe reset {:?} ({:#x}): {err}", - reset.name, reset.id - )) - })?; - } - Ok(()) -} - -fn deassert_resets(resets: &[ResetSpec]) -> Result<(), OnProbeError> { - for reset in resets { - rk3588_reset_deassert(reset.id).map_err(|err| { - OnProbeError::other(format!( - "failed to deassert RK3588 PCIe reset {:?} ({:#x}): {err}", - reset.name, reset.id - )) - })?; - } - Ok(()) -} - -fn parse_reset_gpio(info: &FdtInfo<'_>, apb_base: u64) -> Result, OnProbeError> { - if let Some(gpio) = parse_gpio_spec(info.node, "reset-gpios")? { - return Ok(Some(gpio)); - } - - if let Some(default) = rk3588_pcie_reset_pin(apb_base) { - warn!( - "Rockchip RK3588 PCIe host {:#x}: reset-gpios missing; using diagnostic fallback \ - GPIO{} pin {}", - apb_base, default.bank, default.pin - ); - return Ok(Some(GpioSpec { - bank: default.bank, - pin: default.pin, - active_high: default.active_high, - })); - } - - Ok(None) -} - -fn parse_gpio_spec( - node_type: NodeType<'_>, - prop_name: &str, -) -> Result, OnProbeError> { - let node = node_type.as_node(); - let Some(prop) = node.get_property(prop_name) else { - return Ok(None); - }; - let mut cells = prop.get_u32_iter(); - let phandle_raw = cells.next().ok_or_else(|| { - OnProbeError::other(format!("[{}] has malformed {prop_name}", node.name())) - })?; - let pin = cells.next().ok_or_else(|| { - OnProbeError::other(format!("[{}] has malformed {prop_name}", node.name())) - })?; - let flags = cells.next().unwrap_or(0); - let bank = gpio_bank_from_phandle(Phandle::from(phandle_raw))?; - Ok(Some(GpioSpec { - bank, - pin: pin.try_into().map_err(|_| { - OnProbeError::other(format!( - "[{}] {prop_name} pin {pin} does not fit RK3588 GPIO", - node.name() - )) - })?, - active_high: flags & 1 == 0, - })) -} - -fn gpio_bank_from_phandle(phandle: Phandle) -> Result { - let fdt = live_fdt()?; - let gpio = fdt - .get_by_phandle(phandle) - .ok_or_else(|| OnProbeError::other(format!("GPIO phandle {phandle:?} not found")))?; - gpio_bank_index(gpio.as_node()).ok_or_else(|| { - OnProbeError::other(format!( - "failed to resolve RK3588 GPIO bank for phandle {phandle:?}" - )) - }) -} - -fn gpio_bank_index(node: &Node) -> Option { - let name = node.name(); - if let Some(name) = name - .strip_prefix("gpio") - .filter(|name| !name.starts_with('@')) - { - if let Some(bank) = name - .chars() - .next() - .and_then(|ch| ch.to_digit(10)) - .and_then(|bank| u8::try_from(bank).ok()) - .filter(|bank| usize::from(*bank) < RK3588_GPIO_BASES.len()) - { - return Some(bank); - } - } - - let address = gpio_bank_address(node)?; - RK3588_GPIO_BASES - .iter() - .position(|base| *base == address) - .and_then(|bank| u8::try_from(bank).ok()) -} - -fn gpio_bank_address(node: &Node) -> Option { - if let Some(address) = node - .name() - .split_once('@') - .and_then(|(_, unit)| u64::from_str_radix(unit, 16).ok()) - { - return Some(address); - } - - let reg = node.get_property("reg")?.get_u32_iter().collect::>(); - match reg.as_slice() { - [addr] => Some(u64::from(*addr)), - cells if cells.len() >= 2 => Some((u64::from(cells[0]) << 32) | u64::from(cells[1])), - _ => None, - } -} - -fn parse_phys(node_type: NodeType<'_>) -> Result, OnProbeError> { - let node = node_type.as_node(); - let Some(prop) = node.get_property("phys") else { - return Ok(Vec::new()); - }; - let cells = prop.get_u32_iter().collect::>(); - if cells.is_empty() { - return Ok(Vec::new()); - } - let phy_names = prop_str_list(node, "phy-names"); - let mut refs = Vec::new(); - let mut index = 0; - let mut offset = 0; - while offset < cells.len() { - let phandle = Phandle::from(cells[offset]); - offset += 1; - let specifier_cells = phy_cells(phandle)?; - if offset + specifier_cells > cells.len() { - return Err(OnProbeError::other(format!( - "[{}] has truncated phys entry for phandle {phandle:?}", - node.name() - ))); - } - let specifier = cells[offset..offset + specifier_cells].to_vec(); - offset += specifier_cells; - refs.push(PhyRef { - phandle, - specifier, - name: phy_names.get(index).cloned(), - }); - index += 1; - } - Ok(refs) -} - -fn init_phys(host_node: NodeType<'_>, phys: &[PhyRef]) -> Result<(), OnProbeError> { - if phys.is_empty() { - warn!( - "Rockchip RK3588 PCIe host {} has no phys property", - host_node.name() - ); - return Ok(()); - } - let fdt = live_fdt()?; - for phy_ref in phys { - let phy = fdt.get_by_phandle(phy_ref.phandle).ok_or_else(|| { - OnProbeError::other(format!( - "PCIe PHY phandle {:?} for {} not found", - phy_ref.phandle, - host_node.name() - )) - })?; - if is_compatible(phy.as_node(), "rockchip,rk3588-pcie3-phy") { - let resources = parse_pcie3_phy(phy)?; - init_pcie3_phy(&resources)?; - } else if is_compatible(phy.as_node(), "rockchip,rk3588-naneng-combphy") { - let Some(&phy_type) = phy_ref.specifier.first() else { - return Err(OnProbeError::other(format!( - "RK3588 combphy {} referenced by {} has no PHY type specifier", - phy.name(), - host_node.name() - ))); - }; - if phy_type != PHY_TYPE_PCIE { - return Err(OnProbeError::other(format!( - "RK3588 combphy {} referenced by {} is type {}, expected PCIe", - phy.name(), - host_node.name(), - phy_type - ))); - } - let resources = parse_combphy(phy)?; - init_combphy(&resources)?; - } else { - return Err(OnProbeError::other(format!( - "unsupported RK3588 PCIe PHY {} referenced by {}", - phy.name(), - host_node.name() - ))); - } - } - Ok(()) -} - -fn parse_pcie3_phy(phy: NodeType<'_>) -> Result { - let node = phy.as_node(); - let reg = phy - .regs() - .into_iter() - .next() - .ok_or_else(|| OnProbeError::other(format!("[{}] has no reg", phy.name())))?; - let phy_grf = prop_phandle(node, "rockchip,phy-grf") - .ok_or_else(|| OnProbeError::other(format!("[{}] has no rockchip,phy-grf", phy.name())))?; - let mut pcie30_phymode = - prop_u32(node, "rockchip,pcie30-phymode").unwrap_or(RK3588_PCIE3PHY_DEFAULT_MODE); - if pcie30_phymode > RK3588_PCIE3PHY_DEFAULT_MODE { - pcie30_phymode = RK3588_PCIE3PHY_DEFAULT_MODE; - } - Ok(Pcie3PhyResources { - name: phy.name().to_string(), - reg, - phy_grf, - pipe_grf: prop_phandle(node, "rockchip,pipe-grf"), - pcie30_phymode, - clocks: clock_specs_for_node(phy), - resets: parse_resets(phy)?, - }) -} - -fn init_pcie3_phy(phy: &Pcie3PhyResources) -> Result<(), OnProbeError> { - let _mmio = RegMmio::map_reg(phy.reg)?; - let phy_grf = RegMmio::map_phandle(phy.phy_grf, "rk3588-pcie3-phy rockchip,phy-grf")?; - let pipe_grf = phy - .pipe_grf - .map(|phandle| RegMmio::map_phandle(phandle, "rk3588-pcie3-phy rockchip,pipe-grf")) - .transpose()?; - - enable_clocks(&phy.clocks)?; - for reset in &phy.resets { - rk3588_reset_assert(reset.id).map_err(|err| { - OnProbeError::other(format!( - "failed to assert RK3588 PCIe3 PHY reset {:?} ({:#x}): {err}", - reset.name, reset.id - )) - })?; - } - axklib::time::busy_wait(Duration::from_micros(1)); - - phy_grf.write32( - RK3588_PCIE3PHY_CMN_CON0, - (0x7 << BIT_WRITEABLE_SHIFT) | phy.pcie30_phymode, - ); - if let Some(pipe_grf) = pipe_grf.as_ref() { - let mode = phy.pcie30_phymode & 3; - if mode != 0 { - pipe_grf.write32(PHP_GRF_PCIESEL_CON, (mode << BIT_WRITEABLE_SHIFT) | mode); - } - } - phy_grf.write32(RK3588_PCIE3PHY_CMN_CON0, (1 << 24) | (1 << 8)); - - for reset in &phy.resets { - rk3588_reset_deassert(reset.id).map_err(|err| { - OnProbeError::other(format!( - "failed to deassert RK3588 PCIe3 PHY reset {:?} ({:#x}): {err}", - reset.name, reset.id - )) - })?; - } - poll_pcie3_sram_ready(&phy_grf, RK3588_PCIE3PHY_PHY0_STATUS1, &phy.name)?; - poll_pcie3_sram_ready(&phy_grf, RK3588_PCIE3PHY_PHY1_STATUS1, &phy.name)?; - info!( - "RK3588 PCIe3 PHY {} initialized, mode={}", - phy.name, phy.pcie30_phymode - ); - Ok(()) -} - -fn poll_pcie3_sram_ready(phy_grf: &RegMmio, offset: usize, name: &str) -> Result<(), OnProbeError> { - for _ in 0..500 { - if phy_grf.read32(offset) & PCIE3PHY_SRAM_INIT_DONE != 0 { - return Ok(()); - } - axklib::time::busy_wait(Duration::from_micros(1)); - } - Err(OnProbeError::other(format!( - "RK3588 PCIe3 PHY {name} SRAM ready timeout at GRF offset {offset:#x}" - ))) -} - -fn parse_combphy(phy: NodeType<'_>) -> Result { - let node = phy.as_node(); - let reg = phy - .regs() - .into_iter() - .next() - .ok_or_else(|| OnProbeError::other(format!("[{}] has no reg", phy.name())))?; - let pipe_grf = prop_phandle(node, "rockchip,pipe-grf") - .ok_or_else(|| OnProbeError::other(format!("[{}] has no rockchip,pipe-grf", phy.name())))?; - let pipe_phy_grf = prop_phandle(node, "rockchip,pipe-phy-grf").ok_or_else(|| { - OnProbeError::other(format!("[{}] has no rockchip,pipe-phy-grf", phy.name())) - })?; - let pcie1ln_sel_bits = node - .get_property("rockchip,pcie1ln-sel-bits") - .map(|prop| { - let vals = prop.get_u32_iter().collect::>(); - if vals.len() != 4 { - return Err(OnProbeError::other(format!( - "[{}] malformed rockchip,pcie1ln-sel-bits", - phy.name() - ))); - } - Ok([vals[0], vals[1], vals[2], vals[3]]) - }) - .transpose()?; - - Ok(CombphyResources { - name: phy.name().to_string(), - reg, - pipe_grf, - pipe_phy_grf, - pcie1ln_sel_bits, - refclk_rate: assigned_clock_rate(node).unwrap_or(100_000_000), - clocks: clock_specs_for_node(phy), - resets: parse_resets(phy)?, - }) -} - -fn init_combphy(phy: &CombphyResources) -> Result<(), OnProbeError> { - let mmio = RegMmio::map_reg(phy.reg)?; - let pipe_grf = RegMmio::map_phandle(phy.pipe_grf, "rk3588-naneng-combphy rockchip,pipe-grf")?; - let phy_grf = RegMmio::map_phandle( - phy.pipe_phy_grf, - "rk3588-naneng-combphy rockchip,pipe-phy-grf", - )?; - - assert_resets(&phy.resets)?; - enable_clocks(&phy.clocks)?; - if let Some([offset, start, end, value]) = phy.pcie1ln_sel_bits { - let mask = bit_range_mask(start, end)?; - pipe_grf.write32( - offset as usize, - (mask << BIT_WRITEABLE_SHIFT) | (value << start), - ); - } - combphy_update(&mmio, 0x7c, bit_range_mask(4, 5)?, 1 << 4); - combphy_param_write(&phy_grf, 0x0000, 0, 15, 0x1000)?; - combphy_param_write(&phy_grf, 0x0004, 0, 15, 0x0000)?; - combphy_param_write(&phy_grf, 0x0008, 0, 15, 0x0101)?; - combphy_param_write(&phy_grf, 0x000c, 0, 15, 0x0200)?; - - match phy.refclk_rate { - 24_000_000 => init_combphy_refclk_24m(&mmio, &phy_grf)?, - 25_000_000 => combphy_param_write(&phy_grf, 0x0004, 13, 14, 0x01)?, - 100_000_000 => init_combphy_refclk_100m(&mmio, &phy_grf)?, - rate => { - return Err(OnProbeError::other(format!( - "RK3588 combphy {} unsupported refclk rate {}", - phy.name, rate - ))); - } - } - - combphy_update(&mmio, 0x19 << 2, 1 << 5, 1 << 5); - deassert_resets(&phy.resets)?; - info!( - "RK3588 Naneng combphy {} initialized for PCIe, refclk={}Hz", - phy.name, phy.refclk_rate - ); - Ok(()) -} - -fn init_combphy_refclk_24m(mmio: &RegMmio, phy_grf: &RegMmio) -> Result<(), OnProbeError> { - combphy_param_write(phy_grf, 0x0004, 13, 14, 0x00)?; - combphy_update(mmio, 0x20 << 2, bit_range_mask(2, 4)?, 0x4 << 2); - mmio.write32(0x1b << 2, 0x00); - mmio.write32(0x0a << 2, 0x90); - mmio.write32(0x0b << 2, 0x02); - mmio.write32(0x0d << 2, 0x57); - mmio.write32(0x0f << 2, 0x5f); - Ok(()) -} - -fn init_combphy_refclk_100m(mmio: &RegMmio, phy_grf: &RegMmio) -> Result<(), OnProbeError> { - combphy_param_write(phy_grf, 0x0004, 13, 14, 0x02)?; - mmio.write32(0x74, 0xc0); - combphy_update(mmio, 0x20 << 2, bit_range_mask(2, 4)?, 0x4 << 2); - mmio.write32(0x1b << 2, 0x4c); - mmio.write32(0x0a << 2, 0x90); - mmio.write32(0x0b << 2, 0x43); - mmio.write32(0x0c << 2, 0x88); - mmio.write32(0x0d << 2, 0x56); - Ok(()) -} - -fn combphy_param_write( - mmio: &RegMmio, - offset: usize, - start: u32, - end: u32, - value: u32, -) -> Result<(), OnProbeError> { - let mask = bit_range_mask(start, end)?; - mmio.write32(offset, (value << start) | (mask << BIT_WRITEABLE_SHIFT)); - Ok(()) -} - -fn combphy_update(mmio: &RegMmio, offset: usize, mask: u32, value: u32) { - mmio.update32(offset, mask, value); -} - -fn bit_range_mask(start: u32, end: u32) -> Result { - if start > end || end >= 32 { - return Err(OnProbeError::other(format!( - "invalid bit range {}..={}", - start, end - ))); - } - let width = end - start + 1; - Ok(if width == 32 { - u32::MAX - } else { - ((1_u32 << width) - 1) << start - }) -} - -fn assigned_clock_rate(node: &Node) -> Option { - node.get_property("assigned-clock-rates") - .and_then(|prop| prop.get_u32_iter().next()) -} - -fn log_resource_summary(resources: &HostResources<'_>) { - info!( - "Rockchip RK3588 PCIe host {:#x}: FDT resources node={}, dbi={:#x}/{:#x}, \ - cfg={:#x}/{:#x}, buses {:#x}..={:#x}, clocks={}, resets={}, power-domains={}, phys={}, \ - supply={:?}, pipe-grf={:?}, reset-gpio={}", - resources.apb.address, - resources.name, - resources.dbi.address, - resources.dbi.size.unwrap_or(0), - resources.cfg_phys, - resources.cfg_size, - resources.bus_base, - resources.bus_base.saturating_add(resources.logical_bus_end), - resources.clocks.len(), - resources.resets.len(), - resources.power_domains.len(), - resources.phys.len(), - resources.supply, - resources.pipe_grf, - reset_gpio_label(resources.reset_gpio) - ); - for phy in &resources.phys { - debug!( - "Rockchip RK3588 PCIe host {:#x}: PHY {:?} phandle={} specifier={:?}", - resources.apb.address, phy.name, phy.phandle, phy.specifier - ); - } -} - -fn reset_gpio_label(gpio: Option) -> String { - match gpio { - Some(gpio) => format!( - "GPIO{} pin {} active-{}", - gpio.bank, - gpio.pin, - if gpio.active_high { "high" } else { "low" } - ), - None => "none".to_string(), - } -} - -fn is_compatible(node: &Node, compatible: &str) -> bool { - node.compatibles().any(|item| item == compatible) -} - -fn phy_cells(phandle: Phandle) -> Result { - let fdt = live_fdt()?; - let phy = fdt - .get_by_phandle(phandle) - .ok_or_else(|| OnProbeError::other(format!("PHY phandle {phandle:?} not found")))?; - phy.as_node() - .get_property("#phy-cells") - .and_then(|prop| prop.get_u32()) - .map(|cells| cells as usize) - .ok_or_else(|| { - OnProbeError::other(format!( - "[{}] has no #phy-cells for phandle {phandle:?}", - phy.name() - )) - }) -} - -fn prop_phandle(node: &Node, prop_name: &str) -> Option { - node.get_property(prop_name) - .and_then(|prop| prop.get_u32()) - .map(Phandle::from) -} - -fn prop_u32(node: &Node, prop_name: &str) -> Option { - node.get_property(prop_name).and_then(|prop| prop.get_u32()) -} - -fn prop_str_list(node: &Node, prop_name: &str) -> Vec { - node.get_property(prop_name) - .map(|prop| prop.as_str_iter().map(|s| s.to_string()).collect()) - .unwrap_or_default() -} - -fn live_fdt() -> Result { - let ptr = somehal::fdt_addr().ok_or_else(|| OnProbeError::other("live FDT not found"))?; - let ptr = NonNull::new(ptr).ok_or_else(|| OnProbeError::other("live FDT pointer is null"))?; - unsafe { Fdt::from_ptr(ptr.as_ptr()) } - .map_err(|err| OnProbeError::other(format!("failed to parse live FDT: {err:?}"))) -} - -fn align_up_4k(size: usize) -> usize { - const MASK: usize = 0xfff; - (size + MASK) & !MASK -} - -#[derive(Clone, Copy)] -struct Rk3588ResetPin { - bank: u8, - pin: u8, - active_high: bool, -} - -fn rk3588_pcie_reset_pin(apb_base: u64) -> Option { - match apb_base { - 0xfe18_0000 => Some(Rk3588ResetPin { - bank: 3, - pin: 11, - active_high: true, - }), - 0xfe19_0000 => Some(Rk3588ResetPin { - bank: 4, - pin: 2, - active_high: true, - }), - _ => None, - } -} - -fn config_window(regs: &[RegFixed], ranges: &[PciRange]) -> Result<(u64, u64), OnProbeError> { - if let Some(reg) = regs.get(2) { - return Ok((reg.address, reg.size.unwrap_or(DEFAULT_CFG_SIZE))); - } - - ranges - .iter() - .find(|range| { - matches!(range.space, PciSpace::Memory32) - && range.size == DEFAULT_CFG_SIZE - && range.cpu_address == range.bus_address - }) - .map(|range| (range.cpu_address, range.size)) - .ok_or_else(|| OnProbeError::other("RK3588 PCIe host has no config window")) -} - -fn bus_range_info(bus_range: Option>) -> (u8, u8) { - let Some(bus_range) = bus_range else { - return (0, u8::MAX); - }; - let bus_base = bus_range.start.min(u32::from(u8::MAX)) as u8; - let logical_end = bus_range - .end - .saturating_sub(bus_range.start) - .clamp(1, u32::from(u8::MAX)) as u8; - (bus_base, logical_end) -} - -fn program_memory_windows( - host: &Rk3588PcieHost, - ranges: &[PciRange], - cfg_phys: u64, - cfg_size: u64, -) { - let mut region = MEM_ATU_FIRST_REGION; - for range in ranges { - if is_config_range(range, cfg_phys, cfg_size) { - continue; - } - match range.space { - PciSpace::Memory32 | PciSpace::Memory64 => { - let window = OutboundWindow { - cpu_base: range.cpu_address, - pci_base: range.bus_address, - size: range.size, - }; - if let Err(err) = host.program_memory_window(region, window) { - warn!( - "PCIe host {:#x}: invalid outbound iATU region {}: {err:?}", - host.apb_phys(), - region - ); - } - debug!( - "PCIe host {:#x}: iATU mem region {} cpu={:#x} pci={:#x} size={:#x}", - host.apb_phys(), - region, - range.cpu_address, - range.bus_address, - range.size - ); - region = region.saturating_add(1); - } - PciSpace::IO => {} - } - } -} - -fn log_direct_endpoint(host: &Rk3588PcieHost) { - if let Some(endpoint) = host.direct_endpoint_info() { - info!( - "PCIe endpoint: {} {:04x}:{:04x} (rev {:02x}, class {:02x}{:02x}{:02x})", - endpoint.address, - endpoint.vendor_id, - endpoint.device_id, - endpoint.revision_id, - endpoint.base_class, - endpoint.sub_class, - endpoint.prog_if - ); - } -} - -fn is_config_range(range: &PciRange, cfg_phys: u64, cfg_size: u64) -> bool { - range.cpu_address == cfg_phys && range.size == cfg_size -} - -fn set_rk3588_bar_range(drv: &mut PcieController, range: &PciRange) { - super::set_pcie_mem_range(drv, range); - if matches!(range.space, PciSpace::Memory32) { - drv.set_mem64( - PciMem64 { - address: range.cpu_address, - size: range.size, - }, - range.prefetchable, - ); - } -} - -mod rk3588_pcie_slot0 { - use super::*; - - module_driver!( - name: "Rockchip RK3588 PCIe host slot0", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-pcie"], - on_probe: probe - } - ], - ); - - fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - probe_rk3588(info, plat_dev) - } -} - -mod rk3588_pcie_slot1 { - use super::*; - - module_driver!( - name: "Rockchip RK3588 PCIe host slot1", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-pcie"], - on_probe: probe - } - ], - ); - - fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - probe_rk3588(info, plat_dev) - } -} - -mod rk3588_pcie_slot2 { - use super::*; - - module_driver!( - name: "Rockchip RK3588 PCIe host slot2", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-pcie"], - on_probe: probe - } - ], - ); - - fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - probe_rk3588(info, plat_dev) - } -} - -mod rk3588_pcie_slot3 { - use super::*; - - module_driver!( - name: "Rockchip RK3588 PCIe host slot3", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-pcie"], - on_probe: probe - } - ], - ); - - fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - probe_rk3588(info, plat_dev) - } -} - -mod rk3588_pcie_slot4 { - use super::*; - - module_driver!( - name: "Rockchip RK3588 PCIe host slot4", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-pcie"], - on_probe: probe - } - ], - ); - - fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - probe_rk3588(info, plat_dev) - } -} diff --git a/platform/axplat-dyn/src/drivers/rknpu/mod.rs b/platform/axplat-dyn/src/drivers/rknpu/mod.rs deleted file mode 100644 index 458ae73e9a..0000000000 --- a/platform/axplat-dyn/src/drivers/rknpu/mod.rs +++ /dev/null @@ -1,67 +0,0 @@ -use alloc::vec::Vec; - -use dma_api::DeviceDma; -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; -use rockchip_npu::{Rknpu, RknpuConfig, RknpuType}; -use rockchip_pm::{PowerDomain, RockchipPM}; - -use crate::drivers::{DmaImpl, iomap}; - -static DMA: DmaImpl = DmaImpl; - -module_driver!( - name: "Rockchip NPU", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["rockchip,rk3588-rknpu"], - on_probe: probe - } - ], -); - -fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let regs = info.node.regs(); - - let config = RknpuConfig { - rknpu_type: RknpuType::Rk3588, - }; - - let mut base_regs = Vec::new(); - let page_size = 0x1000; - for reg in ®s { - let start_raw = reg.address as usize; - let end = start_raw + reg.size.unwrap_or(0x1000) as usize; - - let start = start_raw & !(page_size - 1); - let offset = start_raw - start; - let end = (end + page_size - 1) & !(page_size - 1); - let size = end - start; - - base_regs.push(unsafe { iomap(start.into(), size)?.add(offset) }); - } - - enable_pm(); - - info!("NPU power enabled"); - - let dma = DeviceDma::new(u32::MAX as u64, &DMA); - let npu = Rknpu::new(&base_regs, config, dma); - plat_dev.register(npu); - info!("NPU registered successfully"); - Ok(()) -} - -fn enable_pm() { - let mut pm = rdrive::get_one::() - .expect("RockchipPM not found") - .lock() - .expect("RockchipPM lock failed"); - - // RK3588 NPU power domain IDs (from rockchip-pm rk3588 variant) - pm.power_domain_on(PowerDomain(9)).unwrap(); // NPUTOP - pm.power_domain_on(PowerDomain(8)).unwrap(); // NPU - pm.power_domain_on(PowerDomain(10)).unwrap(); // NPU1 - pm.power_domain_on(PowerDomain(11)).unwrap(); // NPU2 -} diff --git a/platform/axplat-dyn/src/drivers/serial/mod.rs b/platform/axplat-dyn/src/drivers/serial/mod.rs deleted file mode 100644 index 25d315103b..0000000000 --- a/platform/axplat-dyn/src/drivers/serial/mod.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; -use some_serial::{BSerial, ns16550, pl011}; - -use crate::drivers::iomap; - -module_driver!( - name: "common serial", - level: ProbeLevel::PreKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["arm,pl011", "snps,dw-apb-uart"], - on_probe: probe - } - ], -); - -fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - info!("Probing serial device: {}", info.node.name()); - let base_reg = info - .node - .regs() - .into_iter() - .next() - .ok_or(OnProbeError::other(alloc::format!( - "[{}] has no reg", - info.node.name() - )))?; - - let mmio_size = base_reg.size.unwrap_or(0x1000); - let mmio_base = iomap((base_reg.address as usize).into(), mmio_size as usize)?; - - let node = info.node.as_node(); - let clock_freq = prop_u32(node, "clock-frequency").unwrap_or(24_000_000); - let reg_width = prop_u32(node, "reg-io-width").unwrap_or(1) as usize; - let mut serial: Option = None; - for c in node.compatibles() { - if c == "arm,pl011" { - serial = Some(pl011::Pl011::new_boxed(mmio_base, clock_freq)); - break; - } - - if c == "snps,dw-apb-uart" { - serial = Some(ns16550::Ns16550::new_mmio_boxed( - mmio_base, clock_freq, reg_width, - )); - break; - } - } - if let Some(s) = serial { - let base = s.base_addr(); - info!("Serial@{base:#x} registered successfully"); - plat_dev.register(s); - } - - Ok(()) -} - -fn prop_u32(node: &fdt_edit::Node, name: &str) -> Option { - node.get_property(name).and_then(|prop| prop.get_u32()) -} diff --git a/platform/axplat-dyn/src/drivers/usb/mod.rs b/platform/axplat-dyn/src/drivers/usb/mod.rs deleted file mode 100644 index fa5f8f6fa5..0000000000 --- a/platform/axplat-dyn/src/drivers/usb/mod.rs +++ /dev/null @@ -1,186 +0,0 @@ -extern crate alloc; - -#[cfg(feature = "xhci-pci")] -use alloc::vec::Vec; -use core::time::Duration; - -use crab_usb::USBHost; -#[cfg(feature = "xhci-pci")] -use crab_usb::err::USBError; -#[cfg(feature = "xhci-pci")] -use fdt_edit::{Fdt, NodeType}; -use rdrive::DriverGeneric; - -#[cfg(feature = "rockchip-dwc-xhci")] -mod xhci_dwc; -#[cfg(feature = "xhci-mmio")] -mod xhci_mmio; -#[cfg(feature = "xhci-pci")] -mod xhci_pci; - -use super::DmaImpl; - -pub type UsbHostDevice = rdrive::Device; -pub type UsbHostDeviceGuard = rdrive::DeviceGuard; - -impl crab_usb::KernelOp for DmaImpl { - fn delay(&self, duration: Duration) { - axklib::time::busy_wait(duration); - } -} - -pub(crate) static USB_KERNEL: DmaImpl = DmaImpl; - -pub struct PlatformUsbHost { - name: &'static str, - irq_num: Option, - host: USBHost, -} - -impl PlatformUsbHost { - fn new(name: &'static str, host: USBHost, irq_num: Option) -> Self { - Self { - name, - irq_num, - host, - } - } - - pub fn host(&self) -> &USBHost { - &self.host - } - - pub fn host_mut(&mut self) -> &mut USBHost { - &mut self.host - } - - pub fn irq_num(&self) -> Option { - self.irq_num - } -} - -impl DriverGeneric for PlatformUsbHost { - fn name(&self) -> &str { - self.name - } -} - -pub trait PlatformDeviceUsbHost { - fn register_usb_host(self, name: &'static str, host: USBHost, irq_num: Option); -} - -impl PlatformDeviceUsbHost for rdrive::PlatformDevice { - fn register_usb_host(self, name: &'static str, host: USBHost, irq_num: Option) { - self.register(PlatformUsbHost::new(name, host, irq_num)); - } -} - -#[cfg(feature = "xhci-pci")] -fn align_up_4k(size: usize) -> usize { - const MASK: usize = 0xfff; - (size + MASK) & !MASK -} - -fn decode_fdt_irq(interrupts: &[rdrive::probe::fdt::InterruptRef]) -> Option { - let interrupt = interrupts.first()?; - decode_irq_cells(&interrupt.specifier) -} - -fn decode_irq_cells(specifier: &[u32]) -> Option { - match specifier { - [irq] => Some(*irq as usize), - [kind, irq, ..] => match *kind { - 0 => Some(*irq as usize + 32), - 1 => Some(*irq as usize + 16), - _ => Some(*irq as usize), - }, - _ => None, - } -} - -#[cfg(feature = "xhci-pci")] -pub(super) fn resolve_pci_irq_from_fdt( - endpoint: &rdrive::probe::pci::EndpointRc, -) -> Result { - let fdt_addr = somehal::fdt_addr().ok_or_else(|| { - USBError::Other(anyhow::anyhow!( - "PCI USB IRQ mapping requires FDT; ACPI is not supported" - )) - })?; - let fdt = unsafe { Fdt::from_ptr(fdt_addr) } - .map_err(|err| USBError::Other(anyhow::anyhow!("failed to parse live FDT: {err:?}")))?; - - let bus = endpoint.address().bus(); - let pin = endpoint.interrupt_pin(); - if pin == 0 { - return Err(USBError::Other(anyhow::anyhow!( - "PCI USB endpoint {} has no interrupt pin", - endpoint.address() - ))); - } - - let mut candidates = Vec::new(); - let mut exact_range_matches = Vec::new(); - for node in fdt.all_nodes() { - let NodeType::Pci(pci) = node else { - continue; - }; - - match pci.bus_range() { - Some(range) if range.contains(&(bus as u32)) => { - exact_range_matches.push(pci); - candidates.push(pci); - } - Some(_) => {} - None => candidates.push(pci), - } - } - - let pci_host = if exact_range_matches.len() == 1 { - exact_range_matches[0] - } else if exact_range_matches.len() > 1 { - return Err(USBError::Other(anyhow::anyhow!( - "multiple PCI host nodes in live FDT match USB endpoint {} with the same bus-range", - endpoint.address() - ))); - } else if candidates.len() == 1 { - candidates[0] - } else if candidates.is_empty() { - return Err(USBError::Other(anyhow::anyhow!( - "no PCI host node in live FDT matches USB endpoint {}", - endpoint.address() - ))); - } else { - return Err(USBError::Other(anyhow::anyhow!( - "multiple PCI host nodes in live FDT match USB endpoint {} without a unique bus-range \ - match", - endpoint.address() - ))); - }; - - let irq = pci_host - .child_interrupts( - endpoint.address().bus(), - endpoint.address().device(), - endpoint.address().function(), - pin, - ) - .map_err(|err| { - USBError::Other(anyhow::anyhow!( - "failed to resolve PCI interrupt-map entry for USB endpoint {}: {err:?}", - endpoint.address() - )) - })?; - - decode_irq_cells(&irq.irqs).ok_or_else(|| { - USBError::Other(anyhow::anyhow!( - "unsupported PCI interrupt specifier {:?} for USB endpoint {}", - irq.irqs, - endpoint.address() - )) - }) -} - -pub fn usb_host_device() -> Option { - rdrive::get_one() -} diff --git a/platform/axplat-dyn/src/drivers/usb/xhci_mmio.rs b/platform/axplat-dyn/src/drivers/usb/xhci_mmio.rs deleted file mode 100644 index 658dbe48fb..0000000000 --- a/platform/axplat-dyn/src/drivers/usb/xhci_mmio.rs +++ /dev/null @@ -1,54 +0,0 @@ -extern crate alloc; - -use alloc::format; - -use rdrive::{PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo}; - -use super::{PlatformDeviceUsbHost, USB_KERNEL, decode_fdt_irq}; -use crate::drivers::iomap; - -const DRIVER_NAME: &str = "usb-xhci-mmio"; - -module_driver!( - name: "USB xHCI MMIO", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ - ProbeKind::Fdt { - compatibles: &["generic-xhci", "xhci-platform"], - on_probe: probe - } - ], -); - -fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { - let base_reg = info - .node - .regs() - .into_iter() - .next() - .ok_or(OnProbeError::other(alloc::format!( - "[{}] has no reg", - info.node.name() - )))?; - - let mmio_size = base_reg.size.unwrap_or(0x1000) as usize; - let mmio = iomap((base_reg.address as usize).into(), mmio_size)?; - let interrupts = info.interrupts(); - let irq_num = decode_fdt_irq(&interrupts); - - let host = crab_usb::USBHost::new_xhci(mmio, &USB_KERNEL).map_err(|err| { - OnProbeError::other(format!( - "failed to create xHCI host for [{}]: {err}", - info.node.name() - )) - })?; - - plat_dev.register_usb_host(DRIVER_NAME, host, irq_num); - info!( - "xHCI MMIO host registered successfully for {} with irq {:?}", - info.node.name(), - irq_num - ); - Ok(()) -} diff --git a/platform/axplat-dyn/src/drivers/virtio.rs b/platform/axplat-dyn/src/drivers/virtio.rs deleted file mode 100644 index 9ecaacfb2a..0000000000 --- a/platform/axplat-dyn/src/drivers/virtio.rs +++ /dev/null @@ -1,42 +0,0 @@ -use core::{marker::PhantomData, ptr::NonNull}; - -use ax_alloc::{UsageKind, global_allocator}; -use ax_driver_virtio::{BufferDirection, PhysAddr as VirtIoPhysAddr, VirtIoHal}; - -use crate::drivers::iomap; - -pub(crate) struct VirtIoHalImpl(PhantomData<()>); - -unsafe impl VirtIoHal for VirtIoHalImpl { - fn dma_alloc(pages: usize, _direction: BufferDirection) -> (VirtIoPhysAddr, NonNull) { - let vaddr = if let Ok(vaddr) = global_allocator().alloc_pages(pages, 0x1000, UsageKind::Dma) - { - vaddr - } else { - return (0, NonNull::dangling()); - }; - let paddr = somehal::mem::virt_to_phys(vaddr as *mut u8) as VirtIoPhysAddr; - let ptr = NonNull::new(vaddr as _).unwrap(); - (paddr, ptr) - } - - unsafe fn dma_dealloc(_paddr: VirtIoPhysAddr, vaddr: NonNull, pages: usize) -> i32 { - global_allocator().dealloc_pages(vaddr.as_ptr() as usize, pages, UsageKind::Dma); - 0 - } - - #[inline] - unsafe fn mmio_phys_to_virt(paddr: VirtIoPhysAddr, size: usize) -> NonNull { - iomap((paddr as usize).into(), size).expect("failed to map virtio MMIO region") - } - - #[inline] - unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> VirtIoPhysAddr { - let vaddr = buffer.as_ptr() as *mut u8 as usize; - somehal::mem::virt_to_phys(vaddr as *mut u8) as VirtIoPhysAddr - } - - #[inline] - unsafe fn unshare(_paddr: VirtIoPhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { - } -} diff --git a/platform/axplat-dyn/src/generic_timer.rs b/platform/axplat-dyn/src/generic_timer.rs index 534c53dfa8..b0881cc7c9 100644 --- a/platform/axplat-dyn/src/generic_timer.rs +++ b/platform/axplat-dyn/src/generic_timer.rs @@ -25,7 +25,7 @@ pub(crate) fn nanos_to_ticks(nanos: u64) -> u64 { (nanos * freq) / ax_plat::time::NANOS_PER_SEC } -pub(crate) fn try_init_epoch_offset(epoch_time_nanos: u64) -> bool { +pub fn try_init_epoch_offset(epoch_time_nanos: u64) -> bool { let boot_offset = epoch_time_nanos.saturating_sub(ticks_to_nanos(current_ticks())); EPOCH_OFFSET_NANOS .compare_exchange( diff --git a/platform/axplat-dyn/src/lib.rs b/platform/axplat-dyn/src/lib.rs index a59c6f91ea..8d8614d897 100644 --- a/platform/axplat-dyn/src/lib.rs +++ b/platform/axplat-dyn/src/lib.rs @@ -3,6 +3,7 @@ #![feature(used_with_arg)] extern crate alloc; +extern crate ax_driver as _; extern crate somehal; #[macro_use] @@ -20,14 +21,13 @@ mod init; mod irq; mod mem; mod power; -#[cfg(feature = "rknpu")] -pub mod rknpu; #[cfg(not(feature = "irq"))] #[somehal::irq_handler] fn somehal_handle_irq(_irq: somehal::irq::IrqId) {} pub use boot::boot_stack_bounds; +pub use generic_timer::try_init_epoch_offset; // pub mod config { // //! Platform configuration module. diff --git a/platform/axplat-dyn/src/rknpu.rs b/platform/axplat-dyn/src/rknpu.rs deleted file mode 100644 index 493ec3e75d..0000000000 --- a/platform/axplat-dyn/src/rknpu.rs +++ /dev/null @@ -1,54 +0,0 @@ -pub use rockchip_npu::{ - RknpuAction, - ioctrl::{RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit}, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Error { - NotFound, - Busy, - InvalidData, -} - -pub fn is_available() -> bool { - rdrive::get_one::().is_some() -} - -pub fn obj_addr_and_size(handle: u32) -> Result<(usize, usize), Error> { - with_npu(|npu| npu.get_obj_addr_and_size(handle).ok_or(Error::NotFound)) -} - -pub fn submit(args: &mut RknpuSubmit) -> Result<(), Error> { - with_npu(|npu| npu.submit_ioctrl(args).map_err(|_| Error::InvalidData)) -} - -pub fn mem_create(args: &mut RknpuMemCreate) -> Result<(), Error> { - with_npu(|npu| npu.create(args).map_err(|_| Error::InvalidData)) -} - -pub fn mem_sync(args: &mut RknpuMemSync) -> Result<(), Error> { - with_npu(|npu| npu.mem_sync(args).map_err(|_| Error::InvalidData)) -} - -pub fn mem_map_offset(handle: u32) -> Result { - with_npu(|npu| { - npu.get_phys_addr_and_size(handle) - .map(|_| (handle as u64) << 12) - .ok_or(Error::InvalidData) - }) -} - -pub fn action(flags: RknpuAction) -> Result { - with_npu(|npu| npu.action(flags).map_err(|_| Error::InvalidData)) -} - -fn with_npu(f: F) -> Result -where - F: FnOnce(&mut rockchip_npu::Rknpu) -> Result, -{ - let mut npu = rdrive::get_one::() - .ok_or(Error::NotFound)? - .try_lock() - .map_err(|_| Error::Busy)?; - f(&mut npu) -} diff --git a/platform/riscv64-visionfive2/Cargo.toml b/platform/riscv64-visionfive2/Cargo.toml index 5c2e2a562c..7caf1b9d25 100644 --- a/platform/riscv64-visionfive2/Cargo.toml +++ b/platform/riscv64-visionfive2/Cargo.toml @@ -21,6 +21,7 @@ ax-cpu = { workspace = true } ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } ax-plat = { workspace = true } +rdrive.workspace = true ax-riscv-plic = { workspace = true, optional = true } log = "0.4" riscv = "0.14" diff --git a/platform/riscv64-visionfive2/README.md b/platform/riscv64-visionfive2/README.md index 4732935a0f..02d0e39b76 100644 --- a/platform/riscv64-visionfive2/README.md +++ b/platform/riscv64-visionfive2/README.md @@ -74,4 +74,4 @@ TODO: 初始化分区表、OpenSBI 等 2. 启动:最初我们在 U-Boot 中使用 booti 指令启动,因此伪装了 [Linux 启动镜像文件头](https://www.kernel.org/doc/html/v5.8/riscv/boot-image-header.html),即代码 `boot.rs` 中 `.ascii \"MZ\"` 这一段。后来我们才发现可以直接用 `go` 指令更方便地直接进行跳转,不过这一段文件头因为可以兼容两种启动方式就保留了下来。 3. CPU 配置:VIsionFive 2 所使用的 JH7110 处理器有四个 64 位 RISC-V CPU(支持 rv64gc,编号为1-4)和一个 32 位 RISC-V CPU(支持rv32imfc,编号为0),因此 U-Boot 不会在 0 号核上启动,ArceOS 也无法在它上面运行。然而 ArceOS 许多设计都假定 cpuid 从 0 开始,我们把 cpu id 从原始的 1-4 映射到 0-3 来解决这个问题。 -4. 存储设备驱动:[Simple SD/MMC Driver](https://github.com/Starry-OS/simple-sdmmc) +4. 存储设备驱动:当前平台尚未接入新的 `sdmmc-protocol`/host 驱动栈。 diff --git a/platform/riscv64-visionfive2/src/drivers.rs b/platform/riscv64-visionfive2/src/drivers.rs new file mode 100644 index 0000000000..6272be1d86 --- /dev/null +++ b/platform/riscv64-visionfive2/src/drivers.rs @@ -0,0 +1,13 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::StaticDeviceDesc; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/platform/riscv64-visionfive2/src/lib.rs b/platform/riscv64-visionfive2/src/lib.rs index a89f931077..b69fcdca4f 100644 --- a/platform/riscv64-visionfive2/src/lib.rs +++ b/platform/riscv64-visionfive2/src/lib.rs @@ -8,6 +8,7 @@ extern crate ax_plat; mod boot; mod console; +mod drivers; mod init; #[cfg(feature = "irq")] mod irq; diff --git a/platform/somehal/link.ld b/platform/somehal/link.ld index 0e99493660..52466260cc 100644 --- a/platform/somehal/link.ld +++ b/platform/somehal/link.ld @@ -11,8 +11,8 @@ PROVIDE(__somehal_secondary = __somehal_secondary_default); SECTIONS { .driver : ALIGN(4K) { - _sdriver = .; - KEEP(*(.driver.register)) - _edriver = .; + __sdriver_register = .; + KEEP(*(.driver.register*)) + __edriver_register = .; } } INSERT AFTER .data; diff --git a/platform/somehal/src/driver.rs b/platform/somehal/src/driver.rs index 9360ca658b..7cc021f467 100644 --- a/platform/somehal/src/driver.rs +++ b/platform/somehal/src/driver.rs @@ -1,33 +1,11 @@ -use core::ptr::{NonNull, slice_from_raw_parts}; - -use rdrive::register::DriverRegisterSlice; +use core::ptr::NonNull; pub fn rdrive_setup() { - let registers = DriverRegisterSlice::from_raw(driver_registers()); - if let Some(addr) = someboot::fdt_addr() { info!("Initializing rdrive with FDT at {:?}", addr); rdrive::init(rdrive::Platform::Fdt { addr: NonNull::new(addr).unwrap(), }) .unwrap(); - - rdrive::register_append(®isters); - - rdrive::probe_pre_kernel().unwrap(); - } -} - -fn driver_registers() -> &'static [u8] { - unsafe extern "C" { - fn _sdriver(); - fn _edriver(); - } - - unsafe { - &*slice_from_raw_parts( - _sdriver as *const () as *const u8, - _edriver as *const () as usize - _sdriver as *const () as usize, - ) } } diff --git a/platform/x86-qemu-q35/Cargo.toml b/platform/x86-qemu-q35/Cargo.toml index 10f1a16d25..8dffeb7e82 100644 --- a/platform/x86-qemu-q35/Cargo.toml +++ b/platform/x86-qemu-q35/Cargo.toml @@ -34,6 +34,7 @@ ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } log = "0.4" ax-percpu.workspace = true +rdrive.workspace = true multiboot = "0.8" raw-cpuid = "11.5" diff --git a/platform/x86-qemu-q35/src/drivers.rs b/platform/x86-qemu-q35/src/drivers.rs new file mode 100644 index 0000000000..19df95039d --- /dev/null +++ b/platform/x86-qemu-q35/src/drivers.rs @@ -0,0 +1,18 @@ +use ax_plat::drivers::DriversIf; +use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; + +use crate::config::devices; + +const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; + +static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("pci-ecam") + .with_pci_ecam(StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE))]; + +struct DriversIfImpl; + +#[impl_plat_interface] +impl DriversIf for DriversIfImpl { + fn static_devices_fn() -> &'static [StaticDeviceDesc] { + STATIC_DEVICES + } +} diff --git a/platform/x86-qemu-q35/src/lib.rs b/platform/x86-qemu-q35/src/lib.rs index 6afa877c9c..3bdb4d1b96 100644 --- a/platform/x86-qemu-q35/src/lib.rs +++ b/platform/x86-qemu-q35/src/lib.rs @@ -24,6 +24,7 @@ extern crate ax_plat; mod apic; mod boot; mod console; +mod drivers; mod init; mod mem; mod power; @@ -40,6 +41,8 @@ pub mod config { pub mod devices { pub const TIMER_FREQUENCY: usize = 4_000_000_000; // 100 MHz + pub const PCI_ECAM_BASE: usize = 0xb000_0000; + pub const PCI_BUS_END: usize = 0xff; } } diff --git a/scripts/axbuild/src/arceos/build.rs b/scripts/axbuild/src/arceos/build.rs index 6961307dfd..46857f4653 100644 --- a/scripts/axbuild/src/arceos/build.rs +++ b/scripts/axbuild/src/arceos/build.rs @@ -179,9 +179,10 @@ mod tests { #[test] fn resolves_dynamic_platform_features_and_args() { let mut build_info = ArceosBuildInfo::default_for_target("aarch64-unknown-none-softfloat"); - build_info.resolve_features("ax-helloworld", true); + build_info.resolve_features("ax-helloworld", "aarch64-unknown-none-softfloat", true); assert!(build_info.features.contains(&"ax-std/plat-dyn".to_string())); + assert!(build_info.features.contains(&"ax-hal/plat-dyn".to_string())); assert!(!build_info.features.contains(&"ax-std/defplat".to_string())); let args = ArceosBuildInfo::build_cargo_args( @@ -201,9 +202,13 @@ mod tests { #[test] fn resolves_non_dynamic_platform_features_and_args() { let mut build_info = ArceosBuildInfo::default_for_target("aarch64-unknown-none-softfloat"); - build_info.resolve_features("ax-helloworld", false); + build_info.resolve_features("ax-helloworld", "aarch64-unknown-none-softfloat", false); - assert!(build_info.features.contains(&"ax-std/defplat".to_string())); + assert!( + build_info + .features + .contains(&"ax-hal/aarch64-qemu-virt".to_string()) + ); assert!(!build_info.features.contains(&"ax-std/plat-dyn".to_string())); let args = ArceosBuildInfo::build_cargo_args( @@ -229,7 +234,12 @@ mod tests { ..ArceosBuildInfo::default() }; - build_info.resolve_features_with_metadata("starryos", false, &metadata); + build_info.resolve_features_with_metadata( + "starryos", + "aarch64-unknown-none-softfloat", + false, + &metadata, + ); assert!(build_info.features.contains(&"ax-feat/smp".to_string())); } diff --git a/scripts/axbuild/src/arceos/cbuild.rs b/scripts/axbuild/src/arceos/cbuild.rs index 323c0333ef..a432758f7f 100644 --- a/scripts/axbuild/src/arceos/cbuild.rs +++ b/scripts/axbuild/src/arceos/cbuild.rs @@ -193,6 +193,9 @@ fn c_config_features(features: &[String]) -> BTreeSet { features .iter() .filter_map(|feature| { + if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") { + return None; + } feature .strip_prefix("ax-libc/") .or_else(|| feature.strip_prefix("ax-feat/")) @@ -203,7 +206,7 @@ fn c_config_features(features: &[String]) -> BTreeSet { !matches!( *feature, "ax-libc" | "ax-feat" | "ax-std" | "defplat" | "myplat" | "plat-dyn" - ) + ) && !feature.contains('/') }) .map(str::to_string) .collect() @@ -307,6 +310,10 @@ fn map_c_app_features(case_features: &[String], base_features: &[String]) -> Vec .or_else(|| feature.strip_prefix("ax-std/")) .or_else(|| feature.strip_prefix("ax-libc/")) .unwrap_or(feature); + if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") { + features.insert(feature.clone()); + continue; + } match normalized { "ax-std" | "ax-feat" | "ax-libc" => {} "defplat" | "myplat" | "plat-dyn" => { @@ -330,6 +337,10 @@ fn map_c_app_features(case_features: &[String], base_features: &[String]) -> Vec .or_else(|| feature.strip_prefix("ax-std/")) .or_else(|| feature.strip_prefix("ax-libc/")) .unwrap_or(feature); + if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") { + features.insert(feature.clone()); + continue; + } if LIB_FEATURES.contains(&normalized) { features.insert(format!("ax-libc/{normalized}")); } else { @@ -394,6 +405,46 @@ fn link_c_app( .with_context(|| format!("failed to link {}", elf_path.display())) } +#[cfg(test)] +mod tests { + use super::*; + + fn strings(items: &[&str]) -> Vec { + items.iter().map(|item| item.to_string()).collect() + } + + #[test] + fn c_config_features_skips_nested_cargo_only_features() { + let features = c_config_features(&strings(&[ + "ax-libc/net", + "ax-feat/paging", + "ax-driver/pci", + "ax-driver/virtio-net", + "ax-hal/riscv64-qemu-virt", + "some-crate/feature", + ])); + + assert_eq!( + features.into_iter().collect::>(), + vec!["net".to_string(), "paging".to_string()] + ); + } + + #[test] + fn map_c_app_features_preserves_driver_features() { + let features = map_c_app_features( + &strings(&["net", "ax-driver/pci", "ax-driver/virtio-net"]), + &strings(&["ax-hal/riscv64-qemu-virt"]), + ); + + assert!(features.contains(&"ax-libc/net".to_string())); + assert!(features.contains(&"ax-libc/fd".to_string())); + assert!(features.contains(&"ax-driver/pci".to_string())); + assert!(features.contains(&"ax-driver/virtio-net".to_string())); + assert!(features.contains(&"ax-hal/riscv64-qemu-virt".to_string())); + } +} + fn lld_machine(arch: &str) -> anyhow::Result<&'static str> { match arch { "aarch64" => Ok("aarch64elf"), diff --git a/scripts/axbuild/src/axvisor/board.rs b/scripts/axbuild/src/axvisor/board.rs index d82d4f95d9..7f03fdb8c9 100644 --- a/scripts/axbuild/src/axvisor/board.rs +++ b/scripts/axbuild/src/axvisor/board.rs @@ -128,7 +128,7 @@ log = "Info" r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["fs", "sdmmc"] +features = ["fs", "ax-driver/rockchip-sdhci"] log = "Info" plat_dyn = true "#, @@ -169,7 +169,7 @@ log = "Info" r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["rockchip-soc"] +features = ["ax-driver/rockchip-soc"] log = "Info" plat_dyn = true "#, diff --git a/scripts/axbuild/src/axvisor/build.rs b/scripts/axbuild/src/axvisor/build.rs index 6c6742d8eb..f09c126e01 100644 --- a/scripts/axbuild/src/axvisor/build.rs +++ b/scripts/axbuild/src/axvisor/build.rs @@ -125,10 +125,6 @@ fn to_cargo_config( metadata: &cargo_metadata::Metadata, ) -> anyhow::Result { config.target = request.target.clone(); - let plat_dyn = config - .build_info - .effective_plat_dyn(&config.target, request.plat_dyn); - normalize_axvisor_platform_features(&mut config.build_info.features, plat_dyn); let mut cargo = config .build_info .into_prepared_base_cargo_config_with_metadata( @@ -173,8 +169,6 @@ fn patch_axvisor_cargo_config( ); } - let cargo_uses_plat_dyn = cargo.features.iter().any(|f| f == "ax-std/plat-dyn"); - normalize_axvisor_platform_features(&mut cargo.features, cargo_uses_plat_dyn); if request.arch == "x86_64" { x86::normalize_backend_features(&mut cargo.features)?; } @@ -208,29 +202,6 @@ fn ensure_axvisor_bin_arg(args: &mut Vec) { args.push(AXVISOR_PACKAGE.to_string()); } -fn normalize_axvisor_platform_features(features: &mut Vec, plat_dyn: bool) { - let has_axstd_defplat = features.iter().any(|feature| feature == "ax-std/defplat"); - let has_axstd_myplat = features.iter().any(|feature| feature == "ax-std/myplat"); - let has_axstd_plat_dyn = features.iter().any(|feature| feature == "ax-std/plat-dyn"); - - if has_axstd_defplat && !has_axstd_myplat { - for feature in features.iter_mut() { - if feature == "ax-std/defplat" { - *feature = "ax-std/myplat".to_string(); - } - } - } else { - features.retain(|feature| feature != "ax-std/defplat"); - } - - if !plat_dyn - && !has_axstd_plat_dyn - && !features.iter().any(|feature| feature == "ax-std/myplat") - { - features.push("ax-std/myplat".to_string()); - } -} - pub(crate) fn load_target_from_build_config(path: &Path) -> anyhow::Result> { let content = fs::read_to_string(path).map_err(|e| { anyhow!( @@ -427,7 +398,7 @@ mod tests { r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["ept-level-4", "ax-std/bus-mmio"] +features = ["ept-level-4", "ax-driver/fdt"] log = "Info" plat_dyn = true vm_configs = [] @@ -442,7 +413,7 @@ vm_configs = [] .unwrap(); assert!(cargo.features.contains(&"ept-level-4".to_string())); - assert!(cargo.features.contains(&"ax-std/bus-mmio".to_string())); + assert!(cargo.features.contains(&"ax-driver/fdt".to_string())); assert!(path.exists()); } @@ -544,7 +515,7 @@ vm_configs = [] r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "x86_64-unknown-none" -features = ["ept-level-4", "fs", "vmx"] +features = ["ax-hal/x86-qemu-q35", "ept-level-4", "fs", "vmx"] log = "Info" vm_configs = [] "#, @@ -575,18 +546,18 @@ vm_configs = [] assert!(cargo.features.contains(&"vmx".to_string())); assert!(!cargo.features.contains(&"ax-std/plat-dyn".to_string())); assert!(!cargo.features.contains(&"ax-std/defplat".to_string())); - assert!(cargo.features.contains(&"ax-std/myplat".to_string())); + assert!(cargo.features.contains(&"ax-hal/x86-qemu-q35".to_string())); } #[test] - fn load_cargo_config_replaces_axstd_defplat_with_myplat() { + fn load_cargo_config_injects_default_axhal_platform() { let root = tempdir().unwrap(); let config_path = root.path().join(".build.toml"); fs::write( &config_path, r#" env = {} -features = ["ax-std", "ax-std/defplat", "ept-level-4"] +features = ["ax-std", "ept-level-4"] log = "Info" plat_dyn = false "#, @@ -609,7 +580,11 @@ plat_dyn = false .unwrap(); assert!(!cargo.features.contains(&"ax-std/defplat".to_string())); - assert!(cargo.features.contains(&"ax-std/myplat".to_string())); + assert!( + cargo + .features + .contains(&"ax-hal/aarch64-qemu-virt".to_string()) + ); assert!( cargo .target @@ -625,7 +600,7 @@ plat_dyn = false &config_path, r#" env = {} -features = ["ept-level-4", "ax-std/bus-mmio"] +features = ["ept-level-4", "ax-driver/fdt"] log = "Info" "#, ) diff --git a/scripts/axbuild/src/axvisor/config.rs b/scripts/axbuild/src/axvisor/config.rs index 65ef84fb02..2144c8fa2c 100644 --- a/scripts/axbuild/src/axvisor/config.rs +++ b/scripts/axbuild/src/axvisor/config.rs @@ -79,7 +79,7 @@ mod tests { r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["fs", "sdmmc"] +features = ["fs", "ax-driver/rockchip-sdhci"] log = "Info" plat_dyn = true vm_configs = [] @@ -151,7 +151,7 @@ plat_dyn = true r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["rockchip-soc"] +features = ["ax-driver/rockchip-soc"] log = "Info" plat_dyn = true "#, diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index 1aea30774f..58abad4fb5 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -202,6 +202,7 @@ impl BuildInfo { ); cargo.env.extend(std_target.env); prepare_std_build_env(&mut cargo.env, target, metadata)?; + pass_std_build_nested_features(&mut cargo.env, &mut cargo.features); cargo.extra_config = Some(std_cargo_config_path()?.display().to_string()); cargo.to_bin = false; return Ok(cargo); @@ -210,7 +211,7 @@ impl BuildInfo { let plat_dyn = self.effective_plat_dyn(target, plat_dyn_override); self.validated_max_cpu_num()?; self.prepare_non_dynamic_platform_for(package, target, plat_dyn, metadata)?; - self.resolve_features_with_metadata(package, plat_dyn, metadata); + self.resolve_features_with_metadata(package, target, plat_dyn, metadata); let mut extra_rustflags = toolchain_rustflags(&self.env); if self.features.iter().any(|f| f == "kcov") { extra_rustflags.push("-Cllvm-args=-sanitizer-coverage-level=3".to_string()); @@ -282,11 +283,13 @@ impl BuildInfo { pub(crate) fn resolve_features_with_metadata( &mut self, package: &str, + target: &str, plat_dyn: bool, metadata: &Metadata, ) { self.resolve_features_with_prefix_family( package, + target, plat_dyn, detect_ax_feature_prefix_family(package, metadata), ); @@ -295,16 +298,13 @@ impl BuildInfo { fn resolve_features_with_prefix_family( &mut self, package: &str, + target: &str, plat_dyn: bool, prefix_family: anyhow::Result, ) { let prefix_family = self.resolve_ax_feature_prefix_family(package, prefix_family); - let has_myplat = self.features.iter().any(|feature| { - matches!( - feature.as_str(), - "myplat" | "ax-std/myplat" | "ax-feat/myplat" - ) - }); + let has_myplat = has_myplat_feature(&self.features); + let has_defplat = has_defplat_feature(&self.features); self.features.retain(|feature| { !matches!( @@ -327,7 +327,7 @@ impl BuildInfo { } else if has_myplat { self.features .push(format!("{}myplat", prefix_family.prefix())); - } else { + } else if has_defplat { self.features .push(format!("{}defplat", prefix_family.prefix())); } @@ -335,11 +335,25 @@ impl BuildInfo { if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) { self.features.push(format!("{}smp", prefix_family.prefix())); } + self.push_platform_feature(target, plat_dyn, has_myplat); self.features.sort(); self.features.dedup(); } + fn push_platform_feature(&mut self, target: &str, plat_dyn: bool, has_myplat: bool) { + if has_myplat || has_ax_hal_platform_feature(&self.features) { + return; + } + + let feature = if plat_dyn { + "ax-hal/plat-dyn" + } else { + default_ax_hal_platform_feature(target).unwrap_or("ax-hal/defplat") + }; + self.features.push(feature.to_string()); + } + fn resolve_ax_feature_prefix_family( &self, package: &str, @@ -381,11 +395,14 @@ impl BuildInfo { } #[cfg(test)] - pub(crate) fn resolve_features(&mut self, package: &str, plat_dyn: bool) { + pub(crate) fn resolve_features(&mut self, package: &str, target: &str, plat_dyn: bool) { match workspace_metadata() { - Ok(metadata) => self.resolve_features_with_metadata(package, plat_dyn, &metadata), + Ok(metadata) => { + self.resolve_features_with_metadata(package, target, plat_dyn, &metadata) + } Err(err) => self.resolve_features_with_prefix_family( package, + target, plat_dyn, Err(err.context("failed to load workspace metadata")), ), @@ -536,6 +553,22 @@ pub(crate) fn prepare_std_build_env( Ok(()) } +fn pass_std_build_nested_features(envs: &mut HashMap, features: &mut Vec) { + let mut nested = Vec::new(); + features.retain(|feature| { + if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") { + nested.push(feature.clone()); + false + } else { + true + } + }); + if nested.is_empty() { + return; + } + envs.insert("ARCEOS_RUST_FEATURES".to_string(), nested.join(",")); +} + fn std_cargo_config_path() -> anyhow::Result { let path = std_build_dir()?.join("config.toml"); write_if_changed( @@ -655,6 +688,9 @@ fn normalize_std_feature(feature: &str) -> String { .split_once('/') .map(|(_, feature)| format!("arceos-rust/{feature}")) .unwrap_or_else(|| normalized.clone()), + feature if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") => { + normalized + } feature if feature.starts_with("arceos-rust/") => normalized, feature => format!("arceos-rust/{feature}"), } @@ -863,6 +899,76 @@ fn detect_ax_feature_prefix_family( } } +fn has_myplat_feature(features: &[String]) -> bool { + features.iter().any(|feature| { + matches!( + feature.as_str(), + "myplat" | "ax-std/myplat" | "ax-feat/myplat" | "ax-hal/myplat" + ) + }) +} + +fn has_defplat_feature(features: &[String]) -> bool { + features.iter().any(|feature| { + matches!( + feature.as_str(), + "defplat" | "ax-std/defplat" | "ax-feat/defplat" | "ax-hal/defplat" + ) + }) +} + +fn ax_hal_platform_feature_name(feature: &str) -> Option<&str> { + let platform = feature.strip_prefix("ax-hal/")?; + match platform { + "plat-dyn" + | "x86-pc" + | "aarch64-qemu-virt" + | "aarch64-raspi" + | "aarch64-bsta1000b" + | "aarch64-phytium-pi" + | "riscv64-qemu-virt" + | "riscv64-sg2002" + | "riscv64-visionfive2" + | "riscv64-qemu-virt-hv" + | "loongarch64-qemu-virt" + | "x86-qemu-q35" => Some(platform), + _ => None, + } +} + +fn has_ax_hal_platform_feature(features: &[String]) -> bool { + features + .iter() + .any(|feature| ax_hal_platform_feature_name(feature).is_some()) +} + +fn default_ax_hal_platform_feature(target: &str) -> anyhow::Result<&'static str> { + Ok(match target_arch_name(target)? { + "x86_64" => "ax-hal/x86-pc", + "aarch64" => "ax-hal/aarch64-qemu-virt", + "riscv64" => "ax-hal/riscv64-qemu-virt", + "loongarch64" => "ax-hal/loongarch64-qemu-virt", + _ => unreachable!("unsupported arch"), + }) +} + +fn ax_hal_platform_package(platform: &str) -> Option<&'static str> { + match platform { + "x86-pc" => Some("ax-plat-x86-pc"), + "aarch64-qemu-virt" => Some("ax-plat-aarch64-qemu-virt"), + "aarch64-raspi" => Some("ax-plat-aarch64-raspi"), + "aarch64-bsta1000b" => Some("ax-plat-aarch64-bsta1000b"), + "aarch64-phytium-pi" => Some("ax-plat-aarch64-phytium-pi"), + "riscv64-qemu-virt" => Some("ax-plat-riscv64-qemu-virt"), + "riscv64-sg2002" => Some("ax-plat-riscv64-sg2002"), + "riscv64-visionfive2" => Some("axplat-riscv64-visionfive2"), + "riscv64-qemu-virt-hv" => Some("ax-plat-riscv64-qemu-virt"), + "loongarch64-qemu-virt" => Some("ax-plat-loongarch64-qemu-virt"), + "x86-qemu-q35" => Some("axplat-x86-qemu-q35"), + _ => None, + } +} + fn resolve_platform_package( package: &str, target: &str, @@ -872,6 +978,14 @@ fn resolve_platform_package( let arch = target_arch_name(target)?; let package_info = workspace_package(metadata, package)?; + if let Some(platform) = features + .iter() + .find_map(|feature| ax_hal_platform_feature_name(feature)) + .and_then(ax_hal_platform_package) + { + return Ok(platform.to_string()); + } + let explicit_platform_features: Vec<_> = features .iter() .map(|feature| { @@ -888,21 +1002,13 @@ fn resolve_platform_package( }) .collect(); - if let Some(dep) = package_info.dependencies.iter().find(|dep| { - (dep.name.starts_with("axplat-") || dep.name.starts_with("ax-plat-")) - && explicit_platform_features - .iter() - .any(|feature| *feature == linker_platform_name(&dep.name)) - }) { - return Ok(dep.name.clone()); + if let Some(platform) = + explicit_platform_package_from_features(package_info, &explicit_platform_features) + { + return Ok(platform); } - if features.iter().any(|feature| { - matches!( - feature.as_str(), - "myplat" | "ax-std/myplat" | "ax-feat/myplat" - ) - }) { + if has_myplat_feature(features) { if let Some(dep_name) = explicit_myplat_platform_package(package, arch) && package_info .dependencies @@ -956,6 +1062,36 @@ fn explicit_myplat_platform_package(package: &str, arch: &str) -> Option<&'stati } } +fn explicit_platform_package_from_features( + package_info: &Package, + explicit_features: &[&str], +) -> Option { + package_info + .dependencies + .iter() + .find(|dep| { + dependency_is_platform(&dep.name) + && explicit_features.iter().any(|feature| { + *feature == dep.name + || *feature == linker_platform_name(&dep.name) + || feature_enables_dependency(package_info, feature, &dep.name) + }) + }) + .map(|dep| dep.name.clone()) +} + +fn dependency_is_platform(dep_name: &str) -> bool { + dep_name.starts_with("axplat-") || dep_name.starts_with("ax-plat-") +} + +fn feature_enables_dependency(package_info: &Package, feature: &str, dep_name: &str) -> bool { + package_info.features.get(feature).is_some_and(|items| { + items + .iter() + .any(|item| item == dep_name || item == &format!("dep:{dep_name}")) + }) +} + fn myplat_dependency_matches_arch(dep_name: &str, arch: &str) -> bool { myplat_dependency_prefixes_for_arch(arch) .iter() @@ -1228,6 +1364,51 @@ mod tests { ); } + #[test] + fn std_build_nested_features_are_passed_through_not_enabled_on_app() { + let mut envs = HashMap::new(); + let mut features = vec![ + "ax-hal/riscv64-qemu-virt".to_string(), + "ax-driver/pci".to_string(), + "ax-driver/virtio-blk".to_string(), + "ax-driver/virtio-net".to_string(), + "dns".to_string(), + ]; + + pass_std_build_nested_features(&mut envs, &mut features); + + assert_eq!(features, vec!["dns".to_string()]); + assert_eq!( + envs.get("ARCEOS_RUST_FEATURES"), + Some( + &"ax-hal/riscv64-qemu-virt,ax-driver/pci,ax-driver/virtio-blk,ax-driver/virtio-net" + .to_string() + ) + ); + } + + #[test] + fn std_build_runtime_features_are_passed_through_after_normalization() { + let mut info = BuildInfo { + std_build: true, + features: vec![ + "ax-hal/loongarch64-qemu-virt".to_string(), + "dns".to_string(), + ], + ..BuildInfo::default() + }; + + info.resolve_std_features(); + let mut envs = HashMap::new(); + pass_std_build_nested_features(&mut envs, &mut info.features); + + assert_eq!(info.features, vec!["arceos-rust/dns".to_string()]); + assert_eq!( + envs.get("ARCEOS_RUST_FEATURES"), + Some(&"ax-hal/loongarch64-qemu-virt".to_string()) + ); + } + #[test] fn cargo_target_json_path_maps_no_pie_targets() { let cases = [ diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index c0efb77f53..bdf37b129a 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -150,7 +150,7 @@ fn remove_qemu_feature_for_dynamic_platform(cargo: &mut Cargo) { let uses_dynamic_platform = cargo.features.iter().any(|feature| { matches!( feature.as_str(), - "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" + "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" | "ax-hal/plat-dyn" ) }); if uses_dynamic_platform { @@ -159,26 +159,28 @@ fn remove_qemu_feature_for_dynamic_platform(cargo: &mut Cargo) { } fn uses_static_default_platform(features: &[String]) -> bool { - let has_defplat = features.iter().any(|feature| { + let has_static_platform = features.iter().any(|feature| { matches!( feature.as_str(), "defplat" | "ax-feat/defplat" | "ax-std/defplat" ) + }) || features.iter().any(|feature| { + feature.starts_with("ax-hal/") && feature != "ax-hal/plat-dyn" && feature != "ax-hal/myplat" }); let has_dynamic = features.iter().any(|feature| { matches!( feature.as_str(), - "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" + "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" | "ax-hal/plat-dyn" ) }); let has_custom = features.iter().any(|feature| { matches!( feature.as_str(), - "myplat" | "ax-feat/myplat" | "ax-std/myplat" + "myplat" | "ax-feat/myplat" | "ax-std/myplat" | "ax-hal/myplat" ) }); - has_defplat && !has_dynamic && !has_custom + has_static_platform && !has_dynamic && !has_custom } fn ensure_starry_bin_arg( @@ -465,11 +467,9 @@ HELLO = "world" env: HashMap::new(), features: vec![ "common".to_string(), - "ax-feat/bus-mmio".to_string(), - "ax-feat/driver-sdmmc".to_string(), "ax-feat/plat-dyn".to_string(), - "axplat-dyn/rockchip-soc".to_string(), - "axplat-dyn/rockchip-sdhci".to_string(), + "ax-driver/rockchip-soc".to_string(), + "ax-driver/rockchip-sdhci".to_string(), ], log: LogLevel::Info, max_cpu_num: Some(8), @@ -492,12 +492,12 @@ HELLO = "world" assert!( cargo .features - .contains(&"axplat-dyn/rockchip-soc".to_string()) + .contains(&"ax-driver/rockchip-soc".to_string()) ); assert!( cargo .features - .contains(&"axplat-dyn/rockchip-sdhci".to_string()) + .contains(&"ax-driver/rockchip-sdhci".to_string()) ); assert!(!cargo.features.contains(&"qemu".to_string())); assert!(!cargo.env.contains_key("AX_PLATFORM")); diff --git a/scripts/repo/repo.py b/scripts/repo/repo.py index 259fe0fd5c..c5d9251d63 100755 --- a/scripts/repo/repo.py +++ b/scripts/repo/repo.py @@ -494,18 +494,6 @@ def push_subtree(self, url: str, target_dir: str, branch: str = "", force: bool url, branch, ] - # axdriver_crates has duplicate subtree join trailers in the shared - # history. `git subtree push` scans those trailers before splitting and - # can fail with "cache for already exists!" unless we bypass join - # discovery via --ignore-joins. - use_ignore_joins = repo_name == 'axdriver_crates' - if use_ignore_joins: - print( - f"Using --ignore-joins for {repo_name} to avoid duplicate subtree history conflicts.", - flush=True, - ) - push_args.insert(1, '--ignore-joins') - cmd = self._git_subtree_cmd('push', push_args) try: self._run_command(cmd, env=subtree_env) @@ -974,9 +962,7 @@ def main() -> int: description=( "Push local subtree changes to the configured remote branch.\n\n" "Notes:\n" - " - axdriver_crates is pushed with --ignore-joins by default to avoid\n" - " duplicate subtree history conflicts.\n" - " - Other repositories automatically retry with --ignore-joins if\n" + " - Repositories automatically retry with --ignore-joins if\n" " git-subtree reports the known 'cache for already exists!'\n" " error while scanning prior subtree joins." ), diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index 8beb7efaf0..6db251cec6 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -38,7 +38,6 @@ https://github.com/arceos-org/axmm_crates,,components/axmm_crates,ArceOS, https://github.com/arceos-org/page_table_multiarch,,components/page_table_multiarch,ArceOS, https://github.com/arceos-org/percpu,dev,components/percpu,ArceOS, https://github.com/arceos-org/timer_list,,components/timer_list,ArceOS, -https://github.com/arceos-org/axdriver_crates,dev,components/axdriver_crates,ArceOS, https://github.com/arceos-org/axplat_crates,dev,components/axplat_crates,ArceOS, https://github.com/arceos-org/ax-int-ratio,,components/int_ratio,ArceOS, https://github.com/arceos-org/cap_access,,components/cap_access,ArceOS, diff --git a/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml index feffa247a3..df8bbaf350 100644 --- a/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net"] +features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml index feffa247a3..df8bbaf350 100644 --- a/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net"] +features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml index feffa247a3..df8bbaf350 100644 --- a/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net"] +features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml b/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml index feffa247a3..df8bbaf350 100644 --- a/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net"] +features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/rust/backtrace-raw-badfp/Cargo.toml b/test-suit/arceos/rust/backtrace-raw-badfp/Cargo.toml index 3d6d050c18..f245d68bf8 100644 --- a/test-suit/arceos/rust/backtrace-raw-badfp/Cargo.toml +++ b/test-suit/arceos/rust/backtrace-raw-badfp/Cargo.toml @@ -8,5 +8,7 @@ publish = false ax-std = ["dep:ax-std"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, optional = true, features = ["paging", "backtrace"] } axbacktrace = { workspace = true } diff --git a/test-suit/arceos/rust/backtrace-raw-basic/Cargo.toml b/test-suit/arceos/rust/backtrace-raw-basic/Cargo.toml index 0789077e1c..dae7f089bd 100644 --- a/test-suit/arceos/rust/backtrace-raw-basic/Cargo.toml +++ b/test-suit/arceos/rust/backtrace-raw-basic/Cargo.toml @@ -9,5 +9,7 @@ ax-std = ["dep:ax-std"] panic-path = [] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, optional = true, features = ["paging", "backtrace"] } axbacktrace = { workspace = true } diff --git a/test-suit/arceos/rust/backtrace-raw-normal/Cargo.toml b/test-suit/arceos/rust/backtrace-raw-normal/Cargo.toml index de5f1aaaf9..4b2f28fc30 100644 --- a/test-suit/arceos/rust/backtrace-raw-normal/Cargo.toml +++ b/test-suit/arceos/rust/backtrace-raw-normal/Cargo.toml @@ -8,5 +8,7 @@ publish = false ax-std = ["dep:ax-std"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, optional = true, features = ["paging", "backtrace"] } axbacktrace = { workspace = true } diff --git a/test-suit/arceos/rust/backtrace/Cargo.toml b/test-suit/arceos/rust/backtrace/Cargo.toml index 2331f627a3..ae490c133f 100644 --- a/test-suit/arceos/rust/backtrace/Cargo.toml +++ b/test-suit/arceos/rust/backtrace/Cargo.toml @@ -8,5 +8,7 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, optional = true } axbacktrace = { workspace = true } diff --git a/test-suit/arceos/rust/display/Cargo.toml b/test-suit/arceos/rust/display/Cargo.toml index 75b6ac620f..ac4c0b45d3 100644 --- a/test-suit/arceos/rust/display/Cargo.toml +++ b/test-suit/arceos/rust/display/Cargo.toml @@ -8,5 +8,7 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["display"], optional = true } embedded-graphics = "0.8" diff --git a/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml index 63f6e5549d..eda3a4a847 100644 --- a/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml index 63f6e5549d..92f3749d99 100644 --- a/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml index 63f6e5549d..d490390524 100644 --- a/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml index 63f6e5549d..4a36bd895c 100644 --- a/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/exception/Cargo.toml b/test-suit/arceos/rust/exception/Cargo.toml index 936369da40..ddf4dcffd5 100644 --- a/test-suit/arceos/rust/exception/Cargo.toml +++ b/test-suit/arceos/rust/exception/Cargo.toml @@ -11,4 +11,6 @@ publish = false ax-std = ["dep:ax-std"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, optional = true, features = ["paging"] } diff --git a/test-suit/arceos/rust/fs/shell/Cargo.toml b/test-suit/arceos/rust/fs/shell/Cargo.toml index d08ed9e994..793ac84397 100644 --- a/test-suit/arceos/rust/fs/shell/Cargo.toml +++ b/test-suit/arceos/rust/fs/shell/Cargo.toml @@ -15,4 +15,6 @@ default = [] ax-fs-vfs = { workspace = true, optional = true } ax-fs-ramfs = { workspace = true, optional = true } ax-crate-interface = { workspace = true, optional = true } +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "fs"], optional = true } diff --git a/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml index 9c82feb368..928217c53f 100644 --- a/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-std/bus-mmio"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml index 63f6e5549d..3f3e37f2da 100644 --- a/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml index 63f6e5549d..1f6cd09f11 100644 --- a/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml index 63f6e5549d..9b12da4c8d 100644 --- a/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/memtest/Cargo.toml b/test-suit/arceos/rust/memtest/Cargo.toml index 6ef9fec589..6f287d1493 100644 --- a/test-suit/arceos/rust/memtest/Cargo.toml +++ b/test-suit/arceos/rust/memtest/Cargo.toml @@ -8,5 +8,7 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true rand = { version = "0.8", default-features = false, features = ["small_rng"] } ax-std = { workspace = true, features = ["alloc", "multitask"], optional = true } diff --git a/test-suit/arceos/rust/net/echoserver/Cargo.toml b/test-suit/arceos/rust/net/echoserver/Cargo.toml index 2d3910e87b..2d10fb773c 100644 --- a/test-suit/arceos/rust/net/echoserver/Cargo.toml +++ b/test-suit/arceos/rust/net/echoserver/Cargo.toml @@ -8,4 +8,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask", "net"], optional = true } diff --git a/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml index acfb4d47c7..702f89231d 100644 --- a/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = false diff --git a/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml index f326b766d9..90c976edd0 100644 --- a/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml index 63f6e5549d..5187b10d7a 100644 --- a/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml index f326b766d9..47ca5d96c6 100644 --- a/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpclient/Cargo.toml b/test-suit/arceos/rust/net/httpclient/Cargo.toml index 8622ce7516..45ada5309b 100644 --- a/test-suit/arceos/rust/net/httpclient/Cargo.toml +++ b/test-suit/arceos/rust/net/httpclient/Cargo.toml @@ -8,6 +8,8 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask", "net"], optional = true } ax-io.workspace = true diff --git a/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml index 7d4a6d06e1..fa1c413f97 100644 --- a/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/plat-dyn", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = true diff --git a/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml index 63f6e5549d..610dc403ec 100644 --- a/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml index f326b766d9..ee9b43a5d6 100644 --- a/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml index f326b766d9..47ca5d96c6 100644 --- a/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpserver/Cargo.toml b/test-suit/arceos/rust/net/httpserver/Cargo.toml index 9691045d3f..accb64282b 100644 --- a/test-suit/arceos/rust/net/httpserver/Cargo.toml +++ b/test-suit/arceos/rust/net/httpserver/Cargo.toml @@ -8,4 +8,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask", "net"], optional = true } diff --git a/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml index acfb4d47c7..702f89231d 100644 --- a/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = false diff --git a/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml index f326b766d9..90c976edd0 100644 --- a/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml index 63f6e5549d..5187b10d7a 100644 --- a/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml index f326b766d9..47ca5d96c6 100644 --- a/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/udpserver/Cargo.toml b/test-suit/arceos/rust/net/udpserver/Cargo.toml index dd739ac1e6..44261164af 100644 --- a/test-suit/arceos/rust/net/udpserver/Cargo.toml +++ b/test-suit/arceos/rust/net/udpserver/Cargo.toml @@ -6,4 +6,6 @@ authors = ["Dashuai Wu "] publish = false [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["net"], optional = true } diff --git a/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml index acfb4d47c7..702f89231d 100644 --- a/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = false diff --git a/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml index f326b766d9..90c976edd0 100644 --- a/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml index 63f6e5549d..5187b10d7a 100644 --- a/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml index f326b766d9..47ca5d96c6 100644 --- a/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/task/affinity/Cargo.toml b/test-suit/arceos/rust/task/affinity/Cargo.toml index 4a2c0d4026..828a917d3c 100644 --- a/test-suit/arceos/rust/task/affinity/Cargo.toml +++ b/test-suit/arceos/rust/task/affinity/Cargo.toml @@ -11,4 +11,6 @@ sched-rr = ["ax-std?/sched-rr"] sched-cfs = ["ax-std?/sched-cfs"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask"], optional = true } diff --git a/test-suit/arceos/rust/task/ipi/Cargo.toml b/test-suit/arceos/rust/task/ipi/Cargo.toml index 0116e01899..b30faa9ca9 100644 --- a/test-suit/arceos/rust/task/ipi/Cargo.toml +++ b/test-suit/arceos/rust/task/ipi/Cargo.toml @@ -7,4 +7,6 @@ description = "An ArceOS IPI callback delivery regression test" publish = false [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask", "irq", "ipi"], optional = true } diff --git a/test-suit/arceos/rust/task/irq/Cargo.toml b/test-suit/arceos/rust/task/irq/Cargo.toml index ff5d8a5585..e043350857 100644 --- a/test-suit/arceos/rust/task/irq/Cargo.toml +++ b/test-suit/arceos/rust/task/irq/Cargo.toml @@ -7,4 +7,6 @@ description = "A simple demo to test the irq state of tasks under ArceOS" publish = false [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask", "irq"], optional = true } diff --git a/test-suit/arceos/rust/task/lockdep/Cargo.toml b/test-suit/arceos/rust/task/lockdep/Cargo.toml index 1ed67a4d78..333e4de282 100644 --- a/test-suit/arceos/rust/task/lockdep/Cargo.toml +++ b/test-suit/arceos/rust/task/lockdep/Cargo.toml @@ -11,6 +11,8 @@ ax-std = ["dep:ax-std", "dep:ax-kspin", "dep:axfs-ng-vfs"] lockdep = ["ax-std", "ax-std/lockdep"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask"], optional = true } ax-kspin = { workspace = true, optional = true } axfs-ng-vfs = { workspace = true, optional = true } diff --git a/test-suit/arceos/rust/task/parallel/Cargo.toml b/test-suit/arceos/rust/task/parallel/Cargo.toml index 5b67abca1f..5a3280ab2d 100644 --- a/test-suit/arceos/rust/task/parallel/Cargo.toml +++ b/test-suit/arceos/rust/task/parallel/Cargo.toml @@ -8,5 +8,7 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true rand = { version = "0.8", default-features = false, features = ["small_rng"] } ax-std = { workspace = true, features = ["alloc", "multitask", "irq"], optional = true } diff --git a/test-suit/arceos/rust/task/priority/Cargo.toml b/test-suit/arceos/rust/task/priority/Cargo.toml index f850d4eae7..6003536597 100644 --- a/test-suit/arceos/rust/task/priority/Cargo.toml +++ b/test-suit/arceos/rust/task/priority/Cargo.toml @@ -12,4 +12,6 @@ sched-rr = ["ax-std?/sched-rr"] sched-cfs = ["ax-std?/sched-cfs"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask"], optional = true } diff --git a/test-suit/arceos/rust/task/sleep/Cargo.toml b/test-suit/arceos/rust/task/sleep/Cargo.toml index 0a06ad460d..2c4a597dc1 100644 --- a/test-suit/arceos/rust/task/sleep/Cargo.toml +++ b/test-suit/arceos/rust/task/sleep/Cargo.toml @@ -8,4 +8,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask", "irq"], optional = true } diff --git a/test-suit/arceos/rust/task/stack_guard_page/Cargo.toml b/test-suit/arceos/rust/task/stack_guard_page/Cargo.toml index ed02de0bbe..9cc791d3d2 100644 --- a/test-suit/arceos/rust/task/stack_guard_page/Cargo.toml +++ b/test-suit/arceos/rust/task/stack_guard_page/Cargo.toml @@ -6,4 +6,5 @@ authors = ["Yuekai Jia "] publish = false [dependencies] +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask", "stack-guard-page"], optional = true } diff --git a/test-suit/arceos/rust/task/tls/Cargo.toml b/test-suit/arceos/rust/task/tls/Cargo.toml index 5b79f2fa22..78f90e699b 100644 --- a/test-suit/arceos/rust/task/tls/Cargo.toml +++ b/test-suit/arceos/rust/task/tls/Cargo.toml @@ -8,4 +8,6 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["tls", "alloc", "multitask"], optional = true } diff --git a/test-suit/arceos/rust/task/wait_queue/Cargo.toml b/test-suit/arceos/rust/task/wait_queue/Cargo.toml index 673e3089e8..f852b285c4 100644 --- a/test-suit/arceos/rust/task/wait_queue/Cargo.toml +++ b/test-suit/arceos/rust/task/wait_queue/Cargo.toml @@ -7,4 +7,6 @@ description = "A simple demo to test the wait queue for tasks under ArceOS" publish = false [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask", "irq"], optional = true } diff --git a/test-suit/arceos/rust/task/yield/Cargo.toml b/test-suit/arceos/rust/task/yield/Cargo.toml index 255d4afa88..c4c621ed62 100644 --- a/test-suit/arceos/rust/task/yield/Cargo.toml +++ b/test-suit/arceos/rust/task/yield/Cargo.toml @@ -12,4 +12,6 @@ sched-rr = ["ax-std?/sched-rr"] sched-cfs = ["ax-std?/sched-cfs"] [dependencies] +ax-driver.workspace = true +ax-hal.workspace = true ax-std = { workspace = true, features = ["multitask"], optional = true } diff --git a/test-suit/arceos/std/qemu-smp1/arce_agent/Cargo.toml b/test-suit/arceos/std/qemu-smp1/arce_agent/Cargo.toml index 1710d1bd9a..6808916e85 100644 --- a/test-suit/arceos/std/qemu-smp1/arce_agent/Cargo.toml +++ b/test-suit/arceos/std/qemu-smp1/arce_agent/Cargo.toml @@ -16,4 +16,4 @@ log = "0.4" env_logger = "0.11" [target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "net", "fs", "irq", "log-level-warn"] } +arceos-rust = { workspace = true, default-features = true, features = ["log-level-warn"] } diff --git a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-aarch64.toml index 121aa49640..0e3955060b 100644 --- a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["ArceAgent smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-loongarch64.toml index 0705b87577..e44ed5cf2c 100644 --- a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["ArceAgent smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml index 0c50948dda..a9379d53e2 100644 --- a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["ArceAgent smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-x86_64.toml index 1bbdbbf8c7..8ec853a7c0 100644 --- a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["ArceAgent smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml index e50d79d29e..e0c6cb23f5 100644 --- a/test-suit/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml @@ -1,5 +1,7 @@ std = true -features = [] +features = [ + "ax-hal/aarch64-qemu-virt", +] log = "Info" max_cpu_num = 1 diff --git a/test-suit/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml index e50d79d29e..34d8cf6299 100644 --- a/test-suit/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml @@ -1,5 +1,7 @@ std = true -features = [] +features = [ + "ax-hal/loongarch64-qemu-virt", +] log = "Info" max_cpu_num = 1 diff --git a/test-suit/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml index e50d79d29e..c3a335f0cd 100644 --- a/test-suit/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml @@ -1,5 +1,7 @@ std = true -features = [] +features = [ + "ax-hal/riscv64-qemu-virt", +] log = "Info" max_cpu_num = 1 diff --git a/test-suit/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml b/test-suit/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml index e50d79d29e..d3f1f41315 100644 --- a/test-suit/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml @@ -1,5 +1,7 @@ std = true -features = [] +features = [ + "ax-hal/x86-pc", +] log = "Info" max_cpu_num = 1 diff --git a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-aarch64.toml index f917f769bb..f24947844e 100644 --- a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/helloworld/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["Hello, world!"] diff --git a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-loongarch64.toml index ead17ab00a..1ed5634ea2 100644 --- a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/helloworld/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["Hello, world!"] diff --git a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml index ec35592ce7..5569600ea9 100644 --- a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/helloworld/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["Hello, world!"] diff --git a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-x86_64.toml index 50e973c4b0..1eecfeb1b4 100644 --- a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/helloworld/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["Hello, world!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpclient/Cargo.toml b/test-suit/arceos/std/qemu-smp1/httpclient/Cargo.toml index f4b3c1a254..ce17173b57 100644 --- a/test-suit/arceos/std/qemu-smp1/httpclient/Cargo.toml +++ b/test-suit/arceos/std/qemu-smp1/httpclient/Cargo.toml @@ -12,4 +12,4 @@ default = [] dns = ["arceos-rust/dns"] [target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["net", "log-level-debug"] } +arceos-rust = { workspace = true, default-features = true, features = ["log-level-debug"] } diff --git a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-aarch64.toml index 18c8ef011a..351ff11d54 100644 --- a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpclient/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP client tests run OK!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-loongarch64.toml index bb7aec3660..66be5f954e 100644 --- a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpclient/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP client tests run OK!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml index 7480068883..c1871988a9 100644 --- a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpclient/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP client tests run OK!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-x86_64.toml index 9d93da50bf..41f2c40d59 100644 --- a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpclient/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP client tests run OK!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpserver/Cargo.toml b/test-suit/arceos/std/qemu-smp1/httpserver/Cargo.toml index e1880ed916..3706d9518a 100644 --- a/test-suit/arceos/std/qemu-smp1/httpserver/Cargo.toml +++ b/test-suit/arceos/std/qemu-smp1/httpserver/Cargo.toml @@ -8,4 +8,4 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "net", "irq", "log-level-debug"] } +arceos-rust = { workspace = true, default-features = true, features = ["log-level-debug"] } diff --git a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-aarch64.toml index 53495757ea..71ddce3492 100644 --- a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpserver/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP server smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-loongarch64.toml index 54812af3ac..b9b8223d0a 100644 --- a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpserver/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP server smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml index 89a9368a30..2b096111af 100644 --- a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpserver/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP server smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-x86_64.toml index 779e55019a..3b3dad67e5 100644 --- a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpserver/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP server smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/io_test/Cargo.toml b/test-suit/arceos/std/qemu-smp1/io_test/Cargo.toml index 0a91adef8b..98a6c018d4 100644 --- a/test-suit/arceos/std/qemu-smp1/io_test/Cargo.toml +++ b/test-suit/arceos/std/qemu-smp1/io_test/Cargo.toml @@ -6,4 +6,4 @@ authors = ["eternalcomet "] publish = false [target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["fs", "log-level-off"] } +arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } diff --git a/test-suit/arceos/std/qemu-smp1/io_test/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/io_test/qemu-aarch64.toml index 35ac821908..87c46fdb3d 100644 --- a/test-suit/arceos/std/qemu-smp1/io_test/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/io_test/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/io_test/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/io_test/qemu-loongarch64.toml index 2daae832d8..6212c17c23 100644 --- a/test-suit/arceos/std/qemu-smp1/io_test/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/io_test/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml index 00422bd22d..cf72d2902f 100644 --- a/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/io_test/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/io_test/qemu-x86_64.toml index abb909c177..cfc1f9ae17 100644 --- a/test-suit/arceos/std/qemu-smp1/io_test/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/io_test/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/thread_test/Cargo.toml b/test-suit/arceos/std/qemu-smp1/thread_test/Cargo.toml index 5fde2a20e9..7a52b3c793 100644 --- a/test-suit/arceos/std/qemu-smp1/thread_test/Cargo.toml +++ b/test-suit/arceos/std/qemu-smp1/thread_test/Cargo.toml @@ -6,4 +6,4 @@ authors = ["eternalcomet "] publish = false [target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "log-level-off", "irq"] } +arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } diff --git a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-aarch64.toml index b41b560460..771866f619 100644 --- a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/thread_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-loongarch64.toml index d090061148..70f74b0478 100644 --- a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/thread_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml index 4d5d697cf4..951abda65e 100644 --- a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/thread_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-x86_64.toml index efac5612c4..435d37ffcc 100644 --- a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/thread_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/tokio_test/Cargo.toml b/test-suit/arceos/std/qemu-smp1/tokio_test/Cargo.toml index ef8f13e4ae..fe4e9ab85b 100644 --- a/test-suit/arceos/std/qemu-smp1/tokio_test/Cargo.toml +++ b/test-suit/arceos/std/qemu-smp1/tokio_test/Cargo.toml @@ -10,4 +10,4 @@ publish = false tokio = { version = "1", features = ["rt", "macros", "time", "sync"] } [target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "irq", "log-level-off"] } +arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } diff --git a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-aarch64.toml b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-aarch64.toml index 5bec2833f3..cffb37fd52 100644 --- a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-aarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-aarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "128M", "-smp", "1", "-nographic", "-nic", "none", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "cortex-a72", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/tokio_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有 Tokio 测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-loongarch64.toml b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-loongarch64.toml index 367f795cf3..a931d39b3b 100644 --- a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-loongarch64.toml +++ b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-loongarch64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "la464", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/tokio_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有 Tokio 测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml index 4da0045302..b46ea08b21 100644 --- a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/tokio_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有 Tokio 测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-x86_64.toml b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-x86_64.toml index 4e3163ef99..5e88970a32 100644 --- a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-x86_64.toml +++ b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-x86_64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-serial", "mon:stdio"] +args = ["-machine", "q35", "-cpu", "max", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/tokio_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有 Tokio 测试完成 ==="] diff --git a/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml index a41646f5f9..5ad4b8a556 100644 --- a/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", "sdmmc", "rockchip-soc", "fs", diff --git a/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml index 60635fab86..3744939244 100644 --- a/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", + "ax-hal/plat-dyn", "ept-level-4", ] log = "Info" diff --git a/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml index 8d039b5c7e..85565cb686 100644 --- a/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml @@ -1,7 +1,8 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ + "ax-hal/plat-dyn", "ept-level-4", - "ax-std/bus-mmio", + "ax-driver/virtio-blk", "fs", ] log = "Info" diff --git a/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml index 9ac13dbf46..d16951f9f4 100644 --- a/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml @@ -1,5 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ + "ax-driver/virtio-blk", + "ax-driver/pci", "ept-level-4", "fs", ] diff --git a/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml b/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml index c801baa3dd..4e9112e70c 100644 --- a/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml @@ -1,6 +1,8 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-std/bus-mmio", + "ax-hal/riscv64-qemu-virt-hv", + "ax-driver/fdt", + "ax-driver/virtio-blk", "ept-level-4", "fs", "sstc" diff --git a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml index a7d0211ffe..fccc9c223a 100644 --- a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml @@ -1,5 +1,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ + "ax-driver/virtio-blk", + "ax-driver/pci", "ept-level-4", "fs", "vmx", diff --git a/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml index 8d53fc19ec..832d68593f 100644 --- a/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml @@ -1,5 +1,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ + "ax-driver/virtio-blk", + "ax-driver/pci", "ept-level-4", "fs", "svm", diff --git a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml index 6a7869f2a0..8bcd941406 100644 --- a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml @@ -1,7 +1,7 @@ # Build-time config template for StarryOS on SG2002. target = "riscv64gc-unknown-none-elf" env = {} -features = ["sg2002", "myplat", "ax-feat/bus-mmio", "ax-feat/driver-cvsd"] +features = ["sg2002", "myplat", "ax-driver/cvsd"] log = "Info" max_cpu_num = 1 plat_dyn = false diff --git a/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml index 4ebb1f02d9..5ca9f332f2 100644 --- a/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml @@ -3,16 +3,15 @@ target = "aarch64-unknown-none-softfloat" # Runtime U-Boot settings live in `orangepi-5-plus-uboot.toml`. env = {} features = [ - "ax-feat/bus-mmio", - "ax-feat/bus-pci", - "ax-feat/driver-sdmmc", + "ax-hal/plat-dyn", "starry-kernel/plat-dyn", - "axplat-dyn/pci-list-devices", - "axplat-dyn/realtek-rtl8125", - "axplat-dyn/rockchip-soc", - "axplat-dyn/rockchip-dwc-xhci", - "axplat-dyn/rockchip-sdhci", - "axplat-dyn/rockchip-dwmmc", + "ax-driver/pci-list-devices", + "ax-driver/rk3588-pcie", + "ax-driver/realtek-rtl8125", + "ax-driver/rockchip-soc", + "ax-driver/rockchip-dwc-xhci", + "ax-driver/rockchip-sdhci", + "ax-driver/rockchip-dwmmc", "rknpu", ] log = "Info" diff --git a/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml index f155a4a857..2da23ecded 100644 --- a/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml @@ -1,11 +1,13 @@ env = {} features = [ - "starry-kernel/input", - "starry-kernel/plat-dyn", - "starry-kernel/vsock", - "axplat-dyn/intel-net", - "axplat-dyn/virtio-net-pci", - "axplat-dyn/xhci-pci", + "ax-hal/plat-dyn", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", + "ax-driver/intel-net", + "ax-driver/xhci-pci", ] log = "Warn" plat_dyn = true diff --git a/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml index ffb6581a2a..7efd9aa1bd 100644 --- a/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml @@ -1,5 +1,14 @@ target = "x86_64-unknown-none" env = {} log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml index 0925880a34..6f363df452 100644 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,17 @@ target = "aarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu","kcov","smp"] +features = [ + "ax-hal/aarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", + "smp", +] plat_dyn = false max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml index 3f4def6eed..a221d6be8c 100644 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml @@ -1,6 +1,17 @@ target = "loongarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu","kcov","smp"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", + "smp", +] plat_dyn = false max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml index d173165371..4fb164caa3 100644 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml @@ -1,6 +1,17 @@ target = "riscv64gc-unknown-none-elf" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu","kcov","smp"] +features = [ + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", + "smp", +] plat_dyn = false max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml index 0dbff2588b..04efbf58fb 100644 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml @@ -1,6 +1,17 @@ target = "x86_64-unknown-none" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu","kcov","smp"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", + "smp", +] plat_dyn = false max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml index dec7d2d224..71696eca90 100644 --- a/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml @@ -1,5 +1,15 @@ target = "aarch64-unknown-none-softfloat" env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} log = "Warn" -features = ["qemu","kcov"] +features = [ + "ax-hal/aarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml index c3cc63c1a3..e2f2fc3bb9 100644 --- a/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml @@ -1,5 +1,15 @@ target = "loongarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu","kcov"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml index 8f3b44d588..6c4bb244d7 100644 --- a/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml @@ -1,5 +1,15 @@ target = "riscv64gc-unknown-none-elf" env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} log = "Warn" -features = ["qemu","kcov"] +features = [ + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml index b1b43d3b65..4c3d209322 100644 --- a/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml @@ -1,5 +1,15 @@ target = "x86_64-unknown-none" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu","kcov"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "kcov", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml index 5f100f6f65..97cd8c5513 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml @@ -17,11 +17,17 @@ uefi = false to_bin = true shell_prefix = "root@starry:" shell_init_cmd = ''' -apk update && \ -apk add curl && \ -curl --version && \ -curl -i https://baidu.com && \ -echo 'APK_CURL_TEST_PASSED' || \ +i=0 +while [ $i -lt 3 ]; do + apk update && \ + apk add curl && \ + curl --version && \ + curl -i https://baidu.com && \ + echo 'APK_CURL_TEST_PASSED' && \ + exit 0 + i=$((i + 1)) + sleep 2 +done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml index ecf4bff498..3847884017 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml @@ -19,11 +19,17 @@ to_bin = true uefi = false shell_prefix = "root@starry:" shell_init_cmd = ''' -apk update && \ -apk add curl && \ -curl --version && \ -curl -i https://baidu.com && \ -echo 'APK_CURL_TEST_PASSED' || \ +i=0 +while [ $i -lt 3 ]; do + apk update && \ + apk add curl && \ + curl --version && \ + curl -i https://baidu.com && \ + echo 'APK_CURL_TEST_PASSED' && \ + exit 0 + i=$((i + 1)) + sleep 2 +done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml index 1e2b5caaad..85850ec88b 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml @@ -17,11 +17,17 @@ uefi = false to_bin = true shell_prefix = "root@starry:" shell_init_cmd = ''' -apk update && \ -apk add curl && \ -curl --version && \ -curl -i https://baidu.com && \ -echo 'APK_CURL_TEST_PASSED' || \ +i=0 +while [ $i -lt 3 ]; do + apk update && \ + apk add curl && \ + curl --version && \ + curl -i https://baidu.com && \ + echo 'APK_CURL_TEST_PASSED' && \ + exit 0 + i=$((i + 1)) + sleep 2 +done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml index 61f46320f8..6977bb5c39 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml @@ -17,11 +17,17 @@ uefi = false to_bin = false shell_prefix = "root@starry:" shell_init_cmd = ''' -apk update && \ -apk add curl && \ -curl --version && \ -curl -i https://baidu.com && \ -echo 'APK_CURL_TEST_PASSED' || \ +i=0 +while [ $i -lt 3 ]; do + apk update && \ + apk add curl && \ + curl --version && \ + curl -i https://baidu.com && \ + echo 'APK_CURL_TEST_PASSED' && \ + exit 0 + i=$((i + 1)) + sleep 2 +done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] diff --git a/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml index 3a415f78b5..93bdabbd28 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ + "ax-hal/aarch64-qemu-virt", "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml index 0436f2888c..0dc0ec2bb6 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml @@ -1,5 +1,14 @@ target = "loongarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml index 67e8fcf6ac..2df21769c5 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "qemu", # auxilary features + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml index 1c8a2eeca2..94d8986bbb 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml @@ -1,5 +1,14 @@ target = "x86_64-unknown-none" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-smp1/test-mmap-prot-write/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-mmap-prot-write/qemu-x86_64.toml index fd7ed3c8b9..c875507baf 100644 --- a/test-suit/starryos/normal/qemu-smp1/test-mmap-prot-write/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/test-mmap-prot-write/qemu-x86_64.toml @@ -11,4 +11,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/test-mmap-prot-write" success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] -timeout = 30 +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml index 74d1fddc32..9aa6085026 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml @@ -17,4 +17,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/util-linux-test" success_regex = ["(?m)^UTIL LINUX TEST PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^\s*FAIL \|'] -timeout = 60 +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml index acbe1d73ab..8cb956e76a 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml @@ -15,4 +15,4 @@ shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/util-linux-test" success_regex = ["(?m)^UTIL LINUX TEST PASSED\\s*$"] fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^\s*FAIL \|'] -timeout = 60 +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml index 4718fcb433..f7a44bce8b 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ + "ax-hal/aarch64-qemu-virt", "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] max_cpu_num = 4 log = "Warn" diff --git a/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml index 7edef22906..aea79288ad 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml @@ -1,6 +1,15 @@ target = "loongarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml index 42b95968fa..f9e4ff6cb8 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "qemu", # auxilary features + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] max_cpu_num = 4 log = "Warn" diff --git a/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml index 72816270be..3f616671f3 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml @@ -1,6 +1,15 @@ target = "x86_64-unknown-none" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false max_cpu_num = 4 diff --git a/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml index 3a415f78b5..93bdabbd28 100644 --- a/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ + "ax-hal/aarch64-qemu-virt", "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml index 0436f2888c..0dc0ec2bb6 100644 --- a/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml @@ -1,5 +1,14 @@ target = "loongarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml index 67e8fcf6ac..2df21769c5 100644 --- a/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "qemu", # auxilary features + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml b/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml index 1c8a2eeca2..94d8986bbb 100644 --- a/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml @@ -1,5 +1,14 @@ target = "x86_64-unknown-none" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml index 3a415f78b5..93bdabbd28 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ + "ax-hal/aarch64-qemu-virt", "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml index 0436f2888c..0dc0ec2bb6 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml @@ -1,5 +1,14 @@ target = "loongarch64-unknown-none-softfloat" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false diff --git a/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml index 67e8fcf6ac..2df21769c5 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml @@ -1,6 +1,13 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "qemu", # auxilary features + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", ] log = "Warn" plat_dyn = false diff --git a/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml b/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml index 1c8a2eeca2..94d8986bbb 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml @@ -1,5 +1,14 @@ target = "x86_64-unknown-none" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } log = "Warn" -features = ["qemu"] +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] plat_dyn = false From e725e5039827d4b91d1868bb769652109caa0357 Mon Sep 17 00:00:00 2001 From: Feiran Qin <161046517+jakeuibn@users.noreply.github.com> Date: Mon, 25 May 2026 12:21:48 +0800 Subject: [PATCH 15/71] fix(starry-kernel): close EPOLLET race window and NoEvent busy-loop (#910) Two related bugs in the EPOLLET delivery path broke event-driven runtimes on Tokio-style applications (jcode TUI, reqwest, etc.). 1. NoEvent path busy-loop on connected TCP sockets When InterestWaker fires but consume() reports no matching events (a spurious wake, e.g. a shared PollSet wake on an interest registered only for EPOLLIN while the underlying socket has EPOLLOUT-ready), the old code called check_and_register_waker(). That helper polls the file and immediately calls waker.wake_by_ref() if file.poll() is non-empty. Connected TCP sockets always advertise EPOLLOUT, so every spurious NoEvent reissued the waker and re-queued the interest: NoEvent -> check_and_register_waker -> EPOLLOUT present -> wake_by_ref -> re-queued -> NoEvent -> ... The ready_queue filled with phantom entries and epoll_wait returned maxevents (up to 1024) of duplicate notifications, which kept tokio spinning without ever confirming the TCP handshake. Fix: use register_waker_only on the NoEvent path so the interest is re-armed and only fires on a genuine state transition, matching the EPOLLET contract. 2. EventAndRemove race window between mark_not_in_queue() and re-arm After delivering an EPOLLET event, mark_not_in_queue() clears the in-queue flag and then register_waker_only() installs a new InterestWaker. The previous InterestWaker had already been consumed by the wake that delivered the first chunk, leaving the underlying PollSet empty in the gap between the two calls. If the peer writes a second chunk inside that window, poll_update.wake() hits the empty PollSet and the notification is silently dropped. The fresh waker is then installed only after the data has already arrived; no further write occurs and EPOLLET never fires again. The visible symptom was jcode TUI hanging after the first AI response chunk. Fix: after register_waker_only(), re-check the file for IN/RDHUP/HUP events. If data is already present, CAS the in-queue flag back to true and re-push the interest onto the ready queue directly (without going through waker.wake_by_ref(), which would reintroduce the bug-1 busy-loop). EPOLLOUT is intentionally excluded: writable sockets are normally always OUT-ready and a check there would spin. Add bug-epollet-second-chunk regression test covering both paths: an idle EPOLLET interest must not return phantom events from an always-OUT socket, and 32 back-to-back chunk-A/chunk-B rounds must each be reported by their own epoll_wait() call. Co-authored-by: StarryOS Fix --- os/StarryOS/kernel/src/file/epoll.rs | 47 ++++- .../bug-epollet-second-chunk/c/CMakeLists.txt | 8 + .../bug-epollet-second-chunk/c/src/main.c | 188 ++++++++++++++++++ .../normal/qemu-smp1/bugfix/qemu-aarch64.toml | 1 + .../qemu-smp1/bugfix/qemu-loongarch64.toml | 1 + .../normal/qemu-smp1/bugfix/qemu-riscv64.toml | 1 + .../normal/qemu-smp1/bugfix/qemu-x86_64.toml | 1 + 7 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/src/main.c diff --git a/os/StarryOS/kernel/src/file/epoll.rs b/os/StarryOS/kernel/src/file/epoll.rs index 2402e0ec52..04f7a32ed3 100644 --- a/os/StarryOS/kernel/src/file/epoll.rs +++ b/os/StarryOS/kernel/src/file/epoll.rs @@ -461,17 +461,52 @@ impl Epoll { keep.push_back(Arc::downgrade(&interest)); } else { interest.mark_not_in_queue(); + // EPOLLET: install a fresh waker so the next edge + // transition fires. There is a race window between + // mark_not_in_queue() above and register_waker_only() + // below: the previous InterestWaker may have already + // been consumed by the wake that delivered the event + // we are returning here, leaving the underlying + // PollSet empty. If new data arrives in that gap, + // poll_update.wake() hits the empty PollSet and the + // notification is silently dropped — EPOLLET would + // then never fire again because the new waker is + // installed only after the data already arrived. + // Close the window by re-checking the file's poll + // state after registering and re-queueing the + // interest directly if IN-side data is already + // present. EPOLLOUT is intentionally excluded: it + // is normally always ready on writable sockets and + // would cause a busy-loop. self.register_waker_only(&interest); + let in_mask = interest.event.events + & (IoEvents::IN | IoEvents::RDHUP | IoEvents::HUP); + if !in_mask.is_empty() + && let Some(f) = interest.key.get_file() + && !(f.poll() & in_mask).is_empty() + && interest.try_mark_in_queue() + { + self.inner + .ready_queue + .lock() + .push_back(Arc::downgrade(&interest)); + self.inner.poll_ready.wake(); + } } } ConsumeResult::NoEvent => { + // Spurious wakeup: the waker fired but file.poll() did + // not match the interest mask (e.g. a shared PollSet + // wake on a socket that has only EPOLLOUT ready when + // the interest is for EPOLLIN). Re-arm with a plain + // waker registration — using check_and_register_waker + // here would immediately re-queue the interest via + // waker.wake_by_ref() whenever file.poll() is non-empty, + // which a connected TCP socket (always EPOLLOUT-ready) + // satisfies on every iteration, producing a tight loop + // that fills the ready_queue with phantom events. interest.mark_not_in_queue(); - // Events arriving between consume()'s poll and the new - // register() would otherwise be lost: the old waker - // CAS-fails (in_ready_queue still set), and a plain - // register only fires on the next edge. Re-poll after - // registering to recover them. - self.check_and_register_waker(&interest); + self.register_waker_only(&interest); } } } diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/CMakeLists.txt new file mode 100644 index 0000000000..01da6e6371 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(bug-epollet-second-chunk C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(bug-epollet-second-chunk src/main.c) +target_compile_options(bug-epollet-second-chunk PRIVATE -Wall -Wextra -Werror) +install(TARGETS bug-epollet-second-chunk RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/src/main.c new file mode 100644 index 0000000000..420417ad00 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-epollet-second-chunk/c/src/main.c @@ -0,0 +1,188 @@ +/* + * bug-epollet-second-chunk: an EPOLLET registration must report a second + * chunk that arrives after the first chunk was fully drained. + * + * Two related bugs in the EPOLLET delivery path used to break this: + * + * 1. NoEvent path called check_and_register_waker(), which on a connected + * TCP socket immediately observed EPOLLOUT-ready in file.poll() and + * re-fired the waker. That created a tight busy-loop that filled the + * ready_queue with phantom events and starved real wakeups. + * + * 2. EventAndRemove cleared the in-queue flag and then registered a new + * waker. The previous InterestWaker had already been consumed by the + * wake that delivered the first chunk, so writes that arrived between + * mark_not_in_queue() and register_waker_only() hit an empty PollSet + * and the second chunk's wake was silently dropped. + * + * This test exercises the second-chunk path repeatedly to maximise the + * chance of hitting the race window, and also verifies the basic NoEvent + * path by polling with a 0 ms timeout on a connected socket without any + * outstanding data. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ROUNDS 32 + +static int make_loopback_pair(int *cli, int *srv) +{ + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) + return -1; + int opt = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + .sin_port = 0, + }; + if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 + || listen(listen_fd, 1) < 0) { + close(listen_fd); + return -1; + } + socklen_t len = sizeof(addr); + if (getsockname(listen_fd, (struct sockaddr *)&addr, &len) < 0) { + close(listen_fd); + return -1; + } + + int c = socket(AF_INET, SOCK_STREAM, 0); + if (c < 0) { + close(listen_fd); + return -1; + } + if (connect(c, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(c); + close(listen_fd); + return -1; + } + int s = accept(listen_fd, NULL, NULL); + close(listen_fd); + if (s < 0) { + close(c); + return -1; + } + *cli = c; + *srv = s; + return 0; +} + +static int drain(int fd) +{ + char buf[64]; + int total = 0; + for (;;) { + ssize_t n = read(fd, buf, sizeof(buf)); + if (n > 0) { + total += (int)n; + continue; + } + if (n < 0 && errno == EAGAIN) + return total; + return -1; + } +} + +int main(void) +{ + int cli, srv; + if (make_loopback_pair(&cli, &srv) < 0) { + fprintf(stderr, "loopback pair setup failed: %s\n", strerror(errno)); + return 1; + } + + int flags = fcntl(cli, F_GETFL, 0); + fcntl(cli, F_SETFL, flags | O_NONBLOCK); + + int epfd = epoll_create1(EPOLL_CLOEXEC); + if (epfd < 0) { + fprintf(stderr, "epoll_create1: %s\n", strerror(errno)); + return 1; + } + struct epoll_event ev = { + .events = EPOLLIN | EPOLLET, + .data.fd = cli, + }; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, cli, &ev) < 0) { + fprintf(stderr, "epoll_ctl: %s\n", strerror(errno)); + return 1; + } + + /* + * Bug 1: a connected TCP socket has EPOLLOUT always ready. With the + * NoEvent path's old check_and_register_waker, any spurious wake would + * find OUT in file.poll() and re-queue the interest, returning phantom + * events here. After the fix, epoll_wait must time out cleanly. + */ + struct epoll_event evs[8]; + int n = epoll_wait(epfd, evs, 8, 50); + if (n != 0) { + fprintf(stderr, "STARRY_GROUPED_TEST_FAILED: bug-epollet-second-chunk " + "(phantom events from idle socket: n=%d)\n", n); + return 1; + } + + /* + * Bug 2: send a chunk, drain it, send another chunk back-to-back. + * Repeat to maximise the chance of writing in the race window between + * mark_not_in_queue() and the fresh register_waker_only() install. + */ + for (int i = 0; i < ROUNDS; i++) { + char msg[16]; + int len = snprintf(msg, sizeof(msg), "round-%d-A", i); + if (write(srv, msg, (size_t)len) != len) { + fprintf(stderr, "write A failed: %s\n", strerror(errno)); + return 1; + } + n = epoll_wait(epfd, evs, 8, 1000); + if (n != 1) { + fprintf(stderr, + "STARRY_GROUPED_TEST_FAILED: bug-epollet-second-chunk " + "(round=%d chunk A: epoll_wait returned %d, expected 1)\n", + i, n); + return 1; + } + if (drain(cli) <= 0) { + fprintf(stderr, "drain A failed: %s\n", strerror(errno)); + return 1; + } + + len = snprintf(msg, sizeof(msg), "round-%d-B", i); + if (write(srv, msg, (size_t)len) != len) { + fprintf(stderr, "write B failed: %s\n", strerror(errno)); + return 1; + } + n = epoll_wait(epfd, evs, 8, 1000); + if (n != 1) { + fprintf(stderr, + "STARRY_GROUPED_TEST_FAILED: bug-epollet-second-chunk " + "(round=%d chunk B: epoll_wait returned %d, expected 1)\n", + i, n); + return 1; + } + if (drain(cli) <= 0) { + fprintf(stderr, "drain B failed: %s\n", strerror(errno)); + return 1; + } + } + + close(epfd); + close(cli); + close(srv); + + printf("STARRY_GROUPED_TEST_PASSED: bug-epollet-second-chunk\n"); + return 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml index 8c25661e73..30836bfb3b 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml @@ -59,6 +59,7 @@ test_commands = [ "/usr/bin/bug-af-inet6-v4mapped", "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", + "/usr/bin/bug-epollet-second-chunk", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml index af66efe927..037fd24a91 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml @@ -60,6 +60,7 @@ test_commands = [ "/usr/bin/bug-af-inet6-v4mapped", "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", + "/usr/bin/bug-epollet-second-chunk", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml index 24f48d1d2a..6ffb6e8bc3 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml @@ -67,6 +67,7 @@ test_commands = [ "/usr/bin/bug-af-inet6-v4mapped", "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", + "/usr/bin/bug-epollet-second-chunk", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml index 0f8345ef98..7c6c35f3e8 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml @@ -65,6 +65,7 @@ test_commands = [ "/usr/bin/bug-af-inet6-v4mapped", "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", + "/usr/bin/bug-epollet-second-chunk", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", From 445a4f02aea61553d0271414d753e0b8706fad02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 12:27:00 +0800 Subject: [PATCH 16/71] fix(starry-kernel): copy under-aligned epoll_event byte-wise (fixes Go netpoll EFAULT) (#914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 Go runtime 在 StarryOS 上 netpoll 失败:`runtime: netpoll failed` + `fd N failed with 14`(EFAULT),Go 程序无法正常网络轮询。 ## 根因 Go runtime 的 `epollevent` 以 `[8]byte data` 布局,只有 4 字节对齐,传给 `epoll_ctl` 的 `&ev` 可能落在 `4 (mod 8)` 的地址。StarryOS 的 `sys_epoll_ctl` 经类型化 `*const epoll_event`(`get_as_ref`)读取用户内存,VM helper 对非自然对齐(8 字节)的指针返回 `EFAULT`。Linux 内核用 `copy_from_user` 逐字节拷贝、无对齐要求,因此同样的 Go 程序在 Linux 正常、在 StarryOS `EFAULT`;`epoll_wait` 向用户 `events` 数组写回时同理。 ## 修复 新增 `read_epoll_event` / `write_epoll_event`,以字节粒度的 `vm_read_slice` / `vm_write_slice`(经 `*u8`)拷贝 `epoll_event`,不要求其自然对齐,镜像 Linux 的 `copy_from_user` / `__put_user` 语义。`sys_epoll_ctl` 的输入与 `do_epoll_wait` 的输出两条路径均改用。`epoll_event` 是 POD,任意位模式合法,字节拷贝安全。 ## 测试 本地 `cargo xtask starry build --arch x86_64` 通过;对齐路径(既有 `test-epoll`/`test-epoll-eventfd`/`test-epoll-lt`/`test-epoll-pwait2` 回归)行为不变——字节拷贝是对齐读写的超集。 Signed-off-by: Leo Cheng --- .../kernel/src/syscall/io_mpx/epoll.rs | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/os/StarryOS/kernel/src/syscall/io_mpx/epoll.rs b/os/StarryOS/kernel/src/syscall/io_mpx/epoll.rs index 1e26bdf11d..f9e4fe9b6d 100644 --- a/os/StarryOS/kernel/src/syscall/io_mpx/epoll.rs +++ b/os/StarryOS/kernel/src/syscall/io_mpx/epoll.rs @@ -1,4 +1,7 @@ -use core::{mem::size_of, time::Duration}; +use core::{ + mem::{MaybeUninit, size_of}, + time::Duration, +}; use ax_errno::{AxError, AxResult}; use ax_task::future::{self, block_on, poll_io}; @@ -8,7 +11,7 @@ use linux_raw_sys::general::{ EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD, epoll_event, timespec, }; use starry_signal::SignalSet; -use starry_vm::vm_write_slice; +use starry_vm::{vm_read_slice, vm_write_slice}; use crate::{ file::{ @@ -33,6 +36,56 @@ fn check_epoll_events_access(events: UserPtr, maxevents: usize) -> Ok(()) } +/// Reads a single `epoll_event` from user memory without requiring the user +/// pointer to satisfy `epoll_event`'s natural alignment. +/// +/// Linux copies the `struct epoll_event` from user space with `copy_from_user`, +/// which performs a byte-wise copy and imposes no alignment requirement. On +/// architectures where `struct epoll_event` is NOT `__attribute__((packed))` +/// (everything except x86/x86_64), the C struct has 8-byte alignment, but +/// runtimes are free to back it with a less-strictly-aligned buffer. The Go +/// runtime, for instance, lays out its `epollevent` with `[8]byte data` and +/// thus only 4-byte alignment, so `&ev` passed to `epoll_ctl` can land at an +/// address that is `4 (mod 8)`. Reading through a typed `*const epoll_event` +/// (which the generic VM helpers reject when the pointer is unaligned) would +/// then fail with `EFAULT`. Copy at byte granularity to mirror Linux. +fn read_epoll_event(event: UserConstPtr) -> AxResult { + let mut buf = MaybeUninit::::uninit(); + let dst = unsafe { + core::slice::from_raw_parts_mut( + buf.as_mut_ptr().cast::>(), + size_of::(), + ) + }; + vm_read_slice(event.address().as_ptr(), dst)?; + // SAFETY: all bytes were just initialized by the copy above and any bit + // pattern is a valid `epoll_event` (plain old data). + Ok(unsafe { buf.assume_init() }) +} + +/// Writes a single `epoll_event` to the user `events` array slot `index` +/// without requiring the user pointer to satisfy `epoll_event`'s natural +/// alignment. +/// +/// See [`read_epoll_event`] for why epoll user buffers may be under-aligned; +/// Linux's `__put_user` of each field has no alignment requirement, so we copy +/// at byte granularity to match. +fn write_epoll_event( + events: UserPtr, + index: usize, + event: &epoll_event, +) -> AxResult<()> { + let dst = events.as_ptr().wrapping_add(index) as *mut u8; + let src = unsafe { + core::slice::from_raw_parts( + event as *const epoll_event as *const u8, + size_of::(), + ) + }; + vm_write_slice(dst, src)?; + Ok(()) +} + bitflags! { /// Flags for the `epoll_create` syscall. #[derive(Debug, Clone, Copy, Default)] @@ -59,7 +112,7 @@ pub fn sys_epoll_ctl( debug!("sys_epoll_ctl <= epfd: {epfd}, op: {op}, fd: {fd}"); let parse_event = || -> AxResult<(EpollEvent, EpollFlags)> { - let event = event.get_as_ref()?; + let event = read_epoll_event(event)?; let events = IoEvents::from_bits_truncate(event.events); let flags = EpollFlags::from_bits(event.events & !events.bits()).ok_or(AxError::InvalidInput)?; @@ -121,10 +174,7 @@ fn do_epoll_wait( timeout, poll_io(epoll.as_ref(), IoEvents::IN, false, || { epoll.poll_events_with(maxevents, |index, event| { - vm_write_slice( - events.as_ptr().wrapping_add(index), - core::slice::from_ref(&event), - )?; + write_epoll_event(events, index, &event)?; Ok(()) }) }), From 6b59ef7d290dea71d85c73f4b33375170e8dfdd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 12:28:32 +0800 Subject: [PATCH 17/71] fix(starry-kernel): add Threads: line to /proc/[pid]/status and implement /proc/[pid]/statm (#915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 psutil(及基于它的 glances/常见监控)在 StarryOS 上遍历进程即崩溃:`psutil ... num_threads → IndexError: list index out of range`;修掉后又 `FileNotFoundError: /proc//statm`。 ## 根因 1. psutil `Process.num_threads()` 读 `/proc//status`、用正则 `br'Threads:\t(\d+)'` 取值并直接 `[0]` 索引。StarryOS 的 `render_task_status_fields` 渲染了 Tgid/Pid/Uid/Gid/Cpus_allowed* 等,但**缺 `Threads:` 行** → `findall()` 为空 → `[0]` 抛 `IndexError`。psutil `Process.as_dict` 只吞 AccessDenied/ZombieProcess/NotImplementedError,故该异常逐层上抛、令整个 `process_iter` 崩溃。 2. 紧随其后 `memory_info()` 打开 `/proc//statm`,StarryOS procfs **未提供该文件** → `FileNotFoundError`,同样未被 as_dict 吞掉。 ## 修复 1. `render_task_status_fields` 增加 `Threads:\t`(TAB 分隔,匹配 psutil 正则),`` 取自 `proc.threads().len()`,并将 `num_threads` 串入 `render_task_status`/`task_status`/procfs_pid 分支。 2. 新增 `/proc//statm` 节点(`render_thread_statm`):`size`(VSS)由 `aspace.areas()` 求和,`resident` 取 VSS(诚实上界),`text`/`data` 据 EXECUTE/WRITE 标志,`shared`/`lib`/`dirty` 为 0。 二者均为通用 `/proc` ABI 补全,惠及任何读 `num_threads`/进程内存的工具(psutil/ps/top/htop)。 ## 测试 `cargo xtask starry build --arch x86_64` 通过;新增渲染单测断言 TAB 分隔的 `Threads:` 行;glances 头less 监控用例在 x86_64 实测通过(此前因本 bug 崩溃/超时)。 ## 对照 Linux(验证) Linux 内核 fs/proc 为每个 /proc//status 输出 `Threads:\t` 行、并提供 /proc//statm(标准格式,arch 无关);host Linux 上 glances/psutil 据此正常工作。故崩溃非测例/依赖问题,而是 StarryOS procfs 与 Linux 的语义差异;本修使 StarryOS 对齐 Linux。 Signed-off-by: Leo Cheng --- os/StarryOS/kernel/src/pseudofs/proc.rs | 99 ++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/os/StarryOS/kernel/src/pseudofs/proc.rs b/os/StarryOS/kernel/src/pseudofs/proc.rs index 1ab76c59ee..92e8fedf06 100644 --- a/os/StarryOS/kernel/src/pseudofs/proc.rs +++ b/os/StarryOS/kernel/src/pseudofs/proc.rs @@ -18,6 +18,7 @@ use ax_hal::{ paging::MappingFlags, time::{monotonic_time, wall_time}, }; +use ax_memory_addr::PAGE_SIZE_4K; use ax_task::{AxCpuMask, AxTaskRef, TaskState, WeakAxTaskRef, current}; use axfs_ng_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult}; use starry_process::{Pid, Process}; @@ -331,10 +332,12 @@ impl SimpleDirOps for ProcessTaskDir { fn task_status(task: &AxTaskRef) -> String { let thread = task.as_thread(); let cred = thread.cred(); + let num_threads = thread.proc_data.proc.threads().len() as u32; render_task_status( thread.proc_data.proc.pid(), thread.tid() as u64, &cred, + num_threads, task.cpumask(), ax_hal::cpu_num(), ) @@ -344,13 +347,21 @@ fn render_task_status( tgid: u32, pid: u64, cred: &crate::task::Cred, + num_threads: u32, cpumask: AxCpuMask, cpu_num: usize, ) -> String { let cpus_allowed = format_cpumask_hex(cpumask, cpu_num); let cpus_allowed_list = format_cpumask_list(cpumask, cpu_num); - render_task_status_fields(tgid, pid, cred, &cpus_allowed, &cpus_allowed_list) + render_task_status_fields( + tgid, + pid, + cred, + num_threads, + &cpus_allowed, + &cpus_allowed_list, + ) } #[rustfmt::skip] @@ -358,14 +369,23 @@ fn render_task_status_fields( tgid: u32, pid: u64, cred: &crate::task::Cred, + num_threads: u32, cpus_allowed: &str, cpus_allowed_list: &str, ) -> String { + // NOTE: `Threads:\t` is REQUIRED by psutil. `Process.num_threads()` + // does `int(re.compile(br'Threads:\t(\d+)').findall(data)[0])`, which + // raises an *uncaught* IndexError (not NoSuchProcess/AccessDenied/ + // NotImplementedError, the only exceptions `Process.as_dict()` swallows) + // when the line is absent. That crashes any psutil/glances `process_iter`. + // The tab-separated `Uid:`/`Gid:` lines are likewise mandatory for + // `Process.uids()`/`gids()`, which also index `findall(...)[0]` blindly. format!( "Tgid:\t{tgid}\n\ Pid:\t{pid}\n\ Uid:\t{}\t{}\t{}\t{}\n\ Gid:\t{}\t{}\t{}\t{}\n\ + Threads:\t{num_threads}\n\ Cpus_allowed:\t{cpus_allowed}\n\ Cpus_allowed_list:\t{cpus_allowed_list}\n\ Mems_allowed:\t1\n\ @@ -560,11 +580,57 @@ fn render_thread_maps(task: &WeakAxTaskRef) -> VfsResult { Ok(output) } +/// Render `/proc/[pid]/statm` (process memory in pages). +/// +/// Fields (Linux order): `size resident shared text lib data dirty`. +/// psutil's `Process.memory_info()` parses the first 7 ints and computes +/// `memory_percent` from them; the file MUST exist and be parseable, or +/// psutil raises an *uncaught* `FileNotFoundError` (only NoSuchProcess / +/// AccessDenied / ZombieProcess / NotImplementedError are swallowed by +/// `Process.as_dict()`), crashing any `process_iter` (glances / top-likes). +/// +/// `size` (VSS) is summed exactly from the mapped areas. `resident` (RSS) is +/// reported as the full VSS — an honest upper bound rather than a fabricated +/// figure; StarryOS' file-backed areas are lazily/cache populated, so a precise +/// per-page page-table walk for every process on every refresh would be far too +/// expensive under emulation. `shared`/`lib`/`dirty` are 0 (Linux also reports 0 +/// for `lib`/`dirty` since 2.6); `text` and `data` are derived from the areas' +/// executable / writable flags. +fn render_thread_statm(task: &WeakAxTaskRef) -> VfsResult { + let task = match task.upgrade() { + Some(t) => t, + None => return Ok("0 0 0 0 0 0 0\n".into()), + }; + + let aspace_arc = task.as_thread().proc_data.aspace(); + let mm = aspace_arc.lock(); + + let mut size_pages: u64 = 0; + let mut text_pages: u64 = 0; + let mut data_pages: u64 = 0; + for area in mm.areas() { + let pages = (area.size() / PAGE_SIZE_4K) as u64; + size_pages += pages; + let flags = area.flags(); + if flags.contains(MappingFlags::EXECUTE) { + text_pages += pages; + } else if flags.contains(MappingFlags::WRITE) { + data_pages += pages; + } + } + + // size resident shared text lib data dirty + Ok(format!( + "{size_pages} {size_pages} 0 {text_pages} 0 {data_pages} 0\n" + )) +} + impl SimpleDirOps for ThreadDir { fn child_names<'a>(&'a self) -> Box> + 'a> { Box::new( [ "stat", + "statm", "status", "statm", "oom_score_adj", @@ -596,16 +662,22 @@ impl SimpleDirOps for ThreadDir { }) .into() } + "statm" => { + let task = self.task.clone(); + SimpleFile::new_regular(fs, move || render_thread_statm(&task)).into() + } "status" => { let procfs_pid = self.procfs_pid; SimpleFile::new_regular(fs, move || { if let Some(pid) = procfs_pid { let thread = task.as_thread(); let cred = thread.cred(); + let num_threads = thread.proc_data.proc.threads().len() as u32; Ok(render_task_status( pid, pid as u64, &cred, + num_threads, task.cpumask(), ax_hal::cpu_num(), )) @@ -977,7 +1049,14 @@ mod tests { let cpus_allowed = format_cpu_presence_hex(&cpu_presence); let cpus_allowed_list = format_cpu_presence_list(&cpu_presence); - render_task_status_fields(tgid, pid, &Cred::root(), &cpus_allowed, &cpus_allowed_list) + render_task_status_fields( + tgid, + pid, + &Cred::root(), + 1, + &cpus_allowed, + &cpus_allowed_list, + ) } #[test] @@ -1020,4 +1099,20 @@ mod tests { assert!(status.contains("Cpus_allowed:\t0000000a\n")); assert!(status.contains("Cpus_allowed_list:\t1,3\n")); } + + #[test] + fn task_status_emits_tab_separated_threads_line_for_psutil() { + // psutil `Process.num_threads()` parses this line with the regex + // `br'Threads:\t(\d+)'` and blindly indexes `[0]`; a missing line + // raises an uncaught IndexError that crashes glances' process_iter. + let cpu_presence = collect_cpu_presence([0usize], 1); + let cpus_allowed = format_cpu_presence_hex(&cpu_presence); + let cpus_allowed_list = format_cpu_presence_list(&cpu_presence); + let status = + render_task_status_fields(1, 1, &Cred::root(), 3, &cpus_allowed, &cpus_allowed_list); + + assert!(status.contains("Threads:\t3\n")); + // Tab-separated, exactly as the psutil regex expects (not space). + assert!(!status.contains("Threads: 3")); + } } From 1335693986e82d0e55547410b2e463170d64eb63 Mon Sep 17 00:00:00 2001 From: YanLien <128586861+YanLien@users.noreply.github.com> Date: Mon, 25 May 2026 12:30:34 +0800 Subject: [PATCH 18/71] Refactor journal recovery and partition scanning logic (#927) --- .../rsext4/src/blockdev/cached_device.rs | 17 + components/rsext4/src/blockdev/journal.rs | 11 + .../rsext4/src/blockgroup_description/desc.rs | 24 +- components/rsext4/src/ext4/alloc.rs | 6 +- components/rsext4/src/ext4/mkfs.rs | 4 +- components/rsext4/src/ext4/mount.rs | 75 ++-- components/rsext4/src/ext4/sync.rs | 1 + components/rsext4/src/jbd2/jbd2.rs | 254 +++++++++--- components/rsext4/tests/crc_integrity.rs | 382 +++++++++++++++++- os/arceos/modules/axfs/src/partition.rs | 221 +++++++++- 10 files changed, 880 insertions(+), 115 deletions(-) diff --git a/components/rsext4/src/blockdev/cached_device.rs b/components/rsext4/src/blockdev/cached_device.rs index d9ea5ada9e..9b71aca64c 100644 --- a/components/rsext4/src/blockdev/cached_device.rs +++ b/components/rsext4/src/blockdev/cached_device.rs @@ -127,6 +127,23 @@ impl BlockDev { self.buffer.as_mut_slice() } + /// Replaces the cached block contents without writing to the device. + /// + /// The caller supplies the exact bytes that should now be considered the + /// authoritative content of `block_id` (e.g. a journal-side update or a + /// freshly written block). The buffer is overwritten with `data`, the + /// cache key is set to `block_id`, and the block is marked clean so that + /// no flush is triggered. + pub(crate) fn cache_clean_block( + &mut self, + block_id: AbsoluteBN, + data: &[u8; crate::config::BLOCK_SIZE], + ) { + self.buffer.as_mut_slice().copy_from_slice(data); + self.cached_block = Some(block_id); + self.is_dirty = false; + } + /// Flushes a dirty cached block and the underlying device. pub fn flush(&mut self) -> Ext4Result<()> { if self.is_dirty diff --git a/components/rsext4/src/blockdev/journal.rs b/components/rsext4/src/blockdev/journal.rs index 7b602c8360..b401aa2e45 100644 --- a/components/rsext4/src/blockdev/journal.rs +++ b/components/rsext4/src/blockdev/journal.rs @@ -190,6 +190,17 @@ impl Jbd2Dev { /// Reads one block through the cached inner device. pub fn read_block(&mut self, block_id: AbsoluteBN) -> Ext4Result<()> { + if self.journal_use + && let Some(system) = self.system.as_ref() + && let Some(update) = system + .commit_queue + .iter() + .find(|queued| queued.0 == block_id) + { + self.inner.cache_clean_block(block_id, &update.1); + return Ok(()); + } + self.inner.read_block(block_id) } diff --git a/components/rsext4/src/blockgroup_description/desc.rs b/components/rsext4/src/blockgroup_description/desc.rs index ca090840eb..64453f53d2 100644 --- a/components/rsext4/src/blockgroup_description/desc.rs +++ b/components/rsext4/src/blockgroup_description/desc.rs @@ -123,8 +123,8 @@ impl Ext4GroupDesc { self.used_dirs_count(), self.itable_unused(), self.bg_flags, - self.block_bitmap_csum(), - self.inode_bitmap_csum() + self.block_bitmap_csum(superblock), + self.inode_bitmap_csum(superblock) ); return Err(Ext4Error::checksum()); } @@ -172,13 +172,21 @@ impl Ext4GroupDesc { } /// Returns the 32-bit block bitmap checksum. - pub fn block_bitmap_csum(&self) -> u32 { - (self.bg_block_bitmap_csum_hi as u32) << 16 | self.bg_block_bitmap_csum_lo as u32 + pub fn block_bitmap_csum(&self, superblock: &Ext4Superblock) -> u32 { + if superblock.get_desc_size() as usize >= Self::EXT4_DESC_SIZE_64BIT { + (self.bg_block_bitmap_csum_hi as u32) << 16 | self.bg_block_bitmap_csum_lo as u32 + } else { + self.bg_block_bitmap_csum_lo as u32 + } } /// Returns the 32-bit inode bitmap checksum. - pub fn inode_bitmap_csum(&self) -> u32 { - (self.bg_inode_bitmap_csum_hi as u32) << 16 | self.bg_inode_bitmap_csum_lo as u32 + pub fn inode_bitmap_csum(&self, superblock: &Ext4Superblock) -> u32 { + if superblock.get_desc_size() as usize >= Self::EXT4_DESC_SIZE_64BIT { + (self.bg_inode_bitmap_csum_hi as u32) << 16 | self.bg_inode_bitmap_csum_lo as u32 + } else { + self.bg_inode_bitmap_csum_lo as u32 + } } /// Returns whether a computed bitmap checksum matches this descriptor. @@ -187,12 +195,12 @@ impl Ext4GroupDesc { /// descriptors. The high checksum fields are present only in 64-byte /// descriptors. pub fn block_bitmap_csum_matches(&self, superblock: &Ext4Superblock, computed: u32) -> bool { - Self::bitmap_csum_matches(superblock, self.block_bitmap_csum(), computed) + Self::bitmap_csum_matches(superblock, self.block_bitmap_csum(superblock), computed) } /// Returns whether a computed inode bitmap checksum matches this descriptor. pub fn inode_bitmap_csum_matches(&self, superblock: &Ext4Superblock, computed: u32) -> bool { - Self::bitmap_csum_matches(superblock, self.inode_bitmap_csum(), computed) + Self::bitmap_csum_matches(superblock, self.inode_bitmap_csum(superblock), computed) } fn bitmap_csum_matches(superblock: &Ext4Superblock, stored: u32, computed: u32) -> bool { diff --git a/components/rsext4/src/ext4/alloc.rs b/components/rsext4/src/ext4/alloc.rs index b1b18fb0d7..e79e667510 100644 --- a/components/rsext4/src/ext4/alloc.rs +++ b/components/rsext4/src/ext4/alloc.rs @@ -40,13 +40,13 @@ impl Ext4FileSystem { .bitmap_cache .get_or_load(block_dev, cache_key, bitmap_block)?; let expected = ext4_block_bitmap_csum32(&self.superblock, &bm.data); - let stored = desc.block_bitmap_csum(); + let stored = desc.block_bitmap_csum(&self.superblock); if !desc.block_bitmap_csum_matches(&self.superblock, expected) { error!( "alloc_blocks: block bitmap checksum mismatch group={group_idx} \ expected={expected:#x} stored={stored:#x}" ); - return Err(Ext4Error::checksum()); + return Err(Ext4Error::checksum().with_operation("alloc_blocks:block_bitmap")); } } @@ -182,7 +182,7 @@ impl Ext4FileSystem { .get_or_load(block_dev, cache_key, bitmap_block)?; let expected = ext4_inode_bitmap_csum32(&self.superblock, &bm.data); if !desc.inode_bitmap_csum_matches(&self.superblock, expected) { - return Err(Ext4Error::checksum()); + return Err(Ext4Error::checksum().with_operation("alloc_inode:inode_bitmap")); } } diff --git a/components/rsext4/src/ext4/mkfs.rs b/components/rsext4/src/ext4/mkfs.rs index fd33bb0927..627dde653f 100644 --- a/components/rsext4/src/ext4/mkfs.rs +++ b/components/rsext4/src/ext4/mkfs.rs @@ -165,10 +165,10 @@ fn mark_block_bitmap_padding(bitmap: &mut [u8], layout: &FsLayoutInfo, group_id: pub fn mkfs(block_dev: &mut Jbd2Dev) -> Ext4Result<()> { debug!("Start initializing Ext4 filesystem..."); + let old_journal_use = block_dev.is_use_journal(); // Disable journaling while laying out the initial filesystem image. The // journal inode and journal superblock do not exist yet at this stage. block_dev.set_journal_use(false); - let old_jouranl_use = block_dev.is_use_journal(); // Compute the full mkfs layout before any on-disk write happens. let total_blocks = block_dev.total_blocks(); @@ -235,7 +235,7 @@ pub fn mkfs(block_dev: &mut Jbd2Dev) -> Ext4Result<()> { let verify_sb = read_superblock(block_dev)?; // Restore the previous journal setting for the caller. - block_dev.set_journal_use(old_jouranl_use); + block_dev.set_journal_use(old_journal_use); if verify_sb.s_magic == EXT4_SUPER_MAGIC { debug!( diff --git a/components/rsext4/src/ext4/mount.rs b/components/rsext4/src/ext4/mount.rs index 54f245b95f..9ed878283d 100644 --- a/components/rsext4/src/ext4/mount.rs +++ b/components/rsext4/src/ext4/mount.rs @@ -50,6 +50,23 @@ impl Ext4FileSystem { self.superblock.s_feature_incompat &= !Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; } + fn set_recovery_state(&mut self) { + self.superblock.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; + } + + fn valid_lost_found_hint( + &mut self, + block_dev: &mut Jbd2Dev, + ) -> Ext4Result { + let ino = self.superblock.s_lpf_ino; + if ino == 0 { + return Ok(false); + } + + let inode = self.get_inode_by_num(block_dev, InodeNumber::new(ino)?)?; + Ok(inode.i_mode != 0 && inode.is_dir()) + } + fn journal_blocks( &mut self, block_dev: &mut Jbd2Dev, @@ -224,6 +241,8 @@ impl Ext4FileSystem { // mounting from the recovered on-disk state. fs.reload_after_journal_replay(block_dev)?; fs.clear_recovery_state(); + } else if block_dev.is_use_journal() { + fs.set_recovery_state(); } } // If the filesystem was created without a journal (e.g. small images @@ -236,7 +255,6 @@ impl Ext4FileSystem { } // rootinode check ! - debug!("Checking root directory..."); { let root_inode = fs.get_root(block_dev).map_err(|e| { error!("Failed to load root inode: {e}"); @@ -254,30 +272,36 @@ impl Ext4FileSystem { } // Verify the recovery directory after the root directory is known good. - debug!("Checking lost+found directory..."); { - // Trust the superblock hint when present, but still validate via a - // path lookup so stale metadata does not silently pass. - if fs.superblock.s_lpf_ino != 0 { + if fs.valid_lost_found_hint(block_dev)? { let ino = fs.superblock.s_lpf_ino; - debug!("Lost+found inode recorded in superblock: {ino}"); + info!("/lost+found exists (superblock hint inode={ino})"); } else { - debug!("s_lpf_ino is 0, lost+found inode hint missing in superblock"); - } - - match find_file(&mut fs, block_dev, "/lost+found") { - Ok(_inode) => { - info!("/lost+found exists (path resolution)"); + if fs.superblock.s_lpf_ino != 0 { + let ino = fs.superblock.s_lpf_ino; + warn!("s_lpf_ino={ino} is not a valid directory, falling back to path scan"); } - Err(err) if err.code == Errno::ENOENT => { - info!("/lost+found not found by path scan;will create!"); - if create_lost_found_directory(&mut fs, block_dev).is_err() { - warn!("/lost+found missing and create failed"); + + match get_file_inode(&mut fs, block_dev, "/lost+found") { + Ok(Some((ino, inode))) if inode.is_dir() => { + fs.superblock.s_lpf_ino = ino.raw(); + fs.sync_superblock(block_dev)?; + info!("/lost+found exists (path resolution, repaired hint inode={ino})"); + } + Ok(Some((_ino, _inode))) => { + error!("/lost+found exists but is not a directory"); + return Err(Ext4Error::corrupted()); + } + Ok(None) => { + info!("/lost+found not found by path scan;will create!"); + if create_lost_found_directory(&mut fs, block_dev).is_err() { + warn!("/lost+found missing and create failed"); + } + } + Err(err) => { + error!("Failed to resolve /lost+found: {err}"); + return Err(err); } - } - Err(err) => { - error!("Failed to resolve /lost+found: {err}"); - return Err(err); } } } @@ -310,9 +334,10 @@ impl Ext4FileSystem { if ext4_superblock_has_metadata_csum(&fs.superblock) { if !g0.is_inode_bitmap_uninit() { - let expected_inode = + let stored_inode = g0.inode_bitmap_csum(&fs.superblock); + let computed_inode = ext4_inode_bitmap_csum32(&fs.superblock, &inode_bitmap_data.data); - let stored_inode = g0.inode_bitmap_csum(); + let expected_inode = computed_inode; if !g0.inode_bitmap_csum_matches(&fs.superblock, expected_inode) { error!( "Inode bitmap checksum mismatch group=0 expected={expected_inode:#x} \ @@ -326,9 +351,10 @@ impl Ext4FileSystem { } if !g0.is_block_bitmap_uninit() { - let expected_block = + let stored_block = g0.block_bitmap_csum(&fs.superblock); + let computed_block = ext4_block_bitmap_csum32(&fs.superblock, &blockbitmap_data.data); - let stored_block = g0.block_bitmap_csum(); + let expected_block = computed_block; if !g0.block_bitmap_csum_matches(&fs.superblock, expected_block) { error!( "Block bitmap checksum mismatch group=0 expected={expected_block:#x} \ @@ -389,6 +415,7 @@ impl Ext4FileSystem { // The superblock is written with EXT4_VALID_FS cleared so a later mount // can distinguish an unclean shutdown from a real EXT4_ERROR_FS state. fs.sync_filesystem(block_dev)?; + block_dev.umount_commit(); Ok(fs) } diff --git a/components/rsext4/src/ext4/sync.rs b/components/rsext4/src/ext4/sync.rs index 7c8f5326cd..1656c327d3 100644 --- a/components/rsext4/src/ext4/sync.rs +++ b/components/rsext4/src/ext4/sync.rs @@ -31,6 +31,7 @@ impl Ext4FileSystem { // Mark clean in memory first so that sync_filesystem writes the // superblock with s_state = EXT4_VALID_FS through the journal. self.superblock.s_state = Self::clean_state(&self.superblock); + self.superblock.s_feature_incompat &= !Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; self.sync_filesystem(block_dev)?; diff --git a/components/rsext4/src/jbd2/jbd2.rs b/components/rsext4/src/jbd2/jbd2.rs index 8f8f5aaac6..ad6c0dffa8 100644 --- a/components/rsext4/src/jbd2/jbd2.rs +++ b/components/rsext4/src/jbd2/jbd2.rs @@ -26,6 +26,12 @@ struct ReplayTag { flags: u32, } +#[derive(Debug, Clone, Copy)] +struct ReplayPayload { + tag: ReplayTag, + journal_rel: u32, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum ReplayStatus { Complete, @@ -34,9 +40,22 @@ pub(crate) enum ReplayStatus { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ReplayScan { - CleanEnd, - Incomplete { restart_rel: u32 }, - Applied { next_rel: u32, next_seq: u32 }, + CleanEnd { + records: u32, + }, + Incomplete { + restart_rel: u32, + records: u32, + }, + Applied { + next_rel: u32, + next_seq: u32, + records: u32, + payloads: usize, + applied_payloads: usize, + journal_read_ops: usize, + home_write_ops: usize, + }, } struct ReplayRing<'a> { @@ -492,11 +511,6 @@ impl JBD2DEVSYSTEM { Ok(true) } - /// Replays as many complete committed transactions as possible. - pub fn replay(&mut self, block_dev: &mut B) { - let _ = self.replay_with_mapping(block_dev, &[]); - } - fn replay_one_transaction( &self, block_dev: &mut B, @@ -505,15 +519,19 @@ impl JBD2DEVSYSTEM { expect_seq: u32, ) -> ReplayScan { let mut record_rel = start_rel; - let mut meta_blocks: Vec<(ReplayTag, [u8; BLOCK_SIZE])> = Vec::new(); + let mut payloads: Vec = Vec::new(); let mut revoked_blocks = BTreeSet::new(); + let max_records = ring.last_rel - ring.first_rel + 1; + let mut records = 0u32; - loop { + for _ in 0..max_records { + records = records.saturating_add(1); let record_phys = match ring.phys(record_rel) { Ok(block) => block, Err(_) => { return ReplayScan::Incomplete { restart_rel: start_rel, + records, }; } }; @@ -525,6 +543,7 @@ impl JBD2DEVSYSTEM { ); return ReplayScan::Incomplete { restart_rel: start_rel, + records, }; } @@ -536,56 +555,40 @@ impl JBD2DEVSYSTEM { ); if hdr.h_magic != JBD2_MAGIC || hdr.h_sequence != expect_seq { - return if record_rel == start_rel { - ReplayScan::CleanEnd - } else { - ReplayScan::Incomplete { - restart_rel: start_rel, - } - }; + return ReplayScan::CleanEnd { records }; } match hdr.h_blocktype { JBD2_BLOCKTYPE_DESCRIPTOR => { let tags = match self.parse_replay_tags(&record_buf, expect_seq) { Some(tags) if !tags.is_empty() => tags, - _ => { + Some(tags) => tags, + None => { return ReplayScan::Incomplete { restart_rel: start_rel, + records, }; } }; - for (idx, tag) in tags.iter().enumerate() { + for tag in tags { ring.advance(&mut record_rel); - let meta_phys = match ring.phys(record_rel) { - Ok(block) => block, - Err(_) => { - return ReplayScan::Incomplete { - restart_rel: start_rel, - }; - } - }; - let mut mbuf = [0u8; BLOCK_SIZE]; - if let Err(e) = block_dev.read(&mut mbuf, meta_phys, 1) { - debug!( - "[JBD2 replay] read meta block failed: idx={idx} \ - rel_block={record_rel} phys_block={meta_phys} err={e:?}" - ); + if ring.phys(record_rel).is_err() { return ReplayScan::Incomplete { restart_rel: start_rel, + records, }; } - debug!( - "[JBD2 replay] tid={expect_seq} loaded meta_idx={idx} from \ - rel_block={record_rel} phys_block={meta_phys}" - ); - meta_blocks.push((*tag, mbuf)); + payloads.push(ReplayPayload { + tag, + journal_rel: record_rel, + }); } } JBD2_BLOCKTYPE_COMMIT => { - for (idx, (tag, data)) in meta_blocks.iter_mut().enumerate() { - let phys = tag.block; + let mut committed: Vec<(usize, ReplayPayload, AbsoluteBN)> = Vec::new(); + for (idx, payload) in payloads.iter().copied().enumerate() { + let phys = payload.tag.block; if revoked_blocks.contains(&phys) { debug!( "[JBD2 replay] tid={expect_seq} skip revoked meta_idx={idx} \ @@ -594,29 +597,102 @@ impl JBD2DEVSYSTEM { continue; } - if (tag.flags & u32::from(JOURNAL_ESCAPE)) != 0 { - data[0..4].copy_from_slice(&JBD2_MAGIC.to_be_bytes()); - debug!("[JBD2 replay] restored escaped journal magic for block {phys}"); + let meta_phys = match ring.phys(payload.journal_rel) { + Ok(block) => block, + Err(_) => { + return ReplayScan::Incomplete { + restart_rel: start_rel, + records, + }; + } + }; + committed.push((idx, payload, meta_phys)); + } + + let mut applied_payloads = 0usize; + let mut journal_read_ops = 0usize; + let mut home_write_ops = 0usize; + let mut pos = 0usize; + while pos < committed.len() { + let run_start = pos; + let mut run_end = pos + 1; + while run_end < committed.len() + && committed[run_end].2.raw() + == committed[run_end - 1].2.raw().saturating_add(1) + { + run_end += 1; } - debug!( - "[JBD2 replay] tid={expect_seq} apply meta_idx={idx} phys_block={phys}" - ); - if let Err(e) = block_dev.write(data, phys, 1) { + let run_len = run_end - run_start; + let first_journal = committed[run_start].2; + let mut data = vec![0u8; run_len * BLOCK_SIZE]; + if let Err(e) = block_dev.read(&mut data, first_journal, run_len as u32) { debug!( - "[JBD2 replay] write meta block failed: idx={idx} \ - phys_block={phys} err={e:?}" + "[JBD2 replay] read committed meta run failed: start_idx={} \ + phys_block={} count={} err={e:?}", + committed[run_start].0, first_journal, run_len ); return ReplayScan::Incomplete { restart_rel: start_rel, + records, }; } - } - if let Err(e) = block_dev.flush() { - debug!("[JBD2 replay] flush after transaction failed: err={e:?}"); - return ReplayScan::Incomplete { - restart_rel: start_rel, - }; + journal_read_ops = journal_read_ops.saturating_add(1); + + let mut write_start = 0usize; + while write_start < run_len { + let first = run_start + write_start; + let first_home = committed[first].1.tag.block; + let mut write_end = write_start + 1; + while write_end < run_len { + let prev = run_start + write_end - 1; + let next = run_start + write_end; + let prev_home = committed[prev].1.tag.block.raw(); + let next_home = committed[next].1.tag.block.raw(); + if next_home != prev_home.saturating_add(1) { + break; + } + write_end += 1; + } + + for rel_idx in write_start..write_end { + let absolute_idx = run_start + rel_idx; + let (idx, payload, _) = committed[absolute_idx]; + let off = rel_idx * BLOCK_SIZE; + if (payload.tag.flags & u32::from(JOURNAL_ESCAPE)) != 0 { + data[off..off + 4].copy_from_slice(&JBD2_MAGIC.to_be_bytes()); + debug!( + "[JBD2 replay] restored escaped journal magic for block {}", + payload.tag.block + ); + } + debug!( + "[JBD2 replay] tid={expect_seq} apply meta_idx={idx} \ + phys_block={}", + payload.tag.block + ); + } + + let off = write_start * BLOCK_SIZE; + let bytes = &data[off..write_end * BLOCK_SIZE]; + let write_count = write_end - write_start; + if let Err(e) = block_dev.write(bytes, first_home, write_count as u32) { + debug!( + "[JBD2 replay] write meta run failed: start_idx={} \ + home_block={} count={} err={e:?}", + committed[first].0, first_home, write_count + ); + return ReplayScan::Incomplete { + restart_rel: start_rel, + records, + }; + } + home_write_ops = home_write_ops.saturating_add(1); + applied_payloads = applied_payloads.saturating_add(write_count); + write_start = write_end; + } + + pos = run_end; } let mut next_rel = record_rel; @@ -624,6 +700,11 @@ impl JBD2DEVSYSTEM { return ReplayScan::Applied { next_rel, next_seq: expect_seq.wrapping_add(1), + records, + payloads: payloads.len(), + applied_payloads, + journal_read_ops, + home_write_ops, }; } JBD2_BLOCKTYPE_REVOKE => { @@ -632,24 +713,24 @@ impl JBD2DEVSYSTEM { None => { return ReplayScan::Incomplete { restart_rel: start_rel, + records, }; } }; revoked_blocks.extend(blocks); } _ => { - return if record_rel == start_rel { - ReplayScan::CleanEnd - } else { - ReplayScan::Incomplete { - restart_rel: start_rel, - } - }; + return ReplayScan::CleanEnd { records }; } } ring.advance(&mut record_rel); } + + ReplayScan::Incomplete { + restart_rel: start_rel, + records, + } } /// Replays committed transactions using the journal inode logical-block map. @@ -692,9 +773,32 @@ impl JBD2DEVSYSTEM { self.start_block, ring.first_rel, ring.last_rel, journal_rel, maxlen, expect_seq, ); + let mut total_transactions = 0usize; + let mut total_records = 0u64; + let mut total_payloads = 0usize; + let mut total_applied_payloads = 0usize; + let mut total_journal_read_ops = 0usize; + let mut total_home_write_ops = 0usize; + let status = loop { match self.replay_one_transaction(block_dev, &ring, journal_rel, expect_seq) { - ReplayScan::Applied { next_rel, next_seq } => { + ReplayScan::Applied { + next_rel, + next_seq, + records, + payloads, + applied_payloads, + journal_read_ops, + home_write_ops, + } => { + total_transactions = total_transactions.saturating_add(1); + total_records = total_records.saturating_add(u64::from(records)); + total_payloads = total_payloads.saturating_add(payloads); + total_applied_payloads = + total_applied_payloads.saturating_add(applied_payloads); + total_journal_read_ops = + total_journal_read_ops.saturating_add(journal_read_ops); + total_home_write_ops = total_home_write_ops.saturating_add(home_write_ops); journal_rel = next_rel; expect_seq = next_seq; self.jbd2_super_block.s_start = journal_rel; @@ -705,11 +809,18 @@ impl JBD2DEVSYSTEM { self.jbd2_super_block.s_sequence, self.jbd2_super_block.s_start ); } - ReplayScan::CleanEnd => { + ReplayScan::CleanEnd { records } => { + total_records = total_records.saturating_add(u64::from(records)); self.jbd2_super_block.s_start = 0; + self.jbd2_super_block.s_sequence = expect_seq; + self.sequence = expect_seq; break ReplayStatus::Complete; } - ReplayScan::Incomplete { restart_rel } => { + ReplayScan::Incomplete { + restart_rel, + records, + } => { + total_records = total_records.saturating_add(u64::from(records)); self.jbd2_super_block.s_start = restart_rel; self.jbd2_super_block.s_sequence = expect_seq; self.sequence = expect_seq; @@ -734,8 +845,17 @@ impl JBD2DEVSYSTEM { let _ = block_dev.flush(); } debug!( - "[JBD2 replay] end: final_sequence={} final_s_start={} ", - self.jbd2_super_block.s_sequence, self.jbd2_super_block.s_start + "[JBD2 replay] end: status={status:?} transactions={} records={} payloads={} \ + applied_payloads={} journal_read_ops={} home_write_ops={} final_sequence={} \ + final_s_start={}", + total_transactions, + total_records, + total_payloads, + total_applied_payloads, + total_journal_read_ops, + total_home_write_ops, + self.jbd2_super_block.s_sequence, + self.jbd2_super_block.s_start ); status diff --git a/components/rsext4/tests/crc_integrity.rs b/components/rsext4/tests/crc_integrity.rs index f6702bc317..1eed0f71a1 100644 --- a/components/rsext4/tests/crc_integrity.rs +++ b/components/rsext4/tests/crc_integrity.rs @@ -20,7 +20,11 @@ use rsext4::{ }, endian::DiskFormat, error::{Errno, Ext4Error, Ext4Result}, - jbd2::jbdstruct::{JBD2_BLOCKTYPE_DESCRIPTOR, JBD2_MAGIC, JournalHeaderS, JournalSuperBllockS}, + jbd2::jbdstruct::{ + JBD2_BLOCKTYPE_DESCRIPTOR, JBD2_BLOCKTYPE_REVOKE, JBD2_FLAG_LAST_TAG, JBD2_FLAG_SAME_UUID, + JBD2_MAGIC, JBD2_UUID_SIZE, JournalBlockTagS, JournalHeaderS, JournalSuperBllockS, + }, + loopfile::resolve_inode_block, superblock::Ext4Superblock, *, }; @@ -32,6 +36,7 @@ struct SharedCrcDevice { data: Rc>>, block_size: u32, now: Rc>, + blocked_read_block: Rc>>, } impl SharedCrcDevice { @@ -40,6 +45,7 @@ impl SharedCrcDevice { data: Rc::new(RefCell::new(vec![0; size])), block_size: BLOCK_SIZE as u32, now: Rc::new(Cell::new(1_700_000_000)), + blocked_read_block: Rc::new(Cell::new(None)), } } @@ -62,6 +68,9 @@ impl SharedCrcDevice { impl BlockDevice for SharedCrcDevice { fn read(&mut self, buffer: &mut [u8], block_id: AbsoluteBN, _count: u32) -> Ext4Result<()> { + if self.blocked_read_block.get() == Some(block_id.raw()) { + return Err(Ext4Error::io()); + } let start = block_id.as_usize()? * self.block_size as usize; let end = start + buffer.len(); if end > self.data.borrow().len() { @@ -174,6 +183,121 @@ fn write_incomplete_journal_descriptor(device: &SharedCrcDevice, journal_block: device.write_block_bytes(journal_block + 1, &descriptor); } +fn write_uncommitted_journal_update( + device: &SharedCrcDevice, + journal_block: u64, + target_block: u64, + payload: &[u8], +) { + let bytes = device.read_block_bytes(journal_block); + let journal_sb = JournalSuperBllockS::from_disk_bytes(&bytes); + + let mut descriptor = vec![0u8; BLOCK_SIZE]; + JournalHeaderS { + h_magic: JBD2_MAGIC, + h_blocktype: JBD2_BLOCKTYPE_DESCRIPTOR, + h_sequence: journal_sb.s_sequence, + } + .to_disk_bytes(&mut descriptor); + JournalBlockTagS { + t_blocknr: target_block as u32, + t_checksum: 0, + t_flags: JBD2_FLAG_LAST_TAG, + } + .to_disk_bytes(&mut descriptor[12..20]); + descriptor[20..20 + JBD2_UUID_SIZE].copy_from_slice(&journal_sb.s_uuid); + device.write_block_bytes(journal_block + 1, &descriptor); + + let mut metadata = vec![0u8; BLOCK_SIZE]; + metadata[..payload.len()].copy_from_slice(payload); + device.write_block_bytes(journal_block + 2, &metadata); +} + +fn write_invalid_journal_revoke(device: &SharedCrcDevice, journal_block: u64) { + let bytes = device.read_block_bytes(journal_block); + let journal_sb = JournalSuperBllockS::from_disk_bytes(&bytes); + + let mut revoke = vec![0u8; BLOCK_SIZE]; + JournalHeaderS { + h_magic: JBD2_MAGIC, + h_blocktype: JBD2_BLOCKTYPE_REVOKE, + h_sequence: journal_sb.s_sequence, + } + .to_disk_bytes(&mut revoke); + revoke[12..16].copy_from_slice(&((BLOCK_SIZE as u32) + 1).to_be_bytes()); + device.write_block_bytes(journal_block + 1, &revoke); +} + +fn write_repeating_journal_descriptors(device: &SharedCrcDevice, journal_block: u64) { + let bytes = device.read_block_bytes(journal_block); + let journal_sb = JournalSuperBllockS::from_disk_bytes(&bytes); + + let mut descriptor = vec![0u8; BLOCK_SIZE]; + JournalHeaderS { + h_magic: JBD2_MAGIC, + h_blocktype: JBD2_BLOCKTYPE_DESCRIPTOR, + h_sequence: journal_sb.s_sequence, + } + .to_disk_bytes(&mut descriptor); + JournalBlockTagS { + t_blocknr: (journal_block - 1) as u32, + t_checksum: 0, + t_flags: JBD2_FLAG_LAST_TAG, + } + .to_disk_bytes(&mut descriptor[12..20]); + descriptor[20..20 + JBD2_UUID_SIZE].copy_from_slice(&journal_sb.s_uuid); + + for rel in journal_sb.s_first..journal_sb.s_maxlen { + device.write_block_bytes(journal_block + u64::from(rel), &descriptor); + } +} + +fn write_uncommitted_journal_updates( + device: &SharedCrcDevice, + journal_block: u64, + target_blocks: &[u64], +) { + let bytes = device.read_block_bytes(journal_block); + let journal_sb = JournalSuperBllockS::from_disk_bytes(&bytes); + + let mut descriptor = vec![0u8; BLOCK_SIZE]; + JournalHeaderS { + h_magic: JBD2_MAGIC, + h_blocktype: JBD2_BLOCKTYPE_DESCRIPTOR, + h_sequence: journal_sb.s_sequence, + } + .to_disk_bytes(&mut descriptor); + + let mut offset = 12usize; + for (idx, target) in target_blocks.iter().enumerate() { + let mut flags = 0; + if idx > 0 { + flags |= JBD2_FLAG_SAME_UUID; + } + if idx == target_blocks.len() - 1 { + flags |= JBD2_FLAG_LAST_TAG; + } + JournalBlockTagS { + t_blocknr: *target as u32, + t_checksum: 0, + t_flags: flags, + } + .to_disk_bytes(&mut descriptor[offset..offset + 8]); + offset += 8; + if idx == 0 { + descriptor[offset..offset + JBD2_UUID_SIZE].copy_from_slice(&journal_sb.s_uuid); + offset += JBD2_UUID_SIZE; + } + } + device.write_block_bytes(journal_block + 1, &descriptor); + + for (idx, _) in target_blocks.iter().enumerate() { + let mut metadata = vec![0u8; BLOCK_SIZE]; + metadata[..8].copy_from_slice(&(idx as u64).to_le_bytes()); + device.write_block_bytes(journal_block + 2 + idx as u64, &metadata); + } +} + #[test] fn checksums_are_persisted_and_clean_remount_preserves_the_written_file() { // Test idea: write one real file, inspect the raw on-disk checksum fields, @@ -197,11 +321,11 @@ fn checksums_are_persisted_and_clean_remount_preserves_the_written_file() { let block_bitmap = device.read_block_bytes(desc.block_bitmap()); let inode_bitmap = device.read_block_bytes(desc.inode_bitmap()); assert_eq!( - desc.block_bitmap_csum(), + desc.block_bitmap_csum(&sb), ext4_block_bitmap_csum32(&sb, &block_bitmap) ); assert_eq!( - desc.inode_bitmap_csum(), + desc.inode_bitmap_csum(&sb), ext4_inode_bitmap_csum32(&sb, &inode_bitmap) ); @@ -238,11 +362,13 @@ fn old_32_byte_descriptors_match_low_16_bits_of_bitmap_checksums() { } #[test] -fn clean_mount_does_not_replay_journal_without_recovery_feature() { - // Test idea: normal journaled operation may leave a non-zero journal - // superblock start value, but ext4 recovery is driven by the superblock - // needs_recovery bit. A clean mount should initialize journal state for - // future writes without treating the journal as mandatory recovery input. +fn incomplete_journal_is_not_replayed_when_recovery_flag_is_clear() { + // Test idea: ext4 recovery is driven by the superblock needs_recovery bit, + // not by leftover journal state. If we clear that bit on disk and leave a + // deliberately broken journal descriptor behind, the next mount must + // ignore the journal contents instead of trying to replay them. The mount + // itself will still set needs_recovery for its own writable session, and a + // clean umount must clear it again before the test ends. let device = SharedCrcDevice::new(100 * 1024 * 1024); let mut jbd2_dev = new_jbd2_dev(device.clone()); mkfs(&mut jbd2_dev).expect("mkfs failed"); @@ -262,14 +388,252 @@ fn clean_mount_does_not_replay_journal_without_recovery_feature() { write_journal_start(&device, journal_block, 1); write_incomplete_journal_descriptor(&device, journal_block); + let clean_mount_sb = read_superblock(&device); + assert_eq!( + clean_mount_sb.s_feature_incompat & Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER, + 0 + ); + let mut remount_dev = new_jbd2_dev(device.clone()); let fs = mount(&mut remount_dev).expect("clean mount should not force journal replay"); - assert_eq!( + assert_ne!( fs.superblock.s_feature_incompat & Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER, 0 ); assert!(remount_dev.is_use_journal()); umount(fs, &mut remount_dev).expect("umount failed"); + + let clean_unmount_sb = read_superblock(&device); + assert_eq!( + clean_unmount_sb.s_feature_incompat & Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER, + 0 + ); + assert_ne!(clean_unmount_sb.s_lpf_ino, 0); +} + +#[test] +fn uncommitted_journal_tail_is_discarded_during_recovery() { + // Test idea: an unclean shutdown may leave a descriptor for a transaction + // that never reached its commit block. The transaction is not durable, so + // recovery must discard the tail instead of failing the whole mount. + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let mut first_mount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut first_mount_dev).expect("mount failed"); + let journal_block = fs + .journal_sb_block_start + .expect("journal superblock should be mapped") + .raw(); + umount(fs, &mut first_mount_dev).expect("umount failed"); + + let mut sb = read_superblock(&device); + sb.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; + sb.update_checksum(); + write_superblock(&device, &sb); + let target_block = journal_block - 1; + let original_target = device.read_block_bytes(target_block); + write_journal_start(&device, journal_block, 1); + write_uncommitted_journal_update( + &device, + journal_block, + target_block, + b"uncommitted metadata payload", + ); + + let mut remount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut remount_dev).expect("mount should discard uncommitted journal tail"); + assert_eq!( + fs.superblock.s_feature_incompat & Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER, + 0 + ); + umount(fs, &mut remount_dev).expect("umount failed"); + + assert_eq!(device.read_block_bytes(target_block), original_target); + + let recovered_journal = device.read_block_bytes(journal_block); + let recovered_journal_sb = JournalSuperBllockS::from_disk_bytes(&recovered_journal); + assert_eq!(recovered_journal_sb.s_start, 0); +} + +#[test] +fn uncommitted_journal_tail_does_not_read_payload_blocks() { + // Test idea: Linux JBD2 first scans control records to find a commit block. + // Payload blocks from an uncommitted transaction must not be read during recovery. + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let mut first_mount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut first_mount_dev).expect("mount failed"); + let journal_block = fs + .journal_sb_block_start + .expect("journal superblock should be mapped") + .raw(); + umount(fs, &mut first_mount_dev).expect("umount failed"); + + let mut sb = read_superblock(&device); + sb.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; + sb.update_checksum(); + write_superblock(&device, &sb); + write_journal_start(&device, journal_block, 1); + write_uncommitted_journal_updates( + &device, + journal_block, + &[journal_block - 1, journal_block - 2], + ); + device.blocked_read_block.set(Some(journal_block + 2)); + + let mut remount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut remount_dev).expect("uncommitted payload should not be read"); + assert_eq!( + fs.superblock.s_feature_incompat & Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER, + 0 + ); + umount(fs, &mut remount_dev).expect("umount failed"); +} + +#[test] +fn invalid_revoke_record_fails_recovery() { + // Test idea: Linux JBD2 treats an expected-sequence revoke block with an + // invalid record count as journal corruption, not as an uncommitted tail. + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let mut first_mount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut first_mount_dev).expect("mount failed"); + let journal_block = fs + .journal_sb_block_start + .expect("journal superblock should be mapped") + .raw(); + umount(fs, &mut first_mount_dev).expect("umount failed"); + + let mut sb = read_superblock(&device); + sb.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; + sb.update_checksum(); + write_superblock(&device, &sb); + write_journal_start(&device, journal_block, 1); + write_invalid_journal_revoke(&device, journal_block); + + let mut remount_dev = new_jbd2_dev(device); + let err = match mount(&mut remount_dev) { + Ok(_) => panic!("invalid revoke block should fail recovery"), + Err(err) => err, + }; + assert_eq!(err.code, Errno::EUCLEAN); +} + +#[test] +fn empty_descriptor_header_is_discarded_during_recovery() { + // Test idea: a crash can leave only the descriptor header without any tags. + // With no commit block, this is an uncommitted tail rather than durable work. + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let mut first_mount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut first_mount_dev).expect("mount failed"); + let journal_block = fs + .journal_sb_block_start + .expect("journal superblock should be mapped") + .raw(); + umount(fs, &mut first_mount_dev).expect("umount failed"); + + let mut sb = read_superblock(&device); + sb.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; + sb.update_checksum(); + write_superblock(&device, &sb); + write_journal_start(&device, journal_block, 1); + write_incomplete_journal_descriptor(&device, journal_block); + + let mut remount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut remount_dev).expect("mount should discard empty descriptor tail"); + assert_eq!( + fs.superblock.s_feature_incompat & Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER, + 0 + ); + umount(fs, &mut remount_dev).expect("umount failed"); +} + +#[test] +fn replay_scan_is_bounded_by_journal_ring_length() { + // Test idea: malformed journal contents that keep looking like the expected + // sequence must not make recovery loop forever around the journal ring. + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let mut first_mount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut first_mount_dev).expect("mount failed"); + let journal_block = fs + .journal_sb_block_start + .expect("journal superblock should be mapped") + .raw(); + umount(fs, &mut first_mount_dev).expect("umount failed"); + + let mut sb = read_superblock(&device); + sb.s_feature_incompat |= Ext4Superblock::EXT4_FEATURE_INCOMPAT_RECOVER; + sb.update_checksum(); + write_superblock(&device, &sb); + write_journal_start(&device, journal_block, 1); + write_repeating_journal_descriptors(&device, journal_block); + + let mut remount_dev = new_jbd2_dev(device); + let err = match mount(&mut remount_dev) { + Ok(_) => panic!("cyclic journal scan should fail recovery"), + Err(err) => err, + }; + assert_eq!(err.code, Errno::EUCLEAN); +} + +#[test] +fn path_resolved_lost_found_rebuilds_superblock_hint() { + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let clean_sb = read_superblock(&device); + assert_ne!(clean_sb.s_lpf_ino, 0); + + let mut missing_hint = clean_sb; + missing_hint.s_lpf_ino = 0; + missing_hint.update_checksum(); + write_superblock(&device, &missing_hint); + + let mut remount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut remount_dev).expect("mount should resolve existing lost+found"); + assert_ne!(fs.superblock.s_lpf_ino, 0); + assert_eq!(fs.superblock.s_lpf_ino, clean_sb.s_lpf_ino); + umount(fs, &mut remount_dev).expect("umount failed"); + + let repaired_sb = read_superblock(&device); + assert_eq!(repaired_sb.s_lpf_ino, clean_sb.s_lpf_ino); +} + +#[test] +fn mount_uses_valid_lost_found_hint_without_root_path_scan() { + let device = SharedCrcDevice::new(100 * 1024 * 1024); + let mut jbd2_dev = new_jbd2_dev(device.clone()); + mkfs(&mut jbd2_dev).expect("mkfs failed"); + + let mut inspect_dev = new_jbd2_dev(device.clone()); + let mut fs = mount(&mut inspect_dev).expect("mount failed"); + let mut root = fs.get_root(&mut inspect_dev).expect("root inode"); + let root_block = resolve_inode_block(&mut inspect_dev, &mut root, 0) + .expect("resolve root block") + .expect("root directory block") + .raw(); + umount(fs, &mut inspect_dev).expect("umount failed"); + + let clean_sb = read_superblock(&device); + assert_ne!(clean_sb.s_lpf_ino, 0); + + device.blocked_read_block.set(Some(root_block)); + let mut remount_dev = new_jbd2_dev(device.clone()); + let fs = mount(&mut remount_dev).expect("mount should trust valid lost+found hint"); + assert_eq!(fs.superblock.s_lpf_ino, clean_sb.s_lpf_ino); } #[test] diff --git a/os/arceos/modules/axfs/src/partition.rs b/os/arceos/modules/axfs/src/partition.rs index d45156d556..e37fae8383 100644 --- a/os/arceos/modules/axfs/src/partition.rs +++ b/os/arceos/modules/axfs/src/partition.rs @@ -114,7 +114,20 @@ pub fn scan_gpt_partitions(disk: &mut Disk) -> AxResult> { } } - // If both GPT fail, treat the whole disk as a single partition + match parse_mbr_partitions(disk, disk_size) { + Ok(partitions) if !partitions.is_empty() => { + info!("Found {} MBR partitions", partitions.len()); + return Ok(partitions); + } + Ok(_) => { + info!("No MBR partitions found"); + } + Err(e) => { + warn!("Failed to parse MBR: {:?}", e); + } + } + + // If both GPT and MBR fail, treat the whole disk as a single partition warn!("No partition table found, treating whole disk as single partition"); let filesystem_type = detect_filesystem_type(disk, 0); let partition = PartitionInfo { @@ -124,7 +137,7 @@ pub fn scan_gpt_partitions(disk: &mut Disk) -> AxResult> { unique_partition_guid: [0; 16], filesystem_uuid: None, starting_lba: 0, - ending_lba: disk_size / 512, + ending_lba: disk_size.saturating_div(512).saturating_sub(1), size_bytes: disk_size, filesystem_type, }; @@ -132,6 +145,210 @@ pub fn scan_gpt_partitions(disk: &mut Disk) -> AxResult> { Ok(vec![partition]) } +fn parse_mbr_partitions(disk: &mut Disk, disk_size: u64) -> AxResult> { + let mut sector = [0u8; 512]; + disk.set_position(0); + read_exact(disk, &mut sector).map_err(|_| AxError::InvalidData)?; + + let disk_blocks = disk_size.saturating_div(512); + let mut partitions = + parse_mbr_partition_entries_with_reader(§or, disk_blocks, |lba, sector| { + disk.set_position(lba * 512); + read_exact(disk, sector).map_err(|_| AxError::InvalidData) + })?; + for partition in &mut partitions { + partition.filesystem_type = detect_filesystem_type(disk, partition.starting_lba); + partition.filesystem_uuid = partition + .filesystem_type + .as_ref() + .and_then(|fs| read_filesystem_uuid_simple(disk, partition.starting_lba, fs)); + info!( + "Found MBR partition {}: '{}' ({} bytes) with filesystem: {:?}, UUID: {:?}", + partition.index, + partition.name, + partition.size_bytes, + partition.filesystem_type, + partition.filesystem_uuid, + ); + } + + Ok(partitions) +} + +#[cfg(test)] +fn parse_mbr_partition_entries( + sector: &[u8; 512], + disk_blocks: u64, +) -> AxResult> { + parse_mbr_partition_entries_with_reader(sector, disk_blocks, |_, _| Err(AxError::Unsupported)) +} + +fn parse_mbr_partition_entries_with_reader( + sector: &[u8; 512], + disk_blocks: u64, + mut read_sector: impl FnMut(u64, &mut [u8; 512]) -> AxResult, +) -> AxResult> { + if !has_mbr_signature(sector) { + return Err(AxError::InvalidData); + } + + let mut partitions = Vec::new(); + for index in 0..4 { + let entry = MbrPartitionEntry::parse(sector, index); + if entry.is_unused() || entry.is_protective() { + continue; + } + if entry.is_extended() { + parse_extended_mbr_partitions( + entry.starting_lba, + disk_blocks, + &mut read_sector, + &mut partitions, + )?; + continue; + } + push_mbr_partition(&mut partitions, index as u32, entry, disk_blocks); + } + + Ok(partitions) +} + +fn parse_extended_mbr_partitions( + extended_base_lba: u64, + disk_blocks: u64, + read_sector: &mut impl FnMut(u64, &mut [u8; 512]) -> AxResult, + partitions: &mut Vec, +) -> AxResult { + let mut ebr_lba = extended_base_lba; + let mut visited = 0; + while visited < 128 { + let mut sector = [0u8; 512]; + read_sector(ebr_lba, &mut sector)?; + if !has_mbr_signature(§or) { + warn!( + "Stopping extended MBR scan at LBA {}: invalid EBR signature", + ebr_lba + ); + break; + } + + let logical = MbrPartitionEntry::parse(§or, 0); + if !logical.is_unused() && !logical.is_extended() && !logical.is_protective() { + let Some(starting_lba) = ebr_lba.checked_add(logical.starting_lba) else { + warn!("Skipping logical MBR partition with overflowing start LBA"); + break; + }; + let entry = MbrPartitionEntry { + starting_lba, + ..logical + }; + push_mbr_partition(partitions, partitions.len() as u32 + 4, entry, disk_blocks); + } + + let next = MbrPartitionEntry::parse(§or, 1); + if !next.is_extended() || next.starting_lba == 0 || next.sector_count == 0 { + break; + } + let Some(next_ebr_lba) = extended_base_lba.checked_add(next.starting_lba) else { + warn!("Stopping extended MBR scan after overflowing next EBR LBA"); + break; + }; + if next_ebr_lba == ebr_lba { + warn!( + "Stopping extended MBR scan at self-referential EBR LBA {}", + ebr_lba + ); + break; + } + ebr_lba = next_ebr_lba; + visited += 1; + } + + if visited == 128 { + warn!("Stopping extended MBR scan after reaching EBR chain limit"); + } + + Ok(()) +} + +fn push_mbr_partition( + partitions: &mut Vec, + index: u32, + entry: MbrPartitionEntry, + disk_blocks: u64, +) { + let Some(ending_lba) = entry.starting_lba.checked_add(entry.sector_count - 1) else { + warn!( + "Skipping MBR partition {} with overflowing LBA range", + index + ); + return; + }; + if disk_blocks != 0 && ending_lba >= disk_blocks { + warn!( + "Skipping MBR partition {} outside disk: start={}, sectors={}, disk_blocks={}", + index, entry.starting_lba, entry.sector_count, disk_blocks + ); + return; + } + + partitions.push(PartitionInfo { + index, + name: format!("partition_{}", index), + partition_type_guid: [0; 16], + unique_partition_guid: [0; 16], + filesystem_uuid: None, + starting_lba: entry.starting_lba, + ending_lba, + size_bytes: entry.sector_count * 512, + filesystem_type: None, + }); +} + +fn has_mbr_signature(sector: &[u8; 512]) -> bool { + sector[510] == 0x55 && sector[511] == 0xaa +} + +#[derive(Clone, Copy)] +struct MbrPartitionEntry { + partition_type: u8, + starting_lba: u64, + sector_count: u64, +} + +impl MbrPartitionEntry { + fn parse(sector: &[u8; 512], index: usize) -> Self { + let offset = 446 + index * 16; + Self { + partition_type: sector[offset + 4], + starting_lba: u32::from_le_bytes([ + sector[offset + 8], + sector[offset + 9], + sector[offset + 10], + sector[offset + 11], + ]) as u64, + sector_count: u32::from_le_bytes([ + sector[offset + 12], + sector[offset + 13], + sector[offset + 14], + sector[offset + 15], + ]) as u64, + } + } + + fn is_unused(&self) -> bool { + self.partition_type == 0 || self.starting_lba == 0 || self.sector_count == 0 + } + + fn is_extended(&self) -> bool { + matches!(self.partition_type, 0x05 | 0x0f | 0x85) + } + + fn is_protective(&self) -> bool { + self.partition_type == 0xee + } +} + /// Parse GPT partition table fn parse_gpt_partitions(disk: &mut Disk) -> AxResult> { let mut partitions = Vec::new(); From 5d06571e31c2ff5bf68175b3b75dc172c15e6b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Mon, 25 May 2026 13:17:14 +0800 Subject: [PATCH 19/71] fix(starry): repair SG2002 CI build (#929) --- os/StarryOS/kernel/src/file/inotify.rs | 6 +-- os/StarryOS/kernel/src/pseudofs/proc.rs | 13 ------- scripts/axbuild/src/context/resolve.rs | 10 ++++- scripts/axbuild/src/context/tests.rs | 52 +++++++++++++++++++++++++ scripts/axbuild/src/starry/build.rs | 17 +++++++- 5 files changed, 78 insertions(+), 20 deletions(-) diff --git a/os/StarryOS/kernel/src/file/inotify.rs b/os/StarryOS/kernel/src/file/inotify.rs index 512fbd8e59..488cfd179e 100644 --- a/os/StarryOS/kernel/src/file/inotify.rs +++ b/os/StarryOS/kernel/src/file/inotify.rs @@ -15,7 +15,6 @@ use ax_errno::{AxError, AxResult}; use ax_sync::Mutex; use ax_task::future::{block_on, poll_io}; use axpoll::{IoEvents, PollSet, Pollable}; -use lazy_static::lazy_static; use linux_raw_sys::{ general::{ IN_ALL_EVENTS, IN_CLOSE_WRITE, IN_CREATE, IN_DELETE, IN_DELETE_SELF, IN_IGNORED, IN_ISDIR, @@ -23,6 +22,7 @@ use linux_raw_sys::{ }, ioctl::FIONREAD, }; +use spin::Lazy; use starry_vm::VmMutPtr; use crate::file::{FileLike, IoDst, IoSrc}; @@ -49,9 +49,7 @@ pub struct Inotify { poll_rx: PollSet, } -lazy_static! { - static ref INOTIFY_INSTANCES: Mutex>> = Mutex::new(Vec::new()); -} +static INOTIFY_INSTANCES: Lazy>>> = Lazy::new(|| Mutex::new(Vec::new())); impl Inotify { pub fn new() -> Arc { diff --git a/os/StarryOS/kernel/src/pseudofs/proc.rs b/os/StarryOS/kernel/src/pseudofs/proc.rs index 92e8fedf06..c639257a97 100644 --- a/os/StarryOS/kernel/src/pseudofs/proc.rs +++ b/os/StarryOS/kernel/src/pseudofs/proc.rs @@ -632,7 +632,6 @@ impl SimpleDirOps for ThreadDir { "stat", "statm", "status", - "statm", "oom_score_adj", "task", "maps", @@ -687,18 +686,6 @@ impl SimpleDirOps for ThreadDir { }) .into() } - "statm" => { - SimpleFile::new_regular(fs, move || { - let aspace = task.as_thread().proc_data.aspace(); - let aspace_lock = aspace.lock(); - // Page size is 4096 on all supported architectures. - let total_pages = aspace_lock.size() / 4096; - let resident = total_pages; - // Format: size resident shared text lib data dt - Ok(format!("{total_pages} {resident} 0 0 0 0 0\n")) - }) - .into() - } "oom_score_adj" => SimpleFile::new_regular( fs, RwFile::new(move |req| match req { diff --git a/scripts/axbuild/src/context/resolve.rs b/scripts/axbuild/src/context/resolve.rs index 106d50c02d..bb0f633535 100644 --- a/scripts/axbuild/src/context/resolve.rs +++ b/scripts/axbuild/src/context/resolve.rs @@ -137,14 +137,20 @@ impl AppContext { .then_some(snapshot.config.as_ref()) .flatten(), ); + let config_target = resolved_config + .as_deref() + .filter(|_| cli.config.is_some() && cli.target.is_none()) + .map(crate::starry::build::load_target_from_build_config) + .transpose()? + .flatten(); let effective_arch = cli.arch.clone().or_else(|| { - if cli.target.is_some() { + if cli.target.is_some() || config_target.is_some() { None } else { snapshot.arch.clone() } }); - let effective_target = cli.target.clone().or_else(|| { + let effective_target = cli.target.clone().or(config_target).or_else(|| { if cli.arch.is_some() { None } else { diff --git a/scripts/axbuild/src/context/tests.rs b/scripts/axbuild/src/context/tests.rs index 82da9694c3..1288a3ccd6 100644 --- a/scripts/axbuild/src/context/tests.rs +++ b/scripts/axbuild/src/context/tests.rs @@ -973,6 +973,58 @@ config = "configs/custom-starry.toml" ); } +#[test] +fn prepare_starry_request_explicit_config_target_overrides_snapshot_target() { + let root = tempdir().unwrap(); + prepare_starry_workspace(root.path()); + let config = root.path().join("configs/sg2002.toml"); + fs::create_dir_all(config.parent().unwrap()).unwrap(); + fs::write( + &config, + r#" +target = "riscv64gc-unknown-none-elf" +env = {} +features = ["sg2002"] +log = "Info" +"#, + ) + .unwrap(); + write_snapshot_text( + root.path(), + STARRY_SNAPSHOT_FILE, + r#" +arch = "aarch64" +target = "aarch64-unknown-none-softfloat" +"#, + ) + .unwrap(); + + let app = test_app_context(root.path()); + + let (request, snapshot) = prepare_starry_request( + &app, + StarryCliArgs { + config: Some(config.clone()), + arch: None, + target: None, + smp: None, + debug: false, + }, + None, + None, + ) + .unwrap(); + + assert_eq!(request.arch, "riscv64"); + assert_eq!(request.target, "riscv64gc-unknown-none-elf"); + assert_eq!(request.build_info_path, config); + assert_eq!(snapshot.arch.as_deref(), Some("riscv64")); + assert_eq!( + snapshot.target.as_deref(), + Some("riscv64gc-unknown-none-elf") + ); +} + #[test] fn prepare_starry_request_rejects_mismatched_arch_and_target() { let root = tempdir().unwrap(); diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index bdf37b129a..f92bb3007b 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -1,9 +1,10 @@ use std::path::{Path, PathBuf}; -use anyhow::Context as _; +use anyhow::{Context as _, anyhow}; use cargo_metadata::Metadata; use ostool::build::config::Cargo; +use super::board; pub type StarryBuildInfo = crate::build::BuildInfo; pub use crate::build::LogLevel; use crate::context::{ResolvedStarryRequest, STARRY_PACKAGE, starry_arch_for_target_checked}; @@ -32,6 +33,20 @@ pub(crate) fn resolve_build_info_path( )) } +pub(crate) fn load_target_from_build_config(path: &Path) -> anyhow::Result> { + let content = std::fs::read_to_string(path) + .map_err(|e| anyhow!("failed to read Starry build config {}: {e}", path.display()))?; + + if let Ok(board_file) = toml::from_str::(&content) { + return Ok(Some(board_file.target)); + } + if toml::from_str::(&content).is_ok() { + return Ok(None); + } + + Err(anyhow!("invalid Starry build config {}", path.display())) +} + #[cfg(test)] pub(crate) fn load_build_info(request: &ResolvedStarryRequest) -> anyhow::Result { let makefile_features = crate::build::makefile_features_from_env(); From a9f9f6cc6e26c77f12963405db805ce2de783a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 13:29:40 +0800 Subject: [PATCH 20/71] fix(starry-signal): keep x86-64 uc_mcontext at Linux ABI offset 40 (#916) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 x86-64 上,依赖 raw ABI 偏移读写信号上下文的运行时会损坏寄存器。最典型是 Go 的异步抢占(SIGURG):其 signal handler 读写 `uc_mcontext.gregs[REG_RSP/REG_RIP]` 并指望 `rt_sigreturn` 按这些写回恢复;在 StarryOS 上 `RSP` 被错位 8 字节、载入相邻的非指针字节 → `unsafe.Slice: len out of range` panic。 ## 根因 `MContext`(对应 Linux `struct sigcontext` / `mcontext_t`)被标注 `#[repr(C, align(16))]`。但 Linux 的 `mcontext_t` 是**自然 8 字节对齐**,且在 `ucontext_t` 里 `uc_mcontext` 位于结构体**中部**(偏移 40,紧随 `uc_flags`/`uc_link`/`uc_stack`)。强制 16 对齐会在 `uc_stack`(@16..40)之后插入 8 字节填充,把 `uc_mcontext` 推到偏移 48 → 其内每个通用寄存器(RSP@160、RIP@168)、fpregs 指针、以及随后的 `uc_sigmask` 全部相对 Linux ABI 偏移 8 字节。Go 按裸偏移访问即错位。 ## 修复 - `MContext`:`#[repr(C, align(16))]` → `#[repr(C)]`(恢复自然 8 对齐 → `uc_mcontext`@40)。 - `UContext`:`#[repr(C)]` → `#[repr(C, align(16))]`。信号帧本身要求落在用户栈 16 对齐处(handler 入口须 `RSP%16==8`);该对齐改由**外层** `UContext` 提供,既保帧对齐、又不再扰动 `uc_mcontext` 的偏移。 - 新增编译期 `const _` 断言锁定 `offset_of!(UContext, mcontext)==40 && offset_of!(UContext, sigmask)==296`,作为 ABI 回归守卫(偏移再被改错即编译失败)。 仅改 `components/starry-signal/src/arch/x86_64.rs`,不触其它 arch/调用方。 ## 对照 Linux(验证) Linux/musl x86-64 `ucontext_t`:`uc_flags`@0、`uc_link`@8、`uc_stack`@16、`uc_mcontext`@40、`uc_sigmask`@296(`struct sigcontext` 256 字节)。本修后 StarryOS 偏移与之逐一吻合(const-assert 编译期证实)。Go runtime 在 host Linux 正常异步抢占即依赖此布局。 ## 测试 `cargo xtask starry build --arch x86_64` 通过(含上述 const-assert);openjdk17(信号密集:GC safepoint/SIGSEGV null-check)x86_64 实测无回归。 Signed-off-by: Leo Cheng --- components/starry-signal/src/arch/x86_64.rs | 29 +++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/components/starry-signal/src/arch/x86_64.rs b/components/starry-signal/src/arch/x86_64.rs index 6bc32e2000..688f794f92 100644 --- a/components/starry-signal/src/arch/x86_64.rs +++ b/components/starry-signal/src/arch/x86_64.rs @@ -16,7 +16,18 @@ signal_trampoline: " ); -#[repr(C, align(16))] +// Linux x86-64 `mcontext_t` (`struct sigcontext`) has NATURAL 8-byte alignment, +// NOT 16. In `ucontext_t`, `uc_mcontext` sits in the MIDDLE of the struct (offset +// 40, after uc_flags/uc_link/uc_stack); forcing `align(16)` here inserts 8 bytes +// of padding and pushes `uc_mcontext` to offset 48 — shifting every general +// register (RSP@160, RIP@168), the fpregs pointer and `uc_sigmask` 8 bytes off +// the Linux ABI. Runtimes that read the context by raw ABI offset — notably Go's +// async preemption, which reads/writes `uc_mcontext.gregs[REG_RSP/REG_RIP]` and +// expects `rt_sigreturn` to honor those writes — then corrupt RSP/RIP (observed: +// `sp` loaded with adjacent non-pointer bytes -> `unsafe.Slice: len out of range`). +// The 16-byte alignment the signal frame itself requires is provided by the outer +// `UContext` (see below), not by over-aligning this inner struct. +#[repr(C)] #[derive(Clone)] pub struct MContext { r8: usize, @@ -108,7 +119,13 @@ impl MContext { } } -#[repr(C)] +// `align(16)` keeps the whole signal frame 16-byte aligned on the user stack (the +// x86-64 signal ABI requires the handler to observe `RSP % 16 == 8` after its +// return address is pushed). This frame alignment was previously (incorrectly) +// supplied by over-aligning the inner `MContext`, which corrupted `uc_mcontext`'s +// offset; aligning the outer `UContext` instead keeps `uc_mcontext` at the Linux +// ABI offset 40 while still guaranteeing frame alignment. +#[repr(C, align(16))] #[derive(Clone)] pub struct UContext { pub flags: usize, @@ -129,3 +146,11 @@ impl UContext { } } } + +const _: () = { + // Lock the Linux/musl x86-64 `ucontext_t` ABI offsets (compile-time regression + // guard for the alignment trap documented on `MContext`): `uc_mcontext`@40, + // `uc_sigmask`@296. + assert!(core::mem::offset_of!(UContext, mcontext) == 40); + assert!(core::mem::offset_of!(UContext, sigmask) == 296); +}; From b66e4d965fb8a72d1f2456b4e5b822c3211fdb10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 13:33:50 +0800 Subject: [PATCH 21/71] fix(axruntime): park secondary harts beyond MAX_CPU_NUM instead of panicking (#919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 当 QEMU 以 `-smp M` 启动、而内核按 `MAX_CPU_NUM == N`(`N < M`)编译时,第 N..M 个 hart 进入 `rust_main_secondary` 后内核 panic(`percpu::init_secondary(cpu_id)` / `AxCpuMask::one_shot(cpu_id)` / `RUN_QUEUES[cpu_id]` 均断言 `index < MAX_CPU_NUM`),启动失败。 ## 根因 `rust_main_secondary(cpu_id)` 未对 `cpu_id` 设上界即去索引按 `MAX_CPU_NUM` 定容的 per-CPU 结构。超额 hart 的 `cpu_id >= MAX_CPU_NUM` 越界。 ## 修复 对齐 Linux 的做法(用前 N 个 CPU、park 多余的):在 `init_secondary` 之前,若 `cpu_id >= MAX_CPU_NUM` 则该 hart 进入 `wait_for_irqs()` 永久 park,绝不触碰按 `MAX_CPU_NUM` 定容的结构。`cpu_id < MAX_CPU_NUM` 的 hart 行为不变。 ## 测试 `cargo xtask starry build --arch x86_64` 通过;`-smp` 与 `MAX_CPU_NUM` 匹配时行为不变(park 分支不触发),`-smp` 超额时多余 hart 安全 park 而非 panic。 Signed-off-by: Leo Cheng --- os/arceos/modules/axruntime/src/mp.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/os/arceos/modules/axruntime/src/mp.rs b/os/arceos/modules/axruntime/src/mp.rs index 3b95570ef0..2d3239fde7 100644 --- a/os/arceos/modules/axruntime/src/mp.rs +++ b/os/arceos/modules/axruntime/src/mp.rs @@ -124,6 +124,18 @@ pub fn start_secondary_cpus(primary_cpu_id: usize) { /// It is called from the bootstrapping code in the specific platform crate. #[ax_plat::secondary_main] pub fn rust_main_secondary(cpu_id: usize) -> ! { + // Park harts whose logical index is beyond the compile-time CPU count: QEMU + // may start more harts (`-smp M`) than the kernel was built for + // (`MAX_CPU_NUM == N`). Mirror Linux — run on the first N CPUs and park the + // excess, rather than panicking in `percpu::init_secondary(cpu_id)` / + // `AxCpuMask::one_shot(cpu_id)` / `RUN_QUEUES[cpu_id]`, which all assert + // `index < MAX_CPU_NUM`. Must precede `init_secondary`, which would otherwise + // mis-index the per-CPU area first. + if cpu_id >= MAX_CPU_NUM { + loop { + ax_hal::asm::wait_for_irqs(); + } + } ax_hal::percpu::init_secondary(cpu_id); #[cfg(feature = "buddy-slab")] ax_alloc::init_percpu_slab(cpu_id); From a8c3d4f831c95da8b2a84ad6eeb227f91e67c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 14:32:27 +0800 Subject: [PATCH 22/71] fix(starry-mm): fix use-after-free when evicting a page-cache page shared across split file mappings (#920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 loongarch64 上 JVM 启动偶发野指针崩溃(读取 jimage 时拿到另一页的数据)。根因是文件页缓存逐出路径的 use-after-free。 ## 根因 文件页缓存按 `CachedFile` 在所有映射同一文件的 area 间共享。`mprotect` 拆分一个文件映射后,`split` 会创建同享该 `CachedFile`、但 `start`/`offset_page` 不同的兄弟 `FileBackendInner`。当对其中一个子 area 做 populate 触发逐出时: 1. 逐出回调原先只覆盖发起 populate 的那个 `inner` 自己的页范围(`on_evict` 的 `checked_sub` 对其它范围返回 `None`),于是**兄弟 area 的 PTE 仍指向已被逐出/即将释放的物理帧**; 2. 且释放帧(drop page)与解除映射的次序不当——若先释放帧,被逐出的 VA 仍映射到一个可被重新分配的帧,被抢占进用户态的兄弟线程就能透过这条 stale 映射读到别页数据。 ## 修复 - `populate` 不再在持有地址空间锁时直接逐出,而是返回一个延迟回调;`AddrSpace::populate` 在释放页表 cursor 借用后再执行 `cb(self)`(`aspace/mod.rs`)。 - 逐出回调中:先 unmap 被逐出的 VA(经 cursor 解除映射并 flush TLB),**再** drop page 释放帧——绝不颠倒次序。 - 将被逐出页路由到**所有**共享该 cache 的 area(遍历 `aspace.areas()` 按 `cache.ptr_eq` 过滤),每个 area 的 `on_evict` 自校验(范围 + area 指针),只有真正的 owner 解除其映射。 ## 对照 Linux(验证) Linux 的 page cache 逐出在解除所有映射该页的 PTE(rmap)后才释放帧;本修使 StarryOS 的逐出遵循同样的"先 unmap 全部 owner、后释放帧"次序。loongarch64 JVM jimage 读取不再出现野指针。 ## 测试 `cargo xtask starry build --arch x86_64` 通过;openjdk17 在 loongarch64 实测稳定(此前因本 UAF 偶发崩溃)。 Signed-off-by: Leo Cheng --- .../kernel/src/mm/aspace/backend/file.rs | 43 +++++++++++++++++-- os/StarryOS/kernel/src/mm/aspace/mod.rs | 27 +++++++++--- os/StarryOS/kernel/src/syscall/mm/mmap.rs | 11 ++++- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/file.rs b/os/StarryOS/kernel/src/mm/aspace/backend/file.rs index 0b7500ae66..4221f25f3f 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/file.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/file.rs @@ -283,8 +283,17 @@ impl BackendOps for FileBackend { flags - MappingFlags::WRITE }; self.0.cache.with_page_or_insert(pn, |page, evicted| { - if let Some((pn, _)) = evicted { - to_be_evicted.push(pn); + if let Some(evicted) = evicted { + // Keep the evicted page (and thus its physical frame) + // alive until `on_evict` below has torn down its mapping + // and flushed the TLB. The eviction listener cannot unmap + // here because the address space is already locked by this + // populate, so the unmap is deferred; freeing the frame now + // (by dropping the page) would leave the evicted VA mapped + // to a frame that can be reallocated, so a sibling thread + // preempted into userspace could read another page's data + // through the stale mapping. + to_be_evicted.push(evicted); } pt.map(addr, page.paddr(), PageSize::Size4K, map_flags)?; pages += 1; @@ -301,8 +310,34 @@ impl BackendOps for FileBackend { } else { let inner = self.0.clone(); Some(Box::new(move |aspace: &mut AddrSpace| { - for pn in to_be_evicted { - inner.on_evict(pn, aspace); + for (pn, page) in to_be_evicted { + // Unmap (and TLB-flush via the cursor) the evicted VA first, + // then drop the page to free its frame — never the reverse. + // + // The page cache is shared across all areas backed by the same + // file. After mprotect splits a file mapping, `split` creates a + // sibling FileBackendInner (same CachedFile, different + // start/offset_page). A populate on one sub-area can evict a page + // owned by another sub-area; `inner.on_evict` only covers + // `inner`'s own page range (its `checked_sub` returns None + // otherwise), so route the evicted page to every area sharing this + // cache. `on_evict` self-validates (range + area ptr_eq), so only + // the true owner unmaps. Without this, the sibling's PTE keeps + // pointing at the just-freed frame — a use-after-free that + // surfaces as a wild pointer (the JVM jimage on loongarch). + let owners: Vec<_> = aspace + .areas() + .filter_map(|area| match area.backend() { + Backend::File(fb) if fb.0.cache.ptr_eq(&inner.cache) => { + Some(fb.0.clone()) + } + _ => None, + }) + .collect(); + for owner in owners { + owner.on_evict(pn, aspace); + } + drop(page); } })) }, diff --git a/os/StarryOS/kernel/src/mm/aspace/mod.rs b/os/StarryOS/kernel/src/mm/aspace/mod.rs index 5c4b0aa0d7..5bf5887c4d 100644 --- a/os/StarryOS/kernel/src/mm/aspace/mod.rs +++ b/os/StarryOS/kernel/src/mm/aspace/mod.rs @@ -188,12 +188,27 @@ impl AddrSpace { self.validate_region(start, size)?; let end = start + size; - let mut modify = self.pt.cursor(); - while let Some(area) = self.areas.find(start) { - let range = VirtAddrRange::new(start, area.end().min(end)); - area.backend() - .populate(range, area.flags(), access_flags, &mut modify)?; - start = area.end(); + loop { + let (area_end, callback) = { + let Some(area) = self.areas.find(start) else { + break; + }; + let range = VirtAddrRange::new(start, area.end().min(end)); + let flags = area.flags(); + let (_, callback) = + area.backend() + .populate(range, flags, access_flags, &mut self.pt.cursor())?; + (area.end(), callback) + }; + // Run the eviction cleanup the populate deferred (unmap + TLB flush + // for page-cache pages evicted during this fill). Dropping it — as + // the old code did — frees an evicted frame while its user PTE still + // points at it: a use-after-free that surfaces as a wild pointer + // under heavy file-backed paging (the JVM jimage on loongarch). + if let Some(cb) = callback { + cb(self); + } + start = area_end; assert!(start.is_aligned_4k()); if start >= end { break; diff --git a/os/StarryOS/kernel/src/syscall/mm/mmap.rs b/os/StarryOS/kernel/src/syscall/mm/mmap.rs index 325656d89c..976447b064 100644 --- a/os/StarryOS/kernel/src/syscall/mm/mmap.rs +++ b/os/StarryOS/kernel/src/syscall/mm/mmap.rs @@ -36,7 +36,7 @@ bitflags::bitflags! { impl From for MappingFlags { fn from(value: MmapProt) -> Self { - let mut flags = MappingFlags::USER; + let mut flags = MappingFlags::empty(); if value.contains(MmapProt::READ) { flags |= MappingFlags::READ; } @@ -52,6 +52,15 @@ impl From for MappingFlags { if value.contains(MmapProt::EXEC) { flags |= MappingFlags::EXECUTE; } + // PROT_NONE must yield empty flags so the PTE is non-present and any + // access faults. Tagging it USER would, on x86_64, still set the PRESENT + // bit (present implies readable on x86) and silently defeat the + // protection — breaking guard pages such as JVM thread-stack guards, + // letting a stack overflow corrupt adjacent memory instead of trapping. + // Only accessible mappings get the USER tag. + if !flags.is_empty() { + flags |= MappingFlags::USER; + } flags } } From 861588eeb1952a3302cbc7771b35fb593ba100dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 14:35:51 +0800 Subject: [PATCH 23/71] fix(axruntime): initialize the page allocator from the largest free RAM region (#922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 loongarch64 上大内存负载(如 gradle 构建的 JVM)即使系统有数 GB 空闲内存仍 OOM,用户态分配被莫名钳制在 ~248 MB。 ## 根因 撑起用户态页填充(`alloc_pages`)的页分配器由 `global_init` 从**单个连续空闲区**初始化;其余空闲区交给 byte/heap 分配器(bitmap 页分配器不支持 `global_add_memory`)。因此 `global_init` 选中的那个区**就是**用户内存的全部来源。原启发式取"`.bss` 之后的第一个空闲区";而 loongarch64 qemu-virt 的物理内存**不连续**——MMIO 洞下方一个 ~248 MB 的低区,外加 `0x8000_0000` 起的多 GB 高区——第一个空闲区恰是小的低区,于是无论总 RAM 多大,用户态分配都被钳在 ~248 MB。 ## 修复 改为选**最大**的空闲区初始化页分配器。单连续 RAM 区的平台(x86/aarch64/riscv64 qemu-virt)不受影响(最大区==唯一区)。 ## 对照 Linux(验证) Linux 经 memblock 使用全部物理 RAM(跨不连续区);本修使 StarryOS 至少把最大区完整供给用户态,消除 loongarch 上"有内存却 OOM"。 ## 测试 `cargo xtask starry build --arch x86_64` 通过;loongarch64 上大内存 JVM 负载不再因 ~248 MB 上限 OOM。 Signed-off-by: Leo Cheng --- os/arceos/modules/axruntime/src/lib.rs | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 1ffb5b211b..c0c2426d23 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -357,21 +357,28 @@ fn init_allocator() { info!("Initialize global memory allocator..."); info!(" use {} allocator.", ax_alloc::global_allocator().name()); + // The page allocator (which backs user-space page population via + // `alloc_pages`) is initialized from a single contiguous region by + // `global_init`; every other free region is handed to the byte/heap + // allocator by `global_add_memory` (the bitmap page allocator does not + // support `add_memory`). So the region chosen for `global_init` *is* the + // entire pool available for user memory. + // + // Pick the LARGEST free region for the page allocator. Platforms with a + // single contiguous RAM region (x86/aarch64/riscv64 qemu-virt) are + // unaffected (largest == the only region). Platforms with disjoint regions + // (loongarch64 qemu-virt: a small ~248 MB low region below the MMIO hole + // plus the multi-GB high region at 0x8000_0000) previously picked the small + // low region — the "first free region after .bss" heuristic — which capped + // all user allocations at ~248 MB regardless of total RAM, OOM'ing large + // workloads (e.g. the gradle build JVM) even with gigabytes free. let mut max_region_size = 0; let mut max_region_paddr = 0.into(); - let mut use_next_free = false; for r in memory_regions() { - if r.name == ".bss" { - use_next_free = true; - } else if r.flags.contains(MemRegionFlags::FREE) { - if use_next_free { - max_region_paddr = r.paddr; - break; - } else if r.size > max_region_size { - max_region_size = r.size; - max_region_paddr = r.paddr; - } + if r.flags.contains(MemRegionFlags::FREE) && r.size > max_region_size { + max_region_size = r.size; + max_region_paddr = r.paddr; } } From 1e3718bdd5b455ea6cd9d5e9c6fc6c131125d06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 14:40:00 +0800 Subject: [PATCH 24/71] feat(starry-task): implement sys_getcpu (#924) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 `getcpu(2)` 未实现,返回 ENOSYS。glibc `sched_getcpu()` 及 NUMA 感知代码(分配器/线程亲和)依赖它。 ## 修复(纯增量) - `syscall/task/thread.rs` 实现 `sys_getcpu(cpu, node, tcache)`:`cpu` 写当前 `this_cpu_id()`、`node` 写 0(单 NUMA 节点),两指针均可为 NULL;`tcache`(已废弃)忽略。 - `syscall/mod.rs` 接入分发 `Sysno::getcpu`。 ## 测试 - `cargo xtask starry build --arch x86_64` 通过。 - 新增 `test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/`(应 review 要求,仿 test-clock-gettime 框架):`syscall(SYS_getcpu, ...)` 验证返回值=0、cpu ∈ [0, nproc)、node==0、NULL 指针(cpu/node/全 NULL)安全、已废弃 tcache 被忽略;由 syscall 层 combined toml 4-arch 自动发现运行。 ## 对照 Linux(验证) 对齐 `sys_getcpu` 语义(写 CPU id 与 NUMA node);单节点系统 node 恒 0。 Signed-off-by: Leo Cheng --- os/StarryOS/kernel/src/syscall/mod.rs | 1 + os/StarryOS/kernel/src/syscall/task/thread.rs | 18 ++++ .../syscall/test-getcpu/c/CMakeLists.txt | 9 ++ .../syscall/test-getcpu/c/src/main.c | 64 ++++++++++++++ .../test-getcpu/c/src/test_framework.h | 85 +++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/test_framework.h diff --git a/os/StarryOS/kernel/src/syscall/mod.rs b/os/StarryOS/kernel/src/syscall/mod.rs index 57cc56858c..9321b6a562 100644 --- a/os/StarryOS/kernel/src/syscall/mod.rs +++ b/os/StarryOS/kernel/src/syscall/mod.rs @@ -498,6 +498,7 @@ pub fn handle_syscall(uctx: &mut UserContext) { // task ops Sysno::execve => sys_execve(uctx, uctx.arg0() as _, uctx.arg1() as _, uctx.arg2() as _), Sysno::set_tid_address => sys_set_tid_address(uctx.arg0()), + Sysno::getcpu => sys_getcpu(uctx.arg0() as _, uctx.arg1() as _, uctx.arg2()), #[cfg(target_arch = "x86_64")] Sysno::arch_prctl => sys_arch_prctl(uctx, uctx.arg0() as _, uctx.arg1() as _), Sysno::prctl => sys_prctl( diff --git a/os/StarryOS/kernel/src/syscall/task/thread.rs b/os/StarryOS/kernel/src/syscall/task/thread.rs index 77143826ab..e1ec225983 100644 --- a/os/StarryOS/kernel/src/syscall/task/thread.rs +++ b/os/StarryOS/kernel/src/syscall/task/thread.rs @@ -24,6 +24,24 @@ pub fn sys_gettid() -> AxResult { Ok(current().as_thread().tid() as _) } +/// `getcpu(2)`: report the CPU and NUMA node the caller is running on. +/// +/// glibc's `sched_getcpu` and NUMA-aware allocators query this. We report the +/// current CPU id and node 0 (single NUMA node); the obsolete `tcache` arg is +/// ignored. Either pointer may be NULL. +pub fn sys_getcpu(cpu: *mut u32, node: *mut u32, _tcache: usize) -> AxResult { + use ax_hal::percpu::this_cpu_id; + use starry_vm::VmMutPtr; + + if !cpu.is_null() { + cpu.vm_write(this_cpu_id() as u32)?; + } + if !node.is_null() { + node.vm_write(0)?; + } + Ok(0) +} + /// ARCH_PRCTL codes /// /// It is only avaliable on x86_64, and is not convenient diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/CMakeLists.txt new file mode 100644 index 0000000000..adca5c509b --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-getcpu C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-getcpu src/main.c) +target_include_directories(test-getcpu PRIVATE src) +target_compile_options(test-getcpu PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-getcpu RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/main.c new file mode 100644 index 0000000000..a2138379e0 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/main.c @@ -0,0 +1,64 @@ +/* + * test_getcpu.c -- getcpu(2) 系统调用测试 + * + * 验证 sys_getcpu(cpu, node, tcache): + * 1. getcpu(&cpu, &node, NULL) 返回 0;cpu ∈ [0, nproc);node == 0(单 NUMA 节点) + * 2. NULL cpu 指针安全(内核仍写回 node) + * 3. NULL node 指针安全(内核仍写回 cpu) + * 4. 三参全 NULL 安全(返回 0,不解引用) + * 5. 已废弃的 tcache 参数被忽略(传任意值仍成功) + */ +#define _GNU_SOURCE +#include "test_framework.h" +#include +#include + +int main(void) +{ + TEST_START("getcpu"); + + long nproc = sysconf(_SC_NPROCESSORS_ONLN); + if (nproc < 1) { + nproc = 1; + } + + /* getcpu(&cpu, &node, NULL): 成功, cpu 范围有效, node == 0 */ + { + unsigned cpu = 0xdeadu; + unsigned node = 0xbeefu; + CHECK_RET(syscall(SYS_getcpu, &cpu, &node, NULL), 0, + "getcpu(&cpu,&node,NULL) 成功"); + CHECK(cpu < (unsigned)nproc, "cpu id 落在 [0, nproc) 范围内"); + CHECK(node == 0u, "node == 0 (单 NUMA 节点)"); + } + + /* NULL cpu 指针安全, 内核仍写回 node */ + { + unsigned node = 0xbeefu; + CHECK_RET(syscall(SYS_getcpu, NULL, &node, NULL), 0, + "getcpu(NULL,&node,NULL) 成功(NULL cpu 安全)"); + CHECK(node == 0u, "NULL cpu 时 node 仍被写为 0"); + } + + /* NULL node 指针安全, 内核仍写回 cpu */ + { + unsigned cpu = 0xdeadu; + CHECK_RET(syscall(SYS_getcpu, &cpu, NULL, NULL), 0, + "getcpu(&cpu,NULL,NULL) 成功(NULL node 安全)"); + CHECK(cpu < (unsigned)nproc, "NULL node 时 cpu 范围仍有效"); + } + + /* 三参全 NULL 安全(不解引用空指针) */ + CHECK_RET(syscall(SYS_getcpu, NULL, NULL, NULL), 0, + "getcpu(NULL,NULL,NULL) 成功(全 NULL 安全)"); + + /* 已废弃的 tcache(第三参)应被忽略 */ + { + unsigned cpu = 0xdeadu; + unsigned node = 0xbeefu; + CHECK_RET(syscall(SYS_getcpu, &cpu, &node, (unsigned long)0xdeadbeef), 0, + "getcpu 忽略已废弃的 tcache 参数"); + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/test_framework.h new file mode 100644 index 0000000000..b697580a68 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-getcpu/c/src/test_framework.h @@ -0,0 +1,85 @@ +#pragma once + +/* 必须在最前面定义,确保 pipe2/gettid 等可用 */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +/* + * StarryOS Syscall Test Framework + * + * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 + * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 + * + * 用法: + * TEST_START("测试名"); + * CHECK(call == expected, "描述"); + * CHECK_ERR(call, EBADF, "描述"); + * TEST_DONE(); + */ + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +/* ---- 核心: 带文件名+行号的检查宏 ---- */ + +/* 检查条件为真 */ +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +/* 检查 syscall 返回特定值 */ +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +/* 检查 syscall 失败且 errno 符合预期 */ +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +/* ---- 测试边界 ---- */ +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 From 98b4f2580e342b919301d558a72c9c51b1658dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Mon, 25 May 2026 14:44:27 +0800 Subject: [PATCH 25/71] fix(starry-task): suspend on SIGSTOP instead of killing (job control) (#925) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 在 origin/dev 上,`check_signals` 把 `SignalOSAction::Stop` 处理成 `do_exit(1, true)`:进程收到 SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU 时不是被挂起,而是被直接退出(杀死)。`SignalOSAction::Continue` 则是空实现,SIGCONT 不做任何事。父进程的 `waitpid(WUNTRACED/WCONTINUED)` 也无从得知子进程的停止/继续。这破坏了 shell 作业控制、`killall5 -STOP/-CONT` 以及 busybox 等依赖作业控制的工具。 ## 根因 缺少真正的作业控制状态机:没有"已停止"标志,没有让停止线程在内核里挂起、等待 SIGCONT(或 SIGKILL)唤醒的机制,也没有把停止/继续状态报告给父进程 `waitpid` 的通道。 ## 修复 - `task/mod.rs`:新增 `enum JobStatus {Stopped(Signo), Continued}` 与受单锁保护的 `struct JobControl {stopped, status, continue_generation}`,作为 `ProcessData` 的字段;并新增 `cont_event: Arc`。配套方法:`is_job_stopped` / `set_job_stopped` / `continue_generation` / `set_job_continued`(唤醒 cont_event) / `clear_job_stop_for_kill` / `cont_event` / `peek_job_status_if` / `take_job_status_if`。 - `task/signal.rs`:`SignalOSAction::Stop` 改为调用 `do_job_stop(thr, signo)` —— 记录停止、向父进程发 SIGCHLD(CLD_STOPPED)、然后 `block_on(poll_fn(...))` 在 cont_event 上挂起(非 interruptible:普通信号不得唤醒已停止进程,只有 continue/kill 才清除停止标志)。`send_signal_to_process` 在发送时处理 SIGCONT(丢弃挂起停止、置 continued、报告 CLD_CONTINUED)与 SIGKILL(强制清除停止以便被杀死)。 - `syscall/task/wait.rs`:`sys_waitpid` 在僵尸回收之外,按 `WUNTRACED`/`WCONTINUED` 经 `peek/take_job_status_if` 报告停止/继续状态,编码为 `(signo<<8)|0x7f`(W_STOPCODE)与 `0xffff`。 为不修改 `starry-signal`(本 PR 仅触及上述三个内核文件),用 `continue_generation` 计数关闭 "STOP 后紧接 CONT" 竞态:停止前快照该计数,若期间有 SIGCONT 递增了它,`set_job_stopped` 返回 false 从而不挂起,等价于上游参考实现里在发送端 `discard_pending` 的效果。 ## 对照 Linux(验证) - SIGSTOP/SIGTSTP/SIGTTIN/SIGTTOU 挂起进程而非退出;SIGCONT 恢复;SIGKILL 即使对已停止进程也能终止。 - `waitpid(WUNTRACED)` 报告 `WIFSTOPPED` 且 `WSTOPSIG == 停止信号`;`waitpid(WCONTINUED)` 报告 `WIFCONTINUED`;状态字编码与 Linux 一致(停止 `(signo<<8)|0x7f`,继续 `0xffff`)。 - 父进程在子进程停止/继续时收到 SIGCHLD(CLD_STOPPED / CLD_CONTINUED)。 ## 测试 新增 `test-suit/starryos/normal/qemu-smp1/test-job-control-stop`(四架构 toml + C 测例):fork 一个忙等子进程,父进程 `kill(SIGSTOP)` → `waitpid(WUNTRACED)` 期望 `WIFSTOPPED`,确认子进程未退出且仍存在;`kill(SIGCONT)` → `waitpid(WCONTINUED)` 期望 `WIFCONTINUED`;`kill(SIGKILL)` 回收,期望 `WIFSIGNALED && WTERMSIG==SIGKILL`。在 starry x86_64(smp=1)实跑:15/15 断言通过,success 模式匹配。`cargo xtask starry build --arch x86_64` 与 `cargo fmt --all -- --check` 均通过。 Signed-off-by: Leo Cheng --- os/StarryOS/kernel/src/syscall/task/wait.rs | 37 ++++- os/StarryOS/kernel/src/task/mod.rs | 149 ++++++++++++++++++ os/StarryOS/kernel/src/task/signal.rs | 100 +++++++++++- .../test-job-control-stop/c/CMakeLists.txt | 16 ++ .../test-job-control-stop/c/src/main.c | 81 ++++++++++ .../c/src/test_framework.h | 65 ++++++++ .../test-job-control-stop/qemu-aarch64.toml | 14 ++ .../qemu-loongarch64.toml | 17 ++ .../test-job-control-stop/qemu-riscv64.toml | 14 ++ .../test-job-control-stop/qemu-x86_64.toml | 14 ++ 10 files changed, 499 insertions(+), 8 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/test_framework.h create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-aarch64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-loongarch64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-riscv64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-x86_64.toml diff --git a/os/StarryOS/kernel/src/syscall/task/wait.rs b/os/StarryOS/kernel/src/syscall/task/wait.rs index 86c3b1a45f..003cf1751c 100644 --- a/os/StarryOS/kernel/src/syscall/task/wait.rs +++ b/os/StarryOS/kernel/src/syscall/task/wait.rs @@ -15,7 +15,8 @@ use starry_signal::SignalInfo; use starry_vm::{VmMutPtr, VmPtr}; use crate::task::{ - AsThread, decode_wait_status, get_task, get_zombie_cred, remove_process, unregister_zombie, + AsThread, JobStatus, decode_wait_status, get_process_data, get_task, get_zombie_cred, + remove_process, unregister_zombie, }; bitflags! { @@ -115,8 +116,38 @@ pub fn sys_waitpid(pid: i32, exit_code: *mut i32, options: u32) -> AxResult ((signo as i32) << 8) | 0x7f, + JobStatus::Continued => 0xffff, + }; + // Publish to userspace before consuming, so a faulting + // `exit_code` pointer leaves the report intact to retry + // (mirrors the zombie-reap ordering above). + if let Some(exit_code) = exit_code.nullable() { + exit_code.vm_write(raw)?; + } + cdata.take_job_status_if(want_stopped, want_continued); + return Ok(Some(child.pid() as _)); + } + } + } + + if options.contains(WaitPidOptions::WNOHANG) { Ok(Some(0)) } else { Ok(None) diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index 59731b0b46..336cef3d57 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -458,6 +458,46 @@ impl VforkDone { } } +/// A pending job-control status change awaiting report to the parent's +/// `waitpid(WUNTRACED | WCONTINUED)`. +#[derive(Clone, Copy)] +pub enum JobStatus { + /// The process stopped after receiving the given job-control signal + /// (`SIGSTOP`/`SIGTSTP`/`SIGTTIN`/`SIGTTOU`). + Stopped(Signo), + /// The process continued after receiving `SIGCONT`. + Continued, +} + +/// Job-control state for a process, kept under a single lock so the stop flag +/// and the pending parent report are updated atomically (a concurrent +/// stop/continue on another CPU must not split the two). +/// +/// `stopped` and `status` are **intentionally independent** and may legitimately +/// diverge — do not collapse them into one field. `stopped` is the live parked +/// state (cleared only by continue/kill); `status` is a one-shot report the +/// parent's `waitpid` consumes (so `stopped == Some` with `status == None` is +/// valid once the report has been reaped). +#[derive(Default)] +struct JobControl { + /// `None` = running, `Some(signo)` = stopped by the given job-control + /// signal. A stopped process parks its threads in the kernel until + /// `SIGCONT` (or `SIGKILL`) is delivered. + stopped: Option, + /// Pending status change for the parent's `waitpid`, consumed once + /// reported. Single-slot: a new stop/continue before the parent reaps the + /// previous one overwrites it (unlike Linux, which queues each SIGCHLD). + /// Adequate for the single-threaded job-control this targets. + status: Option, + /// Bumped on every continue. A thread about to park (`set_job_stopped`) + /// snapshots this; if it changed by the time the thread checks before + /// parking, a `SIGCONT` raced in after the stop was recorded and the park + /// is skipped. This closes the STOP-immediately-followed-by-CONT race + /// (e.g. busybox `killall5 -STOP` then `-CONT`) without having to scrub the + /// pending-signal queue. + continue_generation: u64, +} + /// [`Process`]-shared data. pub struct ProcessData { /// The process. @@ -538,6 +578,13 @@ pub struct ProcessData { /// Set after [`Self::release_aspace_slot_if_needed`] runs so `Drop` does not /// double-decrement [`AddrSpace::process_slots`]. aspace_slot_released: AtomicBool, + + /// Job-control state (stop flag + pending parent report) under one lock. + job_control: SpinNoIrq, + + /// Woken to release threads parked in a job-control stop. Fired by + /// `SIGCONT` (continue) and `SIGKILL` (force-resume so the kill proceeds). + cont_event: Arc, } impl ProcessData { @@ -587,6 +634,9 @@ impl ProcessData { vm_aspace_shared: AtomicBool::new(vm_aspace_shared), aspace_slot_released: AtomicBool::new(false), + + job_control: SpinNoIrq::new(JobControl::default()), + cont_event: Arc::default(), }); // Clone the Arc in a separate statement: a temporary `SpinNoIrq` guard // from `lock()` lives until the end of the statement, so calling @@ -688,6 +738,105 @@ impl ProcessData { self.dumpable.store(dumpable, Ordering::SeqCst); } + /// Returns true if the process is currently job-control stopped. + pub fn is_job_stopped(&self) -> bool { + self.job_control.lock().stopped.is_some() + } + + /// Mark the process stopped by `signo` and queue a `Stopped` report for the + /// parent's `waitpid(WUNTRACED)`. Returns `true` if the caller should park. + /// + /// Returns `false` (and records nothing) when a `SIGCONT` arrived after the + /// stop signal was dequeued but before this call — see + /// [`Self::set_job_continued`] / `continue_generation`. Closing this race at + /// the stop site lets us avoid scrubbing the pending-signal queue (which + /// would require modifying `starry-signal`). + pub fn set_job_stopped(&self, signo: Signo, continue_gen_snapshot: u64) -> bool { + let mut jc = self.job_control.lock(); + if jc.continue_generation != continue_gen_snapshot { + // A continue raced in after we observed `continue_gen_snapshot`; + // honor it and do not stop. + return false; + } + jc.stopped = Some(signo); + jc.status = Some(JobStatus::Stopped(signo)); + true + } + + /// Snapshot the continue generation. Taken right after a stop signal is + /// dequeued and passed to [`Self::set_job_stopped`]; any intervening + /// `SIGCONT` advances the generation and cancels the stop. + pub fn continue_generation(&self) -> u64 { + self.job_control.lock().continue_generation + } + + /// Continue a stopped process: clear the stop, queue a `Continued` report, + /// and wake parked threads. Returns true if it had been stopped. + /// + /// Always advances `continue_generation` so a concurrent stop in progress + /// (signal already dequeued, not yet parked) observes the continue and + /// skips parking. + pub fn set_job_continued(&self) -> bool { + let mut jc = self.job_control.lock(); + jc.continue_generation = jc.continue_generation.wrapping_add(1); + let was_stopped = jc.stopped.take().is_some(); + if was_stopped { + jc.status = Some(JobStatus::Continued); + drop(jc); + // Wake only when a thread was actually parked; avoids spurious + // wakeups on SIGCONT to an already-running process. + self.cont_event.wake(); + } + was_stopped + } + + /// Force-clear the stop (for `SIGKILL`) so a parked thread re-checks and + /// proceeds to terminate. Does not queue a `Continued` report. + pub fn clear_job_stop_for_kill(&self) { + let was_stopped = self.job_control.lock().stopped.take().is_some(); + if was_stopped { + self.cont_event.wake(); + } + } + + /// The wait queue woken when the process is continued or killed. + pub fn cont_event(&self) -> Arc { + self.cont_event.clone() + } + + /// Peek the pending job-control status report (without consuming it) if it + /// matches a kind the caller's `waitpid` flags allow (`WUNTRACED` for + /// stopped, `WCONTINUED` for continued). + pub fn peek_job_status_if( + &self, + want_stopped: bool, + want_continued: bool, + ) -> Option { + let jc = self.job_control.lock(); + match jc.status { + Some(s @ JobStatus::Stopped(_)) if want_stopped => Some(s), + Some(s @ JobStatus::Continued) if want_continued => Some(s), + _ => None, + } + } + + /// Consume the pending job-control status report if it matches a kind the + /// caller's `waitpid` flags allow. Mirrors [`Self::peek_job_status_if`] but + /// clears the slot; call it only after the status has been published to + /// userspace so a faulting copy leaves the report intact to retry. + pub fn take_job_status_if( + &self, + want_stopped: bool, + want_continued: bool, + ) -> Option { + let mut jc = self.job_control.lock(); + match jc.status { + Some(JobStatus::Stopped(_)) if want_stopped => jc.status.take(), + Some(JobStatus::Continued) if want_continued => jc.status.take(), + _ => None, + } + } + /// Get the accumulated CPU time of waited children. pub fn children_cpu_time(&self) -> (TimeValue, TimeValue) { *self.children_cpu_time.lock() diff --git a/os/StarryOS/kernel/src/task/signal.rs b/os/StarryOS/kernel/src/task/signal.rs index 652d8bd4bb..588fbcc818 100644 --- a/os/StarryOS/kernel/src/task/signal.rs +++ b/os/StarryOS/kernel/src/task/signal.rs @@ -1,12 +1,15 @@ +use core::{future::poll_fn, task::Poll}; + use ax_errno::{AxError, AxResult}; use ax_hal::uspace::UserContext; -use ax_task::{TaskInner, current}; +use ax_task::{TaskInner, current, future::block_on}; +use linux_raw_sys::general::{CLD_CONTINUED, CLD_STOPPED}; use starry_process::Pid; -use starry_signal::{SignalInfo, SignalOSAction, SignalSet}; +use starry_signal::{SignalInfo, SignalOSAction, SignalSet, Signo}; use super::{ - AsThread, SYSCALL_INSN_LEN, Thread, do_exit, get_process_data, get_process_group, get_task, - is_zombie_pid, + AsThread, ProcessData, SYSCALL_INSN_LEN, Thread, do_exit, get_process_data, get_process_group, + get_task, is_zombie_pid, }; /// Information needed to restart a syscall if SA_RESTART applies. @@ -150,13 +153,80 @@ pub fn check_signals( } do_exit(128 + signo as i32, true); } - SignalOSAction::Stop => do_exit(1, true), + SignalOSAction::Stop => do_job_stop(thr, signo), SignalOSAction::Continue => {} SignalOSAction::NoFurtherAction => {} } true } +/// Notify a process's parent of a job-control state change by sending it +/// `SIGCHLD` (with `CLD_STOPPED`/`CLD_CONTINUED`) and waking its `waitpid`. +fn notify_parent_job_change(proc_data: &ProcessData, code: i32, status: i32) { + let proc = &proc_data.proc; + let Some(parent) = proc.parent() else { + return; + }; + // si_uid carries the child's real UID; read it from any live thread. + let child_uid = proc + .threads() + .into_iter() + .next() + .and_then(|tid| get_task(tid).ok()) + .map_or(0, |task| task.as_thread().cred().uid); + let sig = SignalInfo::new_sigchld(proc.pid(), child_uid, code, status); + let _ = send_signal_to_process(parent.pid(), Some(sig)); + if let Ok(data) = get_process_data(parent.pid()) { + data.child_exit_event.wake(); + } +} + +/// Enter a job-control stop: record the stop, notify the parent, then park the +/// current thread until `SIGCONT` clears the stop (or `SIGKILL` force-resumes it +/// so the kill can proceed). +/// +/// Uses a plain block — not [`interruptible`](ax_task::future::interruptible) — +/// because an ordinary signal must **not** wake a stopped process; only +/// continue/kill clear `is_job_stopped`. +/// +/// The STOP-immediately-followed-by-CONT race (e.g. busybox `killall5 -STOP` +/// then `-CONT`) is closed by snapshotting `continue_generation` *before* +/// recording the stop: if a `SIGCONT` bumped the generation in between, +/// [`ProcessData::set_job_stopped`] returns `false` and we never park. This +/// replaces the pending-signal scrubbing the reference design used (which would +/// require modifying `starry-signal`). +/// +/// Known limitations (acceptable for the single-threaded shells/tools this +/// targets): +/// - Only the thread that dequeues the stop signal parks; sibling threads of a +/// multi-threaded process keep running until they next hit a stop signal. +/// Linux stops every thread in the group. +fn do_job_stop(thr: &Thread, signo: Signo) { + let proc_data = &thr.proc_data; + // Snapshot before recording the stop so a racing SIGCONT (which advances the + // generation) cancels this stop. + let continue_gen = proc_data.continue_generation(); + if !proc_data.set_job_stopped(signo, continue_gen) { + return; + } + notify_parent_job_change(proc_data, CLD_STOPPED as i32, signo as i32); + + let cont_event = proc_data.cont_event(); + block_on(poll_fn(|cx| { + if !proc_data.is_job_stopped() { + return Poll::Ready(()); + } + cont_event.register(cx.waker()); + // Re-check after registering to avoid a lost wakeup if the continue + // landed between the check above and registration. + if proc_data.is_job_stopped() { + Poll::Pending + } else { + Poll::Ready(()) + } + })); +} + pub fn block_next_signal() { current().as_thread().block_next_signal_check(); } @@ -223,6 +293,26 @@ pub fn send_signal_to_process(pid: Pid, sig: Option) -> AxResult<()> } }; + // Job-control side effects must run at send time: a stopped process is + // parked in the kernel and cannot dequeue SIGCONT itself. + if let Some(sig) = &sig { + match sig.signo() { + // POSIX: SIGCONT resumes a stopped process and reports CLD_CONTINUED. + // `set_job_continued` (evaluated in the guard) always advances the + // process's continue generation as a side effect — so a stop signal + // already dequeued but not yet parked (e.g. killall5's + // kill(-1,SIGSTOP) immediately followed by kill(-1,SIGCONT)) observes + // the continue and skips parking, closing the STOP-then-CONT race + // without scrubbing the pending queue — and returns whether the + // process had actually been stopped; only then do we notify the parent. + Signo::SIGCONT if proc_data.set_job_continued() => { + notify_parent_job_change(&proc_data, CLD_CONTINUED as i32, Signo::SIGCONT as i32); + } + Signo::SIGKILL => proc_data.clear_job_stop_for_kill(), + _ => {} + } + } + if let Some(sig) = sig { let signo = sig.signo(); info!("Send signal {signo:?} to process {pid}"); diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/CMakeLists.txt new file mode 100644 index 0000000000..f81618fcbd --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.20) +project(test-job-control-stop C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(THREADS_PREFER_PTHREAD_FLAG ON) + +find_package(Threads REQUIRED) + +add_executable(test-job-control-stop src/main.c) +target_include_directories(test-job-control-stop PRIVATE src) +target_compile_options(test-job-control-stop PRIVATE -Wall -Wextra -Werror) +target_link_libraries(test-job-control-stop PRIVATE Threads::Threads) + +install(TARGETS test-job-control-stop RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/main.c new file mode 100644 index 0000000000..15bbac1e9e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/main.c @@ -0,0 +1,81 @@ +/* + * test-job-control-stop + * + * Regression test for POSIX job control. A `SIGSTOP` (or SIGTSTP/SIGTTIN/ + * SIGTTOU) must *suspend* the target process, not kill it; the parent's + * waitpid(WUNTRACED) must observe WIFSTOPPED. A subsequent `SIGCONT` must + * resume the process and waitpid(WCONTINUED) must observe WIFCONTINUED. + * Finally SIGKILL must terminate even a stopped process. + * + * Before this fix StarryOS turned SignalOSAction::Stop into do_exit(1): + * the child was *killed* by SIGSTOP, so the WUNTRACED check below would + * have seen WIFEXITED/WIFSIGNALED instead of WIFSTOPPED and FAILed. That + * broke shell job control and busybox `killall5 -STOP/-CONT`. + */ + +#include "test_framework.h" +#include +#include +#include +#include + +int main(void) +{ + TEST_START("job control: SIGSTOP suspends, SIGCONT resumes"); + + pid_t child = fork(); + CHECK(child >= 0, "fork"); + if (child < 0) { + TEST_DONE(); + } + + if (child == 0) { + /* Child: spin forever. It must keep "running" (be schedulable) + * until stopped, and must be re-schedulable after SIGCONT. We rely + * on the parent's kill+waitpid to drive the state transitions. */ + for (;;) { + /* busy-yield so the parent gets CPU time on smp=1 */ + sched_yield(); + } + _exit(0); /* unreachable */ + } + + int st; + + /* --- SIGSTOP must STOP, not kill --- */ + CHECK_RET(kill(child, SIGSTOP), 0, "kill(child, SIGSTOP)"); + + /* waitpid(WUNTRACED) blocks until the child is stopped, then reports it. + * If the old (buggy) behavior were present, the child would have exited + * and we'd get WIFEXITED/WIFSIGNALED here instead. */ + pid_t w = waitpid(child, &st, WUNTRACED); + CHECK_RET(w, child, "waitpid(WUNTRACED) returns child pid"); + CHECK(WIFSTOPPED(st), "child is STOPPED (WIFSTOPPED)"); + CHECK(!WIFEXITED(st), "child did NOT exit on SIGSTOP"); + CHECK(!WIFSIGNALED(st), "child was NOT killed by SIGSTOP"); + if (WIFSTOPPED(st)) { + CHECK(WSTOPSIG(st) == SIGSTOP, "WSTOPSIG == SIGSTOP"); + } + + /* The child must still exist (a stopped process is not reaped). A + * second kill(child, 0) probing existence should succeed. */ + CHECK_RET(kill(child, 0), 0, "stopped child still exists (kill 0)"); + + /* --- SIGCONT must CONTINUE --- */ + CHECK_RET(kill(child, SIGCONT), 0, "kill(child, SIGCONT)"); + + w = waitpid(child, &st, WCONTINUED); + CHECK_RET(w, child, "waitpid(WCONTINUED) returns child pid"); + CHECK(WIFCONTINUED(st), "child reported CONTINUED (WIFCONTINUED)"); + + /* --- SIGKILL must terminate even after a stop/continue cycle --- */ + CHECK_RET(kill(child, SIGKILL), 0, "kill(child, SIGKILL)"); + w = waitpid(child, &st, 0); + CHECK_RET(w, child, "waitpid reaps killed child"); + CHECK(WIFSIGNALED(st), "child terminated by signal"); + if (WIFSIGNALED(st)) { + CHECK(WTERMSIG(st) == SIGKILL, "WTERMSIG == SIGKILL"); + } + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/test_framework.h new file mode 100644 index 0000000000..44ef0b7faf --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/c/src/test_framework.h @@ -0,0 +1,65 @@ +#pragma once + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ + __fail++; \ + } \ +} while(0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-aarch64.toml new file mode 100644 index 0000000000..5925d087dd --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-aarch64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "cortex-a53", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-aarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-job-control-stop" +success_regex = ["(?s)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-loongarch64.toml new file mode 100644 index 0000000000..c460d2ffb1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-loongarch64.toml @@ -0,0 +1,17 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "128M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-job-control-stop" +success_regex = ["(?s)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-riscv64.toml new file mode 100644 index 0000000000..c17a949862 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-riscv64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-job-control-stop" +success_regex = ["(?s)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-x86_64.toml new file mode 100644 index 0000000000..e40c09b397 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-job-control-stop/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-job-control-stop" +success_regex = ["(?s)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 From ad7709d6e1870a752e64d45d35783ad154d21862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Mon, 25 May 2026 15:22:08 +0800 Subject: [PATCH 26/71] refactor(dma-api): split coherent and streaming DMA APIs (#932) * Refactor DMA API usage across various drivers - Updated DMA allocation and deallocation methods to use ContiguousBuffer and CoherentArray types for better memory management. - Replaced deprecated DArray and DBuff types with their new counterparts in the rd-block, net, and usb drivers. - Enhanced memory synchronization methods to ensure proper data handling between CPU and device. - Improved error handling in DMA mapping functions to accommodate new constraints and layout requirements. - Adjusted driver implementations to align with the updated DMA API, ensuring compatibility and performance improvements. * Refactor DMA API for improved coherence and memory management - Updated `CoherentBox` and `ContiguousBox` to use `DmaAllocation` instead of `DCommon` for better memory handling. - Renamed methods in `CoherentBox` and `ContiguousBox` from `as_buff_mut` to `as_bytes_mut` for clarity. - Changed `DmaMapHandle` to use `bounce_ptr` instead of `map_alloc_virt` to better reflect its purpose. - Introduced a new module `op` to handle architecture-specific DMA operations, with aarch64 implementation for cache operations. - Updated `DeviceDma` to use the new `DmaOp` trait for DMA operations, replacing previous OSAL references. - Refactored buffer pool creation methods to use `contiguous_buffer_pool` instead of `new_pool` for consistency. - Removed deprecated code and comments related to previous memory allocation strategies. - Updated tests and driver implementations to align with the new API structure and naming conventions. --- components/axklib/src/dma.rs | 180 ++-- components/dma-api/README.md | 778 +++--------------- components/dma-api/src/array.rs | 462 ++++++----- components/dma-api/src/common.rs | 84 +- components/dma-api/src/dbox.rs | 211 ++--- components/dma-api/src/def.rs | 173 ++-- components/dma-api/src/lib.rs | 559 ++++++------- components/dma-api/src/map_single.rs | 232 ------ .../dma-api/src/{osal => op}/aarch64.rs | 0 components/dma-api/src/op/mod.rs | 195 +++++ components/dma-api/src/{osal => op}/nop.rs | 0 components/dma-api/src/osal/mod.rs | 127 --- components/dma-api/src/pool.rs | 76 +- components/dma-api/src/streaming.rs | 142 ++++ components/dma-api/tests/test.rs | 510 ++++-------- components/dma-api/tests/test_helpers.rs | 371 ++++++--- drivers/ax-driver/src/block/phytium_mci.rs | 2 +- .../src/block/rockchip/sdhci_rk3568.rs | 4 +- drivers/ax-driver/src/block/rockchip_mmc.rs | 4 +- drivers/ax-driver/src/block/rockchip_sd.rs | 2 +- .../ax-driver/src/block/rockchip_sd/block.rs | 2 +- drivers/ax-driver/src/net/fxmac.rs | 8 +- drivers/ax-driver/src/net/ixgbe.rs | 9 +- drivers/ax-driver/src/rknpu.rs | 2 +- drivers/ax-driver/src/usb/mod.rs | 71 +- drivers/blk/dwmmc-host/src/dma.rs | 38 +- drivers/blk/nvme-driver/src/nvme.rs | 15 +- drivers/blk/nvme-driver/src/queue.rs | 11 +- drivers/blk/phytium-mci-host/src/dma.rs | 56 +- drivers/blk/ramdisk/examples/ramdisk.rs | 46 +- drivers/blk/rd-block/src/lib.rs | 59 +- drivers/blk/sdhci-host/src/dma.rs | 47 +- drivers/net/eth-intel/src/e1000/mod.rs | 10 +- drivers/net/rd-net/src/lib.rs | 25 +- drivers/net/realtek-rtl8125/src/lib.rs | 6 +- drivers/net/realtek-rtl8125/src/queue.rs | 6 +- drivers/npu/rockchip-npu/src/gem.rs | 18 +- drivers/npu/rockchip-npu/src/task/mod.rs | 8 +- .../npu/rockchip-npu/src/task/op/matmul.rs | 19 +- .../usb-host/src/backend/kmod/dwc/event.rs | 9 +- .../usb/usb-host/src/backend/kmod/dwc/mod.rs | 15 +- drivers/usb/usb-host/src/backend/kmod/osal.rs | 2 +- .../usb/usb-host/src/backend/kmod/transfer.rs | 61 +- .../usb-host/src/backend/kmod/xhci/context.rs | 58 +- .../src/backend/kmod/xhci/endpoint.rs | 6 +- .../usb-host/src/backend/kmod/xhci/event.rs | 7 +- .../usb-host/src/backend/kmod/xhci/ring.rs | 9 +- .../usb/usb-host/src/backend/ty/transfer.rs | 2 +- drivers/usb/utils/ktest-helper/src/lib.rs | 181 ---- 49 files changed, 1973 insertions(+), 2945 deletions(-) delete mode 100644 components/dma-api/src/map_single.rs rename components/dma-api/src/{osal => op}/aarch64.rs (100%) create mode 100644 components/dma-api/src/op/mod.rs rename components/dma-api/src/{osal => op}/nop.rs (100%) delete mode 100644 components/dma-api/src/osal/mod.rs create mode 100644 components/dma-api/src/streaming.rs diff --git a/components/axklib/src/dma.rs b/components/axklib/src/dma.rs index 5d6ad5df15..21e7ff0510 100644 --- a/components/axklib/src/dma.rs +++ b/components/axklib/src/dma.rs @@ -1,7 +1,9 @@ use core::{alloc::Layout, num::NonZeroUsize, ptr::NonNull}; use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; -use dma_api::{DeviceDma, DmaDirection, DmaError, DmaHandle, DmaMapHandle, DmaOp}; +use dma_api::{ + DeviceDma, DmaAllocHandle, DmaConstraints, DmaDirection, DmaError, DmaMapHandle, DmaOp, +}; pub struct KlibDma; @@ -11,7 +13,7 @@ pub fn op() -> &'static KlibDma { &DMA } -pub fn device(dma_mask: u64) -> DeviceDma { +pub fn device_with_mask(dma_mask: u64) -> DeviceDma { DeviceDma::new(dma_mask, op()) } @@ -26,16 +28,19 @@ impl DmaPages { layout.size().div_ceil(PAGE_SIZE_4K) } - fn layout_align(layout: Layout) -> usize { - layout.align().max(PAGE_SIZE_4K) + fn layout_align(layout: Layout, constraints: DmaConstraints) -> usize { + layout.align().max(constraints.align).max(PAGE_SIZE_4K) } - /// Allocates DMA-coherent pages using the kernel DMA allocator. + /// Allocates DMA-visible pages using the kernel DMA allocator. /// - /// `dma_alloc_pages` is expected to honor `dma_mask` and the requested + /// `dma_alloc_pages` is expected to honor `addr_mask` and the requested /// alignment. The checks below are defensive validation so a bad platform /// allocator fails before the buffer is handed to a device. - unsafe fn alloc_for_layout(dma_mask: u64, layout: Layout) -> Result { + unsafe fn alloc_for_layout( + constraints: DmaConstraints, + layout: Layout, + ) -> Result { if layout.size() == 0 { return Ok(Self { cpu_addr: NonNull::dangling(), @@ -45,23 +50,23 @@ impl DmaPages { } let num_pages = Self::layout_pages(layout); - let align = Self::layout_align(layout); - let cpu_vaddr = crate::klib::dma_alloc_pages(dma_mask, num_pages, align) + let align = Self::layout_align(layout, constraints); + let cpu_vaddr = crate::klib::dma_alloc_pages(constraints.addr_mask, num_pages, align) .map_err(|_| DmaError::NoMemory)?; let cpu_addr = NonNull::new(cpu_vaddr.as_mut_ptr()).ok_or(DmaError::NoMemory)?; let dma_addr = dma_addr_from_vaddr(cpu_vaddr); - if !dma_range_fits_mask(dma_addr, layout.size(), dma_mask) { + if !dma_range_fits_mask(dma_addr, layout.size(), constraints.addr_mask) { unsafe { Self::dealloc_pages(cpu_addr, num_pages) }; return Err(DmaError::DmaMaskNotMatch { addr: dma_addr.into(), - mask: dma_mask, + mask: constraints.addr_mask, }); } - if !dma_addr_is_aligned(dma_addr, layout.align()) { + if !dma_addr_is_aligned(dma_addr, constraints.align.max(layout.align())) { unsafe { Self::dealloc_pages(cpu_addr, num_pages) }; return Err(DmaError::AlignMismatch { - required: layout.align(), + required: constraints.align.max(layout.align()), address: dma_addr.into(), }); } @@ -115,126 +120,75 @@ impl DmaOp for KlibDma { PAGE_SIZE_4K } - unsafe fn map_single( + unsafe fn alloc_contiguous( &self, - dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - align: usize, - direction: DmaDirection, - ) -> Result { - let align = align.max(1); - let layout = Layout::from_size_align(size.get(), align)?; - let dma_addr = dma_addr_from_ptr(addr); - - if dma_range_fits_mask(dma_addr, size.get(), dma_mask) - && dma_addr_is_aligned(dma_addr, align) - { - return Ok(unsafe { DmaMapHandle::new(addr, dma_addr.into(), layout, None) }); - } - - let map_pages = unsafe { DmaPages::alloc_for_layout(dma_mask, layout)? }; - let map_virt = map_pages.cpu_addr; - - if matches!( - direction, - DmaDirection::ToDevice | DmaDirection::Bidirectional - ) { - unsafe { - map_virt - .as_ptr() - .copy_from_nonoverlapping(addr.as_ptr(), size.get()); - } - } - - Ok(unsafe { DmaMapHandle::new(addr, map_pages.dma_addr.into(), layout, Some(map_virt)) }) + constraints: DmaConstraints, + layout: Layout, + ) -> Option { + let pages = unsafe { DmaPages::alloc_for_layout(constraints, layout).ok()? }; + Some(unsafe { DmaAllocHandle::new(pages.cpu_addr, pages.dma_addr.into(), layout) }) } - unsafe fn unmap_single(&self, handle: DmaMapHandle) { - if let Some(map_virt) = handle.alloc_virt() { - let num_pages = DmaPages::layout_pages(handle.layout()); - unsafe { DmaPages::dealloc_pages(map_virt, num_pages) }; - } + unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle) { + let num_pages = DmaPages::layout_pages(handle.layout()); + unsafe { DmaPages::dealloc_pages(handle.as_ptr(), num_pages) }; } - fn prepare_read( + unsafe fn alloc_coherent( &self, - handle: &DmaMapHandle, - offset: usize, - size: usize, - direction: DmaDirection, - ) { - if !matches!( - direction, - DmaDirection::FromDevice | DmaDirection::Bidirectional - ) { - return; + constraints: DmaConstraints, + layout: Layout, + ) -> Option { + let pages = unsafe { DmaPages::alloc_for_layout(constraints, layout).ok()? }; + if CoherentDmaPolicy::make_uncached(&pages, layout).is_err() { + unsafe { DmaPages::dealloc_pages(pages.cpu_addr, pages.num_pages) }; + return None; } - let target = unsafe { handle.as_ptr().add(offset) }; - if let Some(map_virt) = handle.alloc_virt() - && map_virt != handle.as_ptr() - { - let source = unsafe { map_virt.add(offset) }; - self.invalidate(source, size); - unsafe { - target - .as_ptr() - .copy_from_nonoverlapping(source.as_ptr(), size); - } + Some(unsafe { DmaAllocHandle::new(pages.cpu_addr, pages.dma_addr.into(), layout) }) + } + + unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) { + let num_pages = DmaPages::layout_pages(handle.layout()); + if CoherentDmaPolicy::restore_cached(handle.as_ptr(), num_pages).is_err() { return; } - - self.invalidate(target, size); + unsafe { DmaPages::dealloc_pages(handle.as_ptr(), num_pages) }; } - fn confirm_write( + unsafe fn map_streaming( &self, - handle: &DmaMapHandle, - offset: usize, - size: usize, - direction: DmaDirection, - ) { - if !matches!( - direction, - DmaDirection::ToDevice | DmaDirection::Bidirectional - ) { - return; - } + constraints: DmaConstraints, + addr: NonNull, + size: NonZeroUsize, + _direction: DmaDirection, + ) -> Result { + let align = constraints.align.max(1); + let layout = Layout::from_size_align(size.get(), align)?; + let dma_addr = dma_addr_from_ptr(addr); - let source = unsafe { handle.as_ptr().add(offset) }; - if let Some(map_virt) = handle.alloc_virt() - && map_virt != handle.as_ptr() + if dma_range_fits_mask(dma_addr, size.get(), constraints.addr_mask) + && dma_addr_is_aligned(dma_addr, align) { - let target = unsafe { map_virt.add(offset) }; - unsafe { - target - .as_ptr() - .copy_from_nonoverlapping(source.as_ptr(), size); - } - self.flush(target, size); - return; - } - - self.flush(source, size); - } - - unsafe fn alloc_coherent(&self, dma_mask: u64, layout: Layout) -> Option { - let pages = unsafe { DmaPages::alloc_for_layout(dma_mask, layout).ok()? }; - if CoherentDmaPolicy::make_uncached(&pages, layout).is_err() { - unsafe { DmaPages::dealloc_pages(pages.cpu_addr, pages.num_pages) }; - return None; + return Ok(unsafe { DmaMapHandle::new(addr, dma_addr.into(), layout, None) }); } - Some(unsafe { DmaHandle::new(pages.cpu_addr, pages.dma_addr.into(), layout) }) + let map_pages = unsafe { DmaPages::alloc_for_layout(constraints, layout)? }; + Ok(unsafe { + DmaMapHandle::new( + addr, + map_pages.dma_addr.into(), + layout, + Some(map_pages.cpu_addr), + ) + }) } - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { - let num_pages = DmaPages::layout_pages(handle.layout()); - if CoherentDmaPolicy::restore_cached(handle.as_ptr(), num_pages).is_err() { - return; + unsafe fn unmap_streaming(&self, handle: DmaMapHandle) { + if let Some(map_virt) = handle.bounce_ptr() { + let num_pages = DmaPages::layout_pages(handle.layout()); + unsafe { DmaPages::dealloc_pages(map_virt, num_pages) }; } - unsafe { DmaPages::dealloc_pages(handle.as_ptr(), num_pages) }; } } diff --git a/components/dma-api/README.md b/components/dma-api/README.md index 1c37fca07f..b11083de9e 100644 --- a/components/dma-api/README.md +++ b/components/dma-api/README.md @@ -1,708 +1,172 @@ # DMA API -用于 Rust 的 DMA(直接内存访问)抽象 API,提供安全的 DMA 内存操作接口,适用于嵌入式和裸机环境。 - -## 目录 - -- [快速开始](#快速开始) -- [核心概念](#核心概念) -- [使用场景指南](#使用场景指南) -- [API 方法目录](#api-方法目录) -- [完整示例](#完整示例) -- [缓存同步详解](#缓存同步详解) -- [完整 API 参考](#完整-api-参考) +`dma-api` provides typed DMA ownership primitives for drivers. The public +surface separates three different concepts that should not be mixed: + +- `CoherentArray` / `CoherentBox`: CPU and device see the same memory + without explicit cache maintenance. Use these for descriptor rings, command + queues, completion queues, and controller contexts. Coherency does not + provide ordering; drivers still need their normal barriers before doorbells + and after completion ownership changes. +- `ContiguousArray` / `ContiguousBox`: owned device-address-contiguous + DMA memory using normal CPU mapping. Use these for data buffers and buffer + pools. Accessors only touch CPU memory; ownership transfer is explicit via + `sync_for_device(_all)` and `sync_for_cpu(_all)`. +- `StreamingMap`: RAII mapping of an existing caller-owned buffer. Use this + for one transfer of a buffer not owned by `dma-api`. Explicit sync methods + handle cache maintenance and bounce-buffer copy, and `Drop` unmaps. + +`DmaAddr` is the only portable device-visible address type. Backend-private raw +handles are split into `DmaAllocHandle` for owned allocations and +`DmaMapHandle` for streaming mappings. + +## Constraints + +Every allocation and mapping is checked against `DmaConstraints`: + +```rust,ignore +pub struct DmaConstraints { + pub addr_mask: u64, + pub align: usize, + pub boundary: Option, + pub max_segment_size: Option, +} +``` ---- +`DeviceDma::new(dma_mask, op)` is shorthand for +`DmaConstraints::new(dma_mask)`. Use `with_constraints` when a specific queue +or transfer has stronger alignment, boundary, or segment-size requirements. -## 快速开始 +Backends must never hand a driver a DMA address outside the requested mask. For +example, a device created with `DeviceDma::new(u32::MAX as u64, op)` must only +return 32-bit reachable DMA addresses. Streaming mappings may use a fast path +when the original buffer already satisfies the constraints; otherwise they +should allocate an in-mask bounce buffer. -### 1. 实现 `DmaOp` trait +## Backend Contract -首先需要为你的平台实现 `DmaOp` trait,提供底层 DMA 操作支持: +Implement `DmaOp` once for the platform: ```rust,ignore -use dma_api::{DmaOp, DmaDirection, DmaHandle, DmaError}; -use core::{alloc::Layout, ptr::NonNull, num::NonZeroUsize}; +use core::{alloc::Layout, num::NonZeroUsize, ptr::NonNull}; +use dma_api::{ + DmaAllocHandle, DmaConstraints, DmaDirection, DmaError, DmaMapHandle, DmaOp, +}; -struct MyDmaImpl; +struct MyDma; -impl DmaOp for MyDmaImpl { +impl DmaOp for MyDma { fn page_size(&self) -> usize { - 4096 // 返回系统页大小 + 4096 } - unsafe fn map_single( + unsafe fn alloc_contiguous( &self, - dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - align: usize, - direction: DmaDirection, - ) -> Result { - // 实现虚拟地址到 DMA 地址的映射 - // 返回 DmaHandle - todo!() + constraints: DmaConstraints, + layout: Layout, + ) -> Option { + todo!("allocate normal mapped, device-visible contiguous DMA memory") } - unsafe fn unmap_single(&self, handle: DmaHandle) { - // 解除 DMA 映射 - todo!() + unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle) { + todo!("free alloc_contiguous memory") } unsafe fn alloc_coherent( &self, - dma_mask: u64, + constraints: DmaConstraints, layout: Layout, - ) -> Option { - // 分配 DMA 一致性内存 - todo!() + ) -> Option { + todo!("allocate the same constrained memory and apply coherent policy") } - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { - // 释放 DMA 内存 - todo!() + unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) { + todo!("restore mapping policy and free alloc_coherent memory") } -} -``` - -### 2. 选择合适的 DMA 容器 - -根据你的使用场景选择: - -| 需求 | 推荐容器 | 特点 | -|------|---------|------| -| 需要数组类型的 DMA 缓冲区 | `DArray` | 自动缓存同步,固定大小 | -| 需要单个结构体的 DMA 缓冲区 | `DBox` | 自动缓存同步,单个值 | -| 映射现有缓冲区用于 DMA | `SArrayPtr` | 手动缓存同步,单个连续内存区域映射 | - ---- - -## 核心概念 - -### DMA 传输方向 (`DmaDirection`) - -DMA 操作有三种方向,决定了缓存同步的行为: - -```rust,ignore -pub enum DmaDirection { - ToDevice, // CPU → 设备:CPU 写数据,设备读 - FromDevice, // 设备 → CPU:设备写数据,CPU 读 - Bidirectional, // 双向:CPU 和设备都可能读写 -} -``` - -**选择指南**: -- **ToDevice**: 用于发送数据到设备(如网卡发送缓冲区) -- **FromDevice**: 用于接收设备数据(如网卡接收缓冲区) -- **Bidirectional**: 用于双向通信(如驱动程序与设备共享内存) - -### 缓存同步 - -DMA 操作需要处理 CPU 缓存和内存之间的数据一致性: - -| 操作 | ToDevice | FromDevice | Bidirectional | -|------|----------|------------|---------------| -| **写数据前** (`confirm_write`) | ✅ 刷新缓存 | ❌ 无操作 | ✅ 刷新缓存 | -| **读数据前** (`prepare_read`) | ❌ 无操作 | ✅ 使缓存失效 | ✅ 使缓存失效 | - -**自动同步 vs 手动同步**: -- `DArray` 和 `DBox`:每次访问(`read`/`set`/`write`/`modify`)**自动**同步对应元素的缓存 -- `SingleMap`:**不自动**同步,用户必须手动调用 `prepare_read_all()` 和 `confirm_write_all()` - -### DMA 地址掩码 (`dma_mask`) - -`dma_mask` 指定设备可寻址的地址范围: - -- `0xFFFFFFFF` (32 位设备,最多 4GB) -- `0xFFFFFFFFFFFFFFFF` (64 位设备,全地址空间) -- 其他值根据设备硬件限制 - ---- - -## 使用场景指南 - -### 场景 1: DMA 数组缓冲区 - -**用途**:需要固定大小的数组类型 DMA 缓冲区 - -**推荐**:`DArray` - -**特点**: -- ✅ 自动缓存同步(每次 `read`/`set` 时) -- ✅ 固定大小,随机访问 -- ✅ 类型安全 - -**示例**: -```rust,ignore,ignore -let device = DeviceDma::new(0xFFFFFFFF, &DMA_IMPL); - -// 创建 100 个 u32 的 DMA 数组 -let mut dma_array = device.array_zero_with_align::(100, 64, DmaDirection::FromDevice) - .expect("Failed to allocate"); - -dma_array.set(0, 0x12345678); // 写入(自动刷新缓存) -let value = dma_array.read(0); // 读取(自动失效缓存) - -let dma_addr = dma_array.dma_addr(); // 获取 DMA 地址给硬件 -``` - -**适用场景**: -- 网卡数据包缓冲区 -- 音频采样缓冲区 -- 图像帧缓冲区 - ---- - -### 场景 2: DMA 单值容器 - -**用途**:需要单个结构体的 DMA 缓冲区 - -**推荐**:`DBox` - -**特点**: -- ✅ 自动缓存同步(每次 `read`/`write`/`modify` 时) -- ✅ 适合配置寄存器、描述符等 -- ✅ 类型安全 - -**示例**: -```rust,ignore,ignore -#[derive(Default)] -struct Descriptor { - addr: u64, - length: u32, - flags: u32, -} - -let mut dma_desc = device.box_zero_with_align::(64, DmaDirection::ToDevice) - .expect("Failed to allocate"); - -dma_desc.modify(|d| d.length = 4096); // 修改(自动刷新缓存) -let desc = dma_desc.read(); // 读取(自动失效缓存) -``` - -**适用场景**: -- DMA 描述符 -- 设备配置结构 -- 状态寄存器 - ---- - -### 场景 3: 映射现有缓冲区 - -**用途**:将已存在的缓冲区映射用于 DMA - -**推荐**:`SingleMap` -**特点**: -- ⚠️ **手动**缓存同步 -- ✅ 临时映射,RAII 自动解映射 -- ✅ 适用于栈分配或静态缓冲区 - -**示例**: -```rust,ignore,ignore -let mut buffer = [0u8; 4096]; - -// 映射现有缓冲区 -let mapping = device.map_single_array(&buffer, 64, DmaDirection::ToDevice) - .expect("Mapping failed"); - -// ⚠️ 重要:使用前必须手动同步缓存 -mapping.confirm_write_all(); // 将 CPU 数据刷到内存 -// ... DMA 传输 ... -mapping.prepare_read_all(); // 使 CPU 缓存失效,准备接收设备数据 - -let dma_addr = mapping.dma_addr(); - -// 映射在离开作用域时自动解除(不会自动同步缓存) -``` - -**适用场景**: -- 临时 DMA 操作 -- 栈上的小缓冲区 -- 重用已分配的内存 - ---- - -## API 方法目录 - -### 📦 创建方法 - -| 方法 | 用途 | 返回类型 | 缓存同步 | -|------|------|----------|----------| -| [`array_zero(len, dir)`](#device-zeros) | 创建默认对齐的 DMA 数组 | `DArray` | 自动 | -| [`array_zero_with_align(len, align, dir)`](#device-zeros) | 创建指定对齐的 DMA 数组 | `DArray` | 自动 | -| [`box_zero(align, dir)`](#device-zeros) | 创建默认对齐的 DMA Box | `DBox` | 自动 | -| [`box_zero_with_align(align, dir)`](#device-zeros) | 创建指定对齐的 DMA Box | `DBox` | 自动 | -| [`map_single_array(buff, align, dir)`](#device-maps) | 映射现有缓冲区 | `SArrayPtr` | **手动** | - -### 🔍 访问方法(DArray) - -| 方法 | 用途 | 缓存同步 | 返回值 | -|------|------|----------|--------| -| [`read(index)`](#darray-access) | 读取元素 | 自动失效 | `Option` | -| [`set(index, value)`](#darray-access) | 写入元素 | 自动刷新 | `()` | -| [`copy_from_slice(slice)`](#darray-access) | 批量复制 | 自动刷新整个范围 | `()` | - -### 🔍 访问方法(DBox) - -| 方法 | 用途 | 缓存同步 | 返回值 | -|------|------|----------|--------| -| [`read()`](#dbox-access) | 读取值 | 自动失效 | `T` | -| [`write(value)`](#dbox-access) | 写入值 | 自动刷新 | `()` | -| [`modify(f)`](#dbox-access) | 修改值(read-modify-write) | 先失效后刷新 | `()` | - -### 🔄 同步方法(SingleMap) - -| 方法 | 用途 | 适用方向 | -|------|------|----------| -| [`prepare_read_all()`](#singlemap-sync) | 使 CPU 缓存失效 | `FromDevice`, `Bidirectional` | -| [`confirm_write_all()`](#singlemap-sync) | 刷新 CPU 缓存到内存 | `ToDevice`, `Bidirectional` | - -### 📊 信息方法 - -| 方法 | 用途 | 返回值 | -|------|------|--------| -| [`dma_addr()`](#info-methods) | 获取 DMA 地址 | `DmaAddr` | -| [`len()`](#info-methods) | 获取数组长度 | `usize` | - ---- - -## 完整示例 - -### 示例 1: 网卡接收缓冲区 - -```rust,ignore,ignore -use dma_api::{DeviceDma, Direction}; - -// 创建 DMA 设备 -let device = DeviceDma::new(0xFFFFFFFF, &DMA_IMPL); - -// 分配接收缓冲区(1500 字节数据包) -let mut rx_buffer = device.array_zero_with_align::(1500, 64, DmaDirection::FromDevice) - .expect("Failed to allocate RX buffer"); - -// 配置网卡使用这个 DMA 地址 -let dma_addr = rx_buffer.dma_addr(); -nic.set_rx_address(dma_addr.as_u64()); - -// ... 网卡接收数据 ... - -// 读取数据(自动使 CPU 缓存失效) -let data = rx_buffer.read(0).unwrap(); -``` - -### 示例 2: DMA 描述符配置 + unsafe fn map_streaming( + &self, + constraints: DmaConstraints, + addr: NonNull, + size: NonZeroUsize, + direction: DmaDirection, + ) -> Result { + todo!("map the existing buffer or create an in-constraint bounce buffer") + } -```rust,ignore,ignore -#[derive(Default)] -struct DmaDescriptor { - buffer_addr: u64, - length: u32, - control: u32, + unsafe fn unmap_streaming(&self, handle: DmaMapHandle) { + todo!("unmap and release any bounce allocation") + } } - -// 分配描述符 -let mut desc = device.box_zero_with_align::(64, DmaDirection::ToDevice) - .expect("Failed to allocate descriptor"); - -// 配置描述符(自动刷新缓存) -desc.write(DmaDescriptor { - buffer_addr: 0x12345000, - length: 4096, - control: 0x01, -}); - -// 修改描述符(自动失效 → 修改 → 刷新) -desc.modify(|d| d.length = 2048); - -// 获取 DMA 地址给硬件 -let desc_addr = desc.dma_addr(); -``` - -### 示例 3: 临时映射栈缓冲区 - -```rust,ignore,ignore -// 栈上的临时缓冲区 -let mut temp_buf = [0u8; 256]; - -// 映射用于 DMA 写入 -let mapping = device.map_single_array(&temp_buf, 64, DmaDirection::ToDevice) - .expect("Failed to map"); - -// 准备数据写入设备 -temp_buf[0] = 0xAA; -temp_buf[1] = 0xBB; - -// ⚠️ 必须手动刷新缓存 -mapping.confirm_write_all(); - -// ... 启动 DMA 传输 ... - -// 映射在作用域结束时自动解映射 -``` - ---- - -## 缓存同步详解 - -### 自动同步(DArray 和 DBox) - -`DArray` 和 `DBox` 在以下操作时**自动**处理缓存同步: - -| 操作 | 缓存同步行为 | -|------|--------------| -| `DArray::set(i, v)` | 写入后刷新单个元素 | -| `DArray::read(i)` | 读取前失效单个元素 | -| `DArray::copy_from_slice(s)` | 写入后刷新整个范围 | -| `DBox::write(v)` | 写入后刷新 | -| `DBox::read()` | 读取前失效 | -| `DBox::modify(f)` | 失效 → 执行闭包 → 刷新 | - -**优点**:使用简单,不会出错 -**缺点**:频繁同步可能影响性能 - -### 手动同步(SingleMap) - -`SingleMap` **不会**在 Drop 时自动同步缓存,必须显式调用: - -```rust,ignore,ignore -let mapping = device.map_single_array(&buffer, 64, DmaDirection::ToDevice)?; - -// 写入前准备 -mapping.confirm_write_all(); // 将 CPU 数据刷到内存 - -// ... DMA 传输 ... - -// 读取前准备 -mapping.prepare_read_all(); // 使 CPU 缓存失效 - -// Drop 时只会解除映射,不会自动同步 -``` - -**为什么这样设计**: -- 与 Linux DMA API 语义一致 -- 让用户精确控制缓存同步时机 -- 避免不必要的缓存操作 - -### 缓存同步规则 - -根据 DMA 方向选择同步操作: - -| DMA 方向 | 写入设备前 | 读取设备后 | -|----------|-----------|-----------| -| `ToDevice` | `confirm_write_all()` ✅ | ❌ 无需操作 | -| `FromDevice` | ❌ 无需操作 | `prepare_read_all()` ✅ | -| `Bidirectional` | `confirm_write_all()` ✅ | `prepare_read_all()` ✅ | - ---- - -## 完整 API 参考 - -### 核心类型 - -#### `DeviceDma` - -DMA 设备操作接口。 - -**构造函数**: -```rust,ignore -pub fn new(dma_mask: u64, osal: &impl DmaOp) -> Self -``` -- **用途**:创建 DMA 设备实例 -- **参数**: - - `dma_mask`: 设备可寻址的地址掩码(如 `0xFFFFFFFF`) - - `osal`: 实现 `DmaOp` trait 的操作系统抽象层 -- **示例**: -```rust,ignore -let device = DeviceDma::new(0xFFFFFFFF, &DMA_IMPL); -``` - -#### 数组创建方法 - -```rust,ignore -pub fn array_zero( - &self, - len: usize, - direction: DmaDirection, -) -> Result, DmaError> - -pub fn array_zero_with_align( - &self, - len: usize, - align: usize, - direction: DmaDirection, -) -> Result, DmaError> -``` -- **用途**:创建 DMA 可访问的数组,初始化为零 -- **参数**: - - `len`: 数组长度(元素个数) - - `align`: 对齐字节数(`array_zero` 默认为 `core::mem::align_of::()`) - - `direction`: DMA 传输方向 -- **返回**:`DArray` 容器 -- **缓存同步**:自动 -- **示例**: -```rust,ignore -let array = device.array_zero_with_align::(100, 64, DmaDirection::ToDevice)?; -``` - -#### Box 创建方法 - -```rust,ignore -pub fn box_zero( - &self, - direction: DmaDirection, -) -> Result, DmaError> - -pub fn box_zero_with_align( - &self, - align: usize, - direction: DmaDirection, -) -> Result, DmaError> -``` -- **用途**:创建 DMA 可访问的单值容器,初始化为零 -- **参数**: - - `align`: 对齐字节数(`box_zero` 默认为 `core::mem::align_of::()`) - - `direction`: DMA 传输方向 -- **返回**:`DBox` 容器 -- **缓存同步**:自动 -- **示例**: -```rust,ignore -let box_val = device.box_zero_with_align::(64, DmaDirection::Bidirectional)?; -``` - -#### 映射方法 - -```rust,ignore -pub fn map_single_array( - &self, - buff: &[T], - align: usize, - direction: DmaDirection, -) -> Result, DmaError> -``` -- **用途**:将现有缓冲区映射为 DMA 可访问 -- **参数**: - - `buff`: 要映射的缓冲区切片 - - `align`: 对齐字节数 - - `direction`: DMA 传输方向 -- **返回**:`SArrayPtr` 映射句柄 -- **缓存同步**:**手动**(必须使用 `to_vec()` 和 `copy_from_slice()`) -- **示例**: -```rust,ignore -let buf = [0u8; 4096]; -let mapping = device.map_single_array(&buf, 64, DmaDirection::ToDevice)?; -``` - ---- - -#### `DArray` - -DMA 可访问的数组容器,支持自动缓存同步。 - -##### 访问方法 - -```rust,ignore -pub fn read(&self, index: usize) -> Option -``` -- **用途**:读取数组中指定索引的元素 -- **缓存同步**:读取前自动失效 -- **返回**:`Some(T)` 如果索引有效,否则 `None` - -```rust,ignore -pub fn set(&mut self, index: usize, value: T) -> Option<()> -``` -- **用途**:写入数组中指定索引的元素 -- **缓存同步**:写入后自动刷新 -- **返回**:`Some(())` 如果索引有效,否则 `None` - -```rust,ignore -pub fn copy_from_slice(&mut self, src: &[T]) where T: Copy -``` -- **用途**:从 slice 复制数据到数组 -- **缓存同步**:写入后自动刷新整个范围 -- **要求**:`src.len() <= self.len()` - -##### 信息方法 - -```rust,ignore -pub fn dma_addr(&self) -> DmaAddr -``` -- **用途**:获取 DMA 地址,用于配置硬件 -- **返回**:DMA 地址 - -```rust,ignore -pub fn len(&self) -> usize -``` -- **用途**:获取数组长度 -- **返回**:元素个数 - ---- - -#### `DBox` - -DMA 可访问的单值容器,支持自动缓存同步。 - -##### 访问方法 - -```rust,ignore -pub fn read(&self) -> T -``` -- **用途**:读取存储的值 -- **缓存同步**:读取前自动失效 -- **返回**:存储的值 - -```rust,ignore -pub fn write(&mut self, value: T) -``` -- **用途**:写入新值 -- **缓存同步**:写入后自动刷新 - -```rust,ignore -pub fn modify(&mut self, f: F) -``` -- **用途**:修改值(read-modify-write 模式) -- **缓存同步**:读取前失效,写入后刷新 -- **示例**: -```rust,ignore -dma_box.modify(|v| v.field += 10); -``` - -##### 信息方法 - -```rust,ignore -pub fn dma_addr(&self) -> DmaAddr -``` -- **用途**:获取 DMA 地址,用于配置硬件 -- **返回**:DMA 地址 - ---- - -#### `SArrayPtr` - -映射单个连续内存区域的 DMA 数组,RAII 风格自动清理。 - -**注意**:此类型提供手动缓存同步控制,与 `DArray` 不同,它不会在每次访问时自动同步缓存。 - -##### 访问方法 - -```rust,ignore -pub fn read(&self, index: usize) -> Option -``` -- **用途**:读取指定索引的元素(**不自动**同步缓存) -- **返回**:`Some(T)` 如果索引有效,否则 `None` -- **注意**:读取前需手动使用 `to_vec()` 方法 - -```rust,ignore -pub fn set(&mut self, index: usize, value: T) -``` -- **用途**:写入指定索引的元素(**不自动**同步缓存) -- **注意**:写入后需使用 `copy_from_slice()` 刷新缓存 - -```rust,ignore -pub fn copy_from_slice(&mut self, src: &[T]) ``` -- **用途**:从 slice 复制数据到数组 -- **缓存同步**:写入后自动刷新整个范围 -```rust,ignore -pub fn to_vec(&self) -> Vec -``` -- **用途**:将整个数组转换为 Vec -- **缓存同步**:读取前自动失效整个范围 +The default sync methods perform cache maintenance and handle bounce-buffer +copying. Platforms can override them if the architecture needs a different +policy. -##### 信息方法 +## Driver Usage -```rust,ignore -pub fn dma_addr(&self) -> DmaAddr -``` -- **用途**:获取 DMA 地址,用于配置硬件 -- **返回**:DMA 地址 - -```rust,ignore -pub fn len(&self) -> usize -``` -- **用途**:获取数组长度(元素个数) -- **返回**:元素个数 +Descriptor/control memory: ```rust,ignore -pub fn is_empty(&self) -> bool +let mut ring = dma.coherent_array_zero_with_align::(256, 64)?; +ring.set(0, Descriptor::new(buffer_dma)); +doorbell_after_release_barrier(); ``` -- **用途**:检查数组是否为空 -- **返回**:如果长度为 0 返回 `true` ---- - -### 类型定义 - -#### `DmaDirection` - -DMA 传输方向枚举: +Owned data buffers: ```rust,ignore -pub enum DmaDirection { - ToDevice, // DMA_TO_DEVICE: CPU 写入,设备读取 - FromDevice, // DMA_FROM_DEVICE: 设备写入,CPU 读取 - Bidirectional, // DMA_BIDIRECTIONAL: 双向传输 -} +let mut tx = dma.contiguous_array_zero_with_align::( + len, + 64, + DmaDirection::ToDevice, +)?; +tx.copy_from_slice(packet); +tx.sync_for_device_all(); +submit(tx.dma_addr()); ``` -#### `DmaError` - -DMA 操作错误类型: +Device-written owned buffers: ```rust,ignore -pub enum DmaError { - NoMemory, // DMA 分配失败 - LayoutError(LayoutError), // 无效的内存布局 - DmaMaskNotMatch { addr, mask }, // DMA 地址超出设备掩码 - AlignMismatch { required, address }, // 地址对齐不满足要求 - NullPointer, // 提供了空指针 - ZeroSizedBuffer, // 零大小缓冲区不能用于 DMA -} +let rx = dma.contiguous_array_zero_with_align::( + len, + 64, + DmaDirection::FromDevice, +)?; +submit(rx.dma_addr()); +wait_complete(); +rx.sync_for_cpu_all(); +consume(rx.as_slice()); ``` -#### `DmaAddr` - -DMA 地址类型: +Streaming mappings: ```rust,ignore -pub struct DmaAddr(u64); - -impl DmaAddr { - pub fn as_u64(&self) -> u64; // 转换为 u64 - pub fn checked_add(&self, rhs: u64) -> Option; // 安全加法 -} +let map = dma.map_streaming_slice(buffer, 64, DmaDirection::Bidirectional)?; +map.sync_for_device_all(); +submit(map.dma_addr()); +wait_complete(); +map.sync_for_cpu_all(); ``` ---- - -### Linux 等价 API - -| Rust API | Linux Equivalent | -|----------|------------------| -| `DeviceDma::map_single_array()` | `dma_map_single()` | -| `SArrayPtr::drop()` | `dma_unmap_single()` | -| `DmaOp::alloc_coherent()` | `dma_alloc_coherent()` | -| `DmaOp::dealloc_coherent()` | `dma_free_coherent()` | -| `DmaOp::flush()` | `dma_cache_sync()` (DMA_TO_DEVICE) | -| `DmaOp::invalidate()` | `dma_cache_sync()` (DMA_FROM_DEVICE) | - ---- - -### 对齐要求 - -DMA 操作通常需要对齐到特定的边界: +Buffer pools use `ContiguousBufferPool` and return `ContiguousBuffer` values. +They are intended for repeated owned data buffers such as network RX/TX pools +or block read buffers. Reusing a buffer does not imply that the memory is +zeroed again; callers own the content and the explicit sync points. -- 常见对齐值:64、128、256、512、4096 -- `array_zero_with_align()` / `box_zero_with_align()` 的 `align` 参数指定对齐字节数 -- `map_single_array()` 的 `align` 参数也指定对齐要求 -- 确保返回的 DMA 地址满足对齐要求,否则返回 `DmaError::AlignMismatch` +## Choosing A Primitive -### DMA Mask +Use `Coherent*` for hardware metadata whose CPU and device ownership flips +frequently and where per-transfer cache maintenance would be wrong or too +fragile: xHCI contexts and rings, NVMe SQ/CQ, network descriptor rings, +SD/MMC descriptor tables. -`DeviceDma::new()` 的 `dma_mask` 参数指定设备可寻址的地址范围: +Use `Contiguous*` for owned payload memory that needs a contiguous +device-visible DMA address range but not an uncached CPU mapping: block data, +network pools, NVMe PRP data buffers, and accelerator input/output buffers. -- `0xFFFFFFFF` (32 位设备,最多 4GB) -- `0xFFFFFFFFFFFFFFFF` (64 位设备,全地址空间) -- 其他值根据设备硬件限制 -- 如果分配的 DMA 地址超出掩码范围,返回 `DmaError::DmaMaskNotMatch` +Use `StreamingMap` for caller-owned buffers whose lifetime is tied to one DMA +operation: USB transfer buffers, SDHCI/DWMMC/Phytium MCI block request slices, +or any buffer allocated outside the DMA API. diff --git a/components/dma-api/src/array.rs b/components/dma-api/src/array.rs index c0cafa3b02..0a46ab5081 100644 --- a/components/dma-api/src/array.rs +++ b/components/dma-api/src/array.rs @@ -1,230 +1,212 @@ -use core::{alloc::Layout, ptr::NonNull}; - -use crate::{DeviceDma, DmaDirection, DmaError, common::DCommon}; - -/// DMA 可访问的数组容器。 -/// -/// `DArray` 提供固定大小的 DMA 可访问数组,支持自动缓存同步。 -/// 每次访问元素(`read`/`set`)时都会根据 DMA 方向自动处理缓存操作。 -/// -/// # 类型参数 -/// -/// - `T`: 数组元素类型 -/// -/// # 缓存同步 -/// -/// 缓存同步在每次元素访问时自动执行: -/// - `read(index)`: 读取前使 CPU 缓存失效(FromDevice/Bidirectional) -/// - `set(index, value)`: 写入后刷新 CPU 缓存(ToDevice/Bidirectional) -/// - `copy_from_slice(slice)`: 写入后刷新整个范围 -/// -/// # 示例 -/// -/// ```rust,ignore -/// use dma_api::{DeviceDma, DmaDirection}; -/// -/// let device = DeviceDma::new(0xFFFFFFFF, &my_dma_impl); -/// -/// // 创建 100 个 u32 的 DMA 数组 -/// let mut dma_array = device -/// .array_zero_with_align::(100, 64, DmaDirection::FromDevice) -/// .expect("Failed to allocate"); -/// -/// dma_array.set(0, 0x12345678); // 写入(自动刷新缓存) -/// let value = dma_array.read(0); // 读取(自动失效缓存) -/// -/// let dma_addr = dma_array.dma_addr(); // 获取 DMA 地址给硬件 -/// ``` -/// -/// # 生命周期 -/// -/// `DArray` 拥有其分配的 DMA 内存,在离开作用域时自动释放。 -pub struct DArray { - data: DCommon, - _phantom: core::marker::PhantomData, +use core::{alloc::Layout, marker::PhantomData, ptr::NonNull}; + +use crate::{DeviceDma, DmaAddr, DmaDirection, DmaError, DmaPod, common::DmaAllocation}; + +pub struct CoherentArray { + data: DmaAllocation, + _phantom: PhantomData, } -unsafe impl Send for DArray where T: Send {} +unsafe impl Send for CoherentArray {} +unsafe impl Sync for CoherentArray {} -impl DArray { +impl CoherentArray { pub(crate) fn new_zero_with_align( os: &DeviceDma, - size: usize, + len: usize, + align: usize, + ) -> Result { + let layout = array_layout::(len, align)?; + Ok(Self { + data: DmaAllocation::new_zero_coherent(os, layout)?, + _phantom: PhantomData, + }) + } + + pub(crate) fn new_zero(os: &DeviceDma, len: usize) -> Result { + Self::new_zero_with_align(os, len, core::mem::align_of::()) + } + + pub fn dma_addr(&self) -> DmaAddr { + self.data.handle.dma_addr() + } + + pub fn len(&self) -> usize { + len_from_bytes::(self.data.handle.size()) + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn bytes_len(&self) -> usize { + self.data.handle.size() + } + + pub fn read(&self, index: usize) -> Option { + read_at(self.as_ptr(), self.len(), index) + } + + pub fn set(&mut self, index: usize, value: T) { + write_at(self.as_ptr(), self.len(), index, value); + } + + pub fn copy_from_slice(&mut self, src: &[T]) { + copy_from_slice(self.as_ptr(), self.len(), src); + } + + pub fn iter(&self) -> ArrayIter<'_, T, Self> { + ArrayIter { + array: self, + index: 0, + _phantom: PhantomData, + } + } + + pub fn write_with(&mut self, len: usize, f: impl FnOnce(&mut [T]) -> R) -> R { + assert!(len <= self.len(), "range out of bounds"); + let data = unsafe { self.as_mut_slice() }; + f(&mut data[..len]) + } + + pub fn read_with(&self, len: usize, f: impl FnOnce(&[T]) -> R) -> R { + assert!(len <= self.len(), "range out of bounds"); + let data = unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), len) }; + f(data) + } + + pub fn as_ptr(&self) -> NonNull { + self.data.handle.as_ptr().cast::() + } + + pub fn as_slice(&self) -> &[T] { + unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), self.len()) } + } + + /// # Safety + /// + /// The caller must ensure the device is not concurrently accessing this + /// memory in a way that races with CPU writes. + pub unsafe fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { core::slice::from_raw_parts_mut(self.as_ptr().as_ptr(), self.len()) } + } +} + +pub struct ContiguousArray { + data: DmaAllocation, + _phantom: PhantomData, +} + +unsafe impl Send for ContiguousArray {} +unsafe impl Sync for ContiguousArray {} + +impl ContiguousArray { + pub(crate) fn new_zero_with_align( + os: &DeviceDma, + len: usize, align: usize, direction: DmaDirection, ) -> Result { - let layout = Layout::from_size_align( - size * core::mem::size_of::(), - align.max(core::mem::align_of::()), - )?; - let data = DCommon::new_zero(os, layout, direction)?; + let layout = array_layout::(len, align)?; Ok(Self { - data, - _phantom: core::marker::PhantomData, + data: DmaAllocation::new_zero_contiguous(os, layout, direction)?, + _phantom: PhantomData, }) } pub(crate) fn new_zero( os: &DeviceDma, - size: usize, + len: usize, direction: DmaDirection, ) -> Result { - Self::new_zero_with_align(os, size, core::mem::align_of::(), direction) + Self::new_zero_with_align(os, len, core::mem::align_of::(), direction) } - /// 获取 DMA 地址。 - /// - /// 返回设备用于访问此 DMA 缓冲区的物理/DMA 地址。 - /// 将此地址传递给硬件设备以配置 DMA 操作。 - /// - /// # 返回 - /// - /// DMA 地址 - pub fn dma_addr(&self) -> crate::DmaAddr { - self.data.handle.dma_addr + pub fn dma_addr(&self) -> DmaAddr { + self.data.handle.dma_addr() } - /// 获取数组长度(元素个数)。 - /// - /// # 返回 - /// - /// 数组中的元素个数 pub fn len(&self) -> usize { - self.data.handle.size() / core::mem::size_of::() + len_from_bytes::(self.data.handle.size()) } - /// 检查数组是否为空。 - /// - /// # 返回 - /// - /// 如果数组长度为 0 返回 `true`,否则返回 `false` pub fn is_empty(&self) -> bool { self.len() == 0 } - /// 获取数组的字节长度。 - /// - /// # 返回 - /// - /// 数组占用的总字节数 pub fn bytes_len(&self) -> usize { self.data.handle.size() } - /// 读取指定索引的元素。 - /// - /// 根据 DMA 方向自动处理缓存同步: - /// - `FromDevice`/`Bidirectional`: 读取前使 CPU 缓存失效 - /// - `ToDevice`: 无缓存操作 - /// - /// # 参数 - /// - /// - `index`: 元素索引 - /// - /// # 返回 - /// - /// 如果索引有效返回 `Some(T)`,否则返回 `None` pub fn read(&self, index: usize) -> Option { - if index >= self.len() { - return None; - } - - unsafe { - let offset = index * core::mem::size_of::(); - self.data.prepare_read(offset, core::mem::size_of::()); - Some(self.data.handle.cpu_addr.cast().add(index).read()) - } + read_at(self.as_ptr(), self.len(), index) } - /// 设置指定索引的元素值。 - /// - /// 根据 DMA 方向自动处理缓存同步: - /// - `ToDevice`/`Bidirectional`: 写入后刷新 CPU 缓存 - /// - `FromDevice`: 无缓存操作 - /// - /// # 参数 - /// - /// - `index`: 元素索引,必须在范围内 - /// - `value`: 要写入的值 - /// - /// # Panics - /// - /// 如果 `index >= self.len()` 则 panic pub fn set(&mut self, index: usize, value: T) { - assert!( - index < self.len(), - "index out of range, index: {},len: {}", - index, - self.len() - ); + write_at(self.as_ptr(), self.len(), index, value); + } - unsafe { - let offset = index * core::mem::size_of::(); - let ptr = self.data.handle.cpu_addr.cast::().add(index); - ptr.write(value); - self.data.confirm_write(offset, core::mem::size_of::()); - } + pub fn copy_from_slice(&mut self, src: &[T]) { + copy_from_slice(self.as_ptr(), self.len(), src); } - /// 创建迭代器。 - /// - /// 返回一个迭代器,按顺序读取数组元素。 - /// 每次读取都会自动处理缓存同步。 - /// - /// # 返回 - /// - /// `DArrayIter` 迭代器 - pub fn iter(&self) -> DArrayIter<'_, T> { - DArrayIter { + pub fn iter(&self) -> ArrayIter<'_, T, Self> { + ArrayIter { array: self, index: 0, + _phantom: PhantomData, } } - /// 从 slice 复制数据到数组。 - /// - /// 复制完成后刷新整个数组的 CPU 缓存(ToDevice/Bidirectional)。 - /// - /// # 参数 - /// - /// - `src`: 源 slice,长度必须 `<= self.len()` - /// - /// # Panics - /// - /// 如果 `src.len() > self.len()` 则 panic - pub fn copy_from_slice(&mut self, src: &[T]) { - assert!( - src.len() <= self.len(), - "source slice is larger than DArray, src len: {}, DArray len: {}", - src.len(), - self.len() - ); - unsafe { - let dst_ptr = self.data.handle.cpu_addr.as_ptr(); - let len = core::mem::size_of_val(src); - dst_ptr.copy_from_nonoverlapping(src.as_ptr() as *const u8, len); + pub fn sync_for_device(&self, offset: usize, size: usize) { + self.check_range(offset, size); + self.data.sync_for_device(offset, size); + } + + pub fn sync_for_cpu(&self, offset: usize, size: usize) { + self.check_range(offset, size); + self.data.sync_for_cpu(offset, size); + } + + pub fn sync_for_device_all(&self) { + self.data.sync_for_device(0, self.bytes_len()); + } + + pub fn sync_for_cpu_all(&self) { + self.data.sync_for_cpu(0, self.bytes_len()); + } + + pub fn write_with(&mut self, len: usize, f: impl FnOnce(&mut [T]) -> R) -> R { + assert!(len <= self.len(), "range out of bounds"); + { + let data = unsafe { self.as_mut_slice() }; + f(&mut data[..len]) } - self.data.confirm_write_all(); } - /// 在 CPU 读取前同步指定字节范围。 - /// - /// 对 `FromDevice` 和 `Bidirectional` 方向,这会使对应缓存范围失效。 - pub fn prepare_read(&self, offset: usize, size: usize) { - assert!( - offset <= self.bytes_len() && size <= self.bytes_len().saturating_sub(offset), - "range out of bounds, offset: {}, size: {}, bytes_len: {}", - offset, - size, - self.bytes_len() - ); - self.data.prepare_read(offset, size); + pub fn read_with(&self, len: usize, f: impl FnOnce(&[T]) -> R) -> R { + assert!(len <= self.len(), "range out of bounds"); + let data = unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), len) }; + f(data) } - /// 在设备读取前同步指定字节范围。 + pub fn as_ptr(&self) -> NonNull { + self.data.handle.as_ptr().cast::() + } + + pub fn as_slice(&self) -> &[T] { + unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), self.len()) } + } + + /// # Safety /// - /// 对 `ToDevice` 和 `Bidirectional` 方向,这会将对应缓存范围刷回内存。 - pub fn confirm_write(&self, offset: usize, size: usize) { + /// The caller must ensure the device is not concurrently accessing this + /// memory in a way that races with CPU writes. + pub unsafe fn as_mut_slice(&mut self) -> &mut [T] { + unsafe { core::slice::from_raw_parts_mut(self.as_ptr().as_ptr(), self.len()) } + } + + fn check_range(&self, offset: usize, size: usize) { assert!( offset <= self.bytes_len() && size <= self.bytes_len().saturating_sub(offset), "range out of bounds, offset: {}, size: {}, bytes_len: {}", @@ -232,72 +214,50 @@ impl DArray { size, self.bytes_len() ); - self.data.confirm_write(offset, size); } +} + +pub trait DmaArrayRead { + fn len(&self) -> usize; + fn is_empty(&self) -> bool; + fn read(&self, index: usize) -> Option; +} - /// 在 CPU 读取前同步整个数组。 - pub fn prepare_read_all(&self) { - self.data.prepare_read(0, self.bytes_len()); +impl DmaArrayRead for CoherentArray { + fn len(&self) -> usize { + CoherentArray::len(self) } - /// 在设备读取前同步整个数组。 - pub fn confirm_write_all(&self) { - self.data.confirm_write_all(); + fn is_empty(&self) -> bool { + CoherentArray::is_empty(self) } - /// 直接借出一段可写切片,并在闭包返回后自动同步缓存。 - pub fn write_with(&mut self, len: usize, f: impl FnOnce(&mut [T]) -> R) -> R { - assert!( - len <= self.len(), - "range out of bounds, len: {}, array len: {}", - len, - self.len() - ); - let ret = { - let data = unsafe { self.as_mut_slice() }; - f(&mut data[..len]) - }; - self.confirm_write(0, len * core::mem::size_of::()); - ret + fn read(&self, index: usize) -> Option { + CoherentArray::read(self, index) } +} - /// 直接借出一段只读切片,并在闭包调用前自动同步缓存。 - pub fn read_with(&self, len: usize, f: impl FnOnce(&[T]) -> R) -> R { - assert!( - len <= self.len(), - "range out of bounds, len: {}, array len: {}", - len, - self.len() - ); - self.prepare_read(0, len * core::mem::size_of::()); - let data = unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), len) }; - f(data) +impl DmaArrayRead for ContiguousArray { + fn len(&self) -> usize { + ContiguousArray::len(self) } - /// # Safety - /// - /// slice will not auto do cache sync operations. - pub unsafe fn as_mut_slice(&mut self) -> &mut [T] { - let ptr = self.data.handle.cpu_addr; - unsafe { - core::slice::from_raw_parts_mut( - ptr.as_ptr() as *mut T, - self.bytes_len() / core::mem::size_of::(), - ) - } + fn is_empty(&self) -> bool { + ContiguousArray::is_empty(self) } - pub fn as_ptr(&self) -> NonNull { - self.data.handle.as_ptr().cast::() + fn read(&self, index: usize) -> Option { + ContiguousArray::read(self, index) } } -pub struct DArrayIter<'a, T> { - array: &'a DArray, +pub struct ArrayIter<'a, T: DmaPod, A: DmaArrayRead> { + array: &'a A, index: usize, + _phantom: PhantomData, } -impl<'a, T> Iterator for DArrayIter<'a, T> { +impl<'a, T: DmaPod, A: DmaArrayRead> Iterator for ArrayIter<'a, T, A> { type Item = T; fn next(&mut self) -> Option { @@ -309,3 +269,53 @@ impl<'a, T> Iterator for DArrayIter<'a, T> { value } } + +fn array_layout(len: usize, align: usize) -> Result { + let size = len + .checked_mul(core::mem::size_of::()) + .ok_or(DmaError::LayoutError( + Layout::from_size_align(usize::MAX, 1).unwrap_err(), + ))?; + Ok(Layout::from_size_align( + size, + align.max(core::mem::align_of::()), + )?) +} + +fn len_from_bytes(bytes: usize) -> usize { + if core::mem::size_of::() == 0 { + 0 + } else { + bytes / core::mem::size_of::() + } +} + +fn read_at(ptr: NonNull, len: usize, index: usize) -> Option { + if index >= len { + return None; + } + Some(unsafe { ptr.add(index).read() }) +} + +fn write_at(ptr: NonNull, len: usize, index: usize, value: T) { + assert!( + index < len, + "index out of range, index: {}, len: {}", + index, + len + ); + unsafe { ptr.add(index).write(value) }; +} + +fn copy_from_slice(ptr: NonNull, len: usize, src: &[T]) { + assert!( + src.len() <= len, + "source slice is larger than DMA array, src len: {}, array len: {}", + src.len(), + len + ); + unsafe { + ptr.as_ptr() + .copy_from_nonoverlapping(src.as_ptr(), src.len()); + } +} diff --git a/components/dma-api/src/common.rs b/components/dma-api/src/common.rs index 0bdc4cbe5a..8e7b771a70 100644 --- a/components/dma-api/src/common.rs +++ b/components/dma-api/src/common.rs @@ -1,65 +1,81 @@ use core::alloc::Layout; -use crate::{DeviceDma, DmaDirection, DmaError, DmaMapHandle}; +use crate::{DeviceDma, DmaAllocHandle, DmaDirection, DmaError}; -pub(crate) struct DCommon { - pub handle: DmaMapHandle, - pub osal: DeviceDma, - pub direction: DmaDirection, +pub(crate) enum AllocationKind { + Coherent, + Contiguous { direction: DmaDirection }, } -unsafe impl Send for DCommon {} +pub(crate) struct DmaAllocation { + pub handle: DmaAllocHandle, + pub device: DeviceDma, + pub kind: AllocationKind, +} + +unsafe impl Send for DmaAllocation {} -impl DCommon { - pub fn new_zero( +impl DmaAllocation { + pub fn new_zero_coherent(os: &DeviceDma, layout: Layout) -> Result { + let handle = unsafe { os.alloc_coherent(layout) }?; + unsafe { + handle.as_ptr().write_bytes(0, handle.size()); + } + + Ok(Self { + handle, + device: os.clone(), + kind: AllocationKind::Coherent, + }) + } + + pub fn new_zero_contiguous( os: &DeviceDma, layout: Layout, direction: DmaDirection, ) -> Result { - let handle = unsafe { os.alloc_coherent(layout) }?; - let ptr = handle.cpu_addr; + let handle = unsafe { os.alloc_contiguous(layout) }?; unsafe { - ptr.write_bytes(0, handle.size()); + handle.as_ptr().write_bytes(0, handle.size()); } - os.flush_invalidate(ptr, handle.size()); Ok(Self { - handle: DmaMapHandle { - handle, - map_alloc_virt: None, - }, - osal: os.clone(), - direction, + handle, + device: os.clone(), + kind: AllocationKind::Contiguous { direction }, }) } pub fn as_mut_slice(&mut self) -> &mut [u8] { unsafe { - core::slice::from_raw_parts_mut(self.handle.cpu_addr.as_ptr(), self.handle.size()) + core::slice::from_raw_parts_mut(self.handle.as_ptr().as_ptr(), self.handle.size()) } } - pub fn prepare_read(&self, offset: usize, size: usize) { - self.osal - .prepare_read(&self.handle, offset, size, self.direction); - } - - pub fn confirm_write(&self, offset: usize, size: usize) { - self.osal - .confirm_write(&self.handle, offset, size, self.direction); + pub fn sync_for_device(&self, offset: usize, size: usize) { + if let AllocationKind::Contiguous { direction } = self.kind { + self.device + .sync_alloc_for_device(&self.handle, offset, size, direction); + } } - pub fn confirm_write_all(&self) { - self.osal - .confirm_write(&self.handle, 0, self.handle.size(), self.direction); + pub fn sync_for_cpu(&self, offset: usize, size: usize) { + if let AllocationKind::Contiguous { direction } = self.kind { + self.device + .sync_alloc_for_cpu(&self.handle, offset, size, direction); + } } } -impl Drop for DCommon { +impl Drop for DmaAllocation { fn drop(&mut self) { - if self.handle.size() > 0 { - unsafe { - self.osal.dealloc_coherent(self.handle.handle); + if self.handle.size() == 0 { + return; + } + unsafe { + match self.kind { + AllocationKind::Coherent => self.device.dealloc_coherent(self.handle), + AllocationKind::Contiguous { .. } => self.device.dealloc_contiguous(self.handle), } } } diff --git a/components/dma-api/src/dbox.rs b/components/dma-api/src/dbox.rs index 4a222e46bf..16f1f3b182 100644 --- a/components/dma-api/src/dbox.rs +++ b/components/dma-api/src/dbox.rs @@ -1,169 +1,126 @@ -use core::ptr::NonNull; - -use crate::{DeviceDma, DmaAddr, DmaDirection, DmaError, common::DCommon}; - -/// DMA 可访问的单值容器。 -/// -/// `DBox` 提供单个值的 DMA 可访问存储,支持自动缓存同步。 -/// 每次访问值(`read`/`write`/`modify`)时都会根据 DMA 方向自动处理缓存操作。 -/// -/// # 类型参数 -/// -/// - `T`: 存储的值类型 -/// -/// # 缓存同步 -/// -/// 缓存同步在每次访问时自动执行: -/// - `read()`: 读取前使 CPU 缓存失效(FromDevice/Bidirectional) -/// - `write(value)`: 写入后刷新 CPU 缓存(ToDevice/Bidirectional) -/// - `modify(f)`: 先失效缓存,执行闭包,再刷新缓存 -/// -/// # 示例 -/// -/// ```rust,ignore -/// use dma_api::{DeviceDma, DmaDirection}; -/// -/// #[derive(Default)] -/// struct Descriptor { -/// addr: u64, -/// length: u32, -/// } -/// -/// let device = DeviceDma::new(0xFFFFFFFF, &my_dma_impl); -/// -/// // 分配描述符 -/// let mut dma_desc = device -/// .box_zero_with_align::(64, DmaDirection::ToDevice) -/// .expect("Failed to allocate"); -/// -/// // 配置描述符(自动刷新缓存) -/// dma_desc.modify(|d| d.length = 4096); -/// -/// // 获取 DMA 地址给硬件 -/// let desc_addr = dma_desc.dma_addr(); -/// ``` -/// -/// # 生命周期 -/// -/// `DBox` 拥有其分配的 DMA 内存,在离开作用域时自动释放。 -pub struct DBox { - data: DCommon, - _marker: core::marker::PhantomData, +use core::{alloc::Layout, marker::PhantomData, ptr::NonNull}; + +use crate::{DeviceDma, DmaAddr, DmaDirection, DmaError, DmaPod, common::DmaAllocation}; + +pub struct CoherentBox { + data: DmaAllocation, + _marker: PhantomData, } -unsafe impl Send for DBox where T: Send {} +unsafe impl Send for CoherentBox {} +unsafe impl Sync for CoherentBox {} -impl DBox { - pub(crate) fn new_zero(os: &DeviceDma, direction: DmaDirection) -> Result { - let layout = core::alloc::Layout::from_size_align( - core::mem::size_of::(), - core::mem::align_of::(), - )?; - let data = DCommon::new_zero(os, layout, direction)?; +impl CoherentBox { + pub(crate) fn new_zero(os: &DeviceDma) -> Result { + Self::new_zero_with_align(os, core::mem::align_of::()) + } + + pub(crate) fn new_zero_with_align(os: &DeviceDma, align: usize) -> Result { + let data = DmaAllocation::new_zero_coherent(os, box_layout::(align)?)?; Ok(Self { data, - _marker: core::marker::PhantomData, + _marker: PhantomData, }) } + pub fn dma_addr(&self) -> DmaAddr { + self.data.handle.dma_addr() + } + + pub fn read(&self) -> T { + unsafe { self.as_ptr().read() } + } + + pub fn write(&mut self, value: T) { + unsafe { self.as_ptr().write(value) }; + } + + pub fn modify(&mut self, f: impl FnOnce(&mut T)) { + let mut value = self.read(); + f(&mut value); + self.write(value); + } + + pub fn as_ptr(&self) -> NonNull { + self.data.handle.as_ptr().cast::() + } + + /// # Safety + /// + /// The caller must ensure the device is not concurrently accessing this + /// memory in a way that races with CPU writes. + pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] { + self.data.as_mut_slice() + } +} + +pub struct ContiguousBox { + data: DmaAllocation, + _marker: PhantomData, +} + +unsafe impl Send for ContiguousBox {} +unsafe impl Sync for ContiguousBox {} + +impl ContiguousBox { + pub(crate) fn new_zero(os: &DeviceDma, direction: DmaDirection) -> Result { + Self::new_zero_with_align(os, core::mem::align_of::(), direction) + } + pub(crate) fn new_zero_with_align( os: &DeviceDma, align: usize, direction: DmaDirection, ) -> Result { - let layout = core::alloc::Layout::from_size_align( - core::mem::size_of::(), - align.max(core::mem::align_of::()), - )?; - let data = DCommon::new_zero(os, layout, direction)?; + let data = DmaAllocation::new_zero_contiguous(os, box_layout::(align)?, direction)?; Ok(Self { data, - _marker: core::marker::PhantomData, + _marker: PhantomData, }) } - /// 获取 DMA 地址。 - /// - /// 返回设备用于访问此 DMA 缓冲区的物理/DMA 地址。 - /// 将此地址传递给硬件设备以配置 DMA 操作。 - /// - /// # 返回 - /// - /// DMA 地址 pub fn dma_addr(&self) -> DmaAddr { - self.data.handle.dma_addr + self.data.handle.dma_addr() } - /// 读取存储的值。 - /// - /// 根据 DMA 方向自动处理缓存同步: - /// - `FromDevice`/`Bidirectional`: 读取前使 CPU 缓存失效 - /// - `ToDevice`: 无缓存操作 - /// - /// # 返回 - /// - /// 存储的值 pub fn read(&self) -> T { - unsafe { - self.data.prepare_read(0, core::mem::size_of::()); - let ptr = self.data.handle.cpu_addr.cast::(); - ptr.read() - } + unsafe { self.as_ptr().read() } } - /// 写入新值。 - /// - /// 根据 DMA 方向自动处理缓存同步: - /// - `ToDevice`/`Bidirectional`: 写入后刷新 CPU 缓存 - /// - `FromDevice`: 无缓存操作 - /// - /// # 参数 - /// - /// - `value`: 要写入的值 pub fn write(&mut self, value: T) { - unsafe { - let ptr = self.data.handle.cpu_addr.cast::(); - ptr.write(value); - self.data.confirm_write(0, core::mem::size_of::()); - } + unsafe { self.as_ptr().write(value) }; } - /// 修改值(read-modify-write 模式)。 - /// - /// 此方法等价于先调用 `read()`,然后对值执行闭包,最后调用 `write()`。 - /// 缓存同步操作:读取前失效缓存,写入后刷新缓存。 - /// - /// # 参数 - /// - /// - `f`: 修改值的闭包 - /// - /// # 示例 - /// - /// ```rust,ignore - /// dma_box.modify(|v| v.field += 10); - /// ``` pub fn modify(&mut self, f: impl FnOnce(&mut T)) { let mut value = self.read(); f(&mut value); self.write(value); } - /// 获取指向存储值的指针。 - /// - /// # 返回 - /// - /// 指向存储值的非空指针 + pub fn sync_for_device_all(&self) { + self.data.sync_for_device(0, core::mem::size_of::()); + } + + pub fn sync_for_cpu_all(&self) { + self.data.sync_for_cpu(0, core::mem::size_of::()); + } + pub fn as_ptr(&self) -> NonNull { self.data.handle.as_ptr().cast::() } - /// 获取底层缓冲区的可变切片。 - /// /// # Safety /// - /// - 调用者必须确保在使用该切片期间,设备不会访问此内存区域 - /// - 调用者必须手动处理缓存同步(flush/invalidate) - pub unsafe fn as_buff_mut(&mut self) -> &mut [u8] { + /// The caller must ensure the device is not concurrently accessing this + /// memory in a way that races with CPU writes. + pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] { self.data.as_mut_slice() } } + +fn box_layout(align: usize) -> Result { + Ok(Layout::from_size_align( + core::mem::size_of::(), + align.max(core::mem::align_of::()), + )?) +} diff --git a/components/dma-api/src/def.rs b/components/dma-api/src/def.rs index e6f44817fc..fe88785070 100644 --- a/components/dma-api/src/def.rs +++ b/components/dma-api/src/def.rs @@ -1,4 +1,4 @@ -use core::{alloc::Layout, cmp::PartialOrd, ops::Deref, ptr::NonNull}; +use core::{alloc::Layout, cmp::PartialOrd, ptr::NonNull}; use derive_more::{ Add, AddAssign, Debug, Display, Div, From, Into, Mul, MulAssign, Sub, SubAssign, @@ -49,30 +49,52 @@ impl PartialOrd for DmaAddr { } } -/// 物理地址类型 -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, Hash, From, Into, Add, Mul, Sub)] -#[debug("{}", format_args!("{_0:#X}"))] -#[display("{}", format_args!("{_0:#X}"))] -pub struct PhysAddr(u64); +/// Device-visible DMA constraints. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DmaConstraints { + pub addr_mask: u64, + pub align: usize, + pub boundary: Option, + pub max_segment_size: Option, +} -impl PhysAddr { - pub fn as_u64(&self) -> u64 { - self.0 +impl DmaConstraints { + pub const fn new(addr_mask: u64) -> Self { + Self { + addr_mask, + align: 1, + boundary: None, + max_segment_size: None, + } + } + + pub fn with_align(mut self, align: usize) -> Self { + self.align = align.max(1); + self + } + + pub fn with_boundary(mut self, boundary: usize) -> Self { + self.boundary = Some(boundary.max(1)); + self + } + + pub fn with_max_segment_size(mut self, max_segment_size: usize) -> Self { + self.max_segment_size = Some(max_segment_size); + self } } -/// DMA 传输方向 +/// DMA transfer direction. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DmaDirection { - /// 数据从 CPU 传输到设备 (DMA_TO_DEVICE) + /// CPU writes, device reads. ToDevice, - /// 数据从设备传输到 CPU (DMA_FROM_DEVICE) + /// Device writes, CPU reads. FromDevice, - /// 双向传输 (DMA_BIDIRECTIONAL) + /// CPU and device may both read/write. Bidirectional, } -/// DMA 错误类型 #[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] pub enum DmaError { #[error("DMA allocation failed")] @@ -83,49 +105,43 @@ pub enum DmaError { DmaMaskNotMatch { addr: DmaAddr, mask: u64 }, #[error("DMA align mismatch: required={required:#X}, but address={address}")] AlignMismatch { required: usize, address: DmaAddr }, + #[error("DMA segment size {size:#X} exceeds max segment size {max:#X}")] + SegmentTooLarge { size: usize, max: usize }, + #[error("DMA address range crosses boundary {boundary:#X}: addr={addr}, size={size:#X}")] + BoundaryCross { + addr: DmaAddr, + size: usize, + boundary: usize, + }, #[error("Null pointer provided for DMA mapping")] NullPointer, #[error("Zero-sized buffer cannot be used for DMA")] ZeroSizedBuffer, } -/// Handle for DMA memory allocation. +/// Marker for plain data that can be safely stored in typed DMA buffers. /// -/// Manages DMA memory buffers that may require special alignment or DMA address mask -/// constraints. When the original virtual address doesn't meet alignment or mask -/// requirements, an additional aligned buffer is allocated and stored in `alloc_virt`. +/// # Safety +/// +/// Implementors must be `Copy`, have no invalid all-zero bit pattern, and must +/// not own resources or references whose validity can be broken by raw device +/// writes. +pub unsafe trait DmaPod: Copy {} + +unsafe impl DmaPod for T {} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct DmaHandle { - /// Original virtual address provided by the user +pub struct DmaAllocHandle { pub(crate) cpu_addr: NonNull, - /// DMA address visible to devices pub(crate) dma_addr: DmaAddr, - /// Memory layout specification (size and alignment) pub(crate) layout: Layout, - // /// Additional allocated virtual address if the original doesn't satisfy - // /// alignment or DMA mask requirements when mapping for DMA. - // pub(crate) map_alloc_virt: Option>, } -impl DmaHandle { - /// 为 `alloc_coherent` 操作创建 `DmaHandle`。 - /// - /// 此构造函数专门用于 DMA 一致性内存分配场景,其中: - /// - 内存是专门为 DMA 分配的(零初始化) - /// - CPU 和设备看到同一个虚拟地址 - /// - 不需要额外的对齐缓冲区 - /// - /// # 特性保证 - /// - /// - 内存已被零初始化 - /// +impl DmaAllocHandle { /// # Safety /// - /// 调用者必须确保: - /// - `origin_virt` 指向有效内存,生命周期与 handle 相同 - /// - `dma_addr` 是与 `origin_virt` 对应的设备可访问地址 - /// - `layout` 正确描述内存的大小和对齐 - /// - 内存必须保持有效直到被正确释放 + /// `cpu_addr` must point to a live allocation described by `layout`, and + /// `dma_addr` must be the device-visible address for that allocation. pub unsafe fn new(cpu_addr: NonNull, dma_addr: DmaAddr, layout: Layout) -> Self { Self { cpu_addr, @@ -134,27 +150,22 @@ impl DmaHandle { } } - /// Returns the size of the DMA buffer in bytes. pub fn size(&self) -> usize { self.layout.size() } - /// Returns the alignment requirement of the DMA buffer in bytes. pub fn align(&self) -> usize { self.layout.align() } - /// Returns the virtual address to access data. pub fn as_ptr(&self) -> NonNull { self.cpu_addr } - /// Returns the DMA address visible to devices. pub fn dma_addr(&self) -> DmaAddr { self.dma_addr } - /// Returns the memory layout used for this DMA allocation. pub fn layout(&self) -> Layout { self.layout } @@ -162,57 +173,53 @@ impl DmaHandle { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct DmaMapHandle { - pub(crate) handle: DmaHandle, - pub(crate) map_alloc_virt: Option>, -} - -impl Deref for DmaMapHandle { - type Target = DmaHandle; - fn deref(&self) -> &Self::Target { - &self.handle - } + pub(crate) cpu_addr: NonNull, + pub(crate) dma_addr: DmaAddr, + pub(crate) layout: Layout, + pub(crate) bounce_ptr: Option>, } impl DmaMapHandle { - /// 为 `map_single` 操作创建 `DmaMapHandle`。 - /// - /// 此构造函数用于将现有缓冲区映射为 DMA 可访问的场景,其中: - /// - 缓冲区可能已经存在于用户空间 - /// - 如果原地址不满足对齐或掩码要求,会分配额外的对齐缓冲区 - /// - `alloc_virt` 存储额外的对齐缓冲区地址(如果分配了) - /// - /// # 特性保证 - /// - /// - 如果原地址满足要求,`alloc_virt` 为 `None` - /// - 如果分配了对齐缓冲区,`alloc_virt` 包含其地址 - /// /// # Safety /// - /// 调用者必须确保: - /// - `cpu_addr` 指向有效内存,生命周期与 handle 相同 - /// - `dma_addr` 是与 `cpu_addr` 对应的设备可访问地址 - /// - `layout` 正确描述内存的大小和对齐 - /// - `alloc_virt`(如果提供)必须指向有效分配的内存 - /// - 内存必须保持有效直到 `unmap_single` 被调用 - /// - 必须与 `DmaOp::unmap_single` 配对使用以防止内存泄漏 + /// `cpu_addr` must point to the caller-owned mapped buffer for the mapping + /// lifetime. `bounce_ptr`, when present, must point to a live bounce buffer + /// described by `layout`. pub unsafe fn new( cpu_addr: NonNull, dma_addr: DmaAddr, layout: Layout, - alloc_virt: Option>, + bounce_ptr: Option>, ) -> Self { - let handle = DmaHandle { + Self { cpu_addr, dma_addr, layout, - }; - Self { - handle, - map_alloc_virt: alloc_virt, + bounce_ptr, } } - pub fn alloc_virt(&self) -> Option> { - self.map_alloc_virt + pub fn size(&self) -> usize { + self.layout.size() + } + + pub fn align(&self) -> usize { + self.layout.align() + } + + pub fn as_ptr(&self) -> NonNull { + self.cpu_addr + } + + pub fn dma_addr(&self) -> DmaAddr { + self.dma_addr + } + + pub fn layout(&self) -> Layout { + self.layout + } + + pub fn bounce_ptr(&self) -> Option> { + self.bounce_ptr } } diff --git a/components/dma-api/src/lib.rs b/components/dma-api/src/lib.rs index bc1fab01bb..e2536fef22 100644 --- a/components/dma-api/src/lib.rs +++ b/components/dma-api/src/lib.rs @@ -3,412 +3,323 @@ extern crate alloc; -use core::{num::NonZeroUsize, ops::Deref, ptr::NonNull}; +use core::{num::NonZeroUsize, ptr::NonNull}; -mod osal; +mod op; mod array; mod common; mod dbox; mod def; -mod map_single; mod pool; +mod streaming; pub use array::*; pub use dbox::*; pub use def::*; -pub use map_single::*; -pub use osal::DmaOp; +pub use op::DmaOp; pub use pool::*; +pub use streaming::*; -impl Deref for DmaHandle { - type Target = core::alloc::Layout; - fn deref(&self) -> &Self::Target { - &self.layout - } -} - -/// DMA 设备操作接口。 -/// -/// `DeviceDma` 是用于执行 DMA 操作的主要入口点,封装了平台特定的 -/// `DmaOp` 实现,并提供了分配、映射和管理 DMA 内存的方法。 -/// -/// # 创建 -/// -/// 使用 [`DeviceDma::new()`] 创建实例,需要提供: -/// - `dma_mask`: 设备可寻址的地址掩码(如 `0xFFFFFFFF` 表示 32 位设备) -/// - `osal`: 实现 `DmaOp` trait 的平台抽象层 -/// -/// # 示例 -/// -/// ```rust,ignore -/// use dma_api::DeviceDma; -/// -/// let device = DeviceDma::new(0xFFFFFFFF, &my_dma_impl); -/// ``` #[derive(Clone)] pub struct DeviceDma { - os: &'static dyn DmaOp, - mask: u64, + op: &'static dyn DmaOp, + constraints: DmaConstraints, } impl DeviceDma { - /// 创建新的 DMA 设备实例。 - /// - /// # 参数 - /// - /// - `dma_mask`: 设备 DMA 地址掩码,指定设备可寻址的地址范围 - /// - `0xFFFFFFFF`: 32 位设备(最多 4GB) - /// - `0xFFFFFFFFFFFFFFFF`: 64 位设备(全地址空间) - /// - `osal`: 实现 `DmaOp` trait 的平台抽象层引用 - /// - /// # 示例 - /// - /// ```rust,ignore - /// use dma_api::DeviceDma; - /// - /// let device = DeviceDma::new(0xFFFFFFFF, &my_dma_impl); - /// ``` - pub fn new(dma_mask: u64, osal: &'static dyn DmaOp) -> Self { + pub fn new(dma_mask: u64, op: &'static dyn DmaOp) -> Self { + Self { + constraints: DmaConstraints::new(dma_mask), + op, + } + } + + pub fn with_constraints(&self, constraints: DmaConstraints) -> Self { Self { - mask: dma_mask, - os: osal, + op: self.op, + constraints, } } - /// 获取设备的 DMA 地址掩码。 - /// - /// # 返回 - /// - /// 返回设备的 DMA 掩码值,表示设备可寻址的最大地址范围。 + pub fn constraints(&self) -> DmaConstraints { + self.constraints + } + pub fn dma_mask(&self) -> u64 { - self.mask - } - - /// 刷新 CPU 缓存到内存(clean 操作)。 - /// - /// 将指定地址范围的 CPU 缓存数据写回到内存,确保设备可以读取到最新数据。 - /// 用于 `ToDevice` 和 `Bidirectional` 方向的 DMA 传输前。 - /// - /// # 参数 - /// - /// - `addr`: 内存起始地址 - /// - `size`: 内存大小(字节) + self.constraints.addr_mask + } + pub fn flush(&self, addr: NonNull, size: usize) { - self.os.flush(addr, size) - } - - /// 使 CPU 缓存失效(invalidate 操作)。 - /// - /// 使指定地址范围的 CPU 缓存失效,强制 CPU 从内存重新读取数据。 - /// 用于 `FromDevice` 和 `Bidirectional` 方向的 DMA 传输后。 - /// - /// # 参数 - /// - /// - `addr`: 内存起始地址 - /// - `size`: 内存大小(字节) + self.op.flush(addr, size) + } + pub fn invalidate(&self, addr: NonNull, size: usize) { - self.os.invalidate(addr, size) + self.op.invalidate(addr, size) } - /// 刷新并使 CPU 缓存失效(clean and invalidate 操作)。 - /// - /// 同时执行刷新和失效操作,用于确保缓存和内存完全同步。 - /// - /// # 参数 - /// - /// - `addr`: 内存起始地址 - /// - `size`: 内存大小(字节) pub fn flush_invalidate(&self, addr: NonNull, size: usize) { - self.os.flush_invalidate(addr, size) + self.op.flush_invalidate(addr, size) } - /// 获取系统页大小。 - /// - /// # 返回 - /// - /// 返回系统的页大小(字节),通常为 4096。 pub fn page_size(&self) -> usize { - self.os.page_size() - } - - fn prepare_read( - &self, - handle: &DmaMapHandle, - offset: usize, - size: usize, - direction: DmaDirection, - ) { - self.os.prepare_read(handle, offset, size, direction) + self.op.page_size() } - fn confirm_write( + pub(crate) unsafe fn alloc_contiguous( &self, - handle: &DmaMapHandle, - offset: usize, - size: usize, - direction: DmaDirection, - ) { - self.os.confirm_write(handle, offset, size, direction) - } - - unsafe fn alloc_coherent(&self, layout: core::alloc::Layout) -> Result { - let res = unsafe { self.os.alloc_coherent(self.mask, layout) }.ok_or(DmaError::NoMemory)?; - match self.check_handle(&res) { + layout: core::alloc::Layout, + ) -> Result { + let constraints = self.constraints.with_align(layout.align()); + let res = + unsafe { self.op.alloc_contiguous(constraints, layout) }.ok_or(DmaError::NoMemory)?; + match self.check_alloc_handle(&res, constraints) { Ok(()) => Ok(res), Err(e) => { - unsafe { self.dealloc_coherent(res) }; + unsafe { self.op.dealloc_contiguous(res) }; Err(e) } } } - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { - unsafe { self.os.dealloc_coherent(handle) } + pub(crate) unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle) { + unsafe { self.op.dealloc_contiguous(handle) } } - fn check_handle(&self, handle: &DmaHandle) -> Result<(), DmaError> { - let addr: u64 = handle.dma_addr.into(); - - let in_mask = if handle.size() == 0 { - addr <= self.dma_mask() - } else { - addr.checked_add(handle.size().saturating_sub(1) as u64) - .map(|end| end <= self.dma_mask()) - .unwrap_or(false) - }; - - if !in_mask { - return Err(DmaError::DmaMaskNotMatch { - addr: handle.dma_addr, - mask: self.dma_mask(), - }); - } - - let is_aligned = handle - .dma_addr - .as_u64() - .is_multiple_of(handle.align() as u64); - if !is_aligned { - return Err(DmaError::AlignMismatch { - address: handle.dma_addr, - required: handle.align(), - }); + pub(crate) unsafe fn alloc_coherent( + &self, + layout: core::alloc::Layout, + ) -> Result { + let constraints = self.constraints.with_align(layout.align()); + let res = + unsafe { self.op.alloc_coherent(constraints, layout) }.ok_or(DmaError::NoMemory)?; + match self.check_alloc_handle(&res, constraints) { + Ok(()) => Ok(res), + Err(e) => { + unsafe { self.op.dealloc_coherent(res) }; + Err(e) + } } + } - Ok(()) + pub(crate) unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) { + unsafe { self.op.dealloc_coherent(handle) } } - unsafe fn _map_single( + pub(crate) unsafe fn map_streaming( &self, addr: NonNull, size: NonZeroUsize, align: usize, direction: DmaDirection, ) -> Result { - let res = unsafe { self.os.map_single(self.mask, addr, size, align, direction) }?; - match self.check_handle(&res) { + let constraints = self.constraints.with_align(align); + let res = unsafe { self.op.map_streaming(constraints, addr, size, direction) }?; + match self.check_map_handle(&res, constraints) { Ok(()) => Ok(res), Err(e) => { - unsafe { self.unmap_single(res) }; + unsafe { self.op.unmap_streaming(res) }; Err(e) } } } - unsafe fn unmap_single(&self, handle: DmaMapHandle) { - unsafe { self.os.unmap_single(handle) } - } - - /// 创建默认对齐的 DMA 数组。 - /// - /// 分配一个指定大小的 DMA 可访问数组,内存初始化为零。 - /// 数组的对齐方式使用类型 `T` 的默认对齐值。 - /// - /// # 类型参数 - /// - /// - `T`: 数组元素类型,必须是 `Sized` 并且实现了 `Default` - /// - /// # 参数 - /// - /// - `size`: 数组长度(元素个数) - /// - `direction`: DMA 传输方向,决定缓存同步策略 - /// - /// # 返回 - /// - /// 成功时返回 `DArray` 容器,失败时返回 `DmaError` - /// - /// # 示例 - /// - /// ```rust,ignore - /// let dma_array = device.array_zero::(100, DmaDirection::FromDevice)?; - /// ``` - pub fn array_zero( + pub(crate) unsafe fn unmap_streaming(&self, handle: DmaMapHandle) { + unsafe { self.op.unmap_streaming(handle) } + } + + pub(crate) fn sync_alloc_for_device( &self, + handle: &DmaAllocHandle, + offset: usize, size: usize, direction: DmaDirection, - ) -> Result, DmaError> { - array::DArray::new_zero(self, size, direction) - } - - /// 创建指定对齐的 DMA 数组。 - /// - /// 分配一个指定大小和对齐要求的 DMA 可访问数组,内存初始化为零。 - /// - /// # 类型参数 - /// - /// - `T`: 数组元素类型,必须是 `Sized` 并且实现了 `Default` - /// - /// # 参数 - /// - /// - `size`: 数组长度(元素个数) - /// - `align`: 对齐字节数(至少等于 `core::mem::align_of::()`) - /// - `direction`: DMA 传输方向,决定缓存同步策略 - /// - /// # 返回 - /// - /// 成功时返回 `DArray` 容器,失败时返回 `DmaError` - /// - /// # 示例 - /// - /// ```rust,ignore - /// // 创建 64 字节对齐的数组 - /// let dma_array = device - /// .array_zero_with_align::(100, 64, DmaDirection::FromDevice)?; - /// ``` - pub fn array_zero_with_align( + ) { + self.op + .sync_alloc_for_device(handle, offset, size, direction); + } + + pub(crate) fn sync_alloc_for_cpu( &self, + handle: &DmaAllocHandle, + offset: usize, size: usize, + direction: DmaDirection, + ) { + self.op.sync_alloc_for_cpu(handle, offset, size, direction); + } + + pub(crate) fn sync_map_for_device( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + self.op.sync_map_for_device(handle, offset, size, direction); + } + + pub(crate) fn sync_map_for_cpu( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + self.op.sync_map_for_cpu(handle, offset, size, direction); + } + + pub fn coherent_array_zero(&self, len: usize) -> Result, DmaError> { + CoherentArray::new_zero(self, len) + } + + pub fn coherent_array_zero_with_align( + &self, + len: usize, align: usize, + ) -> Result, DmaError> { + CoherentArray::new_zero_with_align(self, len, align) + } + + pub fn contiguous_array_zero( + &self, + len: usize, direction: DmaDirection, - ) -> Result, DmaError> { - array::DArray::new_zero_with_align(self, size, align, direction) - } - - /// 创建默认对齐的 DMA Box。 - /// - /// 分配一个 DMA 可访问的单值容器,内存初始化为零。 - /// 适合存储 DMA 描述符、配置结构等单个对象。 - /// - /// # 类型参数 - /// - /// - `T`: 存储的值类型,必须是 `Sized` 并且实现了 `Default` - /// - /// # 参数 - /// - /// - `direction`: DMA 传输方向,决定缓存同步策略 - /// - /// # 返回 - /// - /// 成功时返回 `DBox` 容器,失败时返回 `DmaError` - /// - /// # 示例 - /// - /// ```rust,ignore - /// #[derive(Default)] - /// struct Descriptor { - /// addr: u64, - /// length: u32, - /// } - /// - /// let dma_desc = device.box_zero::(DmaDirection::ToDevice)?; - /// ``` - pub fn box_zero(&self, direction: DmaDirection) -> Result, DmaError> { - dbox::DBox::new_zero(self, direction) - } - - /// 创建指定对齐的 DMA Box。 - /// - /// 分配一个指定对齐要求的 DMA 可访问单值容器,内存初始化为零。 - /// - /// # 类型参数 - /// - /// - `T`: 存储的值类型,必须是 `Sized` 并且实现了 `Default` - /// - /// # 参数 - /// - /// - `align`: 对齐字节数(至少等于 `core::mem::align_of::()`) - /// - `direction`: DMA 传输方向,决定缓存同步策略 - /// - /// # 返回 - /// - /// 成功时返回 `DBox` 容器,失败时返回 `DmaError` - /// - /// # 示例 - /// - /// ```rust,ignore - /// let dma_desc = device - /// .box_zero_with_align::(64, DmaDirection::ToDevice)?; - /// ``` - pub fn box_zero_with_align( + ) -> Result, DmaError> { + ContiguousArray::new_zero(self, len, direction) + } + + pub fn contiguous_array_zero_with_align( &self, + len: usize, align: usize, direction: DmaDirection, - ) -> Result, DmaError> { - dbox::DBox::new_zero_with_align(self, align, direction) - } - - /// 映射现有缓冲区为 DMA 可访问。 - /// - /// 将已存在的缓冲区(如栈数组或堆分配的 slice)映射为 DMA 可访问区域。 - /// 返回的 `SArrayPtr` 在离开作用域时自动解除映射。 - /// - /// # 缓存同步 - /// - /// **重要**: 此方法创建的映射**不会**自动同步缓存。 - /// 你必须手动调用 `SArrayPtr` 的方法进行缓存同步: - /// - `to_vec()`: 读取前自动失效整个范围 - /// - `copy_from_slice()`: 写入后自动刷新整个范围 - /// - /// # 类型参数 - /// - /// - `T`: 数组元素类型 - /// - /// # 参数 - /// - /// - `buff`: 要映射的缓冲区切片 - /// - `align`: 对齐字节数 - /// - `direction`: DMA 传输方向,决定手动缓存同步的行为 - /// - /// # 返回 - /// - /// 成功时返回 `SArrayPtr` 映射句柄,失败时返回 `DmaError` - /// - /// # 示例 - /// - /// ```rust,ignore - /// let mut buffer = [0u8; 4096]; - /// - /// // 映射用于 DMA 写入 - /// let mapping = device.map_single_array(&buffer, 64, DmaDirection::ToDevice)?; - /// - /// // 必须手动刷新缓存 - /// mapping.copy_from_slice(&data); - /// - /// // ... 启动 DMA 传输 ... - /// - /// // 映射在作用域结束时自动解映射 - /// ``` - pub fn map_single_array( + ) -> Result, DmaError> { + ContiguousArray::new_zero_with_align(self, len, align, direction) + } + + pub fn coherent_box_zero(&self) -> Result, DmaError> { + CoherentBox::new_zero(self) + } + + pub fn coherent_box_zero_with_align( &self, - buff: &[T], align: usize, + ) -> Result, DmaError> { + CoherentBox::new_zero_with_align(self, align) + } + + pub fn contiguous_box_zero( + &self, direction: DmaDirection, - ) -> Result, DmaError> { - SArrayPtr::map_single(self, buff, align, direction) + ) -> Result, DmaError> { + ContiguousBox::new_zero(self, direction) } - pub fn new_pool( + pub fn contiguous_box_zero_with_align( + &self, + align: usize, + direction: DmaDirection, + ) -> Result, DmaError> { + ContiguousBox::new_zero_with_align(self, align, direction) + } + + pub fn map_streaming_slice( + &self, + buff: &mut [T], + align: usize, + direction: DmaDirection, + ) -> Result, DmaError> { + StreamingMap::map(self, buff, align, direction) + } + + pub fn contiguous_buffer_pool( &self, layout: core::alloc::Layout, direction: DmaDirection, cap: usize, - ) -> DArrayPool { - let config = DArrayConfig { + ) -> ContiguousBufferPool { + let config = ContiguousBufferConfig { size: layout.size(), align: layout.align(), direction, }; - DArrayPool::new_pool(self.clone(), config, cap) + ContiguousBufferPool::with_capacity(self.clone(), config, cap) + } + + fn check_alloc_handle( + &self, + handle: &DmaAllocHandle, + constraints: DmaConstraints, + ) -> Result<(), DmaError> { + check_dma_range(handle.dma_addr(), handle.size(), constraints)?; + check_dma_align(handle.dma_addr(), handle.align().max(constraints.align))?; + Ok(()) + } + + fn check_map_handle( + &self, + handle: &DmaMapHandle, + constraints: DmaConstraints, + ) -> Result<(), DmaError> { + check_dma_range(handle.dma_addr(), handle.size(), constraints)?; + check_dma_align(handle.dma_addr(), handle.align().max(constraints.align))?; + Ok(()) + } +} + +fn check_dma_range( + addr: DmaAddr, + size: usize, + constraints: DmaConstraints, +) -> Result<(), DmaError> { + let start = addr.as_u64(); + let in_mask = if size == 0 { + start <= constraints.addr_mask + } else { + start + .checked_add(size.saturating_sub(1) as u64) + .map(|end| end <= constraints.addr_mask) + .unwrap_or(false) + }; + + if !in_mask { + return Err(DmaError::DmaMaskNotMatch { + addr, + mask: constraints.addr_mask, + }); + } + + if let Some(max) = constraints.max_segment_size + && size > max + { + return Err(DmaError::SegmentTooLarge { size, max }); + } + + if let Some(boundary) = constraints.boundary + && size > 0 + { + let boundary = boundary as u64; + let end = start + size.saturating_sub(1) as u64; + if start / boundary != end / boundary { + return Err(DmaError::BoundaryCross { + addr, + size, + boundary: boundary as usize, + }); + } + } + + Ok(()) +} + +fn check_dma_align(addr: DmaAddr, align: usize) -> Result<(), DmaError> { + let align = align.max(1); + if !addr.as_u64().is_multiple_of(align as u64) { + return Err(DmaError::AlignMismatch { + required: align, + address: addr, + }); } + Ok(()) } diff --git a/components/dma-api/src/map_single.rs b/components/dma-api/src/map_single.rs deleted file mode 100644 index a3c0478f8e..0000000000 --- a/components/dma-api/src/map_single.rs +++ /dev/null @@ -1,232 +0,0 @@ -use alloc::vec::Vec; -use core::{num::NonZeroUsize, ptr::NonNull}; - -use crate::{DeviceDma, DmaDirection, DmaError, DmaMapHandle}; - -/// 映射单个连续内存区域的 DMA 数组。 -/// -/// `SArrayPtr` 将现有的缓冲区(如栈数组或堆分配的 slice)映射为 DMA 可访问区域。 -/// 与 `DArray` 不同,此类型提供手动缓存同步控制。 -/// -/// # 缓存同步 -/// -/// **重要**: `SArrayPtr` **不会**在每次访问时自动同步缓存。 -/// 你必须使用特定方法进行缓存同步: -/// - `to_vec()`: 读取前自动失效整个范围 -/// - `copy_from_slice()`: 写入后自动刷新整个范围 -/// - `read()`/`set()`: **不**执行自动缓存同步 -/// -/// # 生命周期 -/// -/// `SArrayPtr` 在离开作用域时自动解除 DMA 映射,但**不会**自动同步缓存。 -/// 必须在映射解除前手动完成必要的缓存同步操作。 -/// -/// # 类型参数 -/// -/// - `T`: 数组元素类型 -/// -/// # 示例 -/// -/// ```rust,ignore -/// use dma_api::{DeviceDma, DmaDirection}; -/// -/// let mut buffer = [0u8; 4096]; -/// let device = DeviceDma::new(0xFFFFFFFF, &my_dma_impl); -/// -/// // 映射用于 DMA 写入 -/// let mut mapping = device.map_single_array(&buffer, 64, DmaDirection::ToDevice)?; -/// -/// // 必须手动刷新缓存 -/// mapping.copy_from_slice(&data); -/// -/// // ... 启动 DMA 传输 ... -/// -/// // 映射在作用域结束时自动解映射 -/// ``` -pub struct SArrayPtr { - handle: DmaMapHandle, - osal: DeviceDma, - pub direction: DmaDirection, - _marker: core::marker::PhantomData<*mut T>, -} - -impl SArrayPtr { - /// Create a new SArrayPtr from a raw pointer and size. - pub(crate) fn map_single( - os: &DeviceDma, - buff: &[T], - align: usize, - direction: DmaDirection, - ) -> Result { - let addr = NonNull::new(buff.as_ptr() as *mut u8).ok_or(DmaError::NullPointer)?; - let size = - NonZeroUsize::new(core::mem::size_of_val(buff)).ok_or(DmaError::ZeroSizedBuffer)?; - let handle = unsafe { os._map_single(addr, size, align, direction)? }; - - Ok(Self { - handle, - osal: os.clone(), - direction, - _marker: core::marker::PhantomData, - }) - } - - /// 从 slice 复制数据到映射的缓冲区。 - /// - /// 复制完成后刷新整个缓冲区的 CPU 缓存(ToDevice/Bidirectional)。 - /// 这是手动同步缓存的主要方式之一。 - /// - /// # 参数 - /// - /// - `src`: 源 slice - /// - /// # Panics - /// - /// 如果源 slice 大于 DMA 缓冲区大小则 panic - pub fn copy_from_slice(&mut self, src: &[T]) { - assert!( - core::mem::size_of_val(src) <= self.handle.size(), - "Source slice is larger than DMA buffer" - ); - unsafe { - let dest_ptr = self.handle.cpu_addr.cast::(); - dest_ptr - .as_ptr() - .copy_from_nonoverlapping(src.as_ptr(), src.len()); - } - self.osal - .confirm_write(&self.handle, 0, self.handle.size(), self.direction); - } - - /// 获取 DMA 地址。 - /// - /// 返回设备用于访问此 DMA 缓冲区的物理/DMA 地址。 - /// - /// # 返回 - /// - /// DMA 地址 - pub fn dma_addr(&self) -> crate::DmaAddr { - self.handle.dma_addr - } - - /// 获取数组长度(元素个数)。 - /// - /// # 返回 - /// - /// 数组中的元素个数 - pub fn len(&self) -> usize { - self.handle.size() / core::mem::size_of::() - } - - /// 检查数组是否为空。 - /// - /// # 返回 - /// - /// 如果数组长度为 0 返回 `true`,否则返回 `false` - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// 读取指定索引的元素(不自动同步缓存)。 - /// - /// **注意**: 此方法**不会**自动同步缓存。 - /// 如果需要缓存同步,请使用 `to_vec()` 方法。 - /// - /// # 参数 - /// - /// - `index`: 元素索引 - /// - /// # 返回 - /// - /// 如果索引有效返回 `Some(T)`,否则返回 `None` - pub fn read(&self, index: usize) -> Option { - if index >= self.len() { - return None; - } - - unsafe { - let offset = index * core::mem::size_of::(); - self.osal.prepare_read( - &self.handle, - offset, - core::mem::size_of::(), - self.direction, - ); - let ptr = self.handle.cpu_addr.cast::().add(index); - Some(ptr.read()) - } - } - - /// 设置指定索引的元素值(不自动同步缓存)。 - /// - /// **注意**: 此方法**不会**自动同步缓存。 - /// 写入后请使用 `copy_from_slice()` 或手动刷新缓存。 - /// - /// # 参数 - /// - /// - `index`: 元素索引 - /// - `value`: 要写入的值 - /// - /// # Panics - /// - /// 如果 `index >= self.len()` 则 panic - pub fn set(&mut self, index: usize, value: T) { - assert!( - index < self.len(), - "index out of range, index: {},len: {}", - index, - self.len() - ); - - unsafe { - let ptr = self.handle.cpu_addr.cast::().add(index); - ptr.write(value); - } - - self.osal.confirm_write( - &self.handle, - index * core::mem::size_of::(), - core::mem::size_of::(), - self.direction, - ); - } - - /// 将整个数组转换为 Vec(自动同步缓存)。 - /// - /// 这是读取映射数据并同步缓存的主要方式。 - /// 读取前会自动使 CPU 缓存失效(FromDevice/Bidirectional)。 - /// - /// # 返回 - /// - /// 包含所有元素的 Vec - pub fn to_vec(&self) -> Vec { - let mut vec: Vec = Vec::with_capacity(self.len()); - self.osal - .prepare_read(&self.handle, 0, self.handle.size(), self.direction); - unsafe { - let src_ptr = self.handle.cpu_addr.as_ptr().cast::(); - let dst_ptr = vec.as_mut_ptr(); - dst_ptr.copy_from_nonoverlapping(src_ptr, self.len()); - vec.set_len(self.len()); - } - vec - } - - pub fn prepare_read_all(&self) { - self.osal - .prepare_read(&self.handle, 0, self.handle.size(), self.direction); - } - - pub fn confirm_write_all(&self) { - self.osal - .confirm_write(&self.handle, 0, self.handle.size(), self.direction); - } -} - -impl Drop for SArrayPtr { - fn drop(&mut self) { - unsafe { - self.osal.unmap_single(self.handle); - } - } -} diff --git a/components/dma-api/src/osal/aarch64.rs b/components/dma-api/src/op/aarch64.rs similarity index 100% rename from components/dma-api/src/osal/aarch64.rs rename to components/dma-api/src/op/aarch64.rs diff --git a/components/dma-api/src/op/mod.rs b/components/dma-api/src/op/mod.rs new file mode 100644 index 0000000000..3b45671b19 --- /dev/null +++ b/components/dma-api/src/op/mod.rs @@ -0,0 +1,195 @@ +use core::{num::NonZeroUsize, ptr::NonNull}; + +use mbarrier::mb; + +use crate::{DmaAllocHandle, DmaConstraints, DmaDirection, DmaError, DmaMapHandle}; + +cfg_if::cfg_if! { + if #[cfg(target_arch = "aarch64")] { + #[path = "aarch64.rs"] + pub mod arch; + } else{ + #[path = "nop.rs"] + pub mod arch; + } +} + +pub trait DmaOp: Sync + Send + 'static { + fn page_size(&self) -> usize; + + /// Allocates a device-visible contiguous DMA address range. + /// + /// The returned CPU mapping is normal memory. Non-coherent platforms must + /// use `sync_alloc_for_device` and `sync_alloc_for_cpu` to transfer + /// ownership between CPU and device. + /// + /// # Safety + /// + /// Implementations must return a live allocation described by `layout`, + /// with a DMA address range satisfying `constraints`, and that allocation + /// must remain valid until `dealloc_contiguous`. + unsafe fn alloc_contiguous( + &self, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option; + + /// # Safety + /// + /// Must be paired with `alloc_contiguous`. + unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle); + + /// Allocates coherent DMA memory. + /// + /// Coherent memory is CPU/device visible without explicit cache + /// maintenance. Ordering barriers are still the driver's responsibility. + /// + /// # Safety + /// + /// Implementations must return a live allocation described by `layout`, + /// with a DMA address range satisfying `constraints`, and with the backend's + /// coherent mapping policy applied until `dealloc_coherent`. + unsafe fn alloc_coherent( + &self, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option; + + /// # Safety + /// + /// Must be paired with `alloc_coherent`. + unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle); + + /// Maps an existing caller-owned buffer for streaming DMA. + /// + /// # Safety + /// + /// `addr..addr + size` must remain live until `unmap_streaming`, and CPU + /// access while the device owns the mapping must follow the sync contract. + unsafe fn map_streaming( + &self, + constraints: DmaConstraints, + addr: NonNull, + size: NonZeroUsize, + direction: DmaDirection, + ) -> Result; + + /// # Safety + /// + /// Must be paired with `map_streaming`. + unsafe fn unmap_streaming(&self, handle: DmaMapHandle); + + fn flush(&self, addr: NonNull, size: usize) { + mb(); + arch::flush(addr, size) + } + + fn invalidate(&self, addr: NonNull, size: usize) { + arch::invalidate(addr, size); + mb(); + } + + fn flush_invalidate(&self, addr: NonNull, size: usize) { + mb(); + arch::flush_invalidate(addr, size); + mb(); + } + + fn sync_alloc_for_device( + &self, + handle: &DmaAllocHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + if matches!( + direction, + DmaDirection::ToDevice | DmaDirection::Bidirectional + ) { + self.flush(unsafe { handle.as_ptr().add(offset) }, size); + } else if matches!(direction, DmaDirection::FromDevice) { + self.invalidate(unsafe { handle.as_ptr().add(offset) }, size); + } + } + + fn sync_alloc_for_cpu( + &self, + handle: &DmaAllocHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + if matches!( + direction, + DmaDirection::FromDevice | DmaDirection::Bidirectional + ) { + self.invalidate(unsafe { handle.as_ptr().add(offset) }, size); + } + } + + fn sync_map_for_device( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + let source = unsafe { handle.as_ptr().add(offset) }; + if let Some(map_virt) = handle.bounce_ptr() + && map_virt != handle.as_ptr() + { + let target = unsafe { map_virt.add(offset) }; + if matches!( + direction, + DmaDirection::ToDevice | DmaDirection::Bidirectional + ) { + unsafe { + target + .as_ptr() + .copy_from_nonoverlapping(source.as_ptr(), size); + } + self.flush(target, size); + } else if matches!(direction, DmaDirection::FromDevice) { + self.invalidate(target, size); + } + return; + } + + match direction { + DmaDirection::ToDevice => self.flush(source, size), + DmaDirection::FromDevice => self.invalidate(source, size), + DmaDirection::Bidirectional => self.flush_invalidate(source, size), + } + } + + fn sync_map_for_cpu( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + if !matches!( + direction, + DmaDirection::FromDevice | DmaDirection::Bidirectional + ) { + return; + } + + let target = unsafe { handle.as_ptr().add(offset) }; + if let Some(map_virt) = handle.bounce_ptr() + && map_virt != handle.as_ptr() + { + let source = unsafe { map_virt.add(offset) }; + self.invalidate(source, size); + unsafe { + target + .as_ptr() + .copy_from_nonoverlapping(source.as_ptr(), size); + } + return; + } + + self.invalidate(target, size); + } +} diff --git a/components/dma-api/src/osal/nop.rs b/components/dma-api/src/op/nop.rs similarity index 100% rename from components/dma-api/src/osal/nop.rs rename to components/dma-api/src/op/nop.rs diff --git a/components/dma-api/src/osal/mod.rs b/components/dma-api/src/osal/mod.rs deleted file mode 100644 index ae21f7b069..0000000000 --- a/components/dma-api/src/osal/mod.rs +++ /dev/null @@ -1,127 +0,0 @@ -use core::{num::NonZeroUsize, ptr::NonNull}; - -use mbarrier::mb; - -use crate::{DmaDirection, DmaError, DmaHandle, DmaMapHandle}; - -cfg_if::cfg_if! { - if #[cfg(target_arch = "aarch64")] { - #[path = "aarch64.rs"] - pub mod arch; - } else{ - #[path = "nop.rs"] - pub mod arch; - } -} - -pub trait DmaOp: Sync + Send + 'static { - fn page_size(&self) -> usize; - - /// 将虚拟地址映射到 DMA 地址 - /// - /// # Safety - /// 只能是单个连续内存块 - unsafe fn map_single( - &self, - dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - align: usize, - direction: DmaDirection, - ) -> Result; - - /// 解除 DMA 映射 - /// - /// # Safety - /// 必须与 map_single 配对使用 - unsafe fn unmap_single(&self, handle: DmaMapHandle); - - /// 写回缓存到内存 (clean) - fn flush(&self, addr: NonNull, size: usize) { - mb(); - arch::flush(addr, size) - } - - /// 使缓存无效 (invalidate) - fn invalidate(&self, addr: NonNull, size: usize) { - arch::invalidate(addr, size); - mb(); - } - - fn flush_invalidate(&self, addr: NonNull, size: usize) { - mb(); - arch::flush_invalidate(addr, size); - mb(); - } - - /// 分配 DMA 可访问内存 - /// # Safety - /// - /// - 调用者必须确保 layout 合法 - /// - 返回的内存必须保证连续 - unsafe fn alloc_coherent( - &self, - dma_mask: u64, - layout: core::alloc::Layout, - ) -> Option; - - /// 释放 DMA 内存 - /// # Safety - /// 调用者必须确保 ptr 和 layout 与 alloc 时匹配 - unsafe fn dealloc_coherent(&self, handle: DmaHandle); - - fn prepare_read( - &self, - handle: &DmaMapHandle, - offset: usize, - size: usize, - direction: DmaDirection, - ) { - if matches!( - direction, - DmaDirection::FromDevice | DmaDirection::Bidirectional - ) { - let origin_ptr = unsafe { handle.cpu_addr.add(offset) }; - - if let Some(virt) = handle.map_alloc_virt - && virt != handle.cpu_addr - { - let map_ptr = unsafe { virt.add(offset) }; - self.invalidate(map_ptr, size); - unsafe { - origin_ptr.copy_from_nonoverlapping(map_ptr, size); - } - } else { - self.invalidate(origin_ptr, size); - } - } - } - - fn confirm_write( - &self, - handle: &DmaMapHandle, - offset: usize, - size: usize, - direction: DmaDirection, - ) { - if matches!( - direction, - DmaDirection::ToDevice | DmaDirection::Bidirectional - ) { - let ptr = unsafe { handle.cpu_addr.add(offset) }; - - if let Some(virt) = handle.map_alloc_virt - && virt != handle.cpu_addr - { - unsafe { - let src = core::slice::from_raw_parts(ptr.as_ptr(), size); - let dst = core::slice::from_raw_parts_mut(virt.as_ptr().add(offset), size); - - dst.copy_from_slice(src); - } - } - - self.flush(ptr, size) - } - } -} diff --git a/components/dma-api/src/pool.rs b/components/dma-api/src/pool.rs index 0f6a11395b..aff7191fd9 100644 --- a/components/dma-api/src/pool.rs +++ b/components/dma-api/src/pool.rs @@ -6,42 +6,42 @@ use core::ops::{Deref, DerefMut}; use ax_kspin::SpinNoIrq as Mutex; -use crate::{DArray, DeviceDma, DmaDirection, DmaError}; +use crate::{ContiguousArray, DeviceDma, DmaDirection, DmaError}; #[derive(Clone, Debug)] -pub(crate) struct DArrayConfig { +pub(crate) struct ContiguousBufferConfig { pub size: usize, pub align: usize, pub direction: DmaDirection, } #[derive(Clone)] -pub struct DArrayPool { +pub struct ContiguousBufferPool { inner: Arc>, } -pub struct DBuff { - data: Option>, +pub struct ContiguousBuffer { + data: Option>, pool: Weak>, } -unsafe impl Send for DBuff {} +unsafe impl Send for ContiguousBuffer {} -impl Deref for DBuff { - type Target = DArray; +impl Deref for ContiguousBuffer { + type Target = ContiguousArray; fn deref(&self) -> &Self::Target { self.data.as_ref().unwrap() } } -impl DerefMut for DBuff { +impl DerefMut for ContiguousBuffer { fn deref_mut(&mut self) -> &mut Self::Target { self.data.as_mut().unwrap() } } -impl Drop for DBuff { +impl Drop for ContiguousBuffer { fn drop(&mut self) { if let Some(data) = self.data.take() && let Some(pool) = self.pool.upgrade() @@ -54,50 +54,51 @@ impl Drop for DBuff { struct Inner { dev: DeviceDma, - config: DArrayConfig, - pool: VecDeque>, + config: ContiguousBufferConfig, + pool: VecDeque>, } impl Inner { - fn alloc(&mut self) -> Option> { + fn alloc(&mut self) -> Option> { self.pool.pop_front() } - fn dealloc(&mut self, dvec: DArray) { - self.pool.push_back(dvec); + fn dealloc(&mut self, data: ContiguousArray) { + self.pool.push_back(data); } } -impl DArrayPool { - pub(crate) fn new_pool(dev: DeviceDma, config: DArrayConfig, cap: usize) -> DArrayPool { +impl ContiguousBufferPool { + pub(crate) fn with_capacity( + dev: DeviceDma, + config: ContiguousBufferConfig, + cap: usize, + ) -> ContiguousBufferPool { let mut pool = VecDeque::with_capacity(cap); for _ in 0..cap { - if let Ok(dvec) = - // DArray::zeros(config.dma_mask, config.size, config.align, config.direction) - DArray::new_zero_with_align( - &dev, - config.size, - config.align, - config.direction, - ) - { - pool.push_back(dvec); + if let Ok(data) = ContiguousArray::new_zero_with_align( + &dev, + config.size, + config.align, + config.direction, + ) { + pool.push_back(data); } } - DArrayPool { + ContiguousBufferPool { inner: Arc::new(Mutex::new(Inner { dev, pool, config })), } } - pub fn alloc(&self) -> Result { + pub fn alloc(&self) -> Result { let config; let dev; { let mut inner = self.inner.lock(); - if let Some(dvec) = inner.alloc() { - return Ok(DBuff { - data: Some(dvec), + if let Some(data) = inner.alloc() { + return Ok(ContiguousBuffer { + data: Some(data), pool: Arc::downgrade(&self.inner), }); } else { @@ -106,9 +107,14 @@ impl DArrayPool { } }; - let dvec = DArray::new_zero_with_align(&dev, config.size, config.align, config.direction)?; - Ok(DBuff { - data: Some(dvec), + let data = ContiguousArray::new_zero_with_align( + &dev, + config.size, + config.align, + config.direction, + )?; + Ok(ContiguousBuffer { + data: Some(data), pool: Arc::downgrade(&self.inner), }) } diff --git a/components/dma-api/src/streaming.rs b/components/dma-api/src/streaming.rs new file mode 100644 index 0000000000..1e574ec042 --- /dev/null +++ b/components/dma-api/src/streaming.rs @@ -0,0 +1,142 @@ +use alloc::vec::Vec; +use core::{marker::PhantomData, num::NonZeroUsize, ptr::NonNull}; + +use crate::{DeviceDma, DmaDirection, DmaError, DmaMapHandle, DmaPod}; + +pub struct StreamingMap { + handle: DmaMapHandle, + device: DeviceDma, + direction: DmaDirection, + _marker: PhantomData<*mut T>, +} + +unsafe impl Send for StreamingMap {} + +impl StreamingMap { + pub(crate) fn map( + os: &DeviceDma, + buff: &mut [T], + align: usize, + direction: DmaDirection, + ) -> Result { + let addr = NonNull::new(buff.as_mut_ptr().cast::()).ok_or(DmaError::NullPointer)?; + let size = + NonZeroUsize::new(core::mem::size_of_val(buff)).ok_or(DmaError::ZeroSizedBuffer)?; + let handle = unsafe { os.map_streaming(addr, size, align, direction)? }; + + Ok(Self { + handle, + device: os.clone(), + direction, + _marker: PhantomData, + }) + } + + pub fn dma_addr(&self) -> crate::DmaAddr { + self.handle.dma_addr() + } + + pub fn len(&self) -> usize { + if core::mem::size_of::() == 0 { + 0 + } else { + self.handle.size() / core::mem::size_of::() + } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn bytes_len(&self) -> usize { + self.handle.size() + } + + pub fn read(&self, index: usize) -> Option { + if index >= self.len() { + return None; + } + Some(unsafe { self.handle.as_ptr().cast::().add(index).read() }) + } + + pub fn set(&mut self, index: usize, value: T) { + assert!( + index < self.len(), + "index out of range, index: {}, len: {}", + index, + self.len() + ); + unsafe { + self.handle.as_ptr().cast::().add(index).write(value); + } + } + + pub fn copy_from_slice(&mut self, src: &[T]) { + assert!( + core::mem::size_of_val(src) <= self.handle.size(), + "source slice is larger than DMA buffer" + ); + unsafe { + self.handle + .as_ptr() + .cast::() + .as_ptr() + .copy_from_nonoverlapping(src.as_ptr(), src.len()); + } + } + + pub fn to_vec(&self) -> Vec { + let mut vec: Vec = Vec::with_capacity(self.len()); + unsafe { + let src_ptr = self.handle.as_ptr().as_ptr().cast::(); + let dst_ptr = vec.as_mut_ptr(); + dst_ptr.copy_from_nonoverlapping(src_ptr, self.len()); + vec.set_len(self.len()); + } + vec + } + + pub fn sync_for_device(&self, offset: usize, size: usize) { + self.check_range(offset, size); + self.device + .sync_map_for_device(&self.handle, offset, size, self.direction); + } + + pub fn sync_for_cpu(&self, offset: usize, size: usize) { + self.check_range(offset, size); + self.device + .sync_map_for_cpu(&self.handle, offset, size, self.direction); + } + + pub fn sync_for_device_all(&self) { + self.device + .sync_map_for_device(&self.handle, 0, self.handle.size(), self.direction); + } + + pub fn sync_for_cpu_all(&self) { + self.device + .sync_map_for_cpu(&self.handle, 0, self.handle.size(), self.direction); + } + + pub fn bounce_ptr(&self) -> Option> { + self.handle.bounce_ptr() + } + + fn check_range(&self, offset: usize, size: usize) { + assert!( + offset <= self.bytes_len() && size <= self.bytes_len().saturating_sub(offset), + "range out of bounds, offset: {}, size: {}, bytes_len: {}", + offset, + size, + self.bytes_len() + ); + } +} + +impl Drop for StreamingMap { + fn drop(&mut self) { + unsafe { + self.device.unmap_streaming(self.handle); + } + } +} diff --git a/components/dma-api/tests/test.rs b/components/dma-api/tests/test.rs index e5d832717b..2ab098ced2 100644 --- a/components/dma-api/tests/test.rs +++ b/components/dma-api/tests/test.rs @@ -2,431 +2,201 @@ mod test_helpers; -use std::{num::NonZeroUsize, ptr::NonNull}; - use dma_api::*; use test_helpers::{DmaOperation, TrackingDmaOp}; -#[test] -fn test_read() { - let mut dma: DArray = new_api() - .array_zero_with_align(10, 0x1000, DmaDirection::FromDevice) - .unwrap(); - - dma.set(0, 1); - - let o = dma.read(0).unwrap(); - - assert_eq!(o, 1); +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[repr(C)] +struct Descriptor { + addr: u64, + len: u32, + flags: u32, } -#[test] -fn test_write() { - let mut dma: DArray = new_api() - .array_zero_with_align(10, 0x1000, DmaDirection::ToDevice) - .unwrap(); - - dma.set(0, 1); - - let o = dma.read(0).unwrap(); - - assert_eq!(o, 1); -} -#[derive(Debug, PartialEq, Eq)] -struct Foo { - foo: u32, - bar: u32, +fn new_tracking_device() -> (DeviceDma, &'static TrackingDmaOp) { + let tracker = Box::new(TrackingDmaOp::new()); + let tracker = Box::leak(tracker); + (DeviceDma::new(u64::MAX, tracker), tracker) } #[test] -fn test_modify() { - let mut dma: DBox = new_api() - .box_zero_with_align(64, DmaDirection::Bidirectional) +fn coherent_array_access_does_not_sync_cache() { + let (dev, tracker) = new_tracking_device(); + let mut ring = dev + .coherent_array_zero_with_align::(8, 64) .unwrap(); - dma.modify(|f| f.bar = 1); - - assert_eq!(dma.read(), Foo { foo: 0, bar: 1 }); -} - -#[test] -fn test_copy() { - let mut dma = new_api() - .array_zero_with_align::(0x40, 0x1000, DmaDirection::Bidirectional) - .unwrap(); - - println!("new dma ok"); - - let src = [1u32; 0x40]; - - dma.copy_from_slice(&src); - - println!("copy ok"); + tracker.clear(); + ring.set( + 0, + Descriptor { + addr: 0x1000, + len: 16, + flags: 1, + }, + ); + assert_eq!(ring.read(0).unwrap().addr, 0x1000); - for (i, &v) in src.iter().enumerate() { - assert_eq!(dma.read(i).unwrap(), v); - } + assert_eq!(tracker.count_sync_alloc_for_device(), 0); + assert_eq!(tracker.count_sync_alloc_for_cpu(), 0); } #[test] -fn test_index() { - let dma = new_api() - .array_zero_with_align::(0x40, 0x1000, DmaDirection::Bidirectional) +fn contiguous_array_syncs_only_when_explicitly_requested() { + let (dev, tracker) = new_tracking_device(); + let mut buff = dev + .contiguous_array_zero_with_align::(16, 64, DmaDirection::ToDevice) .unwrap(); - println!("new dma ok"); - - let a = dma.read(0).unwrap(); - - assert_eq!(a, 0); -} - -#[test] -fn mask_check_rejects_overflow_alloc() { - static DMA: MaskedDma = MaskedDma; - let dev = DeviceDma::new(0x0fff, &DMA); - - let err = dev.array_zero_with_align::(0x1000, 0x1000, DmaDirection::ToDevice); - - assert!(matches!(err, Err(DmaError::DmaMaskNotMatch { .. }))); -} - -#[test] -fn mask_check_rejects_overflow_map() { - static DMA: MaskedDma = MaskedDma; - let dev = DeviceDma::new(0x0fff, &DMA); - - let mut buf = [0u8; 0x1000]; - - let err = dev.map_single_array(&buf, 64, DmaDirection::FromDevice); - - assert!(matches!(err, Err(DmaError::DmaMaskNotMatch { .. }))); -} - -fn new_api() -> DeviceDma { - static IMPL: Impled = Impled; - DeviceDma::new(u64::MAX, &IMPL) -} - -struct Impled; - -impl DmaOp for Impled { - fn page_size(&self) -> usize { - 0x1000 - } - - unsafe fn map_single( - &self, - _dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - align: usize, - _direction: DmaDirection, - ) -> Result { - println!( - "map_single @{:?}, size {:#x}, align: {:#x}", - addr, - size.get(), - align - ); - let layout = core::alloc::Layout::from_size_align(size.get(), align)?; - Ok(unsafe { DmaMapHandle::new(addr, (addr.as_ptr() as u64).into(), layout, None) }) - } - - unsafe fn unmap_single(&self, handle: DmaMapHandle) { - println!( - "unmap_single @{:?}, size {:#x}", - handle.as_ptr(), - handle.size() - ); - } - - fn flush(&self, addr: std::ptr::NonNull, size: usize) { - println!("flush @{:?}, size {size:#x}", addr); - } - - fn invalidate(&self, addr: std::ptr::NonNull, size: usize) { - println!("invalidate @{:?}, size {size:#x}", addr); - } - - unsafe fn alloc_coherent( - &self, - _dma_mask: u64, - layout: core::alloc::Layout, - ) -> Option { - println!( - "alloc_coherent size: {:#x}, align: {:#x}", - layout.size(), - layout.align() - ); - let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; - if ptr.is_null() { - return None; - } - Some(unsafe { DmaHandle::new(NonNull::new(ptr).unwrap(), (ptr as u64).into(), layout) }) - } - - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { - unsafe { std::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; - } -} - -struct MaskedDma; - -impl DmaOp for MaskedDma { - fn page_size(&self) -> usize { - 0x1000 - } - - unsafe fn map_single( - &self, - _dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - align: usize, - _direction: DmaDirection, - ) -> Result { - let layout = core::alloc::Layout::from_size_align(size.get(), align)?; - Ok(unsafe { DmaMapHandle::new(addr, 0x1000u64.into(), layout, None) }) - } - - unsafe fn unmap_single(&self, _handle: DmaMapHandle) {} - - fn flush(&self, _addr: std::ptr::NonNull, _size: usize) {} - - fn invalidate(&self, _addr: std::ptr::NonNull, _size: usize) {} - - unsafe fn alloc_coherent( - &self, - _dma_mask: u64, - layout: core::alloc::Layout, - ) -> Option { - let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; - if ptr.is_null() { - return None; + tracker.clear(); + buff.set(3, 0xA5A5_A5A5); + assert_eq!(tracker.count_sync_alloc_for_device(), 0); + assert_eq!(tracker.count_sync_alloc_for_cpu(), 0); + + buff.sync_for_device(3 * size_of::(), size_of::()); + assert_eq!(tracker.count_sync_alloc_for_device(), 1); + assert_eq!(tracker.count_sync_alloc_for_cpu(), 0); + + let ops = tracker.operations(); + assert!(ops.iter().any(|op| matches!( + op, + DmaOperation::SyncAllocForDevice { + size: 4, + direction: DmaDirection::ToDevice, + .. } - Some(unsafe { DmaHandle::new(NonNull::new(ptr).unwrap(), 0x1000u64.into(), layout) }) - } - - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { - unsafe { std::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; - } + ))); } -// ============================================================================ -// 新增测试: 地址正确性测试 -// ============================================================================ - #[test] -fn test_single_element_address() { - let tracker = Box::new(TrackingDmaOp::new(0)); - let tracker: &'static TrackingDmaOp = Box::leak(tracker); - let dev = DeviceDma::new(u64::MAX, tracker); - - let mut dma: DArray = dev - .array_zero_with_align(1, 64, DmaDirection::ToDevice) +fn contiguous_box_supports_cpu_sync() { + let (dev, tracker) = new_tracking_device(); + let mut status = dev + .contiguous_box_zero_with_align::(64, DmaDirection::FromDevice) .unwrap(); tracker.clear(); - dma.set(0, 0x12345678); - - // 验证有且仅有一次 flush 操作,大小为 4 字节 (u32) - let ops = tracker.get_operations(); - let flush_ops: Vec<_> = ops - .iter() - .filter_map(|op| { - if let DmaOperation::Flush { size, .. } = op { - Some(*size) - } else { - None - } - }) - .collect(); + status.write(Descriptor { + addr: 0, + len: 0, + flags: 0, + }); + status.sync_for_cpu_all(); - assert_eq!(flush_ops.len(), 1); - assert_eq!(flush_ops[0], 4); // u32 = 4 bytes + assert_eq!(tracker.count_sync_alloc_for_device(), 0); + assert_eq!(tracker.count_sync_alloc_for_cpu(), 1); } #[test] -fn test_multi_element_offset() { - let tracker = Box::new(TrackingDmaOp::new(0)); - let tracker: &'static TrackingDmaOp = Box::leak(tracker); +fn streaming_map_has_explicit_device_and_cpu_sync() { + let tracker = Box::new(TrackingDmaOp::new().with_next_dma_addr(0x4000)); + let tracker = Box::leak(tracker); let dev = DeviceDma::new(u64::MAX, tracker); - - let mut dma: DArray = dev - .array_zero_with_align(10, 64, DmaDirection::ToDevice) + let mut backing = [0u8; 128]; + let map = dev + .map_streaming_slice(&mut backing, 64, DmaDirection::Bidirectional) .unwrap(); - tracker.clear(); - - // 设置 index 0, 1, 2 - dma.set(0, 0); - dma.set(1, 1); - dma.set(2, 2); - - // u32 = 4 bytes, 应该有 3 次 flush 操作,每次大小都是 4 字节 - let ops = tracker.get_operations(); - let flush_ops: Vec<_> = ops - .iter() - .filter_map(|op| { - if let DmaOperation::Flush { size, addr } = op { - Some((*addr, *size)) - } else { - None - } - }) - .collect(); - - assert_eq!(flush_ops.len(), 3); - // 验证每次 flush 的大小都是 4 字节 - for (addr, size) in &flush_ops { - assert_eq!(*size, 4); - } + tracker.clear(); + map.sync_for_device_all(); + map.sync_for_cpu_all(); + drop(map); - // 验证地址是递增的,每次增加 4 字节 - assert_eq!(flush_ops[1].0 - flush_ops[0].0, 4); // index 1 - index 0 - assert_eq!(flush_ops[2].0 - flush_ops[1].0, 4); // index 2 - index 1 + assert_eq!(tracker.count_sync_map_for_device(), 1); + assert_eq!(tracker.count_sync_map_for_cpu(), 1); + assert!( + tracker + .operations() + .iter() + .any(|op| matches!(op, DmaOperation::UnmapStreaming { size: 128 })) + ); } #[test] -fn test_different_type_sizes() { - let tracker = Box::new(TrackingDmaOp::new(0)); - let tracker: &'static TrackingDmaOp = Box::leak(tracker); - let dev = DeviceDma::new(u64::MAX, tracker); - - // u8 = 1 byte - let mut dma_u8: DArray = dev - .array_zero_with_align(5, 8, DmaDirection::ToDevice) +fn streaming_bounce_buffer_copies_back_on_cpu_sync() { + let tracker = Box::new(TrackingDmaOp::new().with_next_dma_addr(0x80)); + let tracker = Box::leak(tracker); + let dev = DeviceDma::new(0xff, tracker); + let mut backing = [1u8; 16]; + let map = dev + .map_streaming_slice(&mut backing, 16, DmaDirection::FromDevice) .unwrap(); - tracker.clear(); - dma_u8.set(3, 1); - let ops = tracker.get_operations(); - if let Some(DmaOperation::Flush { size, .. }) = ops.last() { - assert_eq!(*size, 1); // u8 = 1 byte - } else { - panic!("Expected Flush operation"); + assert!(map.bounce_ptr().is_some()); + unsafe { + map.bounce_ptr() + .unwrap() + .as_ptr() + .write_bytes(0x5a, backing.len()); } + map.sync_for_cpu_all(); + drop(map); - // u64 = 8 bytes - let mut dma_u64: DArray = dev - .array_zero_with_align(5, 8, DmaDirection::ToDevice) - .unwrap(); - tracker.clear(); - dma_u64.set(2, 2); - - let ops = tracker.get_operations(); - if let Some(DmaOperation::Flush { size, .. }) = ops.last() { - assert_eq!(*size, 8); // u64 = 8 bytes - } else { - panic!("Expected Flush operation"); - } + assert_eq!(backing, [0x5a; 16]); } -// ============================================================================ -// 新增测试: DmaDirection 行为测试 -// ============================================================================ - #[test] -fn test_direction_to_device() { - let tracker = Box::new(TrackingDmaOp::new(0x1000)); - let tracker: &'static TrackingDmaOp = Box::leak(tracker); - let dev = DeviceDma::new(u64::MAX, tracker); - let mut dma: DArray = dev - .array_zero_with_align(10, 64, DmaDirection::ToDevice) - .unwrap(); - - tracker.clear(); +fn allocation_rejects_backend_address_outside_mask() { + let (dev, tracker) = new_tracking_device(); + tracker.force_next_dma_addr(0x1_0000_0000); + let result = dev + .with_constraints(DmaConstraints::new(u32::MAX as u64)) + .coherent_array_zero_with_align::(4096, 4096); - // set 应该 flush - dma.set(0, 1); - assert_eq!(tracker.count_flush(), 1); - assert_eq!(tracker.count_invalidate(), 0); - - // read 不应该 inv - dma.read(0); - assert_eq!(tracker.count_flush(), 1); // 没有增加 - assert_eq!(tracker.count_invalidate(), 0); // 没有增加 + assert!(matches!(result, Err(DmaError::DmaMaskNotMatch { .. }))); } #[test] -fn test_direction_from_device() { - let tracker = Box::new(TrackingDmaOp::new(0x1000)); - let tracker: &'static TrackingDmaOp = Box::leak(tracker); - let dev = DeviceDma::new(u64::MAX, tracker); - let mut dma: DArray = dev - .array_zero_with_align(10, 64, DmaDirection::FromDevice) +fn low_32bit_allocations_are_validated() { + let tracker = Box::new(TrackingDmaOp::new().with_next_dma_addr(0xffff_f000)); + let tracker = Box::leak(tracker); + let dev = DeviceDma::new(u32::MAX as u64, tracker); + let buff = dev + .contiguous_array_zero_with_align::(0x1000, 0x1000, DmaDirection::ToDevice) .unwrap(); - tracker.clear(); - - // read 应该 inv - dma.read(0); - assert_eq!(tracker.count_flush(), 0); - assert_eq!(tracker.count_invalidate(), 1); - - // set 不应该 flush - tracker.clear(); - dma.set(0, 1); - assert_eq!(tracker.count_flush(), 0); - assert_eq!(tracker.count_invalidate(), 0); + assert!(buff.dma_addr().as_u64() <= u32::MAX as u64); + assert!(tracker.operations().iter().any(|op| matches!( + op, + DmaOperation::AllocContiguous { + mask, + .. + } if *mask == u32::MAX as u64 + ))); } #[test] -fn test_direction_bidirectional() { - let tracker = Box::new(TrackingDmaOp::new(0x1000)); - let tracker: &'static TrackingDmaOp = Box::leak(tracker); - let dev = DeviceDma::new(u64::MAX, tracker); - let mut dma: DArray = dev - .array_zero_with_align(10, 64, DmaDirection::Bidirectional) - .unwrap(); - - tracker.clear(); +fn streaming_map_rejects_backend_address_outside_mask() { + let (dev, tracker) = new_tracking_device(); + let mut backing = [0u8; 128]; + tracker.force_next_dma_addr(0x1_0000_0000); - // set 应该 flush - dma.set(0, 1); - assert_eq!(tracker.count_flush(), 1); + let result = dev + .with_constraints(DmaConstraints::new(u32::MAX as u64)) + .map_streaming_slice(&mut backing, 64, DmaDirection::FromDevice); - // read 应该 inv - dma.read(0); - assert_eq!(tracker.count_invalidate(), 1); + assert!(matches!(result, Err(DmaError::DmaMaskNotMatch { .. }))); } -// ============================================================================ -// 新增测试: Drop 行为测试 -// ============================================================================ - -// 简单的对齐缓冲区模块 -mod align_alloc { - use std::ptr::NonNull; - - pub struct AlignedBuffer { - ptr: NonNull, - _size: usize, - } - - impl AlignedBuffer { - pub fn new() -> Self { - // 分配对齐的内存 - let layout = std::alloc::Layout::from_size_align(0x1000, ALIGNMENT).unwrap(); - let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; - assert!(!ptr.is_null(), "Failed to allocate aligned buffer"); - - Self { - ptr: NonNull::new(ptr).unwrap(), - _size: 0x1000, - } - } - - pub fn as_mut_ptr(&mut self) -> *mut u8 { - self.ptr.as_ptr() +#[test] +fn pool_reuses_contiguous_buffers_without_implicit_zeroing() { + let (dev, tracker) = new_tracking_device(); + let pool = dev.contiguous_buffer_pool( + core::alloc::Layout::from_size_align(64, 64).unwrap(), + DmaDirection::ToDevice, + 1, + ); + + { + let mut buff = pool.alloc().unwrap(); + unsafe { + buff.as_mut_slice()[0] = 0x7e; } } - impl Drop for AlignedBuffer { - fn drop(&mut self) { - let layout = std::alloc::Layout::from_size_align(0x1000, ALIGNMENT).unwrap(); - unsafe { std::alloc::dealloc(self.ptr.as_ptr(), layout) }; - } - } + tracker.clear(); + let buff = pool.alloc().unwrap(); + assert_eq!(buff.as_slice()[0], 0x7e); + assert_eq!(tracker.count_sync_alloc_for_device(), 0); + assert_eq!(tracker.count_sync_alloc_for_cpu(), 0); } diff --git a/components/dma-api/tests/test_helpers.rs b/components/dma-api/tests/test_helpers.rs index 61c8dd582b..01bdbc59aa 100644 --- a/components/dma-api/tests/test_helpers.rs +++ b/components/dma-api/tests/test_helpers.rs @@ -1,8 +1,6 @@ -//! 测试辅助工具模块 -//! -//! 提供用于跟踪和验证 DMA 操作的辅助工具 - use std::{ + alloc::{alloc_zeroed, dealloc}, + collections::HashMap, num::NonZeroUsize, ptr::NonNull, sync::{Arc, Mutex}, @@ -10,210 +8,347 @@ use std::{ use dma_api::*; -/// DMA 操作记录 -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DmaOperation { - /// Flush 操作 (写回缓存到内存) - Flush { addr: usize, size: usize }, - /// Invalidate 操作 (使缓存无效) - Invalidate { addr: usize, size: usize }, - /// MapSingle 操作 - MapSingle { + AllocContiguous { + size: usize, + align: usize, + mask: u64, + }, + DeallocContiguous { + size: usize, + }, + AllocCoherent { + size: usize, + align: usize, + mask: u64, + }, + DeallocCoherent { + size: usize, + }, + MapStreaming { virt_addr: usize, size: usize, + align: usize, + direction: DmaDirection, + mask: u64, + }, + UnmapStreaming { + size: usize, + }, + SyncAllocForDevice { + addr: usize, + size: usize, + direction: DmaDirection, + }, + SyncAllocForCpu { + addr: usize, + size: usize, + direction: DmaDirection, + }, + SyncMapForDevice { + addr: usize, + size: usize, + direction: DmaDirection, + }, + SyncMapForCpu { + addr: usize, + size: usize, direction: DmaDirection, }, - /// UnmapSingle 操作 - UnmapSingle { size: usize }, - /// AllocCoherent 操作 - AllocCoherent { size: usize, align: usize }, - /// DeallocCoherent 操作 - DeallocCoherent { size: usize }, } -/// 记录所有 DMA 操作的测试辅助工具 -/// -/// 这个结构体实现了 `DmaOp` trait,可以拦截并记录所有 DMA 操作, -/// 用于验证地址计算、缓存同步等关键行为。 +#[derive(Clone)] pub struct TrackingDmaOp { operations: Arc>>, - base_addr: usize, + next_dma_addr: Arc>, + forced_dma_addr: Arc>>, + map_allocations: Arc>>, } impl TrackingDmaOp { - /// 创建新的跟踪器 - /// - /// # 参数 - /// - /// * `base_addr` - DMA 地址的基地址,用于计算相对偏移 - pub fn new(base_addr: usize) -> Self { + pub fn new() -> Self { Self { operations: Arc::new(Mutex::new(Vec::new())), - base_addr, + next_dma_addr: Arc::new(Mutex::new(0x1000)), + forced_dma_addr: Arc::new(Mutex::new(None)), + map_allocations: Arc::new(Mutex::new(HashMap::new())), } } - /// 获取所有操作记录的副本 - pub fn get_operations(&self) -> Vec { + pub fn with_next_dma_addr(self, dma_addr: u64) -> Self { + *self.next_dma_addr.lock().unwrap() = dma_addr; + self + } + + pub fn force_next_dma_addr(&self, dma_addr: u64) { + *self.forced_dma_addr.lock().unwrap() = Some(dma_addr); + } + + pub fn operations(&self) -> Vec { self.operations.lock().unwrap().clone() } - /// 清空所有操作记录 pub fn clear(&self) { self.operations.lock().unwrap().clear(); } - /// 统计 flush 操作的次数 - pub fn count_flush(&self) -> usize { + pub fn count_sync_alloc_for_device(&self) -> usize { self.operations .lock() .unwrap() .iter() - .filter(|op| matches!(op, DmaOperation::Flush { .. })) + .filter(|op| matches!(op, DmaOperation::SyncAllocForDevice { .. })) .count() } - /// 统计 invalidate 操作的次数 - pub fn count_invalidate(&self) -> usize { + pub fn count_sync_alloc_for_cpu(&self) -> usize { self.operations .lock() .unwrap() .iter() - .filter(|op| matches!(op, DmaOperation::Invalidate { .. })) + .filter(|op| matches!(op, DmaOperation::SyncAllocForCpu { .. })) .count() } - /// 查找指定偏移和大小的 flush 操作 - /// - /// # 参数 - /// - /// * `offset` - 相对于 base_addr 的偏移量 - /// * `size` - 操作大小 - pub fn find_flush_at(&self, offset: usize, size: usize) -> bool { - let expected_addr = self.base_addr + offset; - self.operations.lock().unwrap().iter().any(|op| { - matches!(op, DmaOperation::Flush { addr, size: s } - if *addr == expected_addr && *s == size) - }) - } - - /// 查找指定偏移和大小的 invalidate 操作 - /// - /// # 参数 - /// - /// * `offset` - 相对于 base_addr 的偏移量 - /// * `size` - 操作大小 - pub fn find_inv_at(&self, offset: usize, size: usize) -> bool { - let expected_addr = self.base_addr + offset; - self.operations.lock().unwrap().iter().any(|op| { - matches!(op, DmaOperation::Invalidate { addr, size: s } - if *addr == expected_addr && *s == size) - }) - } - - /// 获取最后一次 flush 操作的地址和大小 - pub fn last_flush(&self) -> Option<(usize, usize)> { - self.operations.lock().unwrap().iter().rev().find_map(|op| { - if let DmaOperation::Flush { addr, size } = op { - Some((*addr, *size)) - } else { - None - } - }) + pub fn count_sync_map_for_device(&self) -> usize { + self.operations + .lock() + .unwrap() + .iter() + .filter(|op| matches!(op, DmaOperation::SyncMapForDevice { .. })) + .count() } - /// 获取最后一次 invalidate 操作的地址和大小 - pub fn last_invalidate(&self) -> Option<(usize, usize)> { - self.operations.lock().unwrap().iter().rev().find_map(|op| { - if let DmaOperation::Invalidate { addr, size } = op { - Some((*addr, *size)) - } else { - None - } - }) + pub fn count_sync_map_for_cpu(&self) -> usize { + self.operations + .lock() + .unwrap() + .iter() + .filter(|op| matches!(op, DmaOperation::SyncMapForCpu { .. })) + .count() + } + + fn alloc_dma_addr(&self, layout: core::alloc::Layout, constraints: DmaConstraints) -> u64 { + if let Some(addr) = self.forced_dma_addr.lock().unwrap().take() { + return addr; + } + + let mut next = self.next_dma_addr.lock().unwrap(); + let align = constraints.align.max(layout.align()).max(1) as u64; + *next = next.next_multiple_of(align); + let addr = *next; + *next = next + .saturating_add(layout.size().max(1) as u64) + .max(addr + 1); + addr + } + + unsafe fn alloc_handle( + &self, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option { + let ptr = unsafe { alloc_zeroed(layout) }; + let cpu_addr = NonNull::new(ptr)?; + let dma_addr = self.alloc_dma_addr(layout, constraints); + Some(unsafe { DmaAllocHandle::new(cpu_addr, dma_addr.into(), layout) }) } } -// 实现 DmaOp trait impl DmaOp for TrackingDmaOp { fn page_size(&self) -> usize { 0x1000 } - unsafe fn map_single( + unsafe fn alloc_contiguous( &self, - _dma_mask: u64, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option { + self.operations + .lock() + .unwrap() + .push(DmaOperation::AllocContiguous { + size: layout.size(), + align: layout.align(), + mask: constraints.addr_mask, + }); + unsafe { self.alloc_handle(constraints, layout) } + } + + unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle) { + self.operations + .lock() + .unwrap() + .push(DmaOperation::DeallocContiguous { + size: handle.size(), + }); + unsafe { dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; + } + + unsafe fn alloc_coherent( + &self, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option { + self.operations + .lock() + .unwrap() + .push(DmaOperation::AllocCoherent { + size: layout.size(), + align: layout.align(), + mask: constraints.addr_mask, + }); + unsafe { self.alloc_handle(constraints, layout) } + } + + unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) { + self.operations + .lock() + .unwrap() + .push(DmaOperation::DeallocCoherent { + size: handle.size(), + }); + unsafe { dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; + } + + unsafe fn map_streaming( + &self, + constraints: DmaConstraints, addr: NonNull, size: NonZeroUsize, - _align: usize, direction: DmaDirection, ) -> Result { + let align = constraints.align.max(1); + let layout = core::alloc::Layout::from_size_align(size.get(), align)?; self.operations .lock() .unwrap() - .push(DmaOperation::MapSingle { + .push(DmaOperation::MapStreaming { virt_addr: addr.as_ptr() as usize, size: size.get(), + align, direction, + mask: constraints.addr_mask, }); - let layout = core::alloc::Layout::from_size_align(size.get(), 8)?; - Ok(unsafe { DmaMapHandle::new(addr, (addr.as_ptr() as u64).into(), layout, None) }) + let dma_addr = self.alloc_dma_addr(layout, constraints); + let bounce_ptr = if dma_addr != addr.as_ptr() as u64 { + let ptr = unsafe { alloc_zeroed(layout) }; + let ptr = NonNull::new(ptr).ok_or(DmaError::NoMemory)?; + self.map_allocations + .lock() + .unwrap() + .insert(ptr.as_ptr() as usize, layout); + Some(ptr) + } else { + None + }; + + Ok(unsafe { DmaMapHandle::new(addr, dma_addr.into(), layout, bounce_ptr) }) } - unsafe fn unmap_single(&self, handle: DmaMapHandle) { + unsafe fn unmap_streaming(&self, handle: DmaMapHandle) { self.operations .lock() .unwrap() - .push(DmaOperation::UnmapSingle { + .push(DmaOperation::UnmapStreaming { size: handle.size(), }); + if let Some(ptr) = handle.bounce_ptr() + && let Some(layout) = self + .map_allocations + .lock() + .unwrap() + .remove(&(ptr.as_ptr() as usize)) + { + unsafe { dealloc(ptr.as_ptr(), layout) }; + } } - fn flush(&self, addr: NonNull, size: usize) { - self.operations.lock().unwrap().push(DmaOperation::Flush { - addr: addr.as_ptr() as usize, - size, - }); + fn sync_alloc_for_device( + &self, + handle: &DmaAllocHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + self.operations + .lock() + .unwrap() + .push(DmaOperation::SyncAllocForDevice { + addr: unsafe { handle.as_ptr().add(offset).as_ptr() as usize }, + size, + direction, + }); } - fn invalidate(&self, addr: NonNull, size: usize) { + fn sync_alloc_for_cpu( + &self, + handle: &DmaAllocHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { self.operations .lock() .unwrap() - .push(DmaOperation::Invalidate { - addr: addr.as_ptr() as usize, + .push(DmaOperation::SyncAllocForCpu { + addr: unsafe { handle.as_ptr().add(offset).as_ptr() as usize }, size, + direction, }); } - unsafe fn alloc_coherent( + fn sync_map_for_device( &self, - _dma_mask: u64, - layout: core::alloc::Layout, - ) -> Option { + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { self.operations .lock() .unwrap() - .push(DmaOperation::AllocCoherent { - size: layout.size(), - align: layout.align(), + .push(DmaOperation::SyncMapForDevice { + addr: unsafe { handle.as_ptr().add(offset).as_ptr() as usize }, + size, + direction, }); - - let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; - if ptr.is_null() { - return None; + if let Some(map_virt) = handle.bounce_ptr() { + unsafe { + map_virt + .add(offset) + .as_ptr() + .copy_from_nonoverlapping(handle.as_ptr().add(offset).as_ptr(), size); + } } - Some(unsafe { DmaHandle::new(NonNull::new(ptr).unwrap(), (ptr as u64).into(), layout) }) } - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { + fn sync_map_for_cpu( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { self.operations .lock() .unwrap() - .push(DmaOperation::DeallocCoherent { - size: handle.size(), + .push(DmaOperation::SyncMapForCpu { + addr: unsafe { handle.as_ptr().add(offset).as_ptr() as usize }, + size, + direction, }); - unsafe { std::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; + if let Some(map_virt) = handle.bounce_ptr() { + unsafe { + handle + .as_ptr() + .add(offset) + .as_ptr() + .copy_from_nonoverlapping(map_virt.add(offset).as_ptr(), size); + } + } } } diff --git a/drivers/ax-driver/src/block/phytium_mci.rs b/drivers/ax-driver/src/block/phytium_mci.rs index 5dfcb2db07..3856e87193 100644 --- a/drivers/ax-driver/src/block/phytium_mci.rs +++ b/drivers/ax-driver/src/block/phytium_mci.rs @@ -188,7 +188,7 @@ impl rd_block::Interface for MciBlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: axklib::dma::device(u32::MAX as u64), + dma: axklib::dma::device_with_mask(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), diff --git a/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs b/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs index b09fc93e32..6eb78bf20d 100644 --- a/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs +++ b/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs @@ -141,7 +141,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError init_dwcmshc_after_reset(mmio_base); host.set_power(SDHCI_POWER_330); host.enable_interrupts(); - host.set_dma(axklib::dma::device(u32::MAX as u64)); + host.set_dma(axklib::dma::device_with_mask(u32::MAX as u64)); info!("rockchip-rk3568-sdhci: initialize card"); let mut card = SdioSdmmc::new(host); @@ -399,7 +399,7 @@ impl rd_block::Interface for BlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: axklib::dma::device(u32::MAX as u64), + dma: axklib::dma::device_with_mask(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), diff --git a/drivers/ax-driver/src/block/rockchip_mmc.rs b/drivers/ax-driver/src/block/rockchip_mmc.rs index 2cfb6b6749..af00252e3d 100644 --- a/drivers/ax-driver/src/block/rockchip_mmc.rs +++ b/drivers/ax-driver/src/block/rockchip_mmc.rs @@ -93,7 +93,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError .map_err(|e| init_error(base_reg.address, mmio_size, e))?; host.set_power(SDHCI_POWER_330); host.enable_interrupts(); - host.set_dma(axklib::dma::device(u32::MAX as u64)); + host.set_dma(axklib::dma::device_with_mask(u32::MAX as u64)); info!("rockchip-sdhci: initialize card"); let mut card = SdioSdmmc::new(host); @@ -257,7 +257,7 @@ impl rd_block::Interface for BlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: axklib::dma::device(u32::MAX as u64), + dma: axklib::dma::device_with_mask(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), diff --git a/drivers/ax-driver/src/block/rockchip_sd.rs b/drivers/ax-driver/src/block/rockchip_sd.rs index 807f548cf4..e396654c62 100644 --- a/drivers/ax-driver/src/block/rockchip_sd.rs +++ b/drivers/ax-driver/src/block/rockchip_sd.rs @@ -104,7 +104,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError info!("rockchip-dwmmc: reset controller"); host.reset_and_init() .map_err(|e| init_error(base_reg.address, mmio_size, e))?; - host.set_dma(axklib::dma::device(u32::MAX as u64)); + host.set_dma(axklib::dma::device_with_mask(u32::MAX as u64)); info!("rockchip-dwmmc: initialize card"); let mut sd = SdioSdmmc::new(host); diff --git a/drivers/ax-driver/src/block/rockchip_sd/block.rs b/drivers/ax-driver/src/block/rockchip_sd/block.rs index 8c09af9e0e..a78a59fdc2 100644 --- a/drivers/ax-driver/src/block/rockchip_sd/block.rs +++ b/drivers/ax-driver/src/block/rockchip_sd/block.rs @@ -44,7 +44,7 @@ impl rd_block::Interface for SdBlockDevice { raw: dev.clone(), capacity_blocks: self.capacity_blocks, id: 0, - dma: axklib::dma::device(u32::MAX as u64), + dma: axklib::dma::device_with_mask(u32::MAX as u64), slot: BlockRequestSlot::default(), pending: None, completed: Vec::new(), diff --git a/drivers/ax-driver/src/net/fxmac.rs b/drivers/ax-driver/src/net/fxmac.rs index ba1631d908..b5e2ea18a6 100644 --- a/drivers/ax-driver/src/net/fxmac.rs +++ b/drivers/ax-driver/src/net/fxmac.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, collections::VecDeque, sync::Arc, vec, vec::Vec}; use core::{alloc::Layout, cmp, ptr::NonNull}; -use dma_api::{DmaAddr, DmaHandle, DmaOp}; +use dma_api::{DmaAddr, DmaAllocHandle, DmaConstraints, DmaOp}; use fxmac_rs::{FXmac, FXmacGetMacAddress, FXmacLwipPortTx, FXmacRecvHandler, xmac_init}; use rd_net::{DmaBuffer, Event, IRxQueue, ITxQueue, NetError, QueueConfig}; use rdrive::{DriverGeneric, PlatformDevice}; @@ -234,7 +234,9 @@ impl fxmac_rs::KernelFunc for FxmacKernelFunc { log::error!("FXmac DMA allocation layout is invalid: {size} bytes"); return (0, 0); }; - let Some(handle) = (unsafe { axklib::dma::op().alloc_coherent(DMA_MASK, layout) }) else { + let Some(handle) = + (unsafe { axklib::dma::op().alloc_coherent(DmaConstraints::new(DMA_MASK), layout) }) + else { log::error!("FXmac DMA allocation failed: {pages} pages"); return (0, 0); }; @@ -257,7 +259,7 @@ impl fxmac_rs::KernelFunc for FxmacKernelFunc { return; }; let paddr = axklib::mem::virt_to_phys((vaddr.as_ptr() as usize).into()).as_usize(); - let handle = unsafe { DmaHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) }; + let handle = unsafe { DmaAllocHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) }; unsafe { axklib::dma::op().dealloc_coherent(handle) }; } diff --git a/drivers/ax-driver/src/net/ixgbe.rs b/drivers/ax-driver/src/net/ixgbe.rs index acdadad041..21dea1d6f2 100644 --- a/drivers/ax-driver/src/net/ixgbe.rs +++ b/drivers/ax-driver/src/net/ixgbe.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, collections::VecDeque, format, sync::Arc}; use core::{alloc::Layout, cmp, ptr::NonNull, time::Duration}; -use dma_api::{DmaAddr, DmaHandle, DmaOp}; +use dma_api::{DmaAddr, DmaAllocHandle, DmaConstraints, DmaOp}; use ixgbe_driver::{ INTEL_82599, INTEL_VEND, IxgbeDevice, IxgbeError, IxgbeHal, IxgbeNetBuf, MemPool, NicDevice, PhysAddr, @@ -279,8 +279,9 @@ unsafe impl IxgbeHal for IxgbeOsHal { fn dma_alloc(size: usize) -> (PhysAddr, NonNull) { let layout = Layout::from_size_align(size.max(1), 0x1000).expect("ixgbe DMA layout should be valid"); - let handle = unsafe { axklib::dma::op().alloc_coherent(DMA_MASK, layout) } - .expect("ixgbe DMA allocation failed"); + let handle = + unsafe { axklib::dma::op().alloc_coherent(DmaConstraints::new(DMA_MASK), layout) } + .expect("ixgbe DMA allocation failed"); let paddr = handle.dma_addr().as_u64() as usize; let vaddr = handle.as_ptr(); (paddr, vaddr) @@ -290,7 +291,7 @@ unsafe impl IxgbeHal for IxgbeOsHal { let Ok(layout) = Layout::from_size_align(size.max(1), 0x1000) else { return -1; }; - let handle = unsafe { DmaHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) }; + let handle = unsafe { DmaAllocHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) }; unsafe { axklib::dma::op().dealloc_coherent(handle) }; 0 } diff --git a/drivers/ax-driver/src/rknpu.rs b/drivers/ax-driver/src/rknpu.rs index 5386b2d698..a09f202701 100644 --- a/drivers/ax-driver/src/rknpu.rs +++ b/drivers/ax-driver/src/rknpu.rs @@ -55,7 +55,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError info!("NPU power enabled"); - let dma = axklib::dma::device(u32::MAX as u64); + let dma = axklib::dma::device_with_mask(u32::MAX as u64); let npu = Rknpu::new(&base_regs, config, dma); plat_dev.register(npu); info!("NPU registered successfully"); diff --git a/drivers/ax-driver/src/usb/mod.rs b/drivers/ax-driver/src/usb/mod.rs index 546ea6d224..1d76c99e08 100644 --- a/drivers/ax-driver/src/usb/mod.rs +++ b/drivers/ax-driver/src/usb/mod.rs @@ -6,7 +6,7 @@ use core::time::Duration; #[cfg(target_os = "none")] use crab_usb::USBHost; #[cfg(target_os = "none")] -use dma_api::{DmaDirection, DmaError, DmaHandle, DmaMapHandle, DmaOp}; +use dma_api::{DmaAllocHandle, DmaConstraints, DmaDirection, DmaError, DmaMapHandle, DmaOp}; use rdrive::DriverGeneric; #[cfg(all(feature = "rockchip-dwc-xhci", target_os = "none"))] @@ -28,19 +28,42 @@ impl DmaOp for UsbKernel { axklib::dma::op().page_size() } - unsafe fn map_single( + unsafe fn alloc_contiguous( &self, - dma_mask: u64, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option { + unsafe { axklib::dma::op().alloc_contiguous(constraints, layout) } + } + + unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle) { + unsafe { axklib::dma::op().dealloc_contiguous(handle) } + } + + unsafe fn alloc_coherent( + &self, + constraints: DmaConstraints, + layout: core::alloc::Layout, + ) -> Option { + unsafe { axklib::dma::op().alloc_coherent(constraints, layout) } + } + + unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) { + unsafe { axklib::dma::op().dealloc_coherent(handle) } + } + + unsafe fn map_streaming( + &self, + constraints: DmaConstraints, addr: core::ptr::NonNull, size: core::num::NonZeroUsize, - align: usize, direction: DmaDirection, ) -> Result { - unsafe { axklib::dma::op().map_single(dma_mask, addr, size, align, direction) } + unsafe { axklib::dma::op().map_streaming(constraints, addr, size, direction) } } - unsafe fn unmap_single(&self, handle: DmaMapHandle) { - unsafe { axklib::dma::op().unmap_single(handle) } + unsafe fn unmap_streaming(&self, handle: DmaMapHandle) { + unsafe { axklib::dma::op().unmap_streaming(handle) } } fn flush(&self, addr: core::ptr::NonNull, size: usize) { @@ -55,36 +78,44 @@ impl DmaOp for UsbKernel { axklib::dma::op().flush_invalidate(addr, size); } - fn prepare_read( + fn sync_alloc_for_device( &self, - handle: &DmaMapHandle, + handle: &DmaAllocHandle, offset: usize, size: usize, direction: DmaDirection, ) { - axklib::dma::op().prepare_read(handle, offset, size, direction); + axklib::dma::op().sync_alloc_for_device(handle, offset, size, direction); } - fn confirm_write( + fn sync_alloc_for_cpu( &self, - handle: &DmaMapHandle, + handle: &DmaAllocHandle, offset: usize, size: usize, direction: DmaDirection, ) { - axklib::dma::op().confirm_write(handle, offset, size, direction); + axklib::dma::op().sync_alloc_for_cpu(handle, offset, size, direction); } - unsafe fn alloc_coherent( + fn sync_map_for_device( &self, - dma_mask: u64, - layout: core::alloc::Layout, - ) -> Option { - unsafe { axklib::dma::op().alloc_coherent(dma_mask, layout) } + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + axklib::dma::op().sync_map_for_device(handle, offset, size, direction); } - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { - unsafe { axklib::dma::op().dealloc_coherent(handle) } + fn sync_map_for_cpu( + &self, + handle: &DmaMapHandle, + offset: usize, + size: usize, + direction: DmaDirection, + ) { + axklib::dma::op().sync_map_for_cpu(handle, offset, size, direction); } } diff --git a/drivers/blk/dwmmc-host/src/dma.rs b/drivers/blk/dwmmc-host/src/dma.rs index 395237d209..03e46fafe6 100644 --- a/drivers/blk/dwmmc-host/src/dma.rs +++ b/drivers/blk/dwmmc-host/src/dma.rs @@ -1,6 +1,6 @@ use core::{num::NonZeroUsize, ptr::NonNull}; -use dma_api::{DArray, DeviceDma, DmaDirection, SArrayPtr}; +use dma_api::{CoherentArray, DeviceDma, DmaDirection, StreamingMap}; use log::warn; use sdmmc_protocol::{ block::{ @@ -109,8 +109,8 @@ enum BlockRequestKind { }, Read { id: RequestId, - map: SArrayPtr, - _desc: DArray, + map: StreamingMap, + _desc: CoherentArray, cmd_index: u8, phase: Phase, stage: BlockRequestStage, @@ -119,8 +119,8 @@ enum BlockRequestKind { }, Write { id: RequestId, - _map: SArrayPtr, - _desc: DArray, + _map: StreamingMap, + _desc: CoherentArray, cmd_index: u8, phase: Phase, stage: BlockRequestStage, @@ -387,18 +387,14 @@ impl DwMmc { ) -> Result { let block_count = dma_read_block_count(size)?; let map = dma - .map_single_array( - unsafe { core::slice::from_raw_parts(buffer.as_ptr(), size.get()) }, + .map_streaming_slice( + unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) }, BLOCK_SIZE, DmaDirection::FromDevice, ) .map_err(|err| map_dma_error(err, Phase::DataRead))?; let mut desc = dma - .array_zero_with_align::( - block_count as usize, - IDMAC_DESC_ALIGN, - DmaDirection::ToDevice, - ) + .coherent_array_zero_with_align::(block_count as usize, IDMAC_DESC_ALIGN) .map_err(|err| map_dma_error(err, Phase::DataRead))?; let cmd = if block_count == 1 { cmd17(start_block) @@ -430,19 +426,15 @@ impl DwMmc { ) -> Result { let block_count = dma_write_block_count(size)?; let map = dma - .map_single_array( - unsafe { core::slice::from_raw_parts(buffer.as_ptr(), size.get()) }, + .map_streaming_slice( + unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) }, BLOCK_SIZE, DmaDirection::ToDevice, ) .map_err(|err| map_dma_error(err, Phase::DataWrite))?; - map.confirm_write_all(); + map.sync_for_device_all(); let mut desc = dma - .array_zero_with_align::( - block_count as usize, - IDMAC_DESC_ALIGN, - DmaDirection::ToDevice, - ) + .coherent_array_zero_with_align::(block_count as usize, IDMAC_DESC_ALIGN) .map_err(|err| map_dma_error(err, Phase::DataWrite))?; let cmd = if block_count == 1 { cmd24(start_block) @@ -622,7 +614,7 @@ impl DwMmc { cmd: &Command, block_count: u32, buffer_dma: u64, - desc: &mut DArray, + desc: &mut CoherentArray, ) -> Result<(), Error> { if block_count == 0 { return Err(Error::InvalidArgument); @@ -751,7 +743,7 @@ impl DwMmc { stop_after_complete, .. } => { - map.prepare_read_all(); + map.sync_for_cpu_all(); *stage = BlockRequestStage::Stop; *stop_after_complete } @@ -1149,6 +1141,8 @@ fn map_dma_error(err: dma_api::DmaError, phase: Phase) -> Error { dma_api::DmaError::LayoutError(_) | dma_api::DmaError::DmaMaskNotMatch { .. } | dma_api::DmaError::AlignMismatch { .. } + | dma_api::DmaError::SegmentTooLarge { .. } + | dma_api::DmaError::BoundaryCross { .. } | dma_api::DmaError::NullPointer | dma_api::DmaError::ZeroSizedBuffer => Error::InvalidArgument, } diff --git a/drivers/blk/nvme-driver/src/nvme.rs b/drivers/blk/nvme-driver/src/nvme.rs index ab3c16c371..2167979dce 100644 --- a/drivers/blk/nvme-driver/src/nvme.rs +++ b/drivers/blk/nvme-driver/src/nvme.rs @@ -228,12 +228,15 @@ impl Nvme { cmd.cdw0 = CommandSet::cdw0_from_opcode(command::Opcode::IDENTIFY); cmd.cdw10 = T::CNS; - let buff = - self.dma - .array_zero_with_align::(0x1000, 0x1000, DmaDirection::FromDevice)?; + let buff = self.dma.contiguous_array_zero_with_align::( + 0x1000, + 0x1000, + DmaDirection::FromDevice, + )?; cmd.prp1 = buff.dma_addr().as_u64(); self.admin_queue.command_sync(*cmd)?; + buff.sync_for_cpu_all(); let data: Vec = buff.iter().collect(); let res = want.parse(&data); @@ -251,12 +254,13 @@ impl Nvme { "buffer size must be multiple of lba size" ); - let mut dma_buff = self.dma.array_zero_with_align::( + let mut dma_buff = self.dma.contiguous_array_zero_with_align::( buff.len(), ns.lba_size, DmaDirection::ToDevice, )?; dma_buff.copy_from_slice(buff); + dma_buff.sync_for_device_all(); let blk_num = dma_buff.len() / ns.lba_size; @@ -283,7 +287,7 @@ impl Nvme { "buffer size must be multiple of lba size" ); - let dma_buff = self.dma.array_zero_with_align::( + let dma_buff = self.dma.contiguous_array_zero_with_align::( buff.len(), ns.lba_size, DmaDirection::FromDevice, @@ -299,6 +303,7 @@ impl Nvme { ); self.io_queues[0].command_sync(cmd)?; + dma_buff.sync_for_cpu_all(); for (index, value) in dma_buff.iter().enumerate() { buff[index] = value; diff --git a/drivers/blk/nvme-driver/src/queue.rs b/drivers/blk/nvme-driver/src/queue.rs index a36928512e..00d0c76d16 100644 --- a/drivers/blk/nvme-driver/src/queue.rs +++ b/drivers/blk/nvme-driver/src/queue.rs @@ -5,7 +5,7 @@ use core::{ sync::atomic::{AtomicU32, Ordering}, }; -use dma_api::{DArray, DeviceDma, DmaDirection}; +use dma_api::{CoherentArray, DeviceDma}; use log::debug; use tock_registers::register_bitfields; @@ -49,6 +49,7 @@ register_bitfields! [ ]; #[repr(transparent)] +#[derive(Clone, Copy)] pub struct NvmeSubmission([u8; 64]); pub trait Submission { @@ -270,13 +271,13 @@ impl NvmeQueue { } pub struct SubmitQueue { - queue: DArray, + queue: CoherentArray, tail: u32, } impl SubmitQueue { fn new(dma: &DeviceDma, queue_size: usize, page_size: usize) -> Result { - let queue = dma.array_zero_with_align(queue_size, page_size, DmaDirection::ToDevice)?; + let queue = dma.coherent_array_zero_with_align(queue_size, page_size)?; Ok(SubmitQueue { queue, tail: 0 }) } @@ -301,14 +302,14 @@ impl SubmitQueue { } pub struct CompleteQueue { - queue: DArray, + queue: CoherentArray, head: u32, phase: bool, } impl CompleteQueue { fn new(dma: &DeviceDma, queue_size: usize, page_size: usize) -> Result { - let queue = dma.array_zero_with_align(queue_size, page_size, DmaDirection::FromDevice)?; + let queue = dma.coherent_array_zero_with_align(queue_size, page_size)?; Ok(CompleteQueue { queue, head: 0, diff --git a/drivers/blk/phytium-mci-host/src/dma.rs b/drivers/blk/phytium-mci-host/src/dma.rs index e0ca2fca8b..49518d4022 100644 --- a/drivers/blk/phytium-mci-host/src/dma.rs +++ b/drivers/blk/phytium-mci-host/src/dma.rs @@ -1,6 +1,6 @@ use core::{num::NonZeroUsize, ptr::NonNull}; -use dma_api::{DArray, DeviceDma, DmaDirection, SArrayPtr}; +use dma_api::{CoherentArray, DeviceDma, DmaDirection, StreamingMap}; use log::warn; use sdmmc_protocol::{ block::{ @@ -49,8 +49,8 @@ struct IdmacDesc { } struct DmaProgress { - descriptors: DArray, - buffer: SArrayPtr, + descriptors: CoherentArray, + buffer: StreamingMap, desc_count: usize, complete: bool, idmac_done: bool, @@ -438,20 +438,16 @@ impl PhytiumMci { DataDirection::None => return Err(Error::InvalidArgument), _ => return Err(Error::InvalidArgument), }; - let slice = unsafe { core::slice::from_raw_parts(buffer.as_ptr(), len) }; + let slice = unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), len) }; let mapped = dma - .map_single_array(slice, BLOCK_SIZE, dma_direction) + .map_streaming_slice(slice, BLOCK_SIZE, dma_direction) .map_err(|_| Error::Misaligned)?; if matches!(direction, DataDirection::Write) { - mapped.confirm_write_all(); + mapped.sync_for_device_all(); } let desc_count = len.div_ceil(IDMAC_MAX_BUF_SIZE); let mut descriptors = dma - .array_zero_with_align::( - desc_count, - IDMAC_DESC_ALIGN, - DmaDirection::ToDevice, - ) + .coherent_array_zero_with_align::(desc_count, IDMAC_DESC_ALIGN) .map_err(|_| Error::Misaligned)?; let desc_dma = descriptors.dma_addr().as_u64(); let desc_values = build_idmac_descriptors( @@ -770,7 +766,7 @@ impl PhytiumMci { } if is_read { - progress.buffer.prepare_read_all(); + progress.buffer.sync_for_cpu_all(); } progress.complete = true; Ok(BlockPoll::Complete) @@ -1239,11 +1235,11 @@ mod tests { fn progress() -> DmaProgress { let dma = DeviceDma::new(u64::MAX, &TEST_DMA); let descriptors = dma - .array_zero_with_align::(1, IDMAC_DESC_ALIGN, DmaDirection::ToDevice) + .coherent_array_zero_with_align::(1, IDMAC_DESC_ALIGN) .unwrap(); let backing = Box::leak(Box::new(AlignedBlock([0u8; BLOCK_SIZE]))); let buffer = dma - .map_single_array(&backing.0, BLOCK_SIZE, DmaDirection::FromDevice) + .map_streaming_slice(&mut backing.0, BLOCK_SIZE, DmaDirection::FromDevice) .unwrap(); DmaProgress { descriptors, @@ -1260,35 +1256,49 @@ mod tests { static TEST_DMA: TestDma = TestDma; impl dma_api::DmaOp for TestDma { + unsafe fn alloc_contiguous( + &self, + _constraints: dma_api::DmaConstraints, + layout: core::alloc::Layout, + ) -> Option { + let ptr = unsafe { alloc::alloc_zeroed(layout) }; + let ptr = NonNull::new(ptr)?; + Some(unsafe { dma_api::DmaAllocHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) + } + + unsafe fn dealloc_contiguous(&self, handle: dma_api::DmaAllocHandle) { + unsafe { alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; + } + unsafe fn alloc_coherent( &self, - _dma_mask: u64, + _constraints: dma_api::DmaConstraints, layout: core::alloc::Layout, - ) -> Option { + ) -> Option { let ptr = unsafe { alloc::alloc_zeroed(layout) }; let ptr = NonNull::new(ptr)?; - Some(unsafe { dma_api::DmaHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) + Some(unsafe { dma_api::DmaAllocHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) } - unsafe fn dealloc_coherent(&self, handle: dma_api::DmaHandle) { + unsafe fn dealloc_coherent(&self, handle: dma_api::DmaAllocHandle) { unsafe { alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; } - unsafe fn map_single( + unsafe fn map_streaming( &self, - _dma_mask: u64, + constraints: dma_api::DmaConstraints, addr: NonNull, size: NonZeroUsize, - align: usize, _direction: DmaDirection, ) -> Result { - let layout = core::alloc::Layout::from_size_align(size.get(), align).unwrap(); + let layout = + core::alloc::Layout::from_size_align(size.get(), constraints.align.max(1))?; Ok(unsafe { dma_api::DmaMapHandle::new(addr, (addr.as_ptr() as u64).into(), layout, None) }) } - unsafe fn unmap_single(&self, _handle: dma_api::DmaMapHandle) {} + unsafe fn unmap_streaming(&self, _handle: dma_api::DmaMapHandle) {} fn flush(&self, _addr: NonNull, _size: usize) {} fn invalidate(&self, _addr: NonNull, _size: usize) {} diff --git a/drivers/blk/ramdisk/examples/ramdisk.rs b/drivers/blk/ramdisk/examples/ramdisk.rs index c3e068343e..a452c38729 100644 --- a/drivers/blk/ramdisk/examples/ramdisk.rs +++ b/drivers/blk/ramdisk/examples/ramdisk.rs @@ -15,31 +15,49 @@ impl dma_api::DmaOp for ExampleDmaOp { 4096 } - unsafe fn map_single( + unsafe fn alloc_contiguous( &self, - _dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - align: usize, - _direction: dma_api::DmaDirection, - ) -> Result { - let layout = Layout::from_size_align(size.get(), align.max(1))?; - let dma_addr = (addr.as_ptr() as usize as u64).into(); - Ok(unsafe { dma_api::DmaMapHandle::new(addr, dma_addr, layout, None) }) + _constraints: dma_api::DmaConstraints, + layout: Layout, + ) -> Option { + let ptr = unsafe { alloc_zeroed(layout) }; + let ptr = NonNull::new(ptr)?; + let dma_addr = (ptr.as_ptr() as usize as u64).into(); + Some(unsafe { dma_api::DmaAllocHandle::new(ptr, dma_addr, layout) }) } - unsafe fn unmap_single(&self, _handle: dma_api::DmaMapHandle) {} + unsafe fn dealloc_contiguous(&self, handle: dma_api::DmaAllocHandle) { + unsafe { dealloc(handle.as_ptr().as_ptr(), handle.layout()) } + } - unsafe fn alloc_coherent(&self, _dma_mask: u64, layout: Layout) -> Option { + unsafe fn alloc_coherent( + &self, + _constraints: dma_api::DmaConstraints, + layout: Layout, + ) -> Option { let ptr = unsafe { alloc_zeroed(layout) }; let ptr = NonNull::new(ptr)?; let dma_addr = (ptr.as_ptr() as usize as u64).into(); - Some(unsafe { dma_api::DmaHandle::new(ptr, dma_addr, layout) }) + Some(unsafe { dma_api::DmaAllocHandle::new(ptr, dma_addr, layout) }) } - unsafe fn dealloc_coherent(&self, handle: dma_api::DmaHandle) { + unsafe fn dealloc_coherent(&self, handle: dma_api::DmaAllocHandle) { unsafe { dealloc(handle.as_ptr().as_ptr(), handle.layout()) } } + + unsafe fn map_streaming( + &self, + constraints: dma_api::DmaConstraints, + addr: NonNull, + size: NonZeroUsize, + _direction: dma_api::DmaDirection, + ) -> Result { + let layout = Layout::from_size_align(size.get(), constraints.align.max(1))?; + let dma_addr = (addr.as_ptr() as usize as u64).into(); + Ok(unsafe { dma_api::DmaMapHandle::new(addr, dma_addr, layout, None) }) + } + + unsafe fn unmap_streaming(&self, _handle: dma_api::DmaMapHandle) {} } static DMA_OP: ExampleDmaOp = ExampleDmaOp; diff --git a/drivers/blk/rd-block/src/lib.rs b/drivers/blk/rd-block/src/lib.rs index fec815d18c..a1a3601c21 100644 --- a/drivers/blk/rd-block/src/lib.rs +++ b/drivers/blk/rd-block/src/lib.rs @@ -19,7 +19,7 @@ use core::{ task::Poll, }; -use dma_api::{DArrayPool, DBuff, DeviceDma, DmaDirection, DmaOp}; +use dma_api::{ContiguousBuffer, ContiguousBufferPool, DeviceDma, DmaDirection, DmaOp}; use futures::task::AtomicWaker; pub use rdif_block::*; @@ -129,7 +129,7 @@ impl Block { } let layout = Layout::from_size_align(config.size, config.align).ok()?; let dma = DeviceDma::new(config.dma_mask, self.inner.dma_op); - let pool = dma.new_pool(layout, DmaDirection::FromDevice, capacity); + let pool = dma.contiguous_buffer_pool(layout, DmaDirection::FromDevice, capacity); let waker = self.inner.queue_waker_map.register(queue_id); drop(irq_guard); @@ -166,7 +166,7 @@ impl IrqHandler { pub struct CmdQueue { interface: Box, waker: Arc, - pool: DArrayPool, + pool: ContiguousBufferPool, buffer_size: usize, } @@ -174,7 +174,7 @@ impl CmdQueue { fn new( interface: Box, waker: Arc, - pool: DArrayPool, + pool: ContiguousBufferPool, buffer_size: usize, ) -> Self { Self { @@ -250,14 +250,14 @@ impl CmdQueue { pub struct BlockData { block_id: usize, - data: DBuff, + data: ContiguousBuffer, len: usize, } pub struct ReadFuture<'a> { queue: &'a mut CmdQueue, req_ls: Vec<(usize, usize)>, - requested: BTreeMap>, + requested: BTreeMap>, map: BTreeMap, results: BTreeMap>, } @@ -305,6 +305,7 @@ impl<'a> core::future::Future for ReadFuture<'a> { kind, }) { Ok(req_id) => { + buff.sync_for_device_all(); this.map.insert(blk_id, req_id); this.requested.insert(blk_id, Some((buff, len))); } @@ -335,6 +336,7 @@ impl<'a> core::future::Future for ReadFuture<'a> { let (data, len) = buff .take() .expect("DMA read buffer should exist until completion"); + data.sync_for_cpu_all(); this.results.insert( *blk_id, Ok(BlockData { @@ -506,7 +508,7 @@ fn block_ranges( mod tests { use core::{alloc::Layout, num::NonZeroUsize, ptr::NonNull}; - use dma_api::{DmaError, DmaHandle, DmaMapHandle}; + use dma_api::{DmaAllocHandle, DmaConstraints, DmaError, DmaMapHandle}; use super::*; @@ -519,29 +521,46 @@ mod tests { 4096 } - unsafe fn map_single( + unsafe fn alloc_contiguous( &self, - _dma_mask: u64, - addr: NonNull, - size: NonZeroUsize, - _align: usize, - _direction: DmaDirection, - ) -> Result { - let layout = Layout::from_size_align(size.get(), 8)?; - Ok(unsafe { DmaMapHandle::new(addr, (addr.as_ptr() as u64).into(), layout, None) }) + _constraints: DmaConstraints, + layout: Layout, + ) -> Option { + let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + let ptr = NonNull::new(ptr)?; + Some(unsafe { DmaAllocHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) } - unsafe fn unmap_single(&self, _handle: DmaMapHandle) {} + unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle) { + unsafe { std::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; + } - unsafe fn alloc_coherent(&self, _dma_mask: u64, layout: Layout) -> Option { + unsafe fn alloc_coherent( + &self, + _constraints: DmaConstraints, + layout: Layout, + ) -> Option { let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; let ptr = NonNull::new(ptr)?; - Some(unsafe { DmaHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) + Some(unsafe { DmaAllocHandle::new(ptr, (ptr.as_ptr() as u64).into(), layout) }) } - unsafe fn dealloc_coherent(&self, handle: DmaHandle) { + unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) { unsafe { std::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) }; } + + unsafe fn map_streaming( + &self, + constraints: DmaConstraints, + addr: NonNull, + size: NonZeroUsize, + _direction: DmaDirection, + ) -> Result { + let layout = Layout::from_size_align(size.get(), constraints.align.max(1))?; + Ok(unsafe { DmaMapHandle::new(addr, (addr.as_ptr() as u64).into(), layout, None) }) + } + + unsafe fn unmap_streaming(&self, _handle: DmaMapHandle) {} } struct TestBlock { diff --git a/drivers/blk/sdhci-host/src/dma.rs b/drivers/blk/sdhci-host/src/dma.rs index 709e13270b..a461398378 100644 --- a/drivers/blk/sdhci-host/src/dma.rs +++ b/drivers/blk/sdhci-host/src/dma.rs @@ -14,14 +14,13 @@ //! needed before the device reads CPU-written memory and after the //! device writes CPU-read memory. //! -//! That split keeps the SDHCI logic portable across hosted Linux (where -//! `DeviceDma` typically calls `dma_map_single`), bare-metal coherent -//! systems (identity mapping, no cache ops), and bare-metal incoherent -//! systems (identity mapping + dcache flush/invalidate). +//! That split keeps the SDHCI logic portable across hosted kernels, +//! bare-metal coherent systems (identity mapping, no cache ops), and +//! bare-metal incoherent systems (identity mapping + dcache flush/invalidate). use core::{num::NonZeroUsize, ptr::NonNull}; -use dma_api::{DArray, DeviceDma, DmaDirection, SArrayPtr}; +use dma_api::{CoherentArray, DeviceDma, DmaDirection, StreamingMap}; use sdmmc_protocol::{ block::{ BlockPoll, BlockRequestId, BlockTransferDirection, BlockTransferMode, BlockTransferState, @@ -122,8 +121,8 @@ enum BlockRequestKind { }, Read { id: RequestId, - map: SArrayPtr, - _desc: DArray, + map: StreamingMap, + _desc: CoherentArray, cmd_index: u8, phase: Phase, stage: BlockRequestStage, @@ -132,8 +131,8 @@ enum BlockRequestKind { }, Write { id: RequestId, - _map: SArrayPtr, - _desc: DArray, + _map: StreamingMap, + _desc: CoherentArray, cmd_index: u8, phase: Phase, stage: BlockRequestStage, @@ -457,18 +456,14 @@ impl Sdhci { } let block_count = dma_read_block_count(size)?; let map = dma - .map_single_array( - unsafe { core::slice::from_raw_parts(buffer.as_ptr(), size.get()) }, + .map_streaming_slice( + unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) }, BLOCK_SIZE, DmaDirection::FromDevice, ) .map_err(map_dma_error)?; let mut desc = dma - .array_zero_with_align::( - ADMA2_DESC_COUNT, - ADMA2_DESC_ALIGN, - DmaDirection::ToDevice, - ) + .coherent_array_zero_with_align::(ADMA2_DESC_COUNT, ADMA2_DESC_ALIGN) .map_err(map_dma_error)?; let cmd = if block_count == 1 { cmd17(start_block) @@ -510,19 +505,15 @@ impl Sdhci { } let block_count = dma_write_block_count(size)?; let map = dma - .map_single_array( - unsafe { core::slice::from_raw_parts(buffer.as_ptr(), size.get()) }, + .map_streaming_slice( + unsafe { core::slice::from_raw_parts_mut(buffer.as_ptr(), size.get()) }, BLOCK_SIZE, DmaDirection::ToDevice, ) .map_err(map_dma_error)?; - map.confirm_write_all(); + map.sync_for_device_all(); let mut desc = dma - .array_zero_with_align::( - ADMA2_DESC_COUNT, - ADMA2_DESC_ALIGN, - DmaDirection::ToDevice, - ) + .coherent_array_zero_with_align::(ADMA2_DESC_COUNT, ADMA2_DESC_ALIGN) .map_err(map_dma_error)?; let cmd = if block_count == 1 { cmd24(start_block) @@ -709,7 +700,7 @@ impl Sdhci { cmd: &Command, block_count: u32, buffer_dma: u64, - desc: &mut DArray, + desc: &mut CoherentArray, direction: DataDirection, phase: Phase, ) -> Result<(), Error> { @@ -778,7 +769,7 @@ impl Sdhci { stage, .. } => { - map.prepare_read_all(); + map.sync_for_cpu_all(); *stage = BlockRequestStage::Stop; *stop_after_complete } @@ -1022,7 +1013,7 @@ impl Sdhci { } fn build_descriptors_into_dma( - desc: &mut dma_api::DArray, + desc: &mut CoherentArray, base: u64, total_len: usize, phase: Phase, @@ -1167,6 +1158,8 @@ fn map_dma_error(err: dma_api::DmaError) -> Error { dma_api::DmaError::LayoutError(_) | dma_api::DmaError::DmaMaskNotMatch { .. } | dma_api::DmaError::AlignMismatch { .. } + | dma_api::DmaError::SegmentTooLarge { .. } + | dma_api::DmaError::BoundaryCross { .. } | dma_api::DmaError::NullPointer | dma_api::DmaError::ZeroSizedBuffer => Error::InvalidArgument, } diff --git a/drivers/net/eth-intel/src/e1000/mod.rs b/drivers/net/eth-intel/src/e1000/mod.rs index 8bfb969971..4a1e654dea 100644 --- a/drivers/net/eth-intel/src/e1000/mod.rs +++ b/drivers/net/eth-intel/src/e1000/mod.rs @@ -3,7 +3,7 @@ extern crate alloc; use alloc::boxed::Box; use core::mem::size_of; -use dma_api::{DArray, DeviceDma, DmaDirection, DmaOp}; +use dma_api::{CoherentArray, DeviceDma, DmaOp}; use mmio_api::{Mmio, MmioAddr, MmioOp}; use rdif_eth::{DmaBuffer, Event, IRxQueue, ITxQueue, Interface, NetError, QueueConfig}; @@ -84,7 +84,7 @@ impl Interface for E1000 { let desc = self .dma - .array_zero_with_align::(QUEUE_SIZE, 16, DmaDirection::Bidirectional) + .coherent_array_zero_with_align::(QUEUE_SIZE, 16) .ok()?; let desc_base = desc.dma_addr().as_u64(); @@ -121,7 +121,7 @@ impl Interface for E1000 { let desc = self .dma - .array_zero_with_align::(QUEUE_SIZE, 16, DmaDirection::Bidirectional) + .coherent_array_zero_with_align::(QUEUE_SIZE, 16) .ok()?; let desc_base = desc.dma_addr().as_u64(); @@ -180,7 +180,7 @@ impl Interface for E1000 { struct E1000TxQueue { regs: Regs, - desc: DArray, + desc: CoherentArray, dma_mask: u64, bus_addrs: [Option; QUEUE_SIZE], next_submit: usize, @@ -239,7 +239,7 @@ impl ITxQueue for E1000TxQueue { struct E1000RxQueue { regs: Regs, - desc: DArray, + desc: CoherentArray, dma_mask: u64, bus_addrs: [Option; QUEUE_SIZE], next_submit: usize, diff --git a/drivers/net/rd-net/src/lib.rs b/drivers/net/rd-net/src/lib.rs index 4577aff083..2000e325d3 100644 --- a/drivers/net/rd-net/src/lib.rs +++ b/drivers/net/rd-net/src/lib.rs @@ -5,7 +5,7 @@ extern crate alloc; use alloc::{boxed::Box, collections::BTreeMap, sync::Arc}; use core::{alloc::Layout, cell::UnsafeCell}; -use dma_api::{DArrayPool, DBuff, DeviceDma, DmaDirection, DmaOp}; +use dma_api::{ContiguousBuffer, ContiguousBufferPool, DeviceDma, DmaDirection, DmaOp}; use futures::task::AtomicWaker; pub use rdif_eth::*; @@ -159,11 +159,11 @@ fn make_pool( dma_op: &'static dyn DmaOp, config: QueueConfig, direction: DmaDirection, -) -> Result { +) -> Result { let layout = Layout::from_size_align(config.buf_size, config.align.max(1)) .map_err(|_| other_error("invalid queue layout"))?; let dma = DeviceDma::new(config.dma_mask, dma_op); - Ok(dma.new_pool(layout, direction, config.ring_size)) + Ok(dma.contiguous_buffer_pool(layout, direction, config.ring_size)) } pub struct IrqHandler { @@ -187,8 +187,8 @@ impl IrqHandler { pub struct TxQueue { interface: Box, - pool: DArrayPool, - inflight: BTreeMap, + pool: ContiguousBufferPool, + inflight: BTreeMap, config: QueueConfig, _waker: Arc, } @@ -251,7 +251,7 @@ pub struct TxPending<'a> { queue: &'a mut TxQueue, len: usize, bus_addr: u64, - buff: Option, + buff: Option, } impl TxPending<'_> { @@ -273,6 +273,7 @@ impl TxPending<'_> { .buff .as_ref() .expect("tx pending buffer should exist until submit succeeds"); + buff.sync_for_device(0, self.len); self.queue.interface.submit(DmaBuffer { virt: buff.as_ptr(), bus_addr: self.bus_addr, @@ -289,8 +290,8 @@ impl TxPending<'_> { pub struct RxQueue { interface: Box, - pool: DArrayPool, - inflight: BTreeMap, + pool: ContiguousBufferPool, + inflight: BTreeMap, config: QueueConfig, _waker: Arc, } @@ -313,9 +314,10 @@ impl RxQueue { Ok(()) } - fn submit_buffer(&mut self, buff: DBuff) -> Result<(), NetError> { + fn submit_buffer(&mut self, buff: ContiguousBuffer) -> Result<(), NetError> { let bus_addr = buff.dma_addr().as_u64(); let len = self.config.buf_size.min(buff.len()); + buff.sync_for_device(0, len); self.interface.submit(DmaBuffer { virt: buff.as_ptr(), bus_addr, @@ -325,7 +327,7 @@ impl RxQueue { Ok(()) } - fn reclaim_packet(&mut self) -> Result, NetError> { + fn reclaim_packet(&mut self) -> Result, NetError> { let Some((bus_addr, len)) = self.interface.reclaim() else { return Ok(None); }; @@ -333,6 +335,7 @@ impl RxQueue { return Err(other_error("reclaimed unknown rx buffer")); }; let packet_len = len.min(self.config.buf_size).min(buff.len()); + buff.sync_for_cpu(0, packet_len); Ok(Some((buff, packet_len))) } @@ -364,7 +367,7 @@ impl RxQueue { pub struct RxPacket<'a> { queue: &'a mut RxQueue, len: usize, - buff: Option, + buff: Option, } impl RxPacket<'_> { diff --git a/drivers/net/realtek-rtl8125/src/lib.rs b/drivers/net/realtek-rtl8125/src/lib.rs index a1b4f98e81..f366838709 100644 --- a/drivers/net/realtek-rtl8125/src/lib.rs +++ b/drivers/net/realtek-rtl8125/src/lib.rs @@ -5,7 +5,7 @@ extern crate alloc; use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; use descriptor::{RING_END, RxDesc, TxDesc}; -use dma_api::{DeviceDma, DmaDirection, DmaOp}; +use dma_api::{DeviceDma, DmaOp}; use log::info; use mmio_api::{Mmio, MmioAddr, MmioOp}; use queue::{QueueStart, QueueStartState, Rtl8125RxQueue, Rtl8125TxQueue}; @@ -228,7 +228,7 @@ impl Interface for Rtl8125 { let mut desc = self .dma - .array_zero_with_align::(QUEUE_SIZE, DMA_ALIGN, DmaDirection::Bidirectional) + .coherent_array_zero_with_align::(QUEUE_SIZE, DMA_ALIGN) .ok()?; desc.set( QUEUE_SIZE - 1, @@ -267,7 +267,7 @@ impl Interface for Rtl8125 { let desc = self .dma - .array_zero_with_align::(QUEUE_SIZE, DMA_ALIGN, DmaDirection::Bidirectional) + .coherent_array_zero_with_align::(QUEUE_SIZE, DMA_ALIGN) .ok()?; { diff --git a/drivers/net/realtek-rtl8125/src/queue.rs b/drivers/net/realtek-rtl8125/src/queue.rs index bbb5e22981..84f3a67205 100644 --- a/drivers/net/realtek-rtl8125/src/queue.rs +++ b/drivers/net/realtek-rtl8125/src/queue.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; use core::sync::atomic::{Ordering as AtomicOrdering, fence}; -use dma_api::DArray; +use dma_api::CoherentArray; use log::{debug, info, warn}; use rdif_eth::{DmaBuffer, IRxQueue, ITxQueue, NetError, QueueConfig}; use spin::Mutex; @@ -29,7 +29,7 @@ pub(crate) struct QueueStartState { pub(crate) struct Rtl8125TxQueue { pub(crate) regs: Regs, - pub(crate) desc: DArray, + pub(crate) desc: CoherentArray, pub(crate) dma_mask: u64, pub(crate) bus_addrs: [Option; QUEUE_SIZE], pub(crate) next_submit: usize, @@ -155,7 +155,7 @@ impl Rtl8125TxQueue { pub(crate) struct Rtl8125RxQueue { pub(crate) regs: Regs, - pub(crate) desc: DArray, + pub(crate) desc: CoherentArray, pub(crate) dma_mask: u64, pub(crate) start: QueueStart, pub(crate) bus_addrs: [Option; QUEUE_SIZE], diff --git a/drivers/npu/rockchip-npu/src/gem.rs b/drivers/npu/rockchip-npu/src/gem.rs index 84bc81a9b2..4e5272c639 100644 --- a/drivers/npu/rockchip-npu/src/gem.rs +++ b/drivers/npu/rockchip-npu/src/gem.rs @@ -1,6 +1,6 @@ use alloc::collections::btree_map::BTreeMap; -use dma_api::{DArray, DeviceDma, DmaDirection}; +use dma_api::{ContiguousArray, DeviceDma, DmaDirection}; use crate::{ RknpuError, @@ -9,7 +9,7 @@ use crate::{ pub struct GemPool { dma: DeviceDma, - pool: BTreeMap>, + pool: BTreeMap>, handle_counter: u32, } @@ -25,7 +25,11 @@ impl GemPool { pub fn create(&mut self, args: &mut RknpuMemCreate) -> Result<(), RknpuError> { let data = self .dma - .array_zero_with_align::(args.size as _, 0x1000, DmaDirection::Bidirectional) + .contiguous_array_zero_with_align::( + args.size as _, + 0x1000, + DmaDirection::Bidirectional, + ) .map_err(|_| RknpuError::DmaError)?; let handle = self.handle_counter; @@ -80,10 +84,10 @@ impl GemPool { } if args.flags & RKNPU_MEM_SYNC_TO_DEVICE != 0 { - data.confirm_write(offset, size); + data.sync_for_device(offset, size); } if args.flags & RKNPU_MEM_SYNC_FROM_DEVICE != 0 { - data.prepare_read(offset, size); + data.sync_for_cpu(offset, size); } Ok(()) } @@ -94,14 +98,14 @@ impl GemPool { pub fn comfirm_write_all(&mut self) -> Result<(), RknpuError> { for data in self.pool.values_mut() { - data.confirm_write_all(); + data.sync_for_device_all(); } Ok(()) } pub fn prepare_read_all(&mut self) -> Result<(), RknpuError> { for data in self.pool.values_mut() { - data.prepare_read_all(); + data.sync_for_cpu_all(); } Ok(()) } diff --git a/drivers/npu/rockchip-npu/src/task/mod.rs b/drivers/npu/rockchip-npu/src/task/mod.rs index a9cc4a9abf..22340328bc 100644 --- a/drivers/npu/rockchip-npu/src/task/mod.rs +++ b/drivers/npu/rockchip-npu/src/task/mod.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use dma_api::{DArray, DeviceDma, DmaDirection}; +use dma_api::{ContiguousArray, DeviceDma, DmaDirection}; use crate::{JobMode, op::Operation}; @@ -28,7 +28,7 @@ pub struct SubmitRef { pub struct Submit { pub base: SubmitBase, - pub regcmd_all: DArray, + pub regcmd_all: ContiguousArray, pub tasks: Vec, } @@ -44,7 +44,7 @@ impl Submit { }; let regcmd_all = dma - .array_zero_with_align::( + .contiguous_array_zero_with_align::( base.regcfg_amount as usize * tasks.len(), 0x1000, DmaDirection::Bidirectional, @@ -66,7 +66,7 @@ impl Submit { }; task.fill_regcmd(regcmd); } - regcmd_all.confirm_write_all(); + regcmd_all.sync_for_device_all(); Self { base, diff --git a/drivers/npu/rockchip-npu/src/task/op/matmul.rs b/drivers/npu/rockchip-npu/src/task/op/matmul.rs index 6d8b17fcc2..198ef6e0d8 100644 --- a/drivers/npu/rockchip-npu/src/task/op/matmul.rs +++ b/drivers/npu/rockchip-npu/src/task/op/matmul.rs @@ -1,4 +1,4 @@ -use dma_api::{DArray, DeviceDma, DmaDirection}; +use dma_api::{ContiguousArray, DeviceDma, DmaDirection}; use super::super::def::*; use crate::{ @@ -11,9 +11,9 @@ pub struct MatMul { m: u16, k: u16, n: u16, - input: DArray, - weight: DArray, - output: DArray, + input: ContiguousArray, + weight: ContiguousArray, + output: ContiguousArray, } impl MatMul { @@ -23,21 +23,21 @@ impl MatMul { k: k as _, n: n as _, input: dma - .array_zero_with_align::( + .contiguous_array_zero_with_align::( m * k * size_of::(), 0x1000, DmaDirection::Bidirectional, ) .unwrap(), weight: dma - .array_zero_with_align::( + .contiguous_array_zero_with_align::( k * n * size_of::(), 0x1000, DmaDirection::Bidirectional, ) .unwrap(), output: dma - .array_zero_with_align::( + .contiguous_array_zero_with_align::( m * n * size_of::(), 0x1000, DmaDirection::Bidirectional, @@ -58,6 +58,7 @@ impl MatMul { self.input.set(idx, a[src]); } } + self.input.sync_for_device_all(); } fn gen_matul( @@ -417,16 +418,18 @@ impl MatMul { self.weight.set(idx, b[src]); } } + self.weight.sync_for_device_all(); } pub fn get_output(&self, m: usize, n: usize) -> i32 { + self.output.sync_for_cpu_all(); self.output .read(feature_data(self.n as _, self.m as _, 1, 4, n as _, m as _, 1) as usize) .unwrap() } pub fn output_buffer(&self) -> &[i32] { - self.output.prepare_read_all(); + self.output.sync_for_cpu_all(); unsafe { core::slice::from_raw_parts(self.output.as_ptr().as_ptr(), self.output.len()) } } } diff --git a/drivers/usb/usb-host/src/backend/kmod/dwc/event.rs b/drivers/usb/usb-host/src/backend/kmod/dwc/event.rs index 05d7205209..a4f460d9df 100644 --- a/drivers/usb/usb-host/src/backend/kmod/dwc/event.rs +++ b/drivers/usb/usb-host/src/backend/kmod/dwc/event.rs @@ -1,18 +1,15 @@ -use dma_api::{DArray, DmaDirection}; +use dma_api::{ContiguousArray, DmaDirection}; use crate::osal::Kernel; pub struct EventBuffer { - pub buffer: DArray, + pub buffer: ContiguousArray, } impl EventBuffer { pub fn new(size: usize, kernel: &Kernel) -> crate::err::Result { - // let buffer = DVec::zeros(dma_mask as _, size, 0x1000, dma_api::Direction::FromDevice) - // .map_err(|_| crate::err::USBError::NoMemory)?; - let buffer = kernel - .array_zero_with_align(size, 0x1000, DmaDirection::FromDevice) + .contiguous_array_zero_with_align(size, 0x1000, DmaDirection::FromDevice) .map_err(|_| crate::err::USBError::NoMemory)?; Ok(Self { buffer }) diff --git a/drivers/usb/usb-host/src/backend/kmod/dwc/mod.rs b/drivers/usb/usb-host/src/backend/kmod/dwc/mod.rs index 8f53a9456f..500c1f60e1 100644 --- a/drivers/usb/usb-host/src/backend/kmod/dwc/mod.rs +++ b/drivers/usb/usb-host/src/backend/kmod/dwc/mod.rs @@ -6,7 +6,7 @@ use alloc::{boxed::Box, collections::BTreeMap, string::String, sync::Arc, vec, vec::Vec}; use core::ops::{Deref, DerefMut}; -use dma_api::{DArray, DmaDirection}; +use dma_api::{ContiguousArray, DmaDirection}; use event::EventBuffer; use futures::{FutureExt, future::BoxFuture}; use reg::{GCTL, GEVNTSIZ, GHWPARAMS0, GHWPARAMS1, GHWPARAMS3, GHWPARAMS4, GUCTL1, GUSB2PHYCFG}; @@ -123,7 +123,7 @@ pub struct Dwc { revistion: u32, nr_scratch: u32, params: DwcParams, - scratchbuf: Option>, + scratchbuf: Option>, } impl Dwc { @@ -506,20 +506,13 @@ impl Dwc { let scratchbuf = self .kernel() - .array_zero_with_align( + .contiguous_array_zero_with_align( scratch_size, self.kernel().page_size(), DmaDirection::Bidirectional, ) .map_err(|_| USBError::NoMemory)?; - - // let scratchbuf = DVec::zeros( - // self.xhci.dma_mask as _, - // scratch_size, - // page_size(), - // dma_api::Direction::Bidirectional, - // ) - // .map_err(|_| USBError::NoMemory)?; + scratchbuf.sync_for_device_all(); self.scratchbuf = Some(scratchbuf); debug!( diff --git a/drivers/usb/usb-host/src/backend/kmod/osal.rs b/drivers/usb/usb-host/src/backend/kmod/osal.rs index 3f0b3614b9..6b4f13d566 100644 --- a/drivers/usb/usb-host/src/backend/kmod/osal.rs +++ b/drivers/usb/usb-host/src/backend/kmod/osal.rs @@ -1,7 +1,7 @@ use core::{ops::Deref, time::Duration}; use dma_api::DeviceDma; -pub use dma_api::{DmaAddr, DmaDirection, DmaError, DmaHandle, DmaMapHandle, DmaOp}; +pub use dma_api::{DmaAddr, DmaDirection, DmaError, DmaMapHandle, DmaOp}; #[derive(Clone)] pub(crate) struct Kernel { diff --git a/drivers/usb/usb-host/src/backend/kmod/transfer.rs b/drivers/usb/usb-host/src/backend/kmod/transfer.rs index 224e416594..0973130ef6 100644 --- a/drivers/usb/usb-host/src/backend/kmod/transfer.rs +++ b/drivers/usb/usb-host/src/backend/kmod/transfer.rs @@ -25,7 +25,7 @@ impl Transfer { let mapping = if let Some((ptr, len)) = buff.filter(|(_, len)| *len > 0) { let slice = unsafe { core::slice::from_raw_parts_mut(ptr.as_ptr(), len) }; Some( - dma.map_single_array(slice, ALIGN, dma_direction) + dma.map_streaming_slice(slice, ALIGN, dma_direction) .map_err(|err| TransferError::Other(anyhow!("DMA mapping failed: {err}")))?, ) } else { @@ -50,57 +50,6 @@ impl Transfer { Self::new(dma, kind, direction, buff) } - // pub(crate) fn new_in(dma: &Kernel, kind: TransferKind, buff: Pin<&mut [u8]>) -> Self { - // let buffer_addr = buff.as_ptr() as usize; - // let buffer_len = buff.len(); - // trace!( - // "Transfer::new_in: addr={:#x}, len={}", - // buffer_addr, buffer_len - // ); - - // let mapping = if buffer_len > 0 { - // Some( - // dma.map_single_array(buff.get_mut(), ALIGN, DmaDirection::FromDevice) - // .expect("DMA mapping failed"), - // ) - // } else { - // None - // }; - - // Self { - // kind, - // direction: usb_if::transfer::Direction::In, - // mapping, - // transfer_len: 0, - // } - // } - - // pub(crate) fn new_out(kernel: &Kernel, kind: TransferKind, buff: Pin<&[u8]>) -> Self { - // let buffer_addr = buff.as_ptr() as usize; - // let buffer_len = buff.len(); - // trace!( - // "Transfer::new_out: addr={:#x}, len={}", - // buffer_addr, buffer_len - // ); - - // let mapping = if buffer_len > 0 { - // Some( - // kernel - // .map_single_array(buff.get_ref(), ALIGN, DmaDirection::ToDevice) - // .expect("DMA mapping failed"), - // ) - // } else { - // None - // }; - - // Self { - // kind, - // direction: Direction::Out, - // mapping, - // transfer_len: 0, - // } - // } - pub fn buffer_len(&self) -> usize { if let Some(ref mapping) = self.mapping { mapping.len() @@ -117,15 +66,15 @@ impl Transfer { } } - pub fn prepare_read_all(&self) { + pub fn sync_for_cpu_all(&self) { if let Some(ref mapping) = self.mapping { - mapping.prepare_read_all(); + mapping.sync_for_cpu_all(); } } - pub fn confirm_write_all(&self) { + pub fn sync_for_device_all(&self) { if let Some(ref mapping) = self.mapping { - mapping.confirm_write_all(); + mapping.sync_for_device_all(); } } } diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/context.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/context.rs index 900eaad10f..1a0ac47a40 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/context.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/context.rs @@ -1,13 +1,13 @@ use alloc::vec::Vec; -use dma_api::{DArray, DBox, DmaDirection}; +use dma_api::{CoherentArray, CoherentBox, ContiguousArray, DmaDirection}; use xhci::context::{Device32Byte, Device64Byte, Input32Byte, Input64Byte, InputHandler}; use super::SlotId; use crate::{err::*, osal::Kernel}; pub struct DeviceContextList { - pub dcbaa: DArray, + pub dcbaa: CoherentArray, max_slots: usize, } @@ -15,13 +15,13 @@ unsafe impl Send for DeviceContextList {} unsafe impl Sync for DeviceContextList {} pub(crate) struct Context32 { - out: DBox, - input: DBox, + out: CoherentBox, + input: CoherentBox, } pub(crate) struct Context64 { - out: DBox, - input: DBox, + out: CoherentBox, + input: CoherentBox, } pub(crate) enum ContextData { Context32(Context32), @@ -32,17 +32,13 @@ impl ContextData { pub fn new(is_64: bool, dma: &Kernel) -> core::result::Result { if is_64 { Ok(ContextData::Context64(Context64 { - // out: DBox::zero_with_align(dma_mask as _, dma_api::Direction::FromDevice, 64)?, - // input: DBox::zero_with_align(dma_mask as _, dma_api::Direction::ToDevice, 64)?, - out: dma.box_zero_with_align(64, DmaDirection::FromDevice)?, - input: dma.box_zero_with_align(64, DmaDirection::ToDevice)?, + out: dma.coherent_box_zero_with_align(64)?, + input: dma.coherent_box_zero_with_align(64)?, })) } else { Ok(ContextData::Context32(Context32 { - // out: DBox::zero_with_align(dma_mask as _, dma_api::Direction::FromDevice, 64)?, - // input: DBox::zero_with_align(dma_mask as _, dma_api::Direction::ToDevice, 64)?, - out: dma.box_zero_with_align(64, DmaDirection::FromDevice)?, - input: dma.box_zero_with_align(64, DmaDirection::ToDevice)?, + out: dma.coherent_box_zero_with_align(64)?, + input: dma.coherent_box_zero_with_align(64)?, })) } } @@ -115,10 +111,8 @@ impl ContextData { impl DeviceContextList { pub fn new(max_slots: usize, dma: &Kernel) -> Result { - // let dcbaa = DVec::zeros(dma_mask as _, 256, 0x1000, dma_api::Direction::ToDevice) - // .map_err(|_| USBError::NoMemory)?; let dcbaa = dma - .array_zero_with_align(256, dma.page_size(), DmaDirection::ToDevice) + .coherent_array_zero_with_align(256, dma.page_size()) .map_err(|_| USBError::NoMemory)?; Ok(Self { dcbaa, max_slots }) } @@ -134,44 +128,26 @@ impl DeviceContextList { } pub struct ScratchpadBufferArray { - pub entries: DArray, - pub _pages: Vec>, + pub entries: CoherentArray, + pub _pages: Vec>, } impl ScratchpadBufferArray { pub fn new(entries: usize, dma: &Kernel) -> Result { - // let mut entries_vec = DVec::zeros( - // dma_mask as _, - // entries, - // 64, - // dma_api::Direction::Bidirectional, - // ) - // .map_err(|_| USBError::NoMemory)?; - let mut entries_vec = dma - .array_zero_with_align(entries, 64, DmaDirection::Bidirectional) + .coherent_array_zero_with_align(entries, 64) .map_err(|_| USBError::NoMemory)?; - // let pages: Vec> = (0..entries_vec.len()) - // .map(|_| { - // DVec::::zeros( - // dma_mask as _, - // 0x1000, - // 0x1000, - // dma_api::Direction::Bidirectional, - // ) - // .map_err(|_| USBError::NoMemory) - // }) - // .try_collect()?; - let mut pages: Vec> = Vec::with_capacity(entries_vec.len()); + let mut pages: Vec> = Vec::with_capacity(entries_vec.len()); for _ in 0..entries_vec.len() { let page = dma - .array_zero_with_align( + .contiguous_array_zero_with_align( dma.page_size(), dma.page_size(), DmaDirection::Bidirectional, ) .map_err(|_| USBError::NoMemory)?; + page.sync_for_device_all(); pages.push(page); } diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs index 5c11453e58..f0dad33210 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/endpoint.rs @@ -290,7 +290,7 @@ impl Endpoint { let transfer_len = actual_lengths.iter().sum(); transfer.iso_packet_actual_lengths = actual_lengths; if transfer_len > 0 && matches!(transfer.direction, Direction::In) { - transfer.prepare_read_all(); + transfer.sync_for_cpu_all(); } transfer.transfer_len = transfer_len; trace!("ISO transfer data length: {}", transfer.transfer_len); @@ -309,7 +309,7 @@ impl Endpoint { }; if transfer_len > 0 && matches!(transfer.direction, Direction::In) { - transfer.prepare_read_all(); + transfer.sync_for_cpu_all(); } transfer.transfer_len = transfer_len; trace!("Transfer data length: {}", transfer.transfer_len); @@ -605,7 +605,7 @@ impl EndpointOp for Endpoint { let mut data_bus_addr = 0; if transfer.buffer_len() > 0 { if matches!(transfer.direction, Direction::Out) { - transfer.confirm_write_all(); + transfer.sync_for_device_all(); } data_bus_addr = transfer.dma_addr(); let buffer_end = data_bus_addr + transfer.buffer_len() as u64; diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/event.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/event.rs index 27eeae29bb..bbdf20f5ef 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/event.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/event.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use dma_api::{DArray, DmaDirection}; +use dma_api::{CoherentArray, DmaDirection}; use mbarrier::mb; use xhci::ring::trb::event::Allowed; @@ -8,6 +8,7 @@ use super::ring::{Ring, TRBS_PER_SEGMENT}; use crate::{err::*, osal::Kernel}; #[repr(C)] +#[derive(Clone, Copy)] pub struct EventRingSte { pub addr: u64, pub size: u16, @@ -19,7 +20,7 @@ pub struct EventRing { segment_index: usize, trb_index: usize, cycle: bool, - pub ste: DArray, + pub ste: CoherentArray, } unsafe impl Send for EventRing {} @@ -36,7 +37,7 @@ impl EventRing { } let mut ste = dma - .array_zero_with_align(segment_count, 64, DmaDirection::Bidirectional) + .coherent_array_zero_with_align(segment_count, 64) .map_err(|_| USBError::NoMemory)?; for (index, segment) in segments.iter().enumerate() { diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/ring.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/ring.rs index 4c4496656f..c34029cf6b 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/ring.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/ring.rs @@ -1,6 +1,6 @@ use alloc::vec::Vec; -use dma_api::{DArray, DmaDirection}; +use dma_api::{CoherentArray, DmaDirection}; use mbarrier::mb; use xhci::ring::trb::{Link, command, transfer}; @@ -16,7 +16,7 @@ pub(crate) const TRB_SIZE: usize = size_of::(); pub(crate) const TRBS_PER_SEGMENT: usize = 256; const DEFAULT_RING_PAGES: usize = 2; -#[derive(Clone)] +#[derive(Clone, Copy)] #[repr(transparent)] pub struct TrbData([u32; TRB_LEN]); @@ -42,7 +42,7 @@ impl From for TrbData { pub struct Ring { link: bool, - pub trbs: DArray, + pub trbs: CoherentArray, pub i: usize, pub cycle: bool, } @@ -57,7 +57,8 @@ impl Ring { direction: DmaDirection, dma: &Kernel, ) -> core::result::Result { - let trbs = dma.array_zero_with_align(len, dma.page_size(), direction)?; + let _ = direction; + let trbs = dma.coherent_array_zero_with_align(len, dma.page_size())?; Ok(Self { link, diff --git a/drivers/usb/usb-host/src/backend/ty/transfer.rs b/drivers/usb/usb-host/src/backend/ty/transfer.rs index 0e70cc4a39..ad09637974 100644 --- a/drivers/usb/usb-host/src/backend/ty/transfer.rs +++ b/drivers/usb/usb-host/src/backend/ty/transfer.rs @@ -10,7 +10,7 @@ pub struct Transfer { pub kind: TransferKind, pub direction: usb_if::transfer::Direction, #[cfg(kmod)] - pub mapping: Option>, + pub mapping: Option>, #[cfg(umod)] pub buffer: Option<(std::ptr::NonNull, usize)>, pub transfer_len: usize, diff --git a/drivers/usb/utils/ktest-helper/src/lib.rs b/drivers/usb/utils/ktest-helper/src/lib.rs index 5559984a1a..98c9b2d743 100644 --- a/drivers/usb/utils/ktest-helper/src/lib.rs +++ b/drivers/usb/utils/ktest-helper/src/lib.rs @@ -1,183 +1,2 @@ #![cfg(target_os = "none")] #![no_std] - -// extern crate alloc; -// -// use core::{alloc::Layout, num::NonZeroUsize, ptr::NonNull, time::Duration}; -// -// use bare_test::{ -// mem::{PhysAddr, VirtAddr, alloc_with_mask, page_size}, -// time::spin_delay, -// }; -// use crab_usb::*; -// -// pub struct KernelImpl; -// -// impl DmaOp for KernelImpl { -// fn page_size(&self) -> usize { -// page_size() -// } -// -// unsafe fn map_single( -// &self, -// dma_mask: u64, -// addr: NonNull, -// size: NonZeroUsize, -// align: usize, -// _direction: DmaDirection, -// ) -> Result { -// let size = size.get(); -// let orig_phys = PhysAddr::from(VirtAddr::from(addr)).raw() as u64; -// -// let layout = Layout::from_size_align(size, align).unwrap(); -// -// if orig_phys + size as u64 > dma_mask || !orig_phys.is_multiple_of(align as u64) { -// 需要重新分配内存 -// let ptr = unsafe { alloc_with_mask(layout, dma_mask) }; -// if ptr.is_null() { -// return Err(DmaError::NoMemory); -// } -// -// let new_virt = NonNull::new(ptr).unwrap(); -// let new_phys = PhysAddr::from(VirtAddr::from(new_virt)).raw() as u64; -// -// log::debug!( -// "DMA remap: orig_virt={:#p}, orig_phys={:#x} -> new_virt={:#p}, new_phys={:#x}, \ -// size={:#x}", -// addr, -// orig_phys, -// new_virt, -// new_phys, -// size -// ); -// -// unsafe { -// let dst = core::slice::from_raw_parts_mut(new_virt.as_ptr(), size); -// let src = core::slice::from_raw_parts(addr.as_ptr(), size); -// dst.copy_from_slice(src); -// -// important: flush cache to make sure DMA can see the latest data -// self.flush_invalidate(new_virt, size); -// } -// -// Ok(unsafe { DmaMapHandle::new(addr, new_phys.into(), layout, Some(new_virt)) }) -// } else { -// ✅ 原始地址可以使用,直接返回 -// Ok(unsafe { DmaMapHandle::new(addr, orig_phys.into(), layout, None) }) -// } -// } -// -// unsafe fn unmap_single(&self, handle: DmaMapHandle) { -// if let Some(virt) = handle.alloc_virt() { -// 重新分配过,需要释放新分配的内存 -// unsafe { -// alloc::alloc::dealloc(virt.as_ptr(), handle.layout()); -// } -// } -// 没有重新分配,原始 buffer 由调用者管理,不需要释放 -// } -// -// unsafe fn alloc_coherent( -// &self, -// dma_mask: u64, -// layout: core::alloc::Layout, -// ) -> Option { -// let ptr = unsafe { alloc_with_mask(layout, dma_mask) }; -// let ptr = NonNull::new(ptr)?; -// let virt = VirtAddr::from(ptr); -// let phys = PhysAddr::from(virt).raw() as u64; -// -// Some(unsafe { DmaHandle::new(ptr, phys.into(), layout) }) -// } -// -// unsafe fn dealloc_coherent(&self, handle: DmaHandle) { -// unsafe { alloc::alloc::dealloc(handle.as_ptr().as_ptr(), handle.layout()) } -// } -// -// unsafe fn map_single( -// &self, -// dma_mask: u64, -// origin_virt: NonNull, -// size: NonZeroUsize, -// align: usize, -// _direction: crate::DmaDirection, -// ) -> Result { -// let size = size.get(); -// let orig_phys = PhysAddr::from(VirtAddr::from(origin_virt)).raw() as u64; -// -// let layout = Layout::from_size_align(size, align).unwrap(); -// -// if orig_phys + size as u64 > dma_mask || !orig_phys.is_multiple_of(align as u64) { -// // 需要重新分配内存 -// let ptr = unsafe { alloc_with_mask(layout, dma_mask) }; -// if ptr.is_null() { -// return Err(DmaError::NoMemory); -// } -// -// let new_virt = NonNull::new(ptr).unwrap(); -// let new_phys = PhysAddr::from(VirtAddr::from(new_virt)).raw() as u64; -// -// log::debug!( -// "DMA remap: orig_virt={:#x}, orig_phys={:#x} -> new_virt={:#x}, new_phys={:#x}, size={:#x}", -// origin_virt.as_ptr() as usize, -// orig_phys, -// new_virt.as_ptr() as usize, -// new_phys, -// size -// ); -// -// unsafe { -// core::ptr::copy_nonoverlapping(origin_virt.as_ptr(), new_virt.as_ptr(), size); -// } -// -// Ok(DmaHandle { -// dma_addr: new_phys, -// origin_virt, -// alloc_virt: Some(new_virt), -// layout, -// }) -// } else { -// // ✅ 原始地址可以使用,直接返回 -// Ok(DmaHandle { -// dma_addr: orig_phys, -// origin_virt, -// layout, -// alloc_virt: None, -// }) -// } -// } -// -// unsafe fn unmap_single(&self, handle: DmaHandle) { -// if let Some(virt) = handle.alloc_virt { -// // 重新分配过,需要释放新分配的内存 -// unsafe { -// alloc::alloc::dealloc(virt.as_ptr(), handle.layout); -// } -// } -// // 没有重新分配,原始 buffer 由调用者管理,不需要释放 -// } -// -// unsafe fn alloc_coherent(&self, dma_mask: u64, layout: Layout) -> Option { -// let ptr = unsafe { alloc_with_mask(layout, dma_mask) }; -// let ptr = NonNull::new(ptr)?; -// let virt = VirtAddr::from(ptr); -// let phys = PhysAddr::from(virt).raw() as u64; -// -// Some(crab_usb::DmaHandle { -// origin_virt: ptr, -// dma_addr: phys, -// layout, -// alloc_virt: Some(ptr), -// }) -// } -// -// unsafe fn dealloc_coherent(&self, handle: DmaHandle) { -// unsafe { alloc::alloc::dealloc(handle.as_ptr(), handle.layout) } -// } -// } -// -// impl KernelOp for KernelImpl { -// fn delay(&self, duration: Duration) { -// spin_delay(duration); -// } -// } From a7e82c028b68cd1552ed41032cb7c769dabfb4a5 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Mon, 25 May 2026 15:36:17 +0800 Subject: [PATCH 27/71] Migrate CI jobs to self-hosted runners and enable container usage (#928) * chore(ci): update axvisor test jobs to use self-hosted runners * ci: migrate starry QEMU tests to self-hosted runners * fix(ci): enable container usage for starry tests on ubuntu-latest * fix(ci): update CI configuration for starry tests to use ubuntu-latest --- .github/workflows/ci.yml | 14 ++++++++------ docs/docs/build/ci.md | 14 +++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d935830c7..371a3cf5fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -314,18 +314,20 @@ jobs: limit_to_owner: "" main_pr_only: false - name: Test axvisor aarch64 qemu - use_container: true - runs_on: '["ubuntu-latest"]' + use_container: false + runs_on: '["self-hosted","linux","qcs"]' + self_hosted_owner: rcore-os command: cargo xtask axvisor test qemu --arch aarch64 - cache_key: test-axvisor-aarch64 + cache_key: "" container_image: base limit_to_owner: "" main_pr_only: false - name: Test axvisor riscv64 qemu - use_container: true - runs_on: '["ubuntu-latest"]' + use_container: false + runs_on: '["self-hosted","linux","qcs"]' + self_hosted_owner: rcore-os command: cargo xtask axvisor test qemu --arch riscv64 - cache_key: test-axvisor-riscv64 + cache_key: "" container_image: base limit_to_owner: "" main_pr_only: false diff --git a/docs/docs/build/ci.md b/docs/docs/build/ci.md index ff8d5ee05b..de35f96c3d 100644 --- a/docs/docs/build/ci.md +++ b/docs/docs/build/ci.md @@ -91,8 +91,8 @@ push 到 `main` / `dev` 时强制运行 CI 检查。若非 `main` / `dev` 分支 |----------|--------|----------|-----------|----------| | Run clippy | `ubuntu-latest` | 是(`base`) | `clippy` | `cargo xtask clippy --since `;需要完整 git 历史 | | Test with std | `ubuntu-latest` | 是(`base`) | `test-std` | `cargo xtask test`,运行 `scripts/test/std_crates.csv` 中的 host 测试 | -| Test axvisor aarch64 qemu | `ubuntu-latest` | 是(`base`) | `test-axvisor-aarch64` | `cargo xtask axvisor test qemu --arch aarch64` | -| Test axvisor riscv64 qemu | `ubuntu-latest` | 是(`base`) | `test-axvisor-riscv64` | `cargo xtask axvisor test qemu --arch riscv64` | +| Test axvisor aarch64 qemu | `self-hosted linux qcs` | 否 | 无 | `cargo xtask axvisor test qemu --arch aarch64`;`rcore-os` 仓库使用 self-hosted,fork PR 回退到 `ubuntu-latest` + `base` 容器 | +| Test axvisor riscv64 qemu | `self-hosted linux qcs` | 否 | 无 | `cargo xtask axvisor test qemu --arch riscv64`;`rcore-os` 仓库使用 self-hosted,fork PR 回退到 `ubuntu-latest` + `base` 容器 | | Test axvisor loongarch64 qemu | `ubuntu-latest` | 是(`axvisor-lvz`) | `test-axvisor-loongarch64` | `cargo xtask axvisor test qemu --arch loongarch64`,使用带 LVZ 支持的镜像 | | Test starry riscv64 qemu | `ubuntu-latest` | 是(`base`) | `test-starry-riscv64` | `cargo xtask starry test qemu --arch riscv64` | | Test starry aarch64 qemu | `ubuntu-latest` | 是(`base`) | `test-starry-aarch64` | `cargo xtask starry test qemu --arch aarch64` | @@ -112,13 +112,13 @@ StarryOS stress 测试条目保留在 workflow 中,但当前处于注释状态 ## Self-Hosted Runner 约定 -self-hosted runner 任务只在 `rcore-os` 仓库内运行,避免 fork 仓库因没有对应 runner 而长时间排队。迁移到 self-hosted 的 ArceOS QEMU 测试直接在原生 runner 环境中运行,不再套 Docker container。 +self-hosted runner 任务优先在 `rcore-os` 仓库内运行。带 `self_hosted_owner` 的 QEMU 测试在 fork PR 或非 `rcore-os` 仓库中会回退到 `ubuntu-latest` + 对应容器,避免没有对应 runner 时长时间排队。迁移到 self-hosted 的 QEMU 测试直接在原生 runner 环境中运行,不再套 Docker container。 现有 label 约定: | Label | 用途 | |-------|------| -| `self-hosted`, `linux`, `qcs` | ArceOS QEMU 测试 | +| `self-hosted`, `linux`, `qcs` | ArceOS QEMU 测试、Axvisor aarch64/riscv64 QEMU | | `self-hosted`, `linux`, `intel`, `kvm` | Axvisor x86_64 KVM 测试 | | `self-hosted`, `linux`, `board` | 物理板卡测试 | @@ -129,8 +129,8 @@ self-hosted runner 任务只在 `rcore-os` 仓库内运行,避免 fork 仓库 | `sync-lint` | Run sync-lint | `push` 事件 | xtask 与 sync-lint 工具链编译产物 | | `clippy` | Run clippy | `push` 事件 | xtask 与目标 crate 编译产物 | | `test-std` | Test with std | `push` 事件 | host std 测试编译产物 | -| `test-axvisor-aarch64/riscv64/loongarch64` | Test axvisor QEMU | `push` 事件 | 各架构 Axvisor 编译产物 | -| `test-starry-aarch64/riscv64/loongarch64/x86_64` | Test starry QEMU | `push` 事件 | 各架构 StarryOS 编译产物 | +| `test-axvisor-loongarch64` | Test axvisor loongarch64 QEMU | `push` 事件 | Axvisor loongarch64 编译产物 | +| `test-starry-riscv64/aarch64/loongarch64/x86_64` | Test starry riscv64/aarch64/loongarch64/x86_64 QEMU | `push` 事件 | StarryOS QEMU 编译产物 | | 无(`cache_key: ""`) | self-hosted runner job | - | 依赖 self-hosted runner 本地磁盘缓存,不使用 GitHub Actions cache | self-hosted runner 不设置 `cache_key`,避免 `Swatinem/rust-cache@v2` 的 post-job 清理影响 runner 上跨次运行自然积累的共享缓存。 @@ -141,7 +141,7 @@ CI 使用两个容器镜像: | 镜像 | Dockerfile | 用途 | |------|------------|------| -| `base` | `container/Dockerfile` | 常规 clippy、sync-lint、std 测试、Axvisor aarch64/riscv64 QEMU、StarryOS QEMU | +| `base` | `container/Dockerfile` | 常规 clippy、sync-lint、std 测试、StarryOS QEMU,以及 self-hosted QEMU 测试在 fork PR 或非 `rcore-os` 仓库中的回退环境 | | `axvisor-lvz` | `container/Dockerfile.axvisor-lvz` | Axvisor loongarch64 QEMU,额外包含 LVZ 支持 | 基础镜像以 `ubuntu:24.04` 为底,内置 Rust 工具链、QEMU、musl cross-toolchain、libav、libudev 等依赖。容器内的 musl cross-toolchain 已通过 `PATH` 配置好,`reusable-command.yml` 会在 container job 启动时验证 QEMU user emulators 和 musl compiler 是否存在,不再在运行时动态下载。 From 00c466fcca428bcc33286a2b61947c674d728afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Mon, 25 May 2026 16:20:04 +0800 Subject: [PATCH 28/71] chore(axbuild): remove unused feature toggles (#933) --- scripts/axbuild/Cargo.toml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scripts/axbuild/Cargo.toml b/scripts/axbuild/Cargo.toml index 1a85f775b7..e30617bf90 100644 --- a/scripts/axbuild/Cargo.toml +++ b/scripts/axbuild/Cargo.toml @@ -15,7 +15,7 @@ ax-config-gen = { workspace = true } axvmconfig = { workspace = true, features = ["std"] } cargo_metadata = "0.23" chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4.6", features = ["derive"], optional = true } +clap = { version = "4.6", features = ["derive"] } colored = { version = "3" } env_logger = { workspace = true } futures-util = "0.3" @@ -34,7 +34,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1" } sha2 = { version = "0.10" } tar = { version = "0.4" } -tokio = { version = "1.0", features = ["fs", "io-util", "macros", "process", "rt-multi-thread", "time"], optional = true } +tokio = { version = "1.0", features = ["fs", "io-util", "macros", "process", "rt-multi-thread", "time"] } toml.workspace = true tracing = "0.1" tracing-log = "0.2" @@ -56,11 +56,5 @@ windows-sys = { version = "0.59", features = [ [target.'cfg(unix)'.dependencies] libc = "0.2" -[features] -clap = ["dep:clap", "dep:tokio"] -cli = ["dep:clap", "tokio"] -default = ["cli"] -tokio = ["dep:tokio", "dep:clap"] - [dev-dependencies] tempfile = "3" From 56366c7f96ba7eae607a793a7980ccd3c3d2bc9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Mon, 25 May 2026 19:01:51 +0800 Subject: [PATCH 29/71] refactor(driver): move static probes to platform-owned registration (#937) * fix(ax-driver): remove CVSD driver references and update dependencies * fix(lib): unify target architecture checks for module imports * Refactor static device registration and probing in various platforms - Updated the static device registration mechanism to use the new `rdrive::module_driver!` macro, simplifying the driver implementation across multiple platforms (axplat-loongarch64-qemu-virt, axplat-riscv64-qemu-virt, axplat-x86-pc, etc.). - Removed the `DriversIf` interface and associated static device descriptors, transitioning to a more streamlined approach that directly registers devices during probing. - Enhanced the PCI ECAM registration process by integrating legacy IRQ routes and memory mapping directly within the probe functions. - Adjusted Cargo.toml files to include necessary dependencies and features for `ax-driver` and `axklib`. - Cleaned up unused code and comments related to static device descriptors in platforms that do not require them (e.g., riscv64-visionfive2). - Updated documentation to reflect changes in the static probing mechanism and the removal of the `StaticDeviceDesc` structure. * Refactor driver registration and update dependencies - Removed `rdrive` dependency from multiple Cargo.toml files and replaced it with `ax_driver`. - Introduced `ax_driver::model_register!` macro in place of `rdrive::module_driver!` across various driver implementations. - Added `rdrive-macros` as a new dependency in Cargo.toml files where applicable. - Updated the Cargo.lock file to reflect the changes in dependencies. - Created a new test file to validate the usability of the `model_register!` macro. * fix(ax-plat-loongarch64-qemu-virt): select ECAM MMIO mapping by paging * fix(ax-plat): skip inactive static bus probes --- Cargo.lock | 38 ++-- .../axplat_crates/axplat/src/drivers.rs | 19 -- components/axplat_crates/axplat/src/lib.rs | 1 - .../axplat-aarch64-bsta1000b/Cargo.toml | 1 - .../axplat-aarch64-bsta1000b/src/drivers.rs | 12 +- .../axplat-aarch64-phytium-pi/Cargo.toml | 1 - .../axplat-aarch64-phytium-pi/src/drivers.rs | 12 +- .../axplat-aarch64-qemu-virt/Cargo.toml | 5 +- .../axplat-aarch64-qemu-virt/src/drivers.rs | 169 ++++++++++++-- .../axplat-aarch64-qemu-virt/src/lib.rs | 4 + .../platforms/axplat-aarch64-raspi/Cargo.toml | 1 - .../axplat-aarch64-raspi/src/drivers.rs | 12 +- .../axplat-loongarch64-qemu-virt/Cargo.toml | 5 +- .../src/drivers.rs | 64 +++++- .../axplat-loongarch64-qemu-virt/src/lib.rs | 3 + .../axplat-riscv64-qemu-virt/Cargo.toml | 5 +- .../axplat-riscv64-qemu-virt/src/drivers.rs | 145 ++++++++++-- .../axplat-riscv64-qemu-virt/src/lib.rs | 4 + .../axplat-riscv64-sg2002/Cargo.toml | 6 +- .../axplat-riscv64-sg2002/src/drivers.rs | 20 -- .../axplat-riscv64-sg2002/src/drivers/cvsd.rs | 208 ++++++++++++++++++ .../axplat-riscv64-sg2002/src/drivers/mod.rs | 1 + .../axplat-riscv64-sg2002/src/lib.rs | 15 +- .../platforms/axplat-x86-pc/Cargo.toml | 3 +- .../platforms/axplat-x86-pc/src/drivers.rs | 30 ++- .../platforms/axplat-x86-pc/src/lib.rs | 3 + docs/docs/architecture/rdrive-rdif.md | 4 +- drivers/ax-driver/Cargo.toml | 3 +- drivers/ax-driver/build.rs | 18 +- drivers/ax-driver/src/block/ahci.rs | 2 +- drivers/ax-driver/src/block/cvsd.rs | 160 -------------- drivers/ax-driver/src/block/mod.rs | 2 - drivers/ax-driver/src/block/phytium_mci.rs | 2 +- .../src/block/rockchip/sdhci_rk3568.rs | 2 +- drivers/ax-driver/src/block/rockchip_mmc.rs | 2 +- drivers/ax-driver/src/block/rockchip_sd.rs | 2 +- drivers/ax-driver/src/lib.rs | 35 ++- drivers/ax-driver/src/net/intel.rs | 2 +- drivers/ax-driver/src/net/ixgbe.rs | 2 +- drivers/ax-driver/src/net/realtek.rs | 2 +- drivers/ax-driver/src/pci/fdt.rs | 4 +- drivers/ax-driver/src/pci/mod.rs | 77 ++++--- drivers/ax-driver/src/pci/rk3588/slots.rs | 10 +- drivers/ax-driver/src/rknpu.rs | 2 +- drivers/ax-driver/src/serial/mod.rs | 2 +- .../ax-driver/src/soc/rockchip/clk/rk3568.rs | 2 +- .../ax-driver/src/soc/rockchip/clk/rk3588.rs | 2 +- drivers/ax-driver/src/soc/rockchip/pinctrl.rs | 2 +- drivers/ax-driver/src/soc/rockchip/pm.rs | 2 +- drivers/ax-driver/src/soc/scmi.rs | 7 +- drivers/ax-driver/src/time.rs | 2 +- drivers/ax-driver/src/usb/dwc.rs | 2 +- drivers/ax-driver/src/usb/xhci_mmio.rs | 2 +- drivers/ax-driver/src/usb/xhci_pci.rs | 2 +- drivers/ax-driver/src/virtio/block.rs | 4 +- drivers/ax-driver/src/virtio/display.rs | 2 +- drivers/ax-driver/src/virtio/input.rs | 2 +- drivers/ax-driver/src/virtio/mod.rs | 78 +++---- drivers/ax-driver/src/virtio/net.rs | 2 +- drivers/ax-driver/src/virtio/vsock.rs | 2 +- drivers/ax-driver/tests/model_register.rs | 21 ++ drivers/rdrive/src/lib.rs | 10 +- drivers/rdrive/src/probe/static_.rs | 184 +--------------- drivers/rdrive/tests/init_sources.rs | 18 +- drivers/rdrive/tests/phase1.rs | 14 +- .../configs/board/licheerv-nano-sg2002.toml | 1 - os/arceos/modules/axhal/Cargo.toml | 3 + os/arceos/modules/axhal/src/dummy.rs | 9 - os/arceos/modules/axhal/src/lib.rs | 13 -- os/arceos/modules/axruntime/src/lib.rs | 2 +- platform/riscv64-visionfive2/src/drivers.rs | 14 +- platform/x86-qemu-q35/Cargo.toml | 3 +- platform/x86-qemu-q35/src/drivers.rs | 30 ++- platform/x86-qemu-q35/src/lib.rs | 3 + .../build-riscv64gc-unknown-none-elf.toml | 2 +- 75 files changed, 840 insertions(+), 720 deletions(-) delete mode 100644 components/axplat_crates/axplat/src/drivers.rs delete mode 100644 components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs create mode 100644 components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/cvsd.rs create mode 100644 components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/mod.rs delete mode 100644 drivers/ax-driver/src/block/cvsd.rs create mode 100644 drivers/ax-driver/tests/model_register.rs diff --git a/Cargo.lock b/Cargo.lock index 28751eaa9f..fe637345d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -897,6 +897,7 @@ dependencies = [ "rdif-pcie", "rdif-vsock", "rdrive", + "rdrive-macros", "realtek-rtl8125", "rk3588-pci", "rockchip-npu", @@ -904,7 +905,6 @@ dependencies = [ "rockchip-soc", "sdhci-host", "sdmmc-protocol", - "sg200x-bsp", "simple-ahci", "some-serial", "spin", @@ -1334,7 +1334,6 @@ dependencies = [ "ax-plat-aarch64-peripherals", "dw_apb_uart", "log", - "rdrive", ] [[package]] @@ -1364,7 +1363,6 @@ dependencies = [ "ax-plat", "ax-plat-aarch64-peripherals", "log", - "rdrive", ] [[package]] @@ -1373,11 +1371,12 @@ version = "0.5.10" dependencies = [ "ax-config-macros", "ax-cpu", + "ax-driver", "ax-page-table-entry", "ax-plat", "ax-plat-aarch64-peripherals", + "axklib", "log", - "rdrive", ] [[package]] @@ -1391,7 +1390,6 @@ dependencies = [ "ax-plat", "ax-plat-aarch64-peripherals", "log", - "rdrive", ] [[package]] @@ -1400,14 +1398,15 @@ version = "0.5.10" dependencies = [ "ax-config-macros", "ax-cpu", + "ax-driver", "ax-kspin", "ax-lazyinit", "ax-page-table-entry", "ax-plat", + "axklib", "chrono", "log", "loongArch64", - "rdrive", "uart_16550 0.5.0", ] @@ -1427,12 +1426,13 @@ version = "0.5.9" dependencies = [ "ax-config-macros", "ax-cpu", + "ax-driver", "ax-kspin", "ax-lazyinit", "ax-plat", "ax-riscv-plic", + "axklib", "log", - "rdrive", "riscv 0.16.0", "riscv_goldfish", "sbi-rt 0.0.3", @@ -1445,15 +1445,19 @@ version = "0.3.6" dependencies = [ "ax-config-macros", "ax-cpu", + "ax-driver", "ax-kspin", "ax-lazyinit", "ax-plat", "ax-riscv-plic", + "axklib", "dw_uart_rs", "log", - "rdrive", + "rd-block", "riscv 0.16.0", "sbi-rt 0.0.3", + "sg200x-bsp", + "spin", ] [[package]] @@ -1462,17 +1466,18 @@ version = "0.5.10" dependencies = [ "ax-config-macros", "ax-cpu", + "ax-driver", "ax-int-ratio", "ax-kspin", "ax-lazyinit", "ax-percpu", "ax-plat", + "axklib", "bitflags 2.11.1", "heapless 0.9.3", "log", "multiboot", "raw-cpuid 11.6.0", - "rdrive", "uart_16550 0.5.0", "x2apic", "x86", @@ -1838,17 +1843,18 @@ version = "0.4.8" dependencies = [ "ax-config-macros", "ax-cpu", + "ax-driver", "ax-int-ratio", "ax-kspin", "ax-lazyinit", "ax-percpu", "ax-plat", + "axklib", "bitflags 2.11.1", "heapless 0.9.3", "log", "multiboot", "raw-cpuid 11.6.0", - "rdrive", "uart_16550 0.4.0", "x2apic", "x86", @@ -4731,9 +4737,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "6835eea34fb6321b9b3aa7b685c2b433948c09447e389dc017fdf687d5d11e65" dependencies = [ "jiff-static", "log", @@ -4744,9 +4750,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "3c22e04db9c58f5136eb1757f3d5c49a7b187f49e52185228cbd2f5acdfcc08c" dependencies = [ "proc-macro2", "quote", @@ -5103,9 +5109,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "loongArch64" diff --git a/components/axplat_crates/axplat/src/drivers.rs b/components/axplat_crates/axplat/src/drivers.rs deleted file mode 100644 index 1f2e62b451..0000000000 --- a/components/axplat_crates/axplat/src/drivers.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Static platform driver resources. - -use rdrive::probe::static_::StaticDeviceDesc; - -/// Static platform driver resource interface. -/// -/// Static platforms describe probe inputs here. Driver implementations are -/// registered through `.driver.register*` and consume these resources through -/// `ProbeKind::Static`. -#[def_plat_interface] -pub trait DriversIf { - /// Returns the static device descriptors exposed by the platform. - fn static_devices_fn() -> &'static [StaticDeviceDesc]; -} - -/// Returns the static device descriptors exposed by the platform. -pub fn static_devices() -> &'static [StaticDeviceDesc] { - crate::__priv::call_interface!(DriversIf::static_devices_fn) -} diff --git a/components/axplat_crates/axplat/src/lib.rs b/components/axplat_crates/axplat/src/lib.rs index 118b68cc8c..f67d7322f7 100644 --- a/components/axplat_crates/axplat/src/lib.rs +++ b/components/axplat_crates/axplat/src/lib.rs @@ -6,7 +6,6 @@ extern crate ax_plat_macros; pub mod console; -pub mod drivers; pub mod init; #[cfg(feature = "irq")] pub mod irq; diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml index 671960715b..2c30017efb 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml @@ -25,7 +25,6 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } -rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs index 03b8fc9449..206dcb44a1 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs @@ -1,11 +1 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::StaticDeviceDesc; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - &[] - } -} +// This platform has no static model drivers. diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml index 91eef558fe..ec22899bf5 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml @@ -22,7 +22,6 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } -rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs index 03b8fc9449..206dcb44a1 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs @@ -1,11 +1 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::StaticDeviceDesc; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - &[] - } -} +// This platform has no static model drivers. diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml index 5f33f2192c..948e148731 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml @@ -12,6 +12,7 @@ license = "Apache-2.0" [features] fp-simd = ["ax-cpu/fp-simd"] irq = ["ax-plat/irq", "ax-plat-aarch64-peripherals/irq"] +paging = ["dep:axklib"] rtc = [] smp = ["ax-plat/smp"] # Enable the GICv3 distributor + redistributor + system-register CPU @@ -29,8 +30,10 @@ ax-page-table-entry = { workspace = true } ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } +ax-driver = { workspace = true, features = ["pci", "virtio"] } ax-plat = { workspace = true } -rdrive.workspace = true +axklib = { workspace = true, optional = true } +mmio-api.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs index 3004c26739..7ebf1098f0 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs @@ -1,28 +1,157 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; +use alloc::format; +#[cfg(not(feature = "paging"))] +use core::ptr::NonNull; + +use ax_driver::{PlatformDevice, probe::OnProbeError}; +#[cfg(not(feature = "paging"))] +use ax_plat::mem::{pa, phys_to_virt}; +use mmio_api::MmioRaw; +#[cfg(not(feature = "paging"))] +use mmio_api::{MapError, MmioAddr, MmioOp}; use crate::config::devices; const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; const PCI_LEGACY_IRQS: &[usize] = &[35, 36, 37, 38]; +#[cfg(not(feature = "paging"))] +static DIRECT_MMIO: DirectMmio = DirectMmio; + +#[cfg(not(feature = "paging"))] +struct DirectMmio; + +#[cfg(not(feature = "paging"))] +impl MmioOp for DirectMmio { + fn ioremap(&self, addr: MmioAddr, size: usize) -> Result { + let ptr = NonNull::new(phys_to_virt(pa!(addr.as_usize())).as_mut_ptr()) + .ok_or(MapError::Invalid)?; + Ok(unsafe { MmioRaw::new(addr, ptr, size) }) + } + + fn iounmap(&self, _mmio: &MmioRaw) {} +} + +fn map_mmio_raw(base: usize, size: usize) -> Result { + #[cfg(feature = "paging")] + { + axklib::mmio::ioremap_raw(base.into(), size) + } + #[cfg(not(feature = "paging"))] + { + DIRECT_MMIO.ioremap(base.into(), size) + } +} + +fn pcie_mmio_op() -> &'static dyn mmio_api::MmioOp { + #[cfg(feature = "paging")] + { + axklib::mmio::op() + } + #[cfg(not(feature = "paging"))] + { + &DIRECT_MMIO + } +} -static STATIC_DEVICES: &[StaticDeviceDesc] = &[ - StaticDeviceDesc::new("virtio-mmio") - .with_regs(devices::VIRTIO_MMIO_RANGES) - .with_probe_each_reg(), - StaticDeviceDesc::new("pci-ecam") - .with_irqs(PCI_LEGACY_IRQS) - .with_pci_ecam( - StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE) - .with_ranges(devices::PCI_RANGES), - ), -]; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES +mod pci_ecam { + use super::*; + + ax_driver::model_register!( + name: "Static PCIe ECAM", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], + ); + + fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + if !ax_driver::pci::has_static_endpoint_drivers() { + return Err(OnProbeError::NotMatch); + } + + let mem32 = ax_driver::pci::pci_mem32_from_ranges(devices::PCI_RANGES); + let mem64 = ax_driver::pci::pci_mem64_from_ranges(devices::PCI_RANGES); + ax_driver::pci::register_static_legacy_irq_routes(PCI_LEGACY_IRQS, PCI_ECAM_SIZE); + ax_driver::pci::register_ecam_controller_with_mmio_op( + plat_dev, + devices::PCI_ECAM_BASE, + PCI_ECAM_SIZE, + mem32, + mem64, + pcie_mmio_op(), + ) } } + +fn register_virtio_mmio(plat_dev: PlatformDevice, index: usize) -> Result<(), OnProbeError> { + if !ax_driver::virtio::has_static_mmio_drivers() { + return Err(OnProbeError::NotMatch); + } + + let Some((base, size)) = devices::VIRTIO_MMIO_RANGES.get(index).copied() else { + return Err(OnProbeError::NotMatch); + }; + + let mmio = map_mmio_raw(base, size).map_err(|err| { + OnProbeError::other(format!("failed to map virtio-mmio {base:#x}: {err:?}")) + })?; + let Some((ty, transport)) = ax_driver::virtio::probe_mmio_device(mmio.as_ptr(), size) else { + return Err(OnProbeError::NotMatch); + }; + + ax_driver::virtio::register_static_transport(plat_dev, ty, transport) +} + +macro_rules! virtio_mmio_driver { + ($mod_name:ident, $driver_name:literal, $index:expr) => { + mod $mod_name { + use super::*; + + ax_driver::model_register!( + name: $driver_name, + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], + ); + + fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + register_virtio_mmio(plat_dev, $index) + } + } + }; +} + +virtio_mmio_driver!(virtio_mmio_0, "Static VirtIO MMIO 0", 0); +virtio_mmio_driver!(virtio_mmio_1, "Static VirtIO MMIO 1", 1); +virtio_mmio_driver!(virtio_mmio_2, "Static VirtIO MMIO 2", 2); +virtio_mmio_driver!(virtio_mmio_3, "Static VirtIO MMIO 3", 3); +virtio_mmio_driver!(virtio_mmio_4, "Static VirtIO MMIO 4", 4); +virtio_mmio_driver!(virtio_mmio_5, "Static VirtIO MMIO 5", 5); +virtio_mmio_driver!(virtio_mmio_6, "Static VirtIO MMIO 6", 6); +virtio_mmio_driver!(virtio_mmio_7, "Static VirtIO MMIO 7", 7); +virtio_mmio_driver!(virtio_mmio_8, "Static VirtIO MMIO 8", 8); +virtio_mmio_driver!(virtio_mmio_9, "Static VirtIO MMIO 9", 9); +virtio_mmio_driver!(virtio_mmio_10, "Static VirtIO MMIO 10", 10); +virtio_mmio_driver!(virtio_mmio_11, "Static VirtIO MMIO 11", 11); +virtio_mmio_driver!(virtio_mmio_12, "Static VirtIO MMIO 12", 12); +virtio_mmio_driver!(virtio_mmio_13, "Static VirtIO MMIO 13", 13); +virtio_mmio_driver!(virtio_mmio_14, "Static VirtIO MMIO 14", 14); +virtio_mmio_driver!(virtio_mmio_15, "Static VirtIO MMIO 15", 15); +virtio_mmio_driver!(virtio_mmio_16, "Static VirtIO MMIO 16", 16); +virtio_mmio_driver!(virtio_mmio_17, "Static VirtIO MMIO 17", 17); +virtio_mmio_driver!(virtio_mmio_18, "Static VirtIO MMIO 18", 18); +virtio_mmio_driver!(virtio_mmio_19, "Static VirtIO MMIO 19", 19); +virtio_mmio_driver!(virtio_mmio_20, "Static VirtIO MMIO 20", 20); +virtio_mmio_driver!(virtio_mmio_21, "Static VirtIO MMIO 21", 21); +virtio_mmio_driver!(virtio_mmio_22, "Static VirtIO MMIO 22", 22); +virtio_mmio_driver!(virtio_mmio_23, "Static VirtIO MMIO 23", 23); +virtio_mmio_driver!(virtio_mmio_24, "Static VirtIO MMIO 24", 24); +virtio_mmio_driver!(virtio_mmio_25, "Static VirtIO MMIO 25", 25); +virtio_mmio_driver!(virtio_mmio_26, "Static VirtIO MMIO 26", 26); +virtio_mmio_driver!(virtio_mmio_27, "Static VirtIO MMIO 27", 27); +virtio_mmio_driver!(virtio_mmio_28, "Static VirtIO MMIO 28", 28); +virtio_mmio_driver!(virtio_mmio_29, "Static VirtIO MMIO 29", 29); +virtio_mmio_driver!(virtio_mmio_30, "Static VirtIO MMIO 30", 30); +virtio_mmio_driver!(virtio_mmio_31, "Static VirtIO MMIO 31", 31); diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs index e06c0596fb..338b958949 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs @@ -1,4 +1,8 @@ #![no_std] +#![feature(used_with_arg)] + +extern crate alloc; +extern crate ax_driver as _; #[macro_use] extern crate ax_plat; diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml index ab0aa5dc77..fd1626eefa 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml @@ -23,7 +23,6 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } -rdrive.workspace = true [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs b/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs index 03b8fc9449..206dcb44a1 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs @@ -1,11 +1 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::StaticDeviceDesc; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - &[] - } -} +// This platform has no static model drivers. diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml index a52768466d..87fbda9b75 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml @@ -12,6 +12,7 @@ license = "Apache-2.0" [features] fp-simd = ["ax-cpu/fp-simd"] irq = ["ax-plat/irq"] +paging = ["dep:axklib"] rtc = ["dep:chrono"] smp = ["ax-plat/smp", "ax-kspin/smp", "irq"] @@ -26,8 +27,10 @@ uart_16550 = "0.5" ax-config-macros = { workspace = true } ax-cpu = { workspace = true } +ax-driver = { workspace = true, features = ["pci"] } ax-plat = { workspace = true } -rdrive.workspace = true +axklib = { workspace = true, optional = true } +mmio-api.workspace = true [package.metadata.docs.rs] targets = ["loongarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs index dcf69c738c..76b67609ed 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs @@ -1,22 +1,62 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; +#[cfg(not(feature = "paging"))] +use core::ptr::NonNull; + +use ax_driver::{PlatformDevice, probe::OnProbeError}; +#[cfg(not(feature = "paging"))] +use ax_plat::mem::{pa, phys_to_virt}; +#[cfg(not(feature = "paging"))] +use mmio_api::{MapError, MmioAddr, MmioOp, MmioRaw}; use crate::config::devices; const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; const PCI_LEGACY_IRQS: &[usize] = &[16, 17, 18, 19]; +#[cfg(not(feature = "paging"))] +static DIRECT_MMIO: DirectMmio = DirectMmio; + +#[cfg(not(feature = "paging"))] +struct DirectMmio; + +#[cfg(not(feature = "paging"))] +impl MmioOp for DirectMmio { + fn ioremap(&self, addr: MmioAddr, size: usize) -> Result { + let ptr = NonNull::new(phys_to_virt(pa!(addr.as_usize())).as_mut_ptr()) + .ok_or(MapError::Invalid)?; + Ok(unsafe { MmioRaw::new(addr, ptr, size) }) + } -static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("pci-ecam") - .with_irqs(PCI_LEGACY_IRQS) - .with_pci_ecam( - StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE).with_ranges(devices::PCI_RANGES), - )]; + fn iounmap(&self, _mmio: &MmioRaw) {} +} -struct DriversIfImpl; +ax_driver::model_register!( + name: "Static PCIe ECAM", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], +); -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES +fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + if !ax_driver::pci::has_static_endpoint_drivers() { + return Err(OnProbeError::NotMatch); } + + let mem32 = ax_driver::pci::pci_mem32_from_ranges(devices::PCI_RANGES); + let mem64 = ax_driver::pci::pci_mem64_from_ranges(devices::PCI_RANGES); + ax_driver::pci::register_static_legacy_irq_routes(PCI_LEGACY_IRQS, PCI_ECAM_SIZE); + + #[cfg(feature = "paging")] + let mmio_op = axklib::mmio::op(); + #[cfg(not(feature = "paging"))] + let mmio_op = &DIRECT_MMIO; + + ax_driver::pci::register_ecam_controller_with_mmio_op( + plat_dev, + devices::PCI_ECAM_BASE, + PCI_ECAM_SIZE, + mem32, + mem64, + mmio_op, + ) } diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs index 6bec199b1f..5dc8128f4c 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs @@ -1,4 +1,7 @@ #![no_std] +#![feature(used_with_arg)] + +extern crate ax_driver as _; #[macro_use] extern crate log; diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml index 19e657b26d..6cdc7a6442 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml @@ -13,6 +13,7 @@ license = "Apache-2.0" fp-simd = ["ax-cpu/fp-simd"] hypervisor = ["irq", "smp"] irq = ["ax-plat/irq", "dep:ax-riscv-plic"] +paging = ["dep:axklib"] rtc = ["dep:riscv_goldfish"] smp = ["ax-plat/smp"] @@ -28,8 +29,10 @@ some-serial.workspace = true ax-config-macros = { workspace = true } ax-cpu = { workspace = true } +ax-driver = { workspace = true, features = ["pci", "virtio"] } ax-plat = { workspace = true } -rdrive.workspace = true +axklib = { workspace = true, optional = true } +mmio-api.workspace = true [package.metadata.docs.rs] targets = ["riscv64gc-unknown-none-elf"] diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs index 70d11659db..05716514ab 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs @@ -1,28 +1,133 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; +use alloc::format; +#[cfg(not(feature = "paging"))] +use core::ptr::NonNull; + +use ax_driver::{PlatformDevice, probe::OnProbeError}; +#[cfg(not(feature = "paging"))] +use ax_plat::mem::{pa, phys_to_virt}; +use mmio_api::MmioRaw; +#[cfg(not(feature = "paging"))] +use mmio_api::{MapError, MmioAddr, MmioOp}; use crate::config::devices; const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; const PCI_LEGACY_IRQS: &[usize] = &[32, 33, 34, 35]; +#[cfg(not(feature = "paging"))] +static DIRECT_MMIO: DirectMmio = DirectMmio; + +#[cfg(not(feature = "paging"))] +struct DirectMmio; + +#[cfg(not(feature = "paging"))] +impl MmioOp for DirectMmio { + fn ioremap(&self, addr: MmioAddr, size: usize) -> Result { + let ptr = NonNull::new(phys_to_virt(pa!(addr.as_usize())).as_mut_ptr()) + .ok_or(MapError::Invalid)?; + Ok(unsafe { MmioRaw::new(addr, ptr, size) }) + } + + fn iounmap(&self, _mmio: &MmioRaw) {} +} + +fn map_mmio_raw(base: usize, size: usize) -> Result { + #[cfg(feature = "paging")] + { + axklib::mmio::ioremap_raw(base.into(), size) + } + #[cfg(not(feature = "paging"))] + { + DIRECT_MMIO.ioremap(base.into(), size) + } +} + +fn pcie_mmio_op() -> &'static dyn mmio_api::MmioOp { + #[cfg(feature = "paging")] + { + axklib::mmio::op() + } + #[cfg(not(feature = "paging"))] + { + &DIRECT_MMIO + } +} -static STATIC_DEVICES: &[StaticDeviceDesc] = &[ - StaticDeviceDesc::new("virtio-mmio") - .with_regs(devices::VIRTIO_MMIO_RANGES) - .with_probe_each_reg(), - StaticDeviceDesc::new("pci-ecam") - .with_irqs(PCI_LEGACY_IRQS) - .with_pci_ecam( - StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE) - .with_ranges(devices::PCI_RANGES), - ), -]; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES +mod pci_ecam { + use super::*; + + ax_driver::model_register!( + name: "Static PCIe ECAM", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], + ); + + fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + if !ax_driver::pci::has_static_endpoint_drivers() { + return Err(OnProbeError::NotMatch); + } + + let mem32 = ax_driver::pci::pci_mem32_from_ranges(devices::PCI_RANGES); + let mem64 = ax_driver::pci::pci_mem64_from_ranges(devices::PCI_RANGES); + ax_driver::pci::register_static_legacy_irq_routes(PCI_LEGACY_IRQS, PCI_ECAM_SIZE); + ax_driver::pci::register_ecam_controller_with_mmio_op( + plat_dev, + devices::PCI_ECAM_BASE, + PCI_ECAM_SIZE, + mem32, + mem64, + pcie_mmio_op(), + ) } } + +fn register_virtio_mmio(plat_dev: PlatformDevice, index: usize) -> Result<(), OnProbeError> { + if !ax_driver::virtio::has_static_mmio_drivers() { + return Err(OnProbeError::NotMatch); + } + + let Some((base, size)) = devices::VIRTIO_MMIO_RANGES.get(index).copied() else { + return Err(OnProbeError::NotMatch); + }; + + let mmio = map_mmio_raw(base, size).map_err(|err| { + OnProbeError::other(format!("failed to map virtio-mmio {base:#x}: {err:?}")) + })?; + let Some((ty, transport)) = ax_driver::virtio::probe_mmio_device(mmio.as_ptr(), size) else { + return Err(OnProbeError::NotMatch); + }; + + ax_driver::virtio::register_static_transport(plat_dev, ty, transport) +} + +macro_rules! virtio_mmio_driver { + ($mod_name:ident, $driver_name:literal, $index:expr) => { + mod $mod_name { + use super::*; + + ax_driver::model_register!( + name: $driver_name, + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], + ); + + fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + register_virtio_mmio(plat_dev, $index) + } + } + }; +} + +virtio_mmio_driver!(virtio_mmio_0, "Static VirtIO MMIO 0", 0); +virtio_mmio_driver!(virtio_mmio_1, "Static VirtIO MMIO 1", 1); +virtio_mmio_driver!(virtio_mmio_2, "Static VirtIO MMIO 2", 2); +virtio_mmio_driver!(virtio_mmio_3, "Static VirtIO MMIO 3", 3); +virtio_mmio_driver!(virtio_mmio_4, "Static VirtIO MMIO 4", 4); +virtio_mmio_driver!(virtio_mmio_5, "Static VirtIO MMIO 5", 5); +virtio_mmio_driver!(virtio_mmio_6, "Static VirtIO MMIO 6", 6); +virtio_mmio_driver!(virtio_mmio_7, "Static VirtIO MMIO 7", 7); diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs index 6a8d835445..b3c18cbec9 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs @@ -1,4 +1,8 @@ #![no_std] +#![feature(used_with_arg)] + +extern crate alloc; +extern crate ax_driver as _; #[macro_use] extern crate log; diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml b/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml index e1a6ad3639..b08b88146e 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml @@ -25,10 +25,14 @@ dw_uart_rs = "0.2.0" log = { workspace = true } ax-config-macros = { workspace = true } ax-cpu = { workspace = true, features = ["xuantie-c9xx"] } +ax-driver = { workspace = true, features = ["block"] } ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } ax-plat = { workspace = true } -rdrive.workspace = true +axklib.workspace = true +rd-block.workspace = true +sg200x-bsp.workspace = true +spin.workspace = true [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] ax-riscv-plic = { workspace = true, optional = true } diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs deleted file mode 100644 index 9919c1f3cd..0000000000 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers.rs +++ /dev/null @@ -1,20 +0,0 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::StaticDeviceDesc; - -use crate::config::devices; - -static CVSD_REGS: &[(usize, usize)] = &[ - (devices::CVSD_PADDR, 0x1000), - (devices::SYSCON_PADDR, 0x8000), -]; - -static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("cvsd").with_regs(CVSD_REGS)]; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES - } -} diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/cvsd.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/cvsd.rs new file mode 100644 index 0000000000..f3d99d5851 --- /dev/null +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/cvsd.rs @@ -0,0 +1,208 @@ +use alloc::{boxed::Box, format, sync::Arc}; + +use ax_driver::{PlatformDevice, block::PlatformDeviceBlock, probe::OnProbeError}; +use rd_block::{BlkError, BuffConfig, DriverGeneric, Event, IQueue, Interface, Request, RequestId}; +use sg200x_bsp::sdmmc::Sdmmc; +use spin::Mutex; + +use crate::config::devices; + +const BLOCK_SIZE: usize = 512; +const SDMMC_SIZE: usize = 0x1000; +const SYSCON_SIZE: usize = 0x8000; +pub const DEVICE_NAME: &str = "cvsd"; + +ax_driver::model_register!( + name: "Static CVSD", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], +); + +fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + register_mmio( + plat_dev, + devices::CVSD_PADDR, + SDMMC_SIZE, + devices::SYSCON_PADDR, + SYSCON_SIZE, + ) +} + +fn register_mmio( + plat_dev: PlatformDevice, + sdmmc_paddr: usize, + sdmmc_size: usize, + syscon_paddr: usize, + syscon_size: usize, +) -> Result<(), OnProbeError> { + if sdmmc_paddr == 0 || sdmmc_size == 0 || syscon_paddr == 0 || syscon_size == 0 { + return Err(OnProbeError::NotMatch); + } + + let sdmmc = map_region(sdmmc_paddr, sdmmc_size, "CVSD")?; + let syscon = map_region(syscon_paddr, syscon_size, "SYSCON")?; + let driver = + CvsdDriver::new(sdmmc, syscon).map_err(|_| OnProbeError::other("CVSD init failed"))?; + plat_dev.register_block(CvsdBlock::new(driver)); + Ok(()) +} + +fn map_region(address: usize, size: usize, name: &str) -> Result { + let mmio = axklib::mmio::ioremap_raw(address.into(), size) + .map_err(|err| OnProbeError::other(format!("failed to map {name}: {err:?}")))?; + Ok(mmio.as_ptr() as usize) +} + +struct CvsdDriver(Sdmmc); + +// The SG2002 SD/MMC core stores MMIO registers as `UnsafeCell`-backed +// references, so the raw register block is intentionally not `Sync`. +// `CvsdDriver` is owned by `CvsdBlock`, which serializes all access through a +// mutex and never clones the driver, so moving that owner between execution +// contexts is sound. +unsafe impl Send for CvsdDriver {} + +impl CvsdDriver { + fn new(sdmmc: usize, syscon: usize) -> Result { + let sdmmc = unsafe { Sdmmc::new(sdmmc, syscon) }; + sdmmc.init().map_err(|_| ())?; + sdmmc.clk_en(true); + Ok(Self(sdmmc)) + } + + fn num_blocks(&self) -> u64 { + self.0.card_capacity_blocks() + } + + fn checked_lba(block_id: u64, offset: usize) -> Result { + let lba = block_id + .checked_add(offset as u64) + .ok_or(BlkError::InvalidBlockIndex(block_id as usize))?; + u32::try_from(lba).map_err(|_| BlkError::InvalidBlockIndex(block_id as usize)) + } + + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), BlkError> { + if !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(BlkError::NotSupported); + } + + for (i, block) in buf.chunks_exact_mut(BLOCK_SIZE).enumerate() { + self.0 + .read_block(Self::checked_lba(block_id, i)?, block) + .map_err(|_| BlkError::Other("CVSD read failed".into()))?; + } + Ok(()) + } + + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), BlkError> { + if !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(BlkError::NotSupported); + } + + for (i, block) in buf.chunks_exact(BLOCK_SIZE).enumerate() { + self.0 + .write_block(Self::checked_lba(block_id, i)?, block) + .map_err(|_| BlkError::Other("CVSD write failed".into()))?; + } + Ok(()) + } +} + +struct CvsdBlock { + inner: Arc>, + queue_created: bool, + irq_enabled: bool, +} + +impl CvsdBlock { + fn new(driver: CvsdDriver) -> Self { + Self { + inner: Arc::new(Mutex::new(driver)), + queue_created: false, + irq_enabled: false, + } + } +} + +impl DriverGeneric for CvsdBlock { + fn name(&self) -> &str { + DEVICE_NAME + } +} + +impl Interface for CvsdBlock { + fn create_queue(&mut self) -> Option> { + if self.queue_created { + return None; + } + self.queue_created = true; + Some(Box::new(CvsdQueue { + id: 0, + inner: Arc::clone(&self.inner), + })) + } + + fn enable_irq(&mut self) { + self.irq_enabled = true; + } + + fn disable_irq(&mut self) { + self.irq_enabled = false; + } + + fn is_irq_enabled(&self) -> bool { + self.irq_enabled + } + + fn handle_irq(&mut self) -> Event { + Event::none() + } +} + +struct CvsdQueue { + id: usize, + inner: Arc>, +} + +impl IQueue for CvsdQueue { + fn id(&self) -> usize { + self.id + } + + fn num_blocks(&self) -> usize { + self.inner.lock().num_blocks() as usize + } + + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn buff_config(&self) -> BuffConfig { + BuffConfig { + dma_mask: u64::MAX, + align: 0x1000, + size: self.block_size(), + } + } + + fn submit_request(&mut self, request: Request<'_>) -> Result { + match request.kind { + rd_block::RequestKind::Read(mut buffer) => self + .inner + .lock() + .read_blocks(request.block_id as u64, &mut buffer)?, + rd_block::RequestKind::Write(items) => self + .inner + .lock() + .write_blocks(request.block_id as u64, items)?, + } + Ok(RequestId::new(0)) + } + + fn poll_request(&mut self, _request: RequestId) -> Result<(), BlkError> { + Ok(()) + } +} diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/mod.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/mod.rs new file mode 100644 index 0000000000..b19ba2f7a1 --- /dev/null +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/mod.rs @@ -0,0 +1 @@ +mod cvsd; diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs index e0e2d7b2eb..2762b386f9 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs @@ -1,27 +1,22 @@ #![no_std] +#![feature(used_with_arg)] +#![cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + +extern crate alloc; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] #[macro_use] extern crate log; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] #[macro_use] extern crate ax_plat; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod boot; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod console; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod drivers; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod init; -#[cfg(all(feature = "irq", any(target_arch = "riscv32", target_arch = "riscv64")))] +#[cfg(feature = "irq")] mod irq; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod mem; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod power; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] mod time; pub mod config { diff --git a/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml b/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml index d23da85083..808ec12786 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml @@ -26,8 +26,9 @@ ax-percpu.workspace = true heapless = "0.9" ax-config-macros = { workspace = true } ax-cpu = { workspace = true } +ax-driver = { workspace = true, features = ["pci"] } ax-plat = { workspace = true } -rdrive.workspace = true +axklib.workspace = true x86 = "0.52" x86_64 = "0.15.2" diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs b/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs index 19df95039d..24ddd287be 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs +++ b/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs @@ -1,18 +1,28 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; +use ax_driver::{PlatformDevice, probe::OnProbeError}; use crate::config::devices; const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; -static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("pci-ecam") - .with_pci_ecam(StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE))]; +ax_driver::model_register!( + name: "Static PCIe ECAM", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], +); -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES +fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + if !ax_driver::pci::has_static_endpoint_drivers() { + return Err(OnProbeError::NotMatch); } + + ax_driver::pci::register_ecam_controller( + plat_dev, + devices::PCI_ECAM_BASE, + PCI_ECAM_SIZE, + None, + None, + ) } diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs b/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs index f0de88d544..b0dd0ce59d 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs @@ -1,4 +1,7 @@ #![no_std] +#![feature(used_with_arg)] + +extern crate ax_driver as _; #[macro_use] extern crate log; diff --git a/docs/docs/architecture/rdrive-rdif.md b/docs/docs/architecture/rdrive-rdif.md index 2669eab648..eeda79b3f2 100644 --- a/docs/docs/architecture/rdrive-rdif.md +++ b/docs/docs/architecture/rdrive-rdif.md @@ -96,7 +96,7 @@ flowchart TB ```rust pub enum PlatformSource { - Static(&'static [StaticDeviceDesc]), + Static, Fdt(core::ptr::NonNull), Acpi(AcpiRoot), } @@ -113,7 +113,7 @@ pub enum ProbeKind { | backend | 独立状态 | 匹配输入 | probe 输入 | 第一版行为 | | --- | --- | --- | --- | --- | -| `probe::static_` | `System { devices, probed }` | `StaticDeviceDesc` | `StaticInfo` + `PlatformDevice` | 支持静态平台常量注册 | +| `probe::static_` | `System { probed_names }` | `ProbeKind::Static` register name | `PlatformDevice` | 平台 crate 自己注册静态 model driver 并在回调中使用平台常量 | | `probe::fdt` | `System { fdt, phandle_map, probed }` | compatible + node status | `FdtInfo` + `PlatformDevice` | 保留当前 FDT 能力 | | `probe::acpi` | `System { root, probed }` | HID/CID + ACPI device | `AcpiInfo` + `PlatformDevice` | API 存在,返回 unsupported | | `probe::pci` | PCIe controller enumerator | vendor/device/class | endpoint + `PlatformDevice` | 保留当前 PCIe 二阶段 probe | diff --git a/drivers/ax-driver/Cargo.toml b/drivers/ax-driver/Cargo.toml index d3d3b8a066..a37c270951 100644 --- a/drivers/ax-driver/Cargo.toml +++ b/drivers/ax-driver/Cargo.toml @@ -34,7 +34,6 @@ virtio-input = ["input", "virtio"] virtio-socket = ["vsock", "virtio"] ramdisk = ["block", "dep:ramdisk"] -cvsd = ["block", "dep:sg200x-bsp"] bcm2835-sdhci = ["block", "dep:bcm2835-sdhci"] ahci = ["block", "pci", "dep:simple-ahci"] ixgbe = ["net", "pci", "dep:ax-alloc", "dep:ixgbe-driver"] @@ -122,6 +121,7 @@ rdif-pcie = { workspace = true, optional = true } rdif-vsock = { workspace = true, optional = true } realtek-rtl8125 = { workspace = true, optional = true } rdrive.workspace = true +rdrive-macros.workspace = true rk3588-pci = { workspace = true, optional = true } rockchip-npu = { workspace = true, optional = true } rockchip-pm = { workspace = true, optional = true } @@ -129,7 +129,6 @@ rockchip-soc = { workspace = true, optional = true } phytium-mci-host = { workspace = true, optional = true } sdhci-host = { workspace = true, optional = true } sdmmc-protocol = { workspace = true, default-features = false, optional = true } -sg200x-bsp = { workspace = true, optional = true } simple-ahci = { workspace = true, optional = true } some-serial = { workspace = true, optional = true } spin = { workspace = true, optional = true } diff --git a/drivers/ax-driver/build.rs b/drivers/ax-driver/build.rs index da5dc6d3af..50d1994b23 100644 --- a/drivers/ax-driver/build.rs +++ b/drivers/ax-driver/build.rs @@ -37,19 +37,8 @@ fn enable_cfg_flag(key: &str) { fn main() { let has_virtio_core = has_feature("virtio-core"); let has_virtio_dev = has_any_feature(VIRTIO_DEV_FEATURES); - let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); - let target_has_cvsd = matches!(target_arch.as_str(), "riscv32" | "riscv64"); let has_pci = has_feature("pci"); let has_fdt = has_feature("fdt"); - let has_static = has_any_feature(&[ - "pci", - "virtio-blk", - "virtio-gpu", - "virtio-input", - "virtio-net", - "virtio-socket", - "cvsd", - ]); if has_pci { enable_cfg("probe", "pci"); @@ -57,19 +46,16 @@ fn main() { if has_fdt { enable_cfg("probe", "fdt"); } - if has_static { - enable_cfg("probe", "static"); - } if has_virtio_core || has_virtio_dev { enable_cfg_flag("virtio_dev"); } - if has_any_feature(&["ahci", "bcm2835-sdhci"]) || (has_feature("cvsd") && target_has_cvsd) { + if has_any_feature(&["ahci", "bcm2835-sdhci"]) { enable_cfg_flag("sync_block_dev"); } println!( "cargo::rustc-check-cfg=cfg(probe, values({}))", - make_cfg_values(&["pci", "fdt", "static"]) + make_cfg_values(&["pci", "fdt"]) ); println!("cargo::rustc-check-cfg=cfg(virtio_dev)"); println!("cargo::rustc-check-cfg=cfg(sync_block_dev)"); diff --git a/drivers/ax-driver/src/block/ahci.rs b/drivers/ax-driver/src/block/ahci.rs index 31404c922c..c7b54ce28c 100644 --- a/drivers/ax-driver/src/block/ahci.rs +++ b/drivers/ax-driver/src/block/ahci.rs @@ -16,7 +16,7 @@ use super::{SyncBlockOps, register_sync_block}; pub const DEVICE_NAME: &str = "ahci"; -module_driver!( +crate::model_register!( name: "AHCI", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/block/cvsd.rs b/drivers/ax-driver/src/block/cvsd.rs deleted file mode 100644 index 9074b4bd18..0000000000 --- a/drivers/ax-driver/src/block/cvsd.rs +++ /dev/null @@ -1,160 +0,0 @@ -use rdrive::{PlatformDevice, probe::OnProbeError}; -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -use {alloc::format, sg200x_bsp::sdmmc::Sdmmc}; - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -use super::{SyncBlockOps, register_sync_block}; - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -const BLOCK_SIZE: usize = 512; -pub const DEVICE_NAME: &str = "cvsd"; - -#[cfg(probe = "static")] -module_driver!( - name: "Static CVSD", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ProbeKind::Static { - on_probe: probe_static, - }], -); - -#[cfg(probe = "static")] -fn probe_static( - info: rdrive::probe::static_::StaticInfo, - plat_dev: PlatformDevice, -) -> Result<(), OnProbeError> { - if info.name() != DEVICE_NAME { - return Err(OnProbeError::NotMatch); - } - let regs = info.regs(); - let Some((sdmmc_address, sdmmc_size)) = regs.first().copied() else { - return Err(OnProbeError::NotMatch); - }; - let Some((syscon_address, syscon_size)) = regs.get(1).copied() else { - return Err(OnProbeError::NotMatch); - }; - register_mmio( - plat_dev, - sdmmc_address, - sdmmc_size, - syscon_address, - syscon_size, - ) -} - -pub fn register_mmio( - plat_dev: PlatformDevice, - sdmmc_paddr: usize, - sdmmc_size: usize, - syscon_paddr: usize, - syscon_size: usize, -) -> Result<(), OnProbeError> { - if sdmmc_paddr == 0 || sdmmc_size == 0 || syscon_paddr == 0 || syscon_size == 0 { - return Err(OnProbeError::NotMatch); - } - register_mmio_target(plat_dev, sdmmc_paddr, sdmmc_size, syscon_paddr, syscon_size) -} - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -fn register_mmio_target( - plat_dev: PlatformDevice, - sdmmc_paddr: usize, - sdmmc_size: usize, - syscon_paddr: usize, - syscon_size: usize, -) -> Result<(), OnProbeError> { - let sdmmc = map_region(sdmmc_paddr, sdmmc_size, "CVSD")?; - let syscon = map_region(syscon_paddr, syscon_size, "SYSCON")?; - let driver = - CvsdDriver::new(sdmmc, syscon).map_err(|_| OnProbeError::other("CVSD init failed"))?; - register_sync_block(plat_dev, driver); - Ok(()) -} - -#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] -fn register_mmio_target( - _plat_dev: PlatformDevice, - _sdmmc_paddr: usize, - _sdmmc_size: usize, - _syscon_paddr: usize, - _syscon_size: usize, -) -> Result<(), OnProbeError> { - Err(OnProbeError::NotMatch) -} - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -fn map_region(address: usize, size: usize, name: &str) -> Result { - let mmio = axklib::mmio::ioremap_raw(address.into(), size) - .map_err(|err| OnProbeError::other(format!("failed to map {name}: {err:?}")))?; - Ok(mmio.as_ptr() as usize) -} - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -struct CvsdDriver(Sdmmc); - -// The SG2002 SD/MMC core stores MMIO registers as `UnsafeCell`-backed -// references, so the raw register block is intentionally not `Sync`. -// `CvsdDriver` is owned by `SyncBlockDevice`, which serializes all access -// through a mutex and never clones the driver, so moving that owner between -// execution contexts is sound. -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -unsafe impl Send for CvsdDriver {} - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -impl CvsdDriver { - fn new(sdmmc: usize, syscon: usize) -> Result { - let sdmmc = unsafe { Sdmmc::new(sdmmc, syscon) }; - sdmmc.init().map_err(|_| ())?; - sdmmc.clk_en(true); - Ok(Self(sdmmc)) - } - - fn checked_lba(block_id: u64, offset: usize) -> Result { - let lba = block_id - .checked_add(offset as u64) - .ok_or(rd_block::BlkError::InvalidBlockIndex(block_id as usize))?; - u32::try_from(lba).map_err(|_| rd_block::BlkError::InvalidBlockIndex(block_id as usize)) - } -} - -#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] -impl SyncBlockOps for CvsdDriver { - fn name(&self) -> &'static str { - DEVICE_NAME - } - - fn num_blocks(&self) -> u64 { - self.0.card_capacity_blocks() - } - - fn block_size(&self) -> usize { - BLOCK_SIZE - } - - fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), rd_block::BlkError> { - if !buf.len().is_multiple_of(BLOCK_SIZE) { - return Err(rd_block::BlkError::NotSupported); - } - - for (i, block) in buf.chunks_exact_mut(BLOCK_SIZE).enumerate() { - self.0 - .read_block(Self::checked_lba(block_id, i)?, block) - .map_err(|_| rd_block::BlkError::Other("CVSD read failed".into()))?; - } - Ok(()) - } - - fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), rd_block::BlkError> { - if !buf.len().is_multiple_of(BLOCK_SIZE) { - return Err(rd_block::BlkError::NotSupported); - } - - for (i, block) in buf.chunks_exact(BLOCK_SIZE).enumerate() { - self.0 - .write_block(Self::checked_lba(block_id, i)?, block) - .map_err(|_| rd_block::BlkError::Other("CVSD write failed".into()))?; - } - Ok(()) - } -} diff --git a/drivers/ax-driver/src/block/mod.rs b/drivers/ax-driver/src/block/mod.rs index 15a6ef7685..7b21a02895 100644 --- a/drivers/ax-driver/src/block/mod.rs +++ b/drivers/ax-driver/src/block/mod.rs @@ -4,8 +4,6 @@ mod binding; pub mod ahci; #[cfg(feature = "bcm2835-sdhci")] pub mod bcm2835; -#[cfg(feature = "cvsd")] -pub mod cvsd; #[cfg(feature = "phytium-mci")] pub mod phytium_mci; #[cfg(feature = "ramdisk")] diff --git a/drivers/ax-driver/src/block/phytium_mci.rs b/drivers/ax-driver/src/block/phytium_mci.rs index 3856e87193..64d1c3cf51 100644 --- a/drivers/ax-driver/src/block/phytium_mci.rs +++ b/drivers/ax-driver/src/block/phytium_mci.rs @@ -21,7 +21,7 @@ const BLOCK_SIZE: usize = 512; type PhytiumSdMmc = SdioSdmmc; -module_driver!( +crate::model_register!( name: "Phytium MCI", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs b/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs index 6eb78bf20d..1a8cfb68f9 100644 --- a/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs +++ b/drivers/ax-driver/src/block/rockchip/sdhci_rk3568.rs @@ -94,7 +94,7 @@ impl HostClock for RockchipSdhciClock { } } -module_driver!( +crate::model_register!( name: "Rockchip RK3568 sdhci", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/block/rockchip_mmc.rs b/drivers/ax-driver/src/block/rockchip_mmc.rs index af00252e3d..0f83268a64 100644 --- a/drivers/ax-driver/src/block/rockchip_mmc.rs +++ b/drivers/ax-driver/src/block/rockchip_mmc.rs @@ -47,7 +47,7 @@ impl HostClock for RockchipSdhciClock { } } -module_driver!( +crate::model_register!( name: "Rockchip sdhci", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/block/rockchip_sd.rs b/drivers/ax-driver/src/block/rockchip_sd.rs index e396654c62..7c240b020a 100644 --- a/drivers/ax-driver/src/block/rockchip_sd.rs +++ b/drivers/ax-driver/src/block/rockchip_sd.rs @@ -52,7 +52,7 @@ mod phase; use block::SdBlockDevice; use phase::{init_rk3588_sdmmc_phase, tune_rk3588_sdmmc_sample_phase}; -module_driver!( +crate::model_register!( name: "Rockchip SD", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/lib.rs b/drivers/ax-driver/src/lib.rs index e568b03d22..7289ede1fa 100644 --- a/drivers/ax-driver/src/lib.rs +++ b/drivers/ax-driver/src/lib.rs @@ -4,10 +4,39 @@ #![feature(used_with_arg)] extern crate alloc; -#[macro_use] -extern crate rdrive; -module_driver!( +pub use rdrive::{DriverGeneric, IrqId, KError, PlatformDevice, ProbeError, probe, register}; +#[doc(hidden)] +pub use rdrive_macros::__mod_maker; + +#[macro_export] +macro_rules! model_register { + ( + $($i:ident : $t:expr),+, + ) => { + $crate::__mod_maker! { + pub mod some { + #[allow(unused_imports)] + use super::*; + use $crate::register::*; + + /// Static instance of driver registration information. + /// + /// This static variable is placed in the `.driver.register` linker section + /// so that the driver manager can automatically discover and load it during + /// system startup. + #[unsafe(link_section = ".driver.register")] + #[unsafe(no_mangle)] + #[used(linker)] + pub static DRIVER: DriverRegister = DriverRegister { + $($i : $t),+ + }; + } + } + }; +} + +crate::model_register!( name: "ax-driver macro placeholder", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/net/intel.rs b/drivers/ax-driver/src/net/intel.rs index 0c3d722c2b..15ee9aff03 100644 --- a/drivers/ax-driver/src/net/intel.rs +++ b/drivers/ax-driver/src/net/intel.rs @@ -15,7 +15,7 @@ use crate::net::{PlatformDeviceNet, pci_legacy_irq}; const DRIVER_NAME: &str = "eth-intel-e1000"; -module_driver!( +crate::model_register!( name: "Intel E1000 PCI Network", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/net/ixgbe.rs b/drivers/ax-driver/src/net/ixgbe.rs index 21dea1d6f2..e45544e030 100644 --- a/drivers/ax-driver/src/net/ixgbe.rs +++ b/drivers/ax-driver/src/net/ixgbe.rs @@ -27,7 +27,7 @@ const MEM_POOL_ENTRIES: usize = 4096; const MEM_POOL_ENTRY_SIZE: usize = 2048; const DMA_MASK: u64 = u64::MAX; -module_driver!( +crate::model_register!( name: "Intel 82599 PCI Network", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/net/realtek.rs b/drivers/ax-driver/src/net/realtek.rs index c6f87b78b3..73f68acd8b 100644 --- a/drivers/ax-driver/src/net/realtek.rs +++ b/drivers/ax-driver/src/net/realtek.rs @@ -17,7 +17,7 @@ const DRIVER_NAME: &str = "realtek-rtl8125"; const RTL8125_DMA_MASK: u64 = u32::MAX as u64; static REGISTERED_RTL8125: AtomicBool = AtomicBool::new(false); -module_driver!( +crate::model_register!( name: "Realtek RTL8125 PCI Network", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/pci/fdt.rs b/drivers/ax-driver/src/pci/fdt.rs index 19cb8a1ec4..f1919a8f30 100644 --- a/drivers/ax-driver/src/pci/fdt.rs +++ b/drivers/ax-driver/src/pci/fdt.rs @@ -23,7 +23,7 @@ use rdrive::{ #[path = "rk3588.rs"] mod rk3588; -module_driver!( +crate::model_register!( name: "Generic PCIe Controller Driver", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, @@ -234,7 +234,7 @@ mod pci_list_devices { use super::*; - module_driver!( + crate::model_register!( name: "PCI Device Lister", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/pci/mod.rs b/drivers/ax-driver/src/pci/mod.rs index 9295c6489e..05d31b2fdc 100644 --- a/drivers/ax-driver/src/pci/mod.rs +++ b/drivers/ax-driver/src/pci/mod.rs @@ -3,6 +3,7 @@ use alloc::format; use alloc::sync::Arc; use heapless::Vec as ArrayVec; +use mmio_api::MmioOp; #[cfg(virtio_dev)] use rdrive::probe::pci::{Endpoint, EndpointRc}; use rdrive::{ @@ -92,35 +93,23 @@ static LEGACY_IRQ_ROUTES: SpinMutex Result<(), OnProbeError> { - if info.name() != DEVICE_NAME { - return Err(OnProbeError::NotMatch); - } - let Some(ecam) = info.pci_ecam() else { - return Err(OnProbeError::NotMatch); - }; - let mem32 = ecam.mem32.or_else(|| pci_mem32_from_ranges(ecam.ranges)); - let mem64 = ecam.mem64.or_else(|| pci_mem64_from_ranges(ecam.ranges)); - register_static_legacy_irq_routes(info.irqs(), ecam.size); - register_ecam_controller(plat_dev, ecam.base, ecam.size, mem32, mem64) +pub const fn has_static_endpoint_drivers() -> bool { + cfg!(any( + feature = "ahci", + feature = "ixgbe", + feature = "intel-net", + feature = "realtek-rtl8125", + feature = "xhci-pci", + feature = "virtio-blk", + feature = "virtio-net", + feature = "virtio-gpu", + feature = "virtio-input", + feature = "virtio-socket", + feature = "pci-list-devices", + )) } -#[cfg(probe = "static")] -fn register_static_legacy_irq_routes(irqs: &[usize], ecam_size: usize) { +pub fn register_static_legacy_irq_routes(irqs: &[usize], ecam_size: usize) { if irqs.is_empty() { return; } @@ -130,8 +119,7 @@ fn register_static_legacy_irq_routes(irqs: &[usize], ecam_size: usize) { register_legacy_irq_routes(0, bus_end, irqs); } -#[cfg(probe = "static")] -fn pci_mem32_from_ranges(ranges: &[(usize, usize)]) -> Option { +pub fn pci_mem32_from_ranges(ranges: &[(usize, usize)]) -> Option { let (address, size) = ranges.get(1).copied()?; if size == 0 { return None; @@ -142,8 +130,7 @@ fn pci_mem32_from_ranges(ranges: &[(usize, usize)]) -> Option { }) } -#[cfg(probe = "static")] -fn pci_mem64_from_ranges(ranges: &[(usize, usize)]) -> Option { +pub fn pci_mem64_from_ranges(ranges: &[(usize, usize)]) -> Option { let (address, size) = ranges.get(2).copied()?; if size == 0 || usize::BITS <= 32 { return None; @@ -161,14 +148,34 @@ pub fn register_ecam_controller( mem32: Option, mem64: Option, ) -> Result<(), OnProbeError> { + register_ecam_controller_with_mmio_op( + plat_dev, + ecam_base, + ecam_size, + mem32, + mem64, + axklib::mmio::op(), + ) +} + +pub fn register_ecam_controller_with_mmio_op( + plat_dev: PlatformDevice, + ecam_base: usize, + ecam_size: usize, + mem32: Option, + mem64: Option, + mmio_op: &'static dyn MmioOp, +) -> Result<(), OnProbeError> { + if !has_static_endpoint_drivers() { + return Err(OnProbeError::NotMatch); + } + if ecam_base == 0 || ecam_size == 0 { return Err(OnProbeError::NotMatch); } - let mut controller = - rdrive::probe::pci::new_driver_generic(ecam_base, ecam_size, axklib::mmio::op()).map_err( - |err| OnProbeError::other(format!("failed to create PCIe controller: {err:?}")), - )?; + let mut controller = rdrive::probe::pci::new_driver_generic(ecam_base, ecam_size, mmio_op) + .map_err(|err| OnProbeError::other(format!("failed to create PCIe controller: {err:?}")))?; if let Some(mem32) = mem32 { controller.set_mem32(mem32, false); diff --git a/drivers/ax-driver/src/pci/rk3588/slots.rs b/drivers/ax-driver/src/pci/rk3588/slots.rs index 9da250783d..6d08f48839 100644 --- a/drivers/ax-driver/src/pci/rk3588/slots.rs +++ b/drivers/ax-driver/src/pci/rk3588/slots.rs @@ -1,7 +1,7 @@ mod rk3588_pcie_slot0 { use super::*; - module_driver!( + crate::model_register!( name: "Rockchip RK3588 PCIe host slot0", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, @@ -21,7 +21,7 @@ mod rk3588_pcie_slot0 { mod rk3588_pcie_slot1 { use super::*; - module_driver!( + crate::model_register!( name: "Rockchip RK3588 PCIe host slot1", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, @@ -41,7 +41,7 @@ mod rk3588_pcie_slot1 { mod rk3588_pcie_slot2 { use super::*; - module_driver!( + crate::model_register!( name: "Rockchip RK3588 PCIe host slot2", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, @@ -61,7 +61,7 @@ mod rk3588_pcie_slot2 { mod rk3588_pcie_slot3 { use super::*; - module_driver!( + crate::model_register!( name: "Rockchip RK3588 PCIe host slot3", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, @@ -81,7 +81,7 @@ mod rk3588_pcie_slot3 { mod rk3588_pcie_slot4 { use super::*; - module_driver!( + crate::model_register!( name: "Rockchip RK3588 PCIe host slot4", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/rknpu.rs b/drivers/ax-driver/src/rknpu.rs index a09f202701..4bcc299bb5 100644 --- a/drivers/ax-driver/src/rknpu.rs +++ b/drivers/ax-driver/src/rknpu.rs @@ -18,7 +18,7 @@ pub enum Error { InvalidData, } -module_driver!( +crate::model_register!( name: "Rockchip NPU", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/serial/mod.rs b/drivers/ax-driver/src/serial/mod.rs index 69a8a839b9..457da8be25 100644 --- a/drivers/ax-driver/src/serial/mod.rs +++ b/drivers/ax-driver/src/serial/mod.rs @@ -2,7 +2,7 @@ use log::info; use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; use some_serial::{BSerial, ns16550, pl011}; -module_driver!( +crate::model_register!( name: "common serial", level: ProbeLevel::PreKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs b/drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs index 5b5a727df3..1abcbe51d8 100644 --- a/drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs +++ b/drivers/ax-driver/src/soc/rockchip/clk/rk3568.rs @@ -8,7 +8,7 @@ use crate::mmio::iomap; const RK3568_CRU_GRF_BASE: usize = 0xfdc6_0000; const RK3568_CRU_GRF_SIZE: usize = 0x10000; -module_driver!( +crate::model_register!( name: "Rockchip RK3568 CRU", level: ProbeLevel::PostKernel, priority: ProbePriority::CLK, diff --git a/drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs b/drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs index 4292716e92..85d5058876 100644 --- a/drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs +++ b/drivers/ax-driver/src/soc/rockchip/clk/rk3588.rs @@ -22,7 +22,7 @@ use crate::mmio::iomap; const RK3588_CRU_GRF_BASE: usize = 0xfd5b_0000; const RK3588_CRU_GRF_SIZE: usize = 0x1000; -module_driver!( +crate::model_register!( name: "Rockchip RK3588 CRU", level: ProbeLevel::PostKernel, priority: ProbePriority::CLK, diff --git a/drivers/ax-driver/src/soc/rockchip/pinctrl.rs b/drivers/ax-driver/src/soc/rockchip/pinctrl.rs index 5a4d028fd8..ae36280d70 100644 --- a/drivers/ax-driver/src/soc/rockchip/pinctrl.rs +++ b/drivers/ax-driver/src/soc/rockchip/pinctrl.rs @@ -15,7 +15,7 @@ use crate::mmio::iomap; const DRIVER_NAME: &str = "rk3588-pinctrl"; const GPIO_BANK_COUNT: usize = 5; -module_driver!( +crate::model_register!( name: "Rockchip PinCtrl", level: ProbeLevel::PostKernel, priority: ProbePriority::CLK, diff --git a/drivers/ax-driver/src/soc/rockchip/pm.rs b/drivers/ax-driver/src/soc/rockchip/pm.rs index a7d8803020..0b35cd2de6 100644 --- a/drivers/ax-driver/src/soc/rockchip/pm.rs +++ b/drivers/ax-driver/src/soc/rockchip/pm.rs @@ -18,7 +18,7 @@ use rockchip_pm::{PowerDomain, RkBoard, RockchipPM}; use crate::mmio::iomap; -module_driver!( +crate::model_register!( name: "Rockchip Pm", level: ProbeLevel::PostKernel, priority: ProbePriority::CLK, diff --git a/drivers/ax-driver/src/soc/scmi.rs b/drivers/ax-driver/src/soc/scmi.rs index 5506d49284..a46d4fde44 100644 --- a/drivers/ax-driver/src/soc/scmi.rs +++ b/drivers/ax-driver/src/soc/scmi.rs @@ -5,11 +5,8 @@ use arm_scmi_rs::{Scmi, Shmem, Smc}; use ax_kspin::SpinNoIrq as Mutex; use fdt_edit::Phandle; use log::{info, warn}; -use rdrive::{ - DriverGeneric, PlatformDevice, module_driver, probe::OnProbeError, register::FdtInfo, -}; -use crate::mmio::iomap; +use crate::{DriverGeneric, PlatformDevice, mmio::iomap, probe::OnProbeError, register::FdtInfo}; const SCMI_SHMEM_SIZE: usize = 0x100; const RK3588_SCMI_SHMEM_BASE: usize = 0x10f000; @@ -17,7 +14,7 @@ const RK3588_SCMI_SHMEM_BASE: usize = 0x10f000; static SCMI: Mutex>> = Mutex::new(None); static SCMI_REGISTERED: AtomicBool = AtomicBool::new(false); -module_driver!( +crate::model_register!( name: "ARM SCMI SMC", level: ProbeLevel::PostKernel, priority: ProbePriority::CLK, diff --git a/drivers/ax-driver/src/time.rs b/drivers/ax-driver/src/time.rs index 110471e117..590e194d51 100644 --- a/drivers/ax-driver/src/time.rs +++ b/drivers/ax-driver/src/time.rs @@ -4,7 +4,7 @@ use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; use crate::mmio::iomap; -module_driver!( +crate::model_register!( name: "pl031 rtc", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/usb/dwc.rs b/drivers/ax-driver/src/usb/dwc.rs index bcf26b05f0..1e5b5344af 100644 --- a/drivers/ax-driver/src/usb/dwc.rs +++ b/drivers/ax-driver/src/usb/dwc.rs @@ -25,7 +25,7 @@ use crate::{ const DRIVER_NAME: &str = "usb-dwc-xhci"; const OPTIONAL_PHP_POWER_DOMAIN: usize = 32; -module_driver!( +crate::model_register!( name: "USB DWC xHCI", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/usb/xhci_mmio.rs b/drivers/ax-driver/src/usb/xhci_mmio.rs index 9ff962dcba..53e78f54c9 100644 --- a/drivers/ax-driver/src/usb/xhci_mmio.rs +++ b/drivers/ax-driver/src/usb/xhci_mmio.rs @@ -9,7 +9,7 @@ use super::{PlatformDeviceUsbHost, decode_fdt_irq, usb_kernel}; const DRIVER_NAME: &str = "usb-xhci-mmio"; -module_driver!( +crate::model_register!( name: "USB xHCI MMIO", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/usb/xhci_pci.rs b/drivers/ax-driver/src/usb/xhci_pci.rs index 7d1047bf85..418679dc45 100644 --- a/drivers/ax-driver/src/usb/xhci_pci.rs +++ b/drivers/ax-driver/src/usb/xhci_pci.rs @@ -16,7 +16,7 @@ use super::{PlatformDeviceUsbHost, align_up_4k, pci_irq_or_error, usb_kernel}; const DRIVER_NAME: &str = "usb-xhci-pci"; -module_driver!( +crate::model_register!( name: "USB xHCI PCI", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/virtio/block.rs b/drivers/ax-driver/src/virtio/block.rs index c62f14b9ed..0eb6a863cf 100644 --- a/drivers/ax-driver/src/virtio/block.rs +++ b/drivers/ax-driver/src/virtio/block.rs @@ -16,7 +16,7 @@ use crate::{block::PlatformDeviceBlock, virtio::VirtIoHalImpl}; const VIRTIO_BLK_DMA_BUFFER_SIZE: usize = 32 * SECTOR_SIZE; #[cfg(probe = "pci")] -module_driver!( +crate::model_register!( name: "VirtIO Block", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, @@ -35,7 +35,7 @@ fn probe_pci( } #[cfg(probe = "fdt")] -module_driver!( +crate::model_register!( name: "VirtIO MMIO Block", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/virtio/display.rs b/drivers/ax-driver/src/virtio/display.rs index 97e4a214c4..76e512c5a5 100644 --- a/drivers/ax-driver/src/virtio/display.rs +++ b/drivers/ax-driver/src/virtio/display.rs @@ -11,7 +11,7 @@ use virtio_drivers::{Error as VirtIoError, device::gpu::VirtIOGpu, transport::Tr use crate::{display::PlatformDeviceDisplay, virtio::VirtIoHalImpl}; #[cfg(probe = "pci")] -module_driver!( +crate::model_register!( name: "VirtIO GPU", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/virtio/input.rs b/drivers/ax-driver/src/virtio/input.rs index 44aa097a95..a60e12d890 100644 --- a/drivers/ax-driver/src/virtio/input.rs +++ b/drivers/ax-driver/src/virtio/input.rs @@ -15,7 +15,7 @@ use virtio_drivers::{ use crate::{input::PlatformDeviceInput, virtio::VirtIoHalImpl}; #[cfg(probe = "pci")] -module_driver!( +crate::model_register!( name: "VirtIO Input", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/virtio/mod.rs b/drivers/ax-driver/src/virtio/mod.rs index 01e5be1029..8ace7d4174 100644 --- a/drivers/ax-driver/src/virtio/mod.rs +++ b/drivers/ax-driver/src/virtio/mod.rs @@ -21,26 +21,17 @@ pub mod vsock; pub const MMIO_DEVICE_NAME: &str = "virtio-mmio"; -#[cfg(all( - probe = "static", - any( +pub struct VirtIoHalImpl(PhantomData<()>); + +pub const fn has_static_mmio_drivers() -> bool { + cfg!(any( feature = "virtio-blk", feature = "virtio-net", feature = "virtio-gpu", feature = "virtio-input", feature = "virtio-socket", - ) -))] -module_driver!( - name: "Static VirtIO MMIO", - level: ProbeLevel::PostKernel, - priority: ProbePriority::DEFAULT, - probe_kinds: &[ProbeKind::Static { - on_probe: probe_static_mmio, - }], -); - -pub struct VirtIoHalImpl(PhantomData<()>); + )) +} unsafe impl VirtIoHal for VirtIoHalImpl { fn dma_alloc(pages: usize, _direction: BufferDirection) -> (VirtIoPhysAddr, NonNull) { @@ -85,30 +76,15 @@ pub fn probe_mmio_device( Some((transport.device_type(), transport)) } -#[cfg(all( - probe = "static", - any( - feature = "virtio-blk", - feature = "virtio-net", - feature = "virtio-gpu", - feature = "virtio-input", - feature = "virtio-socket", - ) -))] -fn probe_static_mmio( - info: rdrive::probe::static_::StaticInfo, +pub fn register_static_mmio( plat_dev: rdrive::PlatformDevice, + base: usize, + size: usize, ) -> Result<(), rdrive::probe::OnProbeError> { - if info.name() != MMIO_DEVICE_NAME { + if !has_static_mmio_drivers() { return Err(rdrive::probe::OnProbeError::NotMatch); } - let reg = info - .resource_index() - .and_then(|index| info.regs().get(index)) - .or_else(|| info.regs().first()) - .copied() - .ok_or(rdrive::probe::OnProbeError::NotMatch)?; - let (base, size) = reg; + let mmio = axklib::mmio::ioremap_raw(base.into(), size).map_err(|err| { rdrive::probe::OnProbeError::other(alloc::format!( "failed to map virtio-mmio {base:#x}: {err:?}", @@ -120,17 +96,14 @@ fn probe_static_mmio( register_static_transport(plat_dev, ty, transport) } -#[cfg(all( - probe = "static", - any( - feature = "virtio-blk", - feature = "virtio-net", - feature = "virtio-gpu", - feature = "virtio-input", - feature = "virtio-socket", - ) +#[cfg(any( + feature = "virtio-blk", + feature = "virtio-net", + feature = "virtio-gpu", + feature = "virtio-input", + feature = "virtio-socket", ))] -fn register_static_transport( +pub fn register_static_transport( plat_dev: rdrive::PlatformDevice, ty: DeviceType, transport: T, @@ -150,6 +123,21 @@ fn register_static_transport( } } +#[cfg(not(any( + feature = "virtio-blk", + feature = "virtio-net", + feature = "virtio-gpu", + feature = "virtio-input", + feature = "virtio-socket", +)))] +pub fn register_static_transport( + _plat_dev: rdrive::PlatformDevice, + _ty: DeviceType, + _transport: T, +) -> Result<(), rdrive::probe::OnProbeError> { + Err(rdrive::probe::OnProbeError::NotMatch) +} + #[cfg(probe = "fdt")] pub fn probe_fdt_mmio_device( info: &rdrive::register::FdtInfo<'_>, diff --git a/drivers/ax-driver/src/virtio/net.rs b/drivers/ax-driver/src/virtio/net.rs index 714673edcb..c89c3460cb 100644 --- a/drivers/ax-driver/src/virtio/net.rs +++ b/drivers/ax-driver/src/virtio/net.rs @@ -22,7 +22,7 @@ const QUEUE_SIZE: usize = 64; const BUFFER_SIZE: usize = 2048; #[cfg(probe = "pci")] -module_driver!( +crate::model_register!( name: "VirtIO Net", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/src/virtio/vsock.rs b/drivers/ax-driver/src/virtio/vsock.rs index c241cb0f9a..5fd1eeb279 100644 --- a/drivers/ax-driver/src/virtio/vsock.rs +++ b/drivers/ax-driver/src/virtio/vsock.rs @@ -20,7 +20,7 @@ use crate::{virtio::VirtIoHalImpl, vsock::PlatformDeviceVsock}; const DEFAULT_RX_BUFFER_CAPACITY: u32 = 32 * 1024; #[cfg(probe = "pci")] -module_driver!( +crate::model_register!( name: "VirtIO Socket", level: ProbeLevel::PostKernel, priority: ProbePriority::DEFAULT, diff --git a/drivers/ax-driver/tests/model_register.rs b/drivers/ax-driver/tests/model_register.rs new file mode 100644 index 0000000000..0c251d2e6c --- /dev/null +++ b/drivers/ax-driver/tests/model_register.rs @@ -0,0 +1,21 @@ +#![feature(used_with_arg)] + +use ax_driver::{PlatformDevice, probe::OnProbeError}; + +ax_driver::model_register!( + name: "ax-driver model register test", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], +); + +fn probe(_plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + Ok(()) +} + +#[test] +fn model_register_is_usable_from_ax_driver_only() { + let _ = core::mem::size_of::(); +} diff --git a/drivers/rdrive/src/lib.rs b/drivers/rdrive/src/lib.rs index d7f4abfb0e..442567d128 100644 --- a/drivers/rdrive/src/lib.rs +++ b/drivers/rdrive/src/lib.rs @@ -37,7 +37,7 @@ static CONTAINER: Once> = Once::new(); #[derive(Debug, Clone)] pub enum Platform { - Static(&'static [probe::static_::StaticDeviceDesc]), + Static, Fdt { addr: NonNull }, Acpi(probe::acpi::AcpiRoot), } @@ -46,7 +46,7 @@ unsafe impl Send for Platform {} #[derive(Debug, Clone, Copy)] pub enum PlatformSource { - Static(&'static [probe::static_::StaticDeviceDesc]), + Static, Fdt(NonNull), Acpi(probe::acpi::AcpiRoot), } @@ -63,7 +63,7 @@ pub fn is_initialized() -> bool { pub fn init(platform: Platform) -> Result<(), DriverError> { match platform { - Platform::Static(devices) => init_sources(&[PlatformSource::Static(devices)])?, + Platform::Static => init_sources(&[PlatformSource::Static])?, Platform::Fdt { addr } => init_sources(&[PlatformSource::Fdt(addr)])?, Platform::Acpi(root) => init_sources(&[PlatformSource::Acpi(root)])?, } @@ -73,7 +73,7 @@ pub fn init(platform: Platform) -> Result<(), DriverError> { pub fn init_sources(sources: &[PlatformSource]) -> Result<(), DriverError> { for source in sources { match source { - PlatformSource::Static(_) => {} + PlatformSource::Static => {} PlatformSource::Fdt(addr) => probe::fdt::check_addr(*addr)?, PlatformSource::Acpi(root) => probe::acpi::check_root(*root)?, } @@ -81,7 +81,7 @@ pub fn init_sources(sources: &[PlatformSource]) -> Result<(), DriverError> { for source in sources { match source { - PlatformSource::Static(devices) => probe::static_::init(devices)?, + PlatformSource::Static => probe::static_::init()?, PlatformSource::Fdt(addr) => probe::fdt::init(*addr)?, PlatformSource::Acpi(root) => probe::acpi::init(*root)?, } diff --git a/drivers/rdrive/src/probe/static_.rs b/drivers/rdrive/src/probe/static_.rs index 4b14c5f472..11f3369299 100644 --- a/drivers/rdrive/src/probe/static_.rs +++ b/drivers/rdrive/src/probe/static_.rs @@ -3,148 +3,18 @@ use alloc::{collections::btree_set::BTreeSet, vec::Vec}; use spin::{Mutex, Once}; use crate::{ - Descriptor, DeviceId, PlatformDevice, + Descriptor, PlatformDevice, error::DriverError, - probe::{ - OnProbeError, ProbeError, - pci::{PciMem32, PciMem64}, - }, + probe::{OnProbeError, ProbeError}, register::{DriverRegister, ProbeKind}, }; static SYSTEM: Once = Once::new(); -pub type StaticMmioRegion = (usize, usize); +pub type FnOnProbe = fn(plat_dev: PlatformDevice) -> Result<(), OnProbeError>; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct StaticPciEcam { - pub base: usize, - pub size: usize, - pub ranges: &'static [StaticMmioRegion], - pub mem32: Option, - pub mem64: Option, -} - -impl StaticPciEcam { - pub const fn new(base: usize, size: usize) -> Self { - Self { - base, - size, - ranges: &[], - mem32: None, - mem64: None, - } - } - - pub const fn with_ranges(mut self, ranges: &'static [StaticMmioRegion]) -> Self { - self.ranges = ranges; - self - } - - pub const fn with_mem32(mut self, mem32: Option) -> Self { - self.mem32 = mem32; - self - } - - pub const fn with_mem64(mut self, mem64: Option) -> Self { - self.mem64 = mem64; - self - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct StaticDeviceDesc { - pub name: &'static str, - pub irq_parent: Option, - pub regs: &'static [StaticMmioRegion], - pub irqs: &'static [usize], - pub pci_ecam: Option, - pub probe_each_reg: bool, -} - -impl StaticDeviceDesc { - pub const fn new(name: &'static str) -> Self { - Self { - name, - irq_parent: None, - regs: &[], - irqs: &[], - pci_ecam: None, - probe_each_reg: false, - } - } - - pub const fn with_regs(mut self, regs: &'static [StaticMmioRegion]) -> Self { - self.regs = regs; - self - } - - pub const fn with_irqs(mut self, irqs: &'static [usize]) -> Self { - self.irqs = irqs; - self - } - - pub const fn with_irq_parent(mut self, irq_parent: Option) -> Self { - self.irq_parent = irq_parent; - self - } - - pub const fn with_pci_ecam(mut self, pci_ecam: StaticPciEcam) -> Self { - self.pci_ecam = Some(pci_ecam); - self - } - - pub const fn with_probe_each_reg(mut self) -> Self { - self.probe_each_reg = true; - self - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct StaticInfo { - index: usize, - resource_index: Option, - desc: StaticDeviceDesc, -} - -impl StaticInfo { - pub const fn index(&self) -> usize { - self.index - } - - pub const fn resource_index(&self) -> Option { - self.resource_index - } - - pub const fn desc(&self) -> StaticDeviceDesc { - self.desc - } - - pub const fn name(&self) -> &'static str { - self.desc.name - } - - pub const fn irq_parent(&self) -> Option { - self.desc.irq_parent - } - - pub const fn regs(&self) -> &'static [StaticMmioRegion] { - self.desc.regs - } - - pub const fn irqs(&self) -> &'static [usize] { - self.desc.irqs - } - - pub const fn pci_ecam(&self) -> Option { - self.desc.pci_ecam - } -} - -pub type FnOnProbe = fn(info: StaticInfo, plat_dev: PlatformDevice) -> Result<(), OnProbeError>; - -pub fn init(devices: &'static [StaticDeviceDesc]) -> Result<(), DriverError> { - SYSTEM.call_once(|| System::new(devices)); +pub fn init() -> Result<(), DriverError> { + SYSTEM.call_once(System::new); Ok(()) } @@ -155,20 +25,16 @@ pub(crate) fn try_probe_register( } struct System { - devices: &'static [StaticDeviceDesc], - probed_names: Mutex)>>, + probed_names: Mutex>, } impl System { - fn new(devices: &'static [StaticDeviceDesc]) -> Self { + fn new() -> Self { Self { - devices, probed_names: Mutex::new(BTreeSet::new()), } } -} -impl System { fn probe_register( &self, register: &DriverRegister, @@ -178,23 +44,7 @@ impl System { let ProbeKind::Static { on_probe } = probe else { continue; }; - let on_probe = *on_probe; - - for (index, device) in self.devices.iter().enumerate() { - if device.probe_each_reg && !device.regs.is_empty() { - for resource_index in 0..device.regs.len() { - out.push(self.probe_one( - register, - on_probe, - index, - Some(resource_index), - *device, - )); - } - } else { - out.push(self.probe_one(register, on_probe, index, None, *device)); - } - } + out.push(self.probe_one(register, *on_probe)); } Ok(out) @@ -204,27 +54,17 @@ impl System { &self, register: &DriverRegister, on_probe: FnOnProbe, - index: usize, - resource_index: Option, - device: StaticDeviceDesc, ) -> Result<(), OnProbeError> { - let probed_key = (register.name, index, resource_index); - if self.probed_names.lock().contains(&probed_key) { + if self.probed_names.lock().contains(®ister.name) { return Err(OnProbeError::NotMatch); } let mut descriptor = Descriptor::new(); - descriptor.name = device.name; - descriptor.irq_parent = device.irq_parent; - let info = StaticInfo { - index, - resource_index, - desc: device, - }; - let res = on_probe(info, PlatformDevice::new(descriptor)); + descriptor.name = register.name; + let res = on_probe(PlatformDevice::new(descriptor)); if res.is_ok() { - self.probed_names.lock().insert(probed_key); + self.probed_names.lock().insert(register.name); } res diff --git a/drivers/rdrive/tests/init_sources.rs b/drivers/rdrive/tests/init_sources.rs index e00d00b0c6..35106a73c7 100644 --- a/drivers/rdrive/tests/init_sources.rs +++ b/drivers/rdrive/tests/init_sources.rs @@ -1,6 +1,6 @@ use rdrive::{ DriverGeneric, Platform, PlatformDevice, PlatformSource, get_one, init_sources, - probe::{OnProbeError, acpi::AcpiRoot, static_::StaticDeviceDesc}, + probe::{OnProbeError, acpi::AcpiRoot}, probe_all, register::{DriverRegister, ProbeKind, ProbeLevel, ProbePriority}, }; @@ -13,17 +13,7 @@ impl DriverGeneric for MixedSourceDevice { } } -static FAILED_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("failed-device")]; -static SUCCESS_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("success-device")]; - -fn probe_static( - info: rdrive::probe::static_::StaticInfo, - plat_dev: PlatformDevice, -) -> Result<(), OnProbeError> { - if info.name() != "success-device" { - return Err(OnProbeError::NotMatch); - } - +fn probe_static(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { plat_dev.register(MixedSourceDevice); Ok(()) } @@ -40,7 +30,7 @@ static STATIC_REGISTER: DriverRegister = DriverRegister { #[test] fn unsupported_source_does_not_leave_static_backend_initialized() { let err = init_sources(&[ - PlatformSource::Static(FAILED_DEVICES), + PlatformSource::Static, PlatformSource::Acpi(AcpiRoot { rsdp: 0 }), ]) .expect_err("acpi should reject the source set before committing static state"); @@ -49,7 +39,7 @@ fn unsupported_source_does_not_leave_static_backend_initialized() { rdrive::error::DriverError::Unsupported("acpi") )); - rdrive::init(Platform::Static(SUCCESS_DEVICES)).expect("static platform should init"); + rdrive::init(Platform::Static).expect("static platform should init"); rdrive::register_add(STATIC_REGISTER.clone()); probe_all(true).expect("static probe should succeed"); diff --git a/drivers/rdrive/tests/phase1.rs b/drivers/rdrive/tests/phase1.rs index f3b61a0e8f..15f17e8455 100644 --- a/drivers/rdrive/tests/phase1.rs +++ b/drivers/rdrive/tests/phase1.rs @@ -1,6 +1,6 @@ use rdrive::{ DriverGeneric, Platform, PlatformDevice, PlatformSource, get_one, init_sources, - probe::{acpi::AcpiRoot, static_::StaticDeviceDesc}, + probe::{OnProbeError, acpi::AcpiRoot}, probe_all, register::{DriverRegister, ProbeKind, ProbeLevel, ProbePriority}, }; @@ -13,13 +13,7 @@ impl DriverGeneric for StaticTestDevice { } } -static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("static-test-device")]; - -fn probe_static( - info: rdrive::probe::static_::StaticInfo, - plat_dev: PlatformDevice, -) -> Result<(), rdrive::probe::OnProbeError> { - assert_eq!(info.name(), "static-test-device"); +fn probe_static(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { plat_dev.register(StaticTestDevice); Ok(()) } @@ -35,7 +29,7 @@ static STATIC_REGISTER: DriverRegister = DriverRegister { #[test] fn static_probe_registers_device() { - rdrive::init(Platform::Static(STATIC_DEVICES)).expect("static platform should init"); + rdrive::init(Platform::Static).expect("static platform should init"); rdrive::register_add(STATIC_REGISTER.clone()); probe_all(true).expect("static probe should succeed"); @@ -56,7 +50,7 @@ fn acpi_source_is_unsupported() { #[test] fn fdt_phandle_lookup_is_none_without_fdt_source() { - init_sources(&[PlatformSource::Static(STATIC_DEVICES)]).expect("static source should init"); + init_sources(&[PlatformSource::Static]).expect("static source should init"); assert!(rdrive::fdt_phandle_to_device_id(1.into()).is_none()); } diff --git a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml index 2e3d5a70a8..899576e482 100644 --- a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml +++ b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml @@ -2,7 +2,6 @@ env = { UIMAGE = "y" } features = [ "sg2002", "myplat", - "ax-driver/cvsd", ] log = "Info" plat_dyn = false diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index c18a6a8f31..9f18b0dad6 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -76,6 +76,9 @@ paging = [ "dep:ax-alloc", "dep:ax-page-table-multiarch", "ax-page-table-multiarch/ax-errno", + "ax-plat-aarch64-qemu-virt?/paging", + "ax-plat-loongarch64-qemu-virt?/paging", + "ax-plat-riscv64-qemu-virt?/paging", ] tls = ["ax-cpu/tls"] uspace = ["paging", "ax-cpu/uspace", "axplat-dyn?/uspace"] diff --git a/os/arceos/modules/axhal/src/dummy.rs b/os/arceos/modules/axhal/src/dummy.rs index b8246deead..1c95c5fb63 100644 --- a/os/arceos/modules/axhal/src/dummy.rs +++ b/os/arceos/modules/axhal/src/dummy.rs @@ -4,7 +4,6 @@ use ax_plat::irq::{IpiTarget, IrqHandler, IrqIf}; use ax_plat::{ console::ConsoleIf, - drivers::DriversIf, impl_plat_interface, init::InitIf, mem::{MemIf, RawRange}, @@ -17,7 +16,6 @@ struct DummyConsole; struct DummyMem; struct DummyTime; struct DummyPower; -struct DummyDrivers; #[cfg(feature = "irq")] struct DummyIrq; @@ -126,13 +124,6 @@ impl PowerIf for DummyPower { } } -#[impl_plat_interface] -impl DriversIf for DummyDrivers { - fn static_devices_fn() -> &'static [rdrive::probe::static_::StaticDeviceDesc] { - &[] - } -} - #[cfg(feature = "irq")] #[impl_plat_interface] impl IrqIf for DummyIrq { diff --git a/os/arceos/modules/axhal/src/lib.rs b/os/arceos/modules/axhal/src/lib.rs index f32a8860ab..923397af74 100644 --- a/os/arceos/modules/axhal/src/lib.rs +++ b/os/arceos/modules/axhal/src/lib.rs @@ -46,19 +46,6 @@ pub mod mem; pub mod percpu; pub mod time; -/// Static driver resources exported by the selected platform. -pub mod drivers { - #[cfg(not(feature = "plat-dyn"))] - pub fn static_devices() -> &'static [rdrive::probe::static_::StaticDeviceDesc] { - ax_plat::drivers::static_devices() - } - - #[cfg(feature = "plat-dyn")] - pub const fn static_devices() -> &'static [rdrive::probe::static_::StaticDeviceDesc] { - &[] - } -} - #[cfg(feature = "tls")] pub mod tls; diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index c0c2426d23..31f957e60e 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -252,7 +252,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { info!("Initialize platform devices..."); ax_hal::init_later(cpu_id, arg); if !rdrive::is_initialized() { - rdrive::init(rdrive::Platform::Static(ax_hal::drivers::static_devices())) + rdrive::init(rdrive::Platform::Static) .unwrap_or_else(|err| panic!("failed to initialize static rdrive source: {err:?}")); } registers::append_linker_registers(); diff --git a/platform/riscv64-visionfive2/src/drivers.rs b/platform/riscv64-visionfive2/src/drivers.rs index 6272be1d86..206dcb44a1 100644 --- a/platform/riscv64-visionfive2/src/drivers.rs +++ b/platform/riscv64-visionfive2/src/drivers.rs @@ -1,13 +1 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::StaticDeviceDesc; - -static STATIC_DEVICES: &[StaticDeviceDesc] = &[]; - -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES - } -} +// This platform has no static model drivers. diff --git a/platform/x86-qemu-q35/Cargo.toml b/platform/x86-qemu-q35/Cargo.toml index 8dffeb7e82..9f840eadb3 100644 --- a/platform/x86-qemu-q35/Cargo.toml +++ b/platform/x86-qemu-q35/Cargo.toml @@ -26,7 +26,9 @@ smp = ["ax-plat/smp", "ax-kspin/smp"] [target.'cfg(target_arch = "x86_64")'.dependencies] ax-config-macros = { workspace = true } ax-cpu = { workspace = true, features = ["arm-el2"] } +ax-driver = { workspace = true, features = ["pci"] } ax-plat.workspace = true +axklib.workspace = true bitflags = "2.6" heapless = "0.9" ax-int-ratio = { workspace = true } @@ -34,7 +36,6 @@ ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } log = "0.4" ax-percpu.workspace = true -rdrive.workspace = true multiboot = "0.8" raw-cpuid = "11.5" diff --git a/platform/x86-qemu-q35/src/drivers.rs b/platform/x86-qemu-q35/src/drivers.rs index 19df95039d..24ddd287be 100644 --- a/platform/x86-qemu-q35/src/drivers.rs +++ b/platform/x86-qemu-q35/src/drivers.rs @@ -1,18 +1,28 @@ -use ax_plat::drivers::DriversIf; -use rdrive::probe::static_::{StaticDeviceDesc, StaticPciEcam}; +use ax_driver::{PlatformDevice, probe::OnProbeError}; use crate::config::devices; const PCI_ECAM_SIZE: usize = (devices::PCI_BUS_END + 1) << 20; -static STATIC_DEVICES: &[StaticDeviceDesc] = &[StaticDeviceDesc::new("pci-ecam") - .with_pci_ecam(StaticPciEcam::new(devices::PCI_ECAM_BASE, PCI_ECAM_SIZE))]; +ax_driver::model_register!( + name: "Static PCIe ECAM", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Static { + on_probe: probe, + }], +); -struct DriversIfImpl; - -#[impl_plat_interface] -impl DriversIf for DriversIfImpl { - fn static_devices_fn() -> &'static [StaticDeviceDesc] { - STATIC_DEVICES +fn probe(plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + if !ax_driver::pci::has_static_endpoint_drivers() { + return Err(OnProbeError::NotMatch); } + + ax_driver::pci::register_ecam_controller( + plat_dev, + devices::PCI_ECAM_BASE, + PCI_ECAM_SIZE, + None, + None, + ) } diff --git a/platform/x86-qemu-q35/src/lib.rs b/platform/x86-qemu-q35/src/lib.rs index 3bdb4d1b96..556f7f5bba 100644 --- a/platform/x86-qemu-q35/src/lib.rs +++ b/platform/x86-qemu-q35/src/lib.rs @@ -15,6 +15,9 @@ #![no_std] #![cfg(all(target_arch = "x86_64", target_os = "none"))] #![allow(missing_abi)] +#![feature(used_with_arg)] + +extern crate ax_driver as _; #[macro_use] extern crate log; diff --git a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml index 8bcd941406..6b3ab7b2b6 100644 --- a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml @@ -1,7 +1,7 @@ # Build-time config template for StarryOS on SG2002. target = "riscv64gc-unknown-none-elf" env = {} -features = ["sg2002", "myplat", "ax-driver/cvsd"] +features = ["sg2002", "myplat"] log = "Info" max_cpu_num = 1 plat_dyn = false From 11908763bd6bc5c88c96f6289d17dcac0d2ead02 Mon Sep 17 00:00:00 2001 From: Feiran Qin <161046517+jakeuibn@users.noreply.github.com> Date: Mon, 25 May 2026 19:36:18 +0800 Subject: [PATCH 30/71] fix(ax-net-ng): drain entire ARP-pending queue, lift cache TTL and capacity (#911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three related improvements to the ethernet ARP path that together remove the silent-drop and long-stall failure modes seen when applications fan out many concurrent TCP connections through a gateway (browsers, HTTP clients, AI/cloud CLIs like jcode). 1. ETHERNET_MAX_PENDING_PACKETS too small (32 -> 128) Applications opening 10-20 concurrent TCP connections at startup queue each first SYN in the ARP-pending buffer while the gateway MAC is being resolved. 32 slots (~48 KiB) can be exhausted during a real fan-out (tokio reactor flushes a batch of futures with their SYNs at once); the excess SYNs are silently dropped at the link layer and only recover when smoltcp's 1 s TCP retransmit timer rebuilds them through the now-warm neighbor cache, adding a visible 1 s startup stall. 128 slots (~189 KiB) absorbs the burst while keeping the boot-time heap footprint small enough for the 128 MiB QEMU defaults used by the test rigs (raising to 256 was enough to push later mount-heavy syscall tests past the heap-growth limit on riscv64-virt and trigger a StoreFault). 2. NEIGHBOR_TTL too short (60 s -> 300 s) When a streaming response takes longer than 60 s to begin flowing (cold-start + network latency), the cached gateway entry expires exactly as the response begins. The resulting burst of outbound TCP ACKs all re-enter the pending queue at once, overflowing it again and producing the same silent drop. 300 s matches the Linux unicast neighbour default and removes this class of failure. 3. ARP-reply drain stopped at the queue head When an ARP reply arrived the old drain only peeked at the head of the pending queue and stopped as soon as the head's next-hop did not match the just-resolved address. Entries for other already-resolved next-hops queued behind an unresolvable one were permanently blocked (a textbook head-of-line failure). Replace the peek loop with a full pass: dequeue every entry, immediately send the ones whose next-hop is resolved, re-queue the rest in their original order (and re-issue ARP for entries whose neighbour has expired). `kept` is pre-sized to ETHERNET_MAX_PENDING_PACKETS so the drain does not pay heap-realloc cost while servicing an ARP IRQ. This is a defensive correctness fix. In the current ax-net-ng static IP setup the only routing rule is `0.0.0.0/0 via gateway`, so every destination resolves to the same next-hop and the HoL window never opens; the fix matters as soon as a per-subnet on-link rule (already present on the DHCP path) is added on the static path or when a host route via a different next-hop is introduced. Validation * cargo fmt + cargo xtask clippy --package ax-net-ng pass cleanly. * Functional impact validated end-to-end by running jcode (a Tokio / reqwest TUI) on this branch; the previous ~1 s startup stall from the dropped-SYN burst is gone and long streaming responses no longer hit the 60 s TTL re-arming window. Regression test An earlier version of this commit added bug-arp-hol-drain under the bugfix suite, but on closer review the test did not in fact catch either bug: * The capacity-overflow path is not reproducible from sequential userspace syscalls under QEMU SLIRP, because every connect() runs poll_interfaces() which receives the ARP reply and drains the buffer between syscalls. Toggling the constant between 32 and 128 produced identical "64/64 completed in tens of ms" runs, and a 200-connection burst against a 32-slot buffer still completed without any "buffer full" warning. * The HoL drain path cannot be triggered through static-IP routing either, because the only routing rule installed on that path is `0.0.0.0/0 via gateway`, so every connect — even to an unresolvable on-link IP — ends up with next_hop=gateway and the queue only ever holds entries for a single next-hop. Reverting the drain to the buggy peek-and-break still passed the test (confirmed by a kernel-log instrumentation run that showed only ENQUEUE next_hop=10.0.2.2 events). Adding a test that passes equally on the buggy and fixed code is worse than no test, so the file is dropped. A meaningful synthetic regression for these two paths would need either a multi-next-hop routing topology or a way to bypass the per-syscall drain, both of which are out of scope for this change. Co-authored-by: StarryOS Fix --- os/arceos/modules/axnet-ng/src/consts.rs | 15 ++- .../modules/axnet-ng/src/device/ethernet.rs | 97 +++++++++++++------ 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/os/arceos/modules/axnet-ng/src/consts.rs b/os/arceos/modules/axnet-ng/src/consts.rs index 9007ac2aa4..ae4fcc40a9 100644 --- a/os/arceos/modules/axnet-ng/src/consts.rs +++ b/os/arceos/modules/axnet-ng/src/consts.rs @@ -22,4 +22,17 @@ pub const RAW_TX_BUF_LEN: usize = 64 * 1024; pub const LISTEN_QUEUE_SIZE: usize = 512; pub const SOCKET_BUFFER_SIZE: usize = 64; -pub const ETHERNET_MAX_PENDING_PACKETS: usize = 32; +/// Number of outbound packets that can be queued while waiting for ARP +/// resolution of the next hop. +/// +/// 32 was too small in practice: applications that fan out 10-20 concurrent +/// TCP connections at startup (browsers, HTTP clients, CLIs that talk to an +/// AI/cloud API) overflow the queue with their first SYN burst before the +/// gateway ARP reply arrives, and the excess packets are silently dropped. +/// Long-running streams that outlive [`NEIGHBOR_TTL`] also re-enter the +/// queue when the cached neighbour expires mid-flow. +/// +/// 128 slots (~189 KiB at 1514 bytes per packet) covers the bursts these +/// applications produce while keeping the boot-time heap footprint small +/// enough for the 128 MiB QEMU defaults that the test rigs use. +pub const ETHERNET_MAX_PENDING_PACKETS: usize = 128; diff --git a/os/arceos/modules/axnet-ng/src/device/ethernet.rs b/os/arceos/modules/axnet-ng/src/device/ethernet.rs index 002adeeb98..0eb7466b8e 100644 --- a/os/arceos/modules/axnet-ng/src/device/ethernet.rs +++ b/os/arceos/modules/axnet-ng/src/device/ethernet.rs @@ -118,7 +118,13 @@ fn release_ethernet_irq_slot(slot: usize, state: &Arc) { } impl EthernetDevice { - const NEIGHBOR_TTL: Duration = Duration::from_secs(60); + /// Lifetime of a resolved unicast neighbour entry. Linux uses 5 minutes + /// for unicast neighbours; sticking to that value keeps long-running + /// streams (e.g. a cold-start API response that takes >60 s to begin + /// flowing) from invalidating the gateway entry mid-flow, which would + /// otherwise force every queued ACK back into the ARP-pending buffer + /// at once. + const NEIGHBOR_TTL: Duration = Duration::from_secs(300); const ARP_REQUEST_RETRY: Duration = Duration::from_secs(1); pub fn new(name: String, inner: Box, ip: Option) -> Self { @@ -357,40 +363,71 @@ impl EthernetDevice { ); } - if self - .pending_packets - .peek() - .is_ok_and(|it| it.0 == &IpAddress::Ipv4(source_protocol_addr)) - { - while let Ok((&next_hop, buf)) = self.pending_packets.peek() { - // TODO: optimize logic such that one long-pending ARP - // request does not block all other packets - - let Some(neighbor) = self.neighbors.get(&next_hop) else { - break; - }; - if neighbor.expires_at <= now { - // Neighbor is expired, we need to request ARP again + // Drain every entry in the pending queue and either send it (if + // the next-hop is now resolved) or re-queue it in arrival order. + // Peeking the head and stopping on the first mismatch would + // permanently block packets queued behind an unresolvable + // next-hop (e.g. a SYN to a fake IP at the head holds back a + // SYN to the gateway behind it). + // + // The kept buffer is pre-sized so the drain does not have to + // grow it through reallocations while a high-priority ARP IRQ + // is being processed. + let mut kept: Vec<(IpAddress, Vec)> = + Vec::with_capacity(ETHERNET_MAX_PENDING_PACKETS); + for _ in 0..ETHERNET_MAX_PENDING_PACKETS { + let Ok((&next_hop, buf)) = self.pending_packets.peek() else { + break; + }; + enum Action { + Send(EthernetAddress, Vec), + Refresh(Vec), + Keep(Vec), + } + let action = match self.neighbors.get(&next_hop) { + Some(neighbor) if neighbor.expires_at > now => { + Action::Send(neighbor.hardware_address, buf.to_vec()) + } + Some(_) => Action::Refresh(buf.to_vec()), + None => Action::Keep(buf.to_vec()), + }; + let _ = self.pending_packets.dequeue(); + + match action { + Action::Send(mac, payload) => { + let mut inner = self.inner.driver.lock(); + info!( + "{}: sending pending IPv4 packet to {} via {}", + self.name, next_hop, mac + ); + Self::send_to( + &mut **inner, + mac, + payload.len(), + |b| b.copy_from_slice(&payload), + EthernetProtocol::Ipv4, + ); + } + Action::Refresh(payload) => { self.neighbors.remove(&next_hop); let _ = self.request_arp(next_hop, now); - break; + kept.push((next_hop, payload)); + } + Action::Keep(payload) => { + kept.push((next_hop, payload)); } - - let mut inner = self.inner.driver.lock(); - info!( - "{}: sending pending IPv4 packet to {} via {}", - self.name, next_hop, neighbor.hardware_address - ); - Self::send_to( - &mut **inner, - neighbor.hardware_address, - buf.len(), - |b| b.copy_from_slice(buf), - EthernetProtocol::Ipv4, - ); - let _ = self.pending_packets.dequeue(); } } + for (next_hop, payload) in kept { + let Ok(dst) = self.pending_packets.enqueue(payload.len(), next_hop) else { + warn!( + "{}: pending buffer overflow while restoring queue entry to {}", + self.name, next_hop + ); + break; + }; + dst.copy_from_slice(&payload); + } } } } From 46219a54a50a11887038c1f7d91c74509ae7f89b Mon Sep 17 00:00:00 2001 From: Feiran Qin <161046517+jakeuibn@users.noreply.github.com> Date: Mon, 25 May 2026 22:57:27 +0800 Subject: [PATCH 31/71] fix(ax-task): preempt on async wake, guard wait queue against double-enqueue (#912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(ax-task): preempt on async wake, guard wait queue against double-enqueue Two small but related scheduler fixes that together remove a latency spike and a panic seen on async I/O paths. 1. AxWaker::wake_by_ref calls unblock_task with resched=true The previous resched=false meant any future woken by an interrupt or another task only became runnable after the currently running task reached its next voluntary yield point. With the default 10 ms timer tick, the visible latency on a two-hop wake (interrupt -> tty reader -> TUI consumer) was up to 20 ms before the consumer started running. Passing resched=true triggers set_preempt_pending() inside unblock_task so the woken task can take over at the next safe preemption point, collapsing the latency to a few microseconds. 2. blocked_resched skips push_back when the task is already queued Enabling preemptive wakes exposes a race in WaitQueue::wait_until. wait_until acquires the queue lock, sets the task state to Blocked and calls blocked_resched. blocked_resched used to unconditionally set in_wait_queue and push the task into the queue. If an AxWaker firing on an unrelated future preempts the task between the previous push_back and the resched call (which is now possible because the AxWaker arms set_preempt_pending), the task can re-enter wait_until and reach blocked_resched a second time while the previous entry is still in the queue. notify_one_with later pops the stale entry and transfers mutex ownership to it; the task subsequently observes the mutex as already owned and panics with "Thread(N) tried to acquire mutex it already owns" on the next aspace().lock(). Guard the push_back with `if !curr.in_wait_queue()`. The flag is still cleared by notify_one_with after a successful pop, so the skip only ever fires when the previous wait is still pending and the task is genuinely already queued. * test(ax-task): add blocked_resched double-enqueue regression test; enable ax-task in CI std test suite 补充审查意见要求的回归测试,验证 blocked_resched 中 `if !curr.in_wait_queue()` 守护能防止 AxWaker::wake_by_ref 直接唤醒路径导致的双重入队。 测试场景: 1. T1 阻塞在 WaitQueue::wait_until(WQ 有 1 条 stale 条目,in_wait_queue=true) 2. 主任务用 select_run_queue::unblock_task 直接唤醒 T1(不清除 in_wait_queue), 模拟 AxWaker::wake_by_ref with resched=true 的执行路径 3. T1 重跑,条件仍为 false,再次进入 blocked_resched 4. 断言 WQ 最终为空——有幽灵条目则说明双重入队 bug 未被修复 同时修复 ax-task 测试长期未在 CI 运行的问题: - axbuild std 测试 runner 扩展 CSV 格式支持每包 feature 列表 (`package,feat1,feat2,...`,向后兼容旧格式) - std_crates.csv 中 ax-task 改为 `ax-task,multitask,sched-fifo,test`, 使 cargo xtask test 能正常编译并执行调度器测试 Co-Authored-By: Claude Sonnet 4.6 (1M context) --------- Co-authored-by: StarryOS Fix Co-authored-by: Claude Sonnet 4.6 (1M context) --- os/arceos/modules/axtask/src/future/mod.rs | 9 +- os/arceos/modules/axtask/src/run_queue.rs | 14 ++- os/arceos/modules/axtask/src/tests.rs | 67 +++++++++- scripts/axbuild/src/test/std.rs | 135 ++++++++++++++++----- scripts/test/std_crates.csv | 2 +- 5 files changed, 195 insertions(+), 32 deletions(-) diff --git a/os/arceos/modules/axtask/src/future/mod.rs b/os/arceos/modules/axtask/src/future/mod.rs index 018d2958e5..2e1fe4f172 100644 --- a/os/arceos/modules/axtask/src/future/mod.rs +++ b/os/arceos/modules/axtask/src/future/mod.rs @@ -43,7 +43,14 @@ impl Wake for AxWaker { if let Some(task) = self.task.upgrade() { let mut rq = select_run_queue::(&task); *self.woke.lock() = true; - rq.unblock_task(task, false); + // Pass resched=true so set_preempt_pending() is called when the + // task is moved back to ready. Without this an async I/O wake + // sits behind one full timer tick of the currently running task + // before it can run, which manifests as latency spikes on the + // user→tty→TUI hop (any extra keystroke-to-redraw step adds + // ~10 ms). Making the wake preemptive collapses that hop to + // the scheduler's next safe preemption point (microseconds). + rq.unblock_task(task, true); } } } diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index e29eeb86d1..0bfa4172e8 100644 --- a/os/arceos/modules/axtask/src/run_queue.rs +++ b/os/arceos/modules/axtask/src/run_queue.rs @@ -446,9 +446,19 @@ impl CurrentRunQueueRef<'_, G> { // Mark the task as blocked, this has to be done before adding it to the wait queue // while holding the lock of the wait queue. curr.set_state(TaskState::Blocked); - curr.set_in_wait_queue(true); - wq_guard.push_back(curr.clone()); + // The task may already be in this wait queue when an async waker + // (e.g. AxWaker::wake_by_ref with resched=true) preempts the task + // while it is inside WaitQueue::wait_until and re-runs the same + // wait_until call before the previous push_back has been + // consumed. Pushing twice would leave a phantom entry that + // notify_one_with can later hand mutex ownership to while the + // task is running user code, causing a spurious "tried to + // acquire mutex it already owns" panic on the next aspace().lock(). + if !curr.in_wait_queue() { + curr.set_in_wait_queue(true); + wq_guard.push_back(curr.clone()); + } // Drop the lock of wait queue explictly. drop(wq_guard); diff --git a/os/arceos/modules/axtask/src/tests.rs b/os/arceos/modules/axtask/src/tests.rs index 55dc8dfce0..8674a3a7dc 100644 --- a/os/arceos/modules/axtask/src/tests.rs +++ b/os/arceos/modules/axtask/src/tests.rs @@ -5,7 +5,9 @@ use std::{ thread, }; -use crate::{WaitQueue, api as ax_task, current}; +use ax_kernel_guard::NoPreemptIrqSave; + +use crate::{WaitQueue, api as ax_task, current, run_queue::select_run_queue, task::TaskState}; type TestResult = Result<(), Box>; type TestJob = (Box, mpsc::Sender); @@ -160,3 +162,66 @@ fn test_task_join() { } }); } + +// Regression test for the double-enqueue bug fixed in blocked_resched. +// +// Scenario: a task is blocked in WaitQueue::wait_until (in_wait_queue = true, +// one entry in the queue). An AxWaker fires unblock_task directly — the same +// path taken by AxWaker::wake_by_ref with resched=true — which transitions the +// task to Ready without clearing its in_wait_queue flag. The stale queue +// entry is still present. When the task re-runs and re-enters blocked_resched +// (condition still false), the guard `if !curr.in_wait_queue()` must prevent a +// second push_back. Without the guard the queue would accumulate a phantom +// entry; notify_one_with could later hand ownership to the already-running +// task and produce a spurious "tried to acquire mutex it already owns" panic. +#[test] +fn test_blocked_resched_no_double_enqueue() { + run_in_test_scheduler(|| { + static WQ: WaitQueue = WaitQueue::new(); + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + COUNTER.store(0, Ordering::Release); + + // T1 waits until COUNTER reaches 2. + let task1 = ax_task::spawn_raw( + || { + WQ.wait_until(|| COUNTER.load(Ordering::Acquire) >= 2); + }, + "waiter".into(), + RAW_TASK_STACK_SIZE, + ); + + // Let T1 run and block; it now has one entry in WQ with in_wait_queue=true. + ax_task::yield_now(); + + assert_eq!(task1.state(), TaskState::Blocked, "T1 should be blocked"); + assert!(task1.in_wait_queue(), "T1 should have in_wait_queue=true"); + + // Simulate AxWaker::wake_by_ref (resched=true path): unblock T1 directly + // without going through notify_one, so in_wait_queue is NOT cleared and + // the stale queue entry remains. + select_run_queue::(&task1).unblock_task(task1.clone(), false); + assert!( + task1.in_wait_queue(), + "in_wait_queue must stay true after direct unblock (stale entry)" + ); + + // Let T1 run: COUNTER is still 0 so condition is false; T1 re-enters + // blocked_resched. The in_wait_queue guard must prevent a second push. + ax_task::yield_now(); + + // Signal the condition and do exactly one notify. + COUNTER.store(2, Ordering::Release); + assert!(WQ.notify_one(true), "notify_one must find the stale entry"); + + // Wait for T1 to finish. + task1.join(); + + // After T1 exits the queue must be empty. A phantom entry here would + // mean the double-enqueue occurred and the fix is not working. + assert!( + !WQ.notify_one(false), + "wait queue has a phantom entry — blocked_resched double-enqueue not fixed" + ); + }); +} diff --git a/scripts/axbuild/src/test/std.rs b/scripts/axbuild/src/test/std.rs index d11d9646c3..73a0a8a454 100644 --- a/scripts/axbuild/src/test/std.rs +++ b/scripts/axbuild/src/test/std.rs @@ -47,19 +47,33 @@ fn workspace_package_names(metadata: &Metadata) -> HashSet { .collect() } +/// A package entry from the std-crates CSV, with optional feature flags. +#[derive(Debug, Clone, PartialEq, Eq)] +struct TestPackage { + name: String, + /// Feature names to pass via `--features`. Empty means no `--features` flag. + features: Vec, +} + fn load_std_crates( csv_path: &Path, known_packages: &HashSet, -) -> anyhow::Result> { +) -> anyhow::Result> { let contents = fs::read_to_string(csv_path) .with_context(|| format!("failed to read {}", csv_path.display()))?; parse_std_crates_csv(&contents, known_packages) } +/// Parse the std-crates CSV. +/// +/// Each data line may be either `package` or `package,feat1,feat2,...`. The +/// first field must be a known workspace package name; any remaining +/// comma-separated fields are treated as Cargo feature names passed via +/// `--features` when the test is run. fn parse_std_crates_csv( contents: &str, known_packages: &HashSet, -) -> anyhow::Result> { +) -> anyhow::Result> { let mut lines = contents.lines().enumerate().filter_map(|(idx, raw)| { let line = raw.trim(); (!line.is_empty()).then_some((idx + 1, line)) @@ -79,7 +93,18 @@ fn parse_std_crates_csv( let mut packages = Vec::new(); let mut seen = HashSet::new(); - for (line_no, package) in lines { + for (line_no, line) in lines { + let mut fields = line.splitn(2, ','); + let package = fields.next().unwrap_or("").trim(); + let features: Vec = fields + .next() + .unwrap_or("") + .split(',') + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(str::to_owned) + .collect(); + if !known_packages.contains(package) { bail!( "unknown workspace package `{}` at line {}", @@ -90,35 +115,43 @@ fn parse_std_crates_csv( if !seen.insert(package.to_owned()) { bail!("duplicate package `{}` at line {}", package, line_no); } - packages.push(package.to_owned()); + packages.push(TestPackage { + name: package.to_owned(), + features, + }); } Ok(packages) } -fn cargo_test_args(package: &str) -> Vec { - vec!["test".into(), "-p".into(), package.into()] +fn cargo_test_args(pkg: &TestPackage) -> Vec { + let mut args = vec!["test".into(), "-p".into(), pkg.name.clone()]; + if !pkg.features.is_empty() { + args.push("--features".into()); + args.push(pkg.features.join(",")); + } + args } fn run_std_tests( runner: &mut R, workspace_root: &Path, - packages: &[String], + packages: &[TestPackage], ) -> anyhow::Result> { let mut failed = Vec::new(); - for (index, package) in packages.iter().enumerate() { + for (index, pkg) in packages.iter().enumerate() { println!( "[{}/{}] cargo {}", index + 1, packages.len(), - cargo_test_args(package).join(" ") + cargo_test_args(pkg).join(" ") ); - if runner.run_test(workspace_root, package)? { - println!("ok: {}", package); + if runner.run_test(workspace_root, pkg)? { + println!("ok: {}", pkg.name); } else { - eprintln!("failed: {}", package); - failed.push(package.clone()); + eprintln!("failed: {}", pkg.name); + failed.push(pkg.name.clone()); } } @@ -126,14 +159,14 @@ fn run_std_tests( } trait CargoRunner { - fn run_test(&mut self, workspace_root: &Path, package: &str) -> anyhow::Result; + fn run_test(&mut self, workspace_root: &Path, pkg: &TestPackage) -> anyhow::Result; } struct ProcessCargoRunner; impl CargoRunner for ProcessCargoRunner { - fn run_test(&mut self, workspace_root: &Path, package: &str) -> anyhow::Result { - let args = cargo_test_args(package); + fn run_test(&mut self, workspace_root: &Path, pkg: &TestPackage) -> anyhow::Result { + let args = cargo_test_args(pkg); run_cargo_status(workspace_root, &args) } } @@ -170,10 +203,24 @@ mod tests { } impl CargoRunner for FakeCargoRunner { - fn run_test(&mut self, workspace_root: &Path, package: &str) -> anyhow::Result { + fn run_test(&mut self, workspace_root: &Path, pkg: &TestPackage) -> anyhow::Result { self.invocations - .push((workspace_root.to_path_buf(), package.to_string())); - Ok(*self.results.get(package).unwrap_or(&true)) + .push((workspace_root.to_path_buf(), pkg.name.clone())); + Ok(*self.results.get(&pkg.name).unwrap_or(&true)) + } + } + + fn pkg(name: &str) -> TestPackage { + TestPackage { + name: name.to_string(), + features: vec![], + } + } + + fn pkg_with_features(name: &str, features: &[&str]) -> TestPackage { + TestPackage { + name: name.to_string(), + features: features.iter().map(|s| s.to_string()).collect(), } } @@ -182,7 +229,7 @@ mod tests { let packages = parse_std_crates_csv("package\nax-feat\nax-hal\n", &known_packages()).unwrap(); - assert_eq!(packages, vec!["ax-feat".to_string(), "ax-hal".to_string()]); + assert_eq!(packages, vec![pkg("ax-feat"), pkg("ax-hal")]); } #[test] @@ -190,7 +237,24 @@ mod tests { let packages = parse_std_crates_csv("\npackage\n\nax-feat\n\nax-hal\n", &known_packages()).unwrap(); - assert_eq!(packages, vec!["ax-feat".to_string(), "ax-hal".to_string()]); + assert_eq!(packages, vec![pkg("ax-feat"), pkg("ax-hal")]); + } + + #[test] + fn parses_std_csv_with_features() { + let packages = parse_std_crates_csv( + "package\nax-feat,feat-a,feat-b\nax-hal\n", + &known_packages(), + ) + .unwrap(); + + assert_eq!( + packages, + vec![ + pkg_with_features("ax-feat", &["feat-a", "feat-b"]), + pkg("ax-hal") + ] + ); } #[test] @@ -240,11 +304,7 @@ mod tests { #[test] fn std_test_runner_collects_all_failures() { let root = PathBuf::from("/tmp/workspace"); - let packages = vec![ - "ax-feat".to_string(), - "ax-hal".to_string(), - "starry-process".to_string(), - ]; + let packages = vec![pkg("ax-feat"), pkg("ax-hal"), pkg("starry-process")]; let mut runner = FakeCargoRunner::new(&[ ("ax-feat", true), ("ax-hal", false), @@ -270,11 +330,32 @@ mod tests { #[test] fn std_test_runner_returns_empty_failures_when_all_pass() { let root = PathBuf::from("/tmp/workspace"); - let packages = vec!["ax-feat".to_string(), "ax-hal".to_string()]; + let packages = vec![pkg("ax-feat"), pkg("ax-hal")]; let mut runner = FakeCargoRunner::new(&[("ax-feat", true), ("ax-hal", true)]); let failed = run_std_tests(&mut runner, &root, &packages).unwrap(); assert!(failed.is_empty()); } + + #[test] + fn cargo_test_args_without_features() { + let p = pkg("ax-hal"); + assert_eq!(cargo_test_args(&p), vec!["test", "-p", "ax-hal"]); + } + + #[test] + fn cargo_test_args_with_features() { + let p = pkg_with_features("ax-task", &["multitask", "sched-fifo", "test"]); + assert_eq!( + cargo_test_args(&p), + vec![ + "test", + "-p", + "ax-task", + "--features", + "multitask,sched-fifo,test" + ] + ); + } } diff --git a/scripts/test/std_crates.csv b/scripts/test/std_crates.csv index 9193eed899..3b9b0c0058 100644 --- a/scripts/test/std_crates.csv +++ b/scripts/test/std_crates.csv @@ -43,7 +43,7 @@ ax-display ax-driver ax-hal ax-sync -ax-task +ax-task,multitask,sched-fifo,test ax-input ax-ipi ax-log From 056fa5f0751f547d7e1d3c54efb0d4c0a581cc8d Mon Sep 17 00:00:00 2001 From: Feiran Qin <161046517+jakeuibn@users.noreply.github.com> Date: Mon, 25 May 2026 22:58:23 +0800 Subject: [PATCH 32/71] fix(starry-kernel,x86-qemu-q35): probe terminal size, deliver SIGWINCH, batch ONLCR writes (#913) Four serial-console / TTY improvements that together make TUI applications (ratatui-based clients, anything that reacts to SIGWINCH or that draws a full frame per render) usable on the QEMU serial console. 1. NTTY probes the connected terminal's real dimensions On NTTY initialisation (synchronously, before the input poll task is spawned) the kernel sends the standard cursor-position-report sequence (`\x1b7\x1b[9999;9999H\x1b[6n\x1b8`) and parses the `\x1b[rows;colsR` reply. TIOCGWINSZ then reports the real host terminal size instead of a stale fallback, which is what TUI applications rely on to lay out panels and to translate mouse coordinates to widgets. The wait is bounded to ~100 ms of wall time via `ax_hal::time::wall_time()` so a host that ignores CPR does not delay boot. 2. Default window size 28x110 -> 24x80 24x80 is the standard VT100 fallback that applications expect when TIOCGWINSZ reports a "default" terminal. The previous value was an arbitrary serial-friendly size that confused applications on first paint. 3. SIGWINCH on TIOCSWINSZ Match Linux tty_do_resize(): when an ioctl changes the recorded window size, send SIGWINCH to the foreground process group so TUI applications can re-layout immediately when the user resizes the host terminal. 4. Batched ONLCR output and atomic UART writes write_output_bytes() now collects the `\n -> \r\n` translation into a single Vec and issues exactly one writer.write() call. The previous per-newline issue path produced O(newlines) separate writes; a typical TUI frame contains many cursor-movement newlines, so the COM1 driver acquired/released its lock dozens of times for a single frame and terminal emulators rendered the frame line-by-line (visible flicker). On the x86-qemu-q35 platform the ConsoleIfImpl::write_bytes implementation also holds the COM1 lock for the whole call so that one logical write is never interleaved with output from another task and pays the lock-acquire cost once per write_bytes rather than once per byte. Co-authored-by: StarryOS Fix --- .../kernel/src/pseudofs/dev/tty/mod.rs | 20 ++++- .../kernel/src/pseudofs/dev/tty/ntty.rs | 85 ++++++++++++++++++- .../src/pseudofs/dev/tty/terminal/ldisc.rs | 26 +++--- .../src/pseudofs/dev/tty/terminal/mod.rs | 6 +- platform/x86-qemu-q35/src/console.rs | 14 +-- 5 files changed, 128 insertions(+), 23 deletions(-) diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs index 3b5c331e6e..ddc1161e4f 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs @@ -13,6 +13,7 @@ use ax_task::current; use axfs_ng_vfs::NodeFlags; use axpoll::{IoEvents, Pollable}; use starry_process::Process; +use starry_signal::{SignalInfo, Signo}; use starry_vm::{VmMutPtr, VmPtr}; use self::terminal::{ @@ -28,7 +29,7 @@ pub use self::{ }; use crate::{ pseudofs::DeviceOps, - task::{AsThread, get_process_group}, + task::{AsThread, get_process_group, send_signal_to_process_group}, }; const ANSI_CURSOR_POSITION_REQUEST: &[u8] = b"\x1b[6n"; @@ -153,7 +154,22 @@ impl DeviceOps for Tty { } TIOCSWINSZ => { let window_size = (arg as *const WindowSize).vm_read()?; - *self.terminal.window_size.lock() = window_size; + let old = { + let mut guard = self.terminal.window_size.lock(); + let old = *guard; + *guard = window_size; + old + }; + // Match Linux tty_do_resize(): notify the foreground process + // group via SIGWINCH so TUI applications (e.g. ratatui) can + // re-layout when the user resizes the host terminal. + let changed = old.ws_row != window_size.ws_row || old.ws_col != window_size.ws_col; + if changed && let Some(pg) = self.terminal.job_control.foreground() { + let _ = send_signal_to_process_group( + pg.pgid(), + Some(SignalInfo::new_kernel(Signo::SIGWINCH)), + ); + } } TIOCSPTLCK => {} TIOCGPTN => { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs index 75106cf3e7..c1082a7454 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs @@ -5,7 +5,10 @@ use spin::Lazy; use super::{ Tty, - terminal::ldisc::{ProcessMode, TtyConfig, TtyRead, TtyWrite}, + terminal::{ + WindowSize, + ldisc::{ProcessMode, TtyConfig, TtyRead, TtyWrite}, + }, }; pub type NTtyDriver = Tty; @@ -39,8 +42,28 @@ fn handle_console_input_irq(_irq_num: usize) { } fn new_n_tty() -> Arc { + // Synchronously query the connected terminal's dimensions before the + // poll-reader task is spawned so TIOCGWINSZ reports the real host + // terminal size. TUI applications (e.g. ratatui-based clients) use the + // size both to lay out panels and to map mouse-event coordinates; + // returning a stale fallback misplaces the UI and leaves clicks/scroll + // outside the rendered widgets. Falls back silently to the default + // 24x80 if the host terminal does not support CPR. + let terminal = { + let t = super::terminal::Terminal::default(); + if let Some((rows, cols)) = query_console_size() { + *t.window_size.lock() = WindowSize { + ws_row: rows, + ws_col: cols, + ws_xpixel: 0, + ws_ypixel: 0, + }; + } + Arc::new(t) + }; + Tty::new( - Arc::default(), + terminal, TtyConfig { reader: Console, writer: Console, @@ -49,6 +72,64 @@ fn new_n_tty() -> Arc { ) } +/// Probe the connected terminal for its current size using the +/// standard cursor-position-report sequence. +/// +/// Sequence: save cursor (DECSC) -> move to (9999, 9999) -> request +/// cursor position (CPR) -> restore cursor (DECRC). The terminal +/// clamps the move to its actual bottom-right corner before reporting +/// back, so the reply `\x1b[rows;colsR` reflects the real geometry. +/// Spin-waits up to roughly 100 ms for the reply and returns `None` +/// on timeout or parse failure. +/// +/// Called once during NTTY initialisation, before the polling reader +/// task is spawned, so there is no concurrent consumer racing on the +/// UART receive FIFO. +fn query_console_size() -> Option<(u16, u16)> { + ax_hal::console::write_bytes(b"\x1b7\x1b[9999;9999H\x1b[6n\x1b8"); + + let mut buf = [0u8; 32]; + let mut len = 0usize; + + // Spin up to ~100 ms (in wall time, polled via ax_hal::time::wall_time) + // for the `R` terminator. Hosts that ignore CPR (jcode running under + // a non-interactive serial, automated CI runners) will time out and + // we fall back to the 24x80 default without blocking boot further. + let deadline = ax_hal::time::wall_time() + core::time::Duration::from_millis(100); + 'collect: while ax_hal::time::wall_time() < deadline { + let mut tmp = [0u8; 1]; + if ax_hal::console::read_bytes(&mut tmp) > 0 { + if len < buf.len() { + buf[len] = tmp[0]; + len += 1; + } else { + // Buffer full without seeing 'R'; give up rather than + // spinning until the deadline on a misbehaving terminal. + break 'collect; + } + if tmp[0] == b'R' { + break 'collect; + } + } + core::hint::spin_loop(); + } + + if len == 0 { + return None; + } + + let r_pos = buf[..len].iter().rposition(|&b| b == b'R')?; + let escape_pos = buf[..r_pos].windows(2).rposition(|w| w == b"\x1b[")?; + let inner = core::str::from_utf8(&buf[escape_pos + 2..r_pos]).ok()?; + let mut parts = inner.splitn(2, ';'); + let rows: u16 = parts.next()?.parse().ok()?; + let cols: u16 = parts.next()?.parse().ok()?; + if rows == 0 || cols == 0 { + return None; + } + Some((rows, cols)) +} + fn console_irq_mode() -> Option { let irq = ax_hal::console::irq_num()?; if !ax_hal::irq::register(irq, handle_console_input_irq) { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/ldisc.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/ldisc.rs index 5cfe6b9c4f..1d723d0617 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/ldisc.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/ldisc.rs @@ -64,19 +64,25 @@ pub fn write_output_bytes(writer: &W, term: &Termios2, buf return; } - let mut start = 0; - for (i, &byte) in buf.iter().enumerate() { + // Collect output with \n -> \r\n translation into a single buffer so the + // underlying writer sees exactly one call per write_output_bytes() instead + // of one call per newline. A TUI frame typically contains many cursor- + // movement newlines; the per-newline approach forced the UART driver to + // acquire/release its lock dozens of times for a single frame and made + // terminal emulators render the frame line-by-line (visible flicker). + let extra = buf.iter().filter(|&&b| b == b'\n').count(); + if extra == 0 { + writer.write(buf); + return; + } + let mut out = alloc::vec::Vec::with_capacity(buf.len() + extra); + for &byte in buf { if byte == b'\n' { - if start < i { - writer.write(&buf[start..i]); - } - writer.write(b"\r\n"); - start = i + 1; + out.push(b'\r'); } + out.push(byte); } - if start < buf.len() { - writer.write(&buf[start..]); - } + writer.write(&out); } struct InputReader { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/mod.rs index b37ad5dcc6..8bb78ef55a 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/terminal/mod.rs @@ -30,8 +30,10 @@ impl Default for Terminal { Self { job_control: job::JobControl::new(), window_size: SpinNoIrq::new(WindowSize { - ws_row: 28, - ws_col: 110, + // 24x80 is the standard VT100 fallback that applications + // expect when TIOCGWINSZ reports a "default" terminal. + ws_row: 24, + ws_col: 80, ws_xpixel: 0, ws_ypixel: 0, }), diff --git a/platform/x86-qemu-q35/src/console.rs b/platform/x86-qemu-q35/src/console.rs index a125cc017f..6aa6139834 100644 --- a/platform/x86-qemu-q35/src/console.rs +++ b/platform/x86-qemu-q35/src/console.rs @@ -20,11 +20,6 @@ use uart_16550::SerialPort; static COM1: SpinNoIrq = unsafe { SpinNoIrq::new(SerialPort::new(0x3f8)) }; -/// Writes a byte to the console. -pub fn putchar(c: u8) { - COM1.lock().send(c) -} - /// Reads a byte from the console, or returns [`None`] if no input is available. pub fn getchar() -> Option { COM1.lock().try_receive().ok() @@ -40,8 +35,13 @@ struct ConsoleIfImpl; impl ConsoleIf for ConsoleIfImpl { /// Writes given bytes to the console. fn write_bytes(bytes: &[u8]) { - for c in bytes { - putchar(*c); + // Hold COM1 for the entire write so that one logical output + // (e.g. a full ANSI TUI frame) is never interleaved with output + // from another task. This also avoids paying lock-acquire cost + // for every byte, which is visible on large frame dumps. + let mut com = COM1.lock(); + for &c in bytes { + com.send(c); } } From ca22fd7471ee200b9f5d2811d5b32f53ce0aa6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Tue, 26 May 2026 00:02:12 +0800 Subject: [PATCH 33/71] =?UTF-8?q?Revert=20"fix(ax-task):=20preempt=20on=20?= =?UTF-8?q?async=20wake,=20guard=20wait=20queue=20against=20double-?= =?UTF-8?q?=E2=80=A6"=20(#939)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d26c7ddb92269eac5e1e9fa7be60088f7b7a97a9. --- os/arceos/modules/axtask/src/future/mod.rs | 9 +- os/arceos/modules/axtask/src/run_queue.rs | 14 +-- os/arceos/modules/axtask/src/tests.rs | 67 +--------- scripts/axbuild/src/test/std.rs | 135 +++++---------------- scripts/test/std_crates.csv | 2 +- 5 files changed, 32 insertions(+), 195 deletions(-) diff --git a/os/arceos/modules/axtask/src/future/mod.rs b/os/arceos/modules/axtask/src/future/mod.rs index 2e1fe4f172..018d2958e5 100644 --- a/os/arceos/modules/axtask/src/future/mod.rs +++ b/os/arceos/modules/axtask/src/future/mod.rs @@ -43,14 +43,7 @@ impl Wake for AxWaker { if let Some(task) = self.task.upgrade() { let mut rq = select_run_queue::(&task); *self.woke.lock() = true; - // Pass resched=true so set_preempt_pending() is called when the - // task is moved back to ready. Without this an async I/O wake - // sits behind one full timer tick of the currently running task - // before it can run, which manifests as latency spikes on the - // user→tty→TUI hop (any extra keystroke-to-redraw step adds - // ~10 ms). Making the wake preemptive collapses that hop to - // the scheduler's next safe preemption point (microseconds). - rq.unblock_task(task, true); + rq.unblock_task(task, false); } } } diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index 0bfa4172e8..e29eeb86d1 100644 --- a/os/arceos/modules/axtask/src/run_queue.rs +++ b/os/arceos/modules/axtask/src/run_queue.rs @@ -446,19 +446,9 @@ impl CurrentRunQueueRef<'_, G> { // Mark the task as blocked, this has to be done before adding it to the wait queue // while holding the lock of the wait queue. curr.set_state(TaskState::Blocked); + curr.set_in_wait_queue(true); - // The task may already be in this wait queue when an async waker - // (e.g. AxWaker::wake_by_ref with resched=true) preempts the task - // while it is inside WaitQueue::wait_until and re-runs the same - // wait_until call before the previous push_back has been - // consumed. Pushing twice would leave a phantom entry that - // notify_one_with can later hand mutex ownership to while the - // task is running user code, causing a spurious "tried to - // acquire mutex it already owns" panic on the next aspace().lock(). - if !curr.in_wait_queue() { - curr.set_in_wait_queue(true); - wq_guard.push_back(curr.clone()); - } + wq_guard.push_back(curr.clone()); // Drop the lock of wait queue explictly. drop(wq_guard); diff --git a/os/arceos/modules/axtask/src/tests.rs b/os/arceos/modules/axtask/src/tests.rs index 8674a3a7dc..55dc8dfce0 100644 --- a/os/arceos/modules/axtask/src/tests.rs +++ b/os/arceos/modules/axtask/src/tests.rs @@ -5,9 +5,7 @@ use std::{ thread, }; -use ax_kernel_guard::NoPreemptIrqSave; - -use crate::{WaitQueue, api as ax_task, current, run_queue::select_run_queue, task::TaskState}; +use crate::{WaitQueue, api as ax_task, current}; type TestResult = Result<(), Box>; type TestJob = (Box, mpsc::Sender); @@ -162,66 +160,3 @@ fn test_task_join() { } }); } - -// Regression test for the double-enqueue bug fixed in blocked_resched. -// -// Scenario: a task is blocked in WaitQueue::wait_until (in_wait_queue = true, -// one entry in the queue). An AxWaker fires unblock_task directly — the same -// path taken by AxWaker::wake_by_ref with resched=true — which transitions the -// task to Ready without clearing its in_wait_queue flag. The stale queue -// entry is still present. When the task re-runs and re-enters blocked_resched -// (condition still false), the guard `if !curr.in_wait_queue()` must prevent a -// second push_back. Without the guard the queue would accumulate a phantom -// entry; notify_one_with could later hand ownership to the already-running -// task and produce a spurious "tried to acquire mutex it already owns" panic. -#[test] -fn test_blocked_resched_no_double_enqueue() { - run_in_test_scheduler(|| { - static WQ: WaitQueue = WaitQueue::new(); - static COUNTER: AtomicUsize = AtomicUsize::new(0); - - COUNTER.store(0, Ordering::Release); - - // T1 waits until COUNTER reaches 2. - let task1 = ax_task::spawn_raw( - || { - WQ.wait_until(|| COUNTER.load(Ordering::Acquire) >= 2); - }, - "waiter".into(), - RAW_TASK_STACK_SIZE, - ); - - // Let T1 run and block; it now has one entry in WQ with in_wait_queue=true. - ax_task::yield_now(); - - assert_eq!(task1.state(), TaskState::Blocked, "T1 should be blocked"); - assert!(task1.in_wait_queue(), "T1 should have in_wait_queue=true"); - - // Simulate AxWaker::wake_by_ref (resched=true path): unblock T1 directly - // without going through notify_one, so in_wait_queue is NOT cleared and - // the stale queue entry remains. - select_run_queue::(&task1).unblock_task(task1.clone(), false); - assert!( - task1.in_wait_queue(), - "in_wait_queue must stay true after direct unblock (stale entry)" - ); - - // Let T1 run: COUNTER is still 0 so condition is false; T1 re-enters - // blocked_resched. The in_wait_queue guard must prevent a second push. - ax_task::yield_now(); - - // Signal the condition and do exactly one notify. - COUNTER.store(2, Ordering::Release); - assert!(WQ.notify_one(true), "notify_one must find the stale entry"); - - // Wait for T1 to finish. - task1.join(); - - // After T1 exits the queue must be empty. A phantom entry here would - // mean the double-enqueue occurred and the fix is not working. - assert!( - !WQ.notify_one(false), - "wait queue has a phantom entry — blocked_resched double-enqueue not fixed" - ); - }); -} diff --git a/scripts/axbuild/src/test/std.rs b/scripts/axbuild/src/test/std.rs index 73a0a8a454..d11d9646c3 100644 --- a/scripts/axbuild/src/test/std.rs +++ b/scripts/axbuild/src/test/std.rs @@ -47,33 +47,19 @@ fn workspace_package_names(metadata: &Metadata) -> HashSet { .collect() } -/// A package entry from the std-crates CSV, with optional feature flags. -#[derive(Debug, Clone, PartialEq, Eq)] -struct TestPackage { - name: String, - /// Feature names to pass via `--features`. Empty means no `--features` flag. - features: Vec, -} - fn load_std_crates( csv_path: &Path, known_packages: &HashSet, -) -> anyhow::Result> { +) -> anyhow::Result> { let contents = fs::read_to_string(csv_path) .with_context(|| format!("failed to read {}", csv_path.display()))?; parse_std_crates_csv(&contents, known_packages) } -/// Parse the std-crates CSV. -/// -/// Each data line may be either `package` or `package,feat1,feat2,...`. The -/// first field must be a known workspace package name; any remaining -/// comma-separated fields are treated as Cargo feature names passed via -/// `--features` when the test is run. fn parse_std_crates_csv( contents: &str, known_packages: &HashSet, -) -> anyhow::Result> { +) -> anyhow::Result> { let mut lines = contents.lines().enumerate().filter_map(|(idx, raw)| { let line = raw.trim(); (!line.is_empty()).then_some((idx + 1, line)) @@ -93,18 +79,7 @@ fn parse_std_crates_csv( let mut packages = Vec::new(); let mut seen = HashSet::new(); - for (line_no, line) in lines { - let mut fields = line.splitn(2, ','); - let package = fields.next().unwrap_or("").trim(); - let features: Vec = fields - .next() - .unwrap_or("") - .split(',') - .map(str::trim) - .filter(|s| !s.is_empty()) - .map(str::to_owned) - .collect(); - + for (line_no, package) in lines { if !known_packages.contains(package) { bail!( "unknown workspace package `{}` at line {}", @@ -115,43 +90,35 @@ fn parse_std_crates_csv( if !seen.insert(package.to_owned()) { bail!("duplicate package `{}` at line {}", package, line_no); } - packages.push(TestPackage { - name: package.to_owned(), - features, - }); + packages.push(package.to_owned()); } Ok(packages) } -fn cargo_test_args(pkg: &TestPackage) -> Vec { - let mut args = vec!["test".into(), "-p".into(), pkg.name.clone()]; - if !pkg.features.is_empty() { - args.push("--features".into()); - args.push(pkg.features.join(",")); - } - args +fn cargo_test_args(package: &str) -> Vec { + vec!["test".into(), "-p".into(), package.into()] } fn run_std_tests( runner: &mut R, workspace_root: &Path, - packages: &[TestPackage], + packages: &[String], ) -> anyhow::Result> { let mut failed = Vec::new(); - for (index, pkg) in packages.iter().enumerate() { + for (index, package) in packages.iter().enumerate() { println!( "[{}/{}] cargo {}", index + 1, packages.len(), - cargo_test_args(pkg).join(" ") + cargo_test_args(package).join(" ") ); - if runner.run_test(workspace_root, pkg)? { - println!("ok: {}", pkg.name); + if runner.run_test(workspace_root, package)? { + println!("ok: {}", package); } else { - eprintln!("failed: {}", pkg.name); - failed.push(pkg.name.clone()); + eprintln!("failed: {}", package); + failed.push(package.clone()); } } @@ -159,14 +126,14 @@ fn run_std_tests( } trait CargoRunner { - fn run_test(&mut self, workspace_root: &Path, pkg: &TestPackage) -> anyhow::Result; + fn run_test(&mut self, workspace_root: &Path, package: &str) -> anyhow::Result; } struct ProcessCargoRunner; impl CargoRunner for ProcessCargoRunner { - fn run_test(&mut self, workspace_root: &Path, pkg: &TestPackage) -> anyhow::Result { - let args = cargo_test_args(pkg); + fn run_test(&mut self, workspace_root: &Path, package: &str) -> anyhow::Result { + let args = cargo_test_args(package); run_cargo_status(workspace_root, &args) } } @@ -203,24 +170,10 @@ mod tests { } impl CargoRunner for FakeCargoRunner { - fn run_test(&mut self, workspace_root: &Path, pkg: &TestPackage) -> anyhow::Result { + fn run_test(&mut self, workspace_root: &Path, package: &str) -> anyhow::Result { self.invocations - .push((workspace_root.to_path_buf(), pkg.name.clone())); - Ok(*self.results.get(&pkg.name).unwrap_or(&true)) - } - } - - fn pkg(name: &str) -> TestPackage { - TestPackage { - name: name.to_string(), - features: vec![], - } - } - - fn pkg_with_features(name: &str, features: &[&str]) -> TestPackage { - TestPackage { - name: name.to_string(), - features: features.iter().map(|s| s.to_string()).collect(), + .push((workspace_root.to_path_buf(), package.to_string())); + Ok(*self.results.get(package).unwrap_or(&true)) } } @@ -229,7 +182,7 @@ mod tests { let packages = parse_std_crates_csv("package\nax-feat\nax-hal\n", &known_packages()).unwrap(); - assert_eq!(packages, vec![pkg("ax-feat"), pkg("ax-hal")]); + assert_eq!(packages, vec!["ax-feat".to_string(), "ax-hal".to_string()]); } #[test] @@ -237,24 +190,7 @@ mod tests { let packages = parse_std_crates_csv("\npackage\n\nax-feat\n\nax-hal\n", &known_packages()).unwrap(); - assert_eq!(packages, vec![pkg("ax-feat"), pkg("ax-hal")]); - } - - #[test] - fn parses_std_csv_with_features() { - let packages = parse_std_crates_csv( - "package\nax-feat,feat-a,feat-b\nax-hal\n", - &known_packages(), - ) - .unwrap(); - - assert_eq!( - packages, - vec![ - pkg_with_features("ax-feat", &["feat-a", "feat-b"]), - pkg("ax-hal") - ] - ); + assert_eq!(packages, vec!["ax-feat".to_string(), "ax-hal".to_string()]); } #[test] @@ -304,7 +240,11 @@ mod tests { #[test] fn std_test_runner_collects_all_failures() { let root = PathBuf::from("/tmp/workspace"); - let packages = vec![pkg("ax-feat"), pkg("ax-hal"), pkg("starry-process")]; + let packages = vec![ + "ax-feat".to_string(), + "ax-hal".to_string(), + "starry-process".to_string(), + ]; let mut runner = FakeCargoRunner::new(&[ ("ax-feat", true), ("ax-hal", false), @@ -330,32 +270,11 @@ mod tests { #[test] fn std_test_runner_returns_empty_failures_when_all_pass() { let root = PathBuf::from("/tmp/workspace"); - let packages = vec![pkg("ax-feat"), pkg("ax-hal")]; + let packages = vec!["ax-feat".to_string(), "ax-hal".to_string()]; let mut runner = FakeCargoRunner::new(&[("ax-feat", true), ("ax-hal", true)]); let failed = run_std_tests(&mut runner, &root, &packages).unwrap(); assert!(failed.is_empty()); } - - #[test] - fn cargo_test_args_without_features() { - let p = pkg("ax-hal"); - assert_eq!(cargo_test_args(&p), vec!["test", "-p", "ax-hal"]); - } - - #[test] - fn cargo_test_args_with_features() { - let p = pkg_with_features("ax-task", &["multitask", "sched-fifo", "test"]); - assert_eq!( - cargo_test_args(&p), - vec![ - "test", - "-p", - "ax-task", - "--features", - "multitask,sched-fifo,test" - ] - ); - } } diff --git a/scripts/test/std_crates.csv b/scripts/test/std_crates.csv index 3b9b0c0058..9193eed899 100644 --- a/scripts/test/std_crates.csv +++ b/scripts/test/std_crates.csv @@ -43,7 +43,7 @@ ax-display ax-driver ax-hal ax-sync -ax-task,multitask,sched-fifo,test +ax-task ax-input ax-ipi ax-log From 5254d6f43b70f9c9130c47fd98ee6a4c0b2ec9b6 Mon Sep 17 00:00:00 2001 From: YanLien <128586861+YanLien@users.noreply.github.com> Date: Tue, 26 May 2026 10:13:20 +0800 Subject: [PATCH 34/71] feat(axvisor): add PhytiumPi and ROC-RK3568 board tests (#934) --- .github/workflows/ci.yml | 29 +++-- Cargo.lock | 3 + components/axvm/src/config.rs | 5 + os/axvisor/configs/board/phytiumpi.toml | 2 +- os/axvisor/configs/board/roc-rk3568-pc.toml | 2 +- os/axvisor/src/vmm/fdt/create.rs | 107 ++++++++++++------ os/axvisor/src/vmm/fdt/parser.rs | 89 +++++---------- .../build-aarch64-unknown-none-softfloat.toml | 9 ++ .../smoke/board-phytiumpi-linux.toml | 11 ++ .../build-aarch64-unknown-none-softfloat.toml | 10 ++ .../smoke/board-roc-rk3568-pc-linux.toml | 11 ++ 11 files changed, 173 insertions(+), 105 deletions(-) create mode 100644 test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml create mode 100644 test-suit/axvisor/normal/board-phytiumpi/smoke/board-phytiumpi-linux.toml create mode 100644 test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml create mode 100644 test-suit/axvisor/normal/board-roc-rk3568-pc/smoke/board-roc-rk3568-pc-linux.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 371a3cf5fd..a3796fc505 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -334,18 +334,7 @@ jobs: - name: Test axvisor loongarch64 qemu use_container: true runs_on: '["ubuntu-latest"]' - command: | - set -eux - cargo xtask arceos build --arch loongarch64 -p ax-helloworld --smp 1 - vmconfig=tmp/ci-arceos-loongarch64-qemu-smp1.toml - cp os/axvisor/configs/vms/arceos-loongarch64-qemu-smp1.toml "${vmconfig}" - sed -i 's|^image_location = "fs"|image_location = "memory"|' "${vmconfig}" - sed -i 's|^kernel_path = .*|kernel_path = "'"${GITHUB_WORKSPACE}"'/target/loongarch64-unknown-none-softfloat/release/ax-helloworld.bin"|' "${vmconfig}" - cargo xtask axvisor qemu \ - --arch loongarch64 \ - --config os/axvisor/configs/board/qemu-loongarch64.toml \ - --vmconfigs "${vmconfig}" \ - --qemu-config os/axvisor/configs/qemu/qemu-loongarch64.toml + command: cargo xtask axvisor test qemu --arch loongarch64 cache_key: test-axvisor-loongarch64 container_image: axvisor-lvz limit_to_owner: "" @@ -498,6 +487,22 @@ jobs: container_image: "" limit_to_owner: rcore-os main_pr_only: false + - name: Test axvisor self-hosted board roc-rk3568-pc-linux + use_container: false + runs_on: '["self-hosted","linux","board"]' + command: cargo xtask axvisor test board --board roc-rk3568-pc-linux + cache_key: "" + container_image: "" + limit_to_owner: rcore-os + main_pr_only: false + - name: Test axvisor self-hosted board phytiumpi-linux + use_container: false + runs_on: '["self-hosted","linux","board"]' + command: cargo xtask axvisor test board --board phytiumpi-linux + cache_key: "" + container_image: "" + limit_to_owner: rcore-os + main_pr_only: false - name: Test starry self-hosted board orangepi-5-plus use_container: false runs_on: '["self-hosted","linux","board"]' diff --git a/Cargo.lock b/Cargo.lock index fe637345d4..27d724317d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1377,6 +1377,7 @@ dependencies = [ "ax-plat-aarch64-peripherals", "axklib", "log", + "mmio-api", ] [[package]] @@ -1407,6 +1408,7 @@ dependencies = [ "chrono", "log", "loongArch64", + "mmio-api", "uart_16550 0.5.0", ] @@ -1433,6 +1435,7 @@ dependencies = [ "ax-riscv-plic", "axklib", "log", + "mmio-api", "riscv 0.16.0", "riscv_goldfish", "sbi-rt 0.0.3", diff --git a/components/axvm/src/config.rs b/components/axvm/src/config.rs index b4abdadf4b..fcd47dbdb8 100644 --- a/components/axvm/src/config.rs +++ b/components/axvm/src/config.rs @@ -368,6 +368,11 @@ impl PhysCpuList { pub fn set_guest_cpu_sets(&mut self, phys_cpu_sets: Vec) { self.phys_cpu_sets = Some(phys_cpu_sets); } + + /// Sets the CPU IDs exposed to the guest. + pub fn set_guest_phys_cpu_ids(&mut self, phys_cpu_ids: Vec) { + self.phys_cpu_ids = Some(phys_cpu_ids); + } } #[cfg(test)] diff --git a/os/axvisor/configs/board/phytiumpi.toml b/os/axvisor/configs/board/phytiumpi.toml index e8dca5f660..42a0ed4050 100644 --- a/os/axvisor/configs/board/phytiumpi.toml +++ b/os/axvisor/configs/board/phytiumpi.toml @@ -1,7 +1,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "fs", - "sdmmc", + "phytium-mci", ] log = "Info" plat_dyn = true diff --git a/os/axvisor/configs/board/roc-rk3568-pc.toml b/os/axvisor/configs/board/roc-rk3568-pc.toml index e8dca5f660..211950cdb6 100644 --- a/os/axvisor/configs/board/roc-rk3568-pc.toml +++ b/os/axvisor/configs/board/roc-rk3568-pc.toml @@ -1,7 +1,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "fs", - "sdmmc", + "rockchip-sdhci", ] log = "Info" plat_dyn = true diff --git a/os/axvisor/src/vmm/fdt/create.rs b/os/axvisor/src/vmm/fdt/create.rs index 0e6e2bd5a3..e1b57f28a5 100644 --- a/os/axvisor/src/vmm/fdt/create.rs +++ b/os/axvisor/src/vmm/fdt/create.rs @@ -241,43 +241,35 @@ fn is_ancestor_of_passthrough_device(node_path: &str, passthrough_device_names: } /// Determine if CPU node is needed +fn cpu_node_id(node_path: &str) -> Option { + node_path + .strip_prefix("/cpus/cpu@") + .and_then(|rest| rest.split('/').next()) + .and_then(|id| usize::from_str_radix(id, 16).ok()) +} + +fn cpu_reg_address(node: &Node) -> Option { + node.reg() + .and_then(|mut reg| reg.next()) + .map(|reg_entry| reg_entry.address as usize) +} + fn need_cpu_node(phys_cpu_ids: &[usize], node: &Node, node_path: &str) -> bool { if !node_path.starts_with("/cpus/cpu@") { return true; } - if let Some(cpu_id) = node_path - .strip_prefix("/cpus/cpu@") - .and_then(|rest| rest.split('/').next()) - .and_then(|id| usize::from_str_radix(id, 16).ok()) - && phys_cpu_ids.contains(&cpu_id) - { - return true; + if let Some(cpu_id) = cpu_node_id(node_path) { + return phys_cpu_ids.contains(&cpu_id); } - if let Some(mut cpu_reg) = node.reg() - && let Some(reg_entry) = cpu_reg.next() - { - let cpu_address = reg_entry.address as usize; + if let Some(cpu_address) = cpu_reg_address(node) { debug!( "Checking CPU node {} with address 0x{:x}", node.name(), cpu_address ); - if phys_cpu_ids.contains(&cpu_address) { - debug!( - "CPU node {} with address 0x{:x} is in phys_cpu_ids, including in guest FDT", - node.name(), - cpu_address - ); - return true; - } - - debug!( - "CPU node {} with address 0x{:x} is NOT in phys_cpu_ids, skipping", - node.name(), - cpu_address - ); + return phys_cpu_ids.contains(&cpu_address); } false @@ -521,12 +513,15 @@ pub fn update_fdt( let fdt = Fdt::from_bytes(fdt_bytes) .map_err(|e| format!("Failed to parse cached guest FDT: {e:#?}")) .expect("Failed to parse cached guest FDT"); - // Keep boot metadata such as /chosen from the host FDT. - let host_fdt = Fdt::from_bytes(super::get_host_fdt()) - .map_err(|e| format!("Failed to parse host FDT while updating guest FDT: {e:#?}")) - .expect("Failed to parse host FDT while updating guest FDT"); + // Keep boot metadata such as /chosen from the host FDT when it is available. + let host_fdt_bytes = super::try_get_host_fdt(); + let host_fdt = host_fdt_bytes.map(|bytes| { + Fdt::from_bytes(bytes) + .map_err(|e| format!("Failed to parse host FDT while updating guest FDT: {e:#?}")) + .expect("Failed to parse host FDT while updating guest FDT") + }); let new_fdt_bytes = - patch_guest_fdt_for_runtime(&fdt, &vm.memory_regions(), crate_config, &host_fdt); + patch_guest_fdt_for_runtime(&fdt, &vm.memory_regions(), crate_config, host_fdt.as_ref()); // Recompute the DTB load address from the runtime memory layout. let dest_addr = calculate_dtb_load_addr(vm.clone(), new_fdt_bytes.len()); @@ -535,9 +530,52 @@ pub fn update_fdt( #[cfg(test)] mod tests { - use super::{initrd_range_from_image_config, sanitize_bootargs}; + use super::{cpu_node_id, initrd_range_from_image_config, need_cpu_node, sanitize_bootargs}; use axaddrspace::GuestPhysAddr; use axvm::config::RamdiskInfo; + use fdt_parser::Fdt; + + fn test_fdt(dts: &str) -> Fdt<'static> { + let mut writer = super::FdtWriter::new().unwrap(); + let root = writer.begin_node("").unwrap(); + let cpus = writer.begin_node("cpus").unwrap(); + writer.property_u32("#address-cells", 2).unwrap(); + writer.property_u32("#size-cells", 0).unwrap(); + + for line in dts.lines().map(str::trim).filter(|line| !line.is_empty()) { + let (name, reg) = line.split_once('=').unwrap(); + let node = writer.begin_node(name).unwrap(); + writer.property_u32("device_type", 0).unwrap(); + let reg = usize::from_str_radix(reg, 16).unwrap(); + writer.property_array_u32("reg", &[0, reg as u32]).unwrap(); + writer.end_node(node).unwrap(); + } + + writer.end_node(cpus).unwrap(); + writer.end_node(root).unwrap(); + let bytes = writer.finish().unwrap().leak(); + Fdt::from_bytes(bytes).unwrap() + } + + #[test] + fn cpu_node_selection_uses_node_id_when_reg_differs() { + let fdt = test_fdt("cpu@0=200\ncpu@100=0\ncpu@101=100"); + let nodes: Vec<_> = fdt.all_nodes().collect(); + let paths = crate::vmm::fdt::build_all_node_paths(&nodes); + let selected: Vec<_> = nodes + .iter() + .zip(paths.iter()) + .filter(|(_, path)| path.starts_with("/cpus/cpu@")) + .filter_map(|(node, path)| need_cpu_node(&[0x100], node, path).then(|| path.clone())) + .collect(); + + assert_eq!(selected, ["/cpus/cpu@100"]); + } + + #[test] + fn cpu_node_id_parses_hex_unit_address() { + assert_eq!(cpu_node_id("/cpus/cpu@100"), Some(0x100)); + } #[test] fn initrd_range_requires_both_address_and_size() { @@ -616,7 +654,7 @@ pub(crate) fn patch_guest_fdt_for_runtime( fdt: &Fdt, memory_regions: &[VMMemoryRegion], crate_config: &AxVMCrateConfig, - host_fdt: &Fdt, + host_fdt: Option<&Fdt>, ) -> Vec { let mut new_fdt = FdtWriter::new().unwrap(); let mut previous_node_level = 0usize; @@ -660,7 +698,10 @@ pub(crate) fn patch_guest_fdt_for_runtime( assert_eq!(node_stack.len(), 1); // Restore /chosen from the host FDT when it is missing. - if !has_chosen && let Some(chosen_node) = host_fdt.find_nodes("/chosen").next() { + if !has_chosen + && let Some(host_fdt) = host_fdt + && let Some(chosen_node) = host_fdt.find_nodes("/chosen").next() + { let chosen = new_fdt.begin_node("chosen").unwrap(); for prop in chosen_node.propertys() { new_fdt.property(prop.name, prop.raw_value()).unwrap(); diff --git a/os/axvisor/src/vmm/fdt/parser.rs b/os/axvisor/src/vmm/fdt/parser.rs index f5f3750a5c..24ba3dbee0 100644 --- a/os/axvisor/src/vmm/fdt/parser.rs +++ b/os/axvisor/src/vmm/fdt/parser.rs @@ -17,9 +17,9 @@ use alloc::{string::ToString, vec::Vec}; use ax_hal::{dtb, mem}; use axaddrspace::MappingFlags; -#[cfg(target_arch = "aarch64")] -use axvm::config::PassThroughDeviceConfig; -use axvm::config::{AxVMConfig, AxVMCrateConfig, VmMemConfig, VmMemMappingType}; +use axvm::config::{ + AxVMConfig, AxVMCrateConfig, PassThroughDeviceConfig, VmMemConfig, VmMemMappingType, +}; use fdt_parser::{Fdt, FdtHeader, PciRange, PciSpace}; use crate::vmm::fdt::crate_guest_fdt_with_cache; @@ -28,10 +28,6 @@ use crate::vmm::fdt::create::update_cpu_node; const PAGE_SIZE_4K: usize = 0x1000; -pub fn get_host_fdt() -> &'static [u8] { - try_get_host_fdt().expect("Failed to get host FDT from boot context") -} - pub fn try_get_host_fdt() -> Option<&'static [u8]> { const FDT_VALID_MAGIC: u32 = 0xd00d_feed; let bootarg: usize = dtb::get_bootarg(); @@ -382,66 +378,43 @@ pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVM .as_ref() .expect("ERROR: phys_cpu_ids not found in config.toml"); - // Collect all CPU node information into Vec to avoid using iterators multiple times let cpu_nodes_info: Vec<_> = host_cpus .iter() .filter_map(|cpu_node| { - if let Some(mut cpu_reg) = cpu_node.reg() { - if let Some(r) = cpu_reg.next() { - info!( - "CPU node: {}, phys_cpu_id: 0x{:x}", - cpu_node.name(), - r.address - ); - Some((cpu_node.name().to_string(), r.address as usize)) - } else { - None - } - } else { - None - } + let node_id = cpu_node + .name() + .strip_prefix("cpu@") + .and_then(|id| usize::from_str_radix(id, 16).ok())?; + let cpu_reg = cpu_node.reg().and_then(|mut reg| reg.next())?; + let guest_cpu_id = cpu_reg.address as usize; + info!( + "CPU node: {}, node_id: 0x{:x}, guest_cpu_id: 0x{:x}", + cpu_node.name(), + node_id, + guest_cpu_id + ); + Some((node_id, guest_cpu_id)) }) .collect(); - // Create mapping from phys_cpu_id to physical CPU index - // Collect all unique CPU addresses, maintaining the order of appearance in the device tree - let mut unique_cpu_addresses = Vec::new(); - for (_, cpu_address) in &cpu_nodes_info { - if !unique_cpu_addresses.contains(cpu_address) { - unique_cpu_addresses.push(*cpu_address); - } else { - panic!("Duplicate CPU address found"); - } - } - - // Assign index to each CPU address in the device tree and print detailed information - for (index, &cpu_address) in unique_cpu_addresses.iter().enumerate() { - // Find all CPU nodes using this address - for (cpu_name, node_address) in &cpu_nodes_info { - if *node_address == cpu_address { - debug!( - " CPU node: {cpu_name}, address: 0x{cpu_address:x}, assigned index: {index}" - ); - break; // Print each address only once - } - } - } - // Calculate phys_cpu_sets based on phys_cpu_ids in vcpu_mappings let mut new_phys_cpu_sets = Vec::new(); + let mut guest_phys_cpu_ids = Vec::new(); for phys_cpu_id in phys_cpu_ids { - // Find the index corresponding to phys_cpu_id in unique_cpu_addresses - if let Some(cpu_index) = unique_cpu_addresses + if let Some((cpu_index, (_, guest_cpu_id))) = cpu_nodes_info .iter() - .position(|&addr| addr == *phys_cpu_id) + .enumerate() + .find(|(_, (node_id, _))| node_id == phys_cpu_id) { - let cpu_mask = 1usize << cpu_index; // Convert index to mask bit + let cpu_mask = 1usize << cpu_index; new_phys_cpu_sets.push(cpu_mask); + guest_phys_cpu_ids.push(*guest_cpu_id); debug!( - "vCPU {} with phys_cpu_id 0x{:x} mapped to CPU index {} (mask: 0x{:x})", + "vCPU {} with phys_cpu_id 0x{:x} mapped to CPU index {} (mask: 0x{:x}), guest CPU ID 0x{:x}", vm_cfg.id(), phys_cpu_id, cpu_index, - cpu_mask + cpu_mask, + guest_cpu_id ); } else { error!( @@ -452,12 +425,12 @@ pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVM } } - // Update phys_cpu_sets in VM configuration (if VM configuration supports setting) info!("Calculated phys_cpu_sets: {new_phys_cpu_sets:?}"); + info!("Calculated guest phys_cpu_ids: {guest_phys_cpu_ids:?}"); - vm_cfg - .phys_cpu_ls_mut() - .set_guest_cpu_sets(new_phys_cpu_sets); + let phys_cpu_ls = vm_cfg.phys_cpu_ls_mut(); + phys_cpu_ls.set_guest_cpu_sets(new_phys_cpu_sets); + phys_cpu_ls.set_guest_phys_cpu_ids(guest_phys_cpu_ids); debug!( "vcpu_mappings: {:?}", @@ -515,7 +488,7 @@ fn add_device_address_config( }; // Add new device configuration - let pt_dev = axvm::config::PassThroughDeviceConfig { + let pt_dev = PassThroughDeviceConfig { name: device_name, base_gpa: base_address, base_hpa: base_address, @@ -550,7 +523,7 @@ fn add_pci_ranges_config(vm_cfg: &mut AxVMConfig, node_name: &str, range: &PciRa }; // Add new device configuration - let pt_dev = axvm::config::PassThroughDeviceConfig { + let pt_dev = PassThroughDeviceConfig { name: device_name, base_gpa: base_address, base_hpa: base_address, diff --git a/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..c460cd1906 --- /dev/null +++ b/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,9 @@ +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +features = [ + "fs", + "phytium-mci", +] +log = "Info" +plat_dyn = true +target = "aarch64-unknown-none-softfloat" +vm_configs = ["os/axvisor/configs/vms/linux-aarch64-e2000-smp1.toml"] diff --git a/test-suit/axvisor/normal/board-phytiumpi/smoke/board-phytiumpi-linux.toml b/test-suit/axvisor/normal/board-phytiumpi/smoke/board-phytiumpi-linux.toml new file mode 100644 index 0000000000..c3ddd35b1e --- /dev/null +++ b/test-suit/axvisor/normal/board-phytiumpi/smoke/board-phytiumpi-linux.toml @@ -0,0 +1,11 @@ +board_type = "PhytiumPi" +fail_regex = [ + "(?i)\\bpanic(?:ked)?\\b", + "(?i)kernel panic", + "(?i)login incorrect", + "(?i)permission denied", +] +success_regex = [ + "(?m)^phytiumpi login:", +] +timeout = 300 diff --git a/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..722f81a042 --- /dev/null +++ b/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,10 @@ +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +features = [ + "rockchip-sdhci", + "rockchip-soc", + "fs", +] +log = "Info" +plat_dyn = true +target = "aarch64-unknown-none-softfloat" +vm_configs = ["os/axvisor/configs/vms/linux-aarch64-rk3568-smp1.toml"] diff --git a/test-suit/axvisor/normal/board-roc-rk3568-pc/smoke/board-roc-rk3568-pc-linux.toml b/test-suit/axvisor/normal/board-roc-rk3568-pc/smoke/board-roc-rk3568-pc-linux.toml new file mode 100644 index 0000000000..b9f26923a5 --- /dev/null +++ b/test-suit/axvisor/normal/board-roc-rk3568-pc/smoke/board-roc-rk3568-pc-linux.toml @@ -0,0 +1,11 @@ +board_type = "ROC-RK3568-PC" +fail_regex = [ + "(?i)\\bpanic(?:ked)?\\b", + "(?i)kernel panic", + "(?i)login incorrect", + "(?i)permission denied", +] +success_regex = [ + "(?m)login:", +] +timeout = 300 From 7ac4c3e8706bb0c5f817f744ba3776768ae05169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Tue, 26 May 2026 11:12:34 +0800 Subject: [PATCH 35/71] test(starryos): time and fail fast busybox cases (#944) * test(starryos): time and fail fast busybox cases * fix: update dependencies and increment package versions in Cargo.lock --- Cargo.lock | 8 +- .../qemu-smp1/busybox/sh/busybox-tests.sh | 1011 +++++++++++------ 2 files changed, 689 insertions(+), 330 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27d724317d..7e4f41f15e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4276,9 +4276,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -6711,9 +6711,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", diff --git a/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh b/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh index 33b97f8352..598c4958b8 100644 --- a/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh +++ b/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh @@ -3,932 +3,1273 @@ PASS=0; FAIL=0; SKIP=0 export PATH="${PATH:-/bin:/usr/bin:/sbin:/usr/sbin}" +bb_now_ms() { + if IFS=' .' read -r _bb_sec _bb_frac _bb_rest < /proc/uptime; then + _bb_frac=${_bb_frac:-0}000 + _bb_ms=${_bb_frac%${_bb_frac#???}} + while :; do + case $_bb_ms in + 0[0-9]*) _bb_ms=${_bb_ms#0} ;; + *) break ;; + esac + done + _bb_ms=${_bb_ms:-0} + echo $((_bb_sec * 1000 + _bb_ms)) + else + date +%s000 2>/dev/null || echo 0 + fi +} + +bb_case_start() { + BB_CASE_NAME=$1 + BB_CASE_START_MS=$(bb_now_ms) +} + +bb_case_print_time() { + _bb_now=$(bb_now_ms) + _bb_elapsed=$((_bb_now - BB_CASE_START_MS)) + if [ "$_bb_elapsed" -lt 0 ]; then + _bb_elapsed=0 + fi + echo "TIME: $BB_CASE_NAME elapsed=${_bb_elapsed}ms" +} + +bb_case_pass() { + bb_case_print_time + PASS=$((PASS+1)) +} + +bb_case_fail() { + bb_case_print_time + FAIL=$((FAIL+1)) + echo "FAIL: $BB_CASE_NAME" + echo "=== BusyBox Test Summary ===" + echo "PASS: $PASS FAIL: $FAIL TOTAL: $((PASS+FAIL))" + exit 1 +} + +bb_case_start "busybox_adjtimex" _t=$({ timeout 10 sh -c "busybox adjtimex 2>&1"; } 2>&1) -if [ -n "$_t" ]; then echo "PASS: busybox_adjtimex"; PASS=$((PASS+1)); else echo "FAIL: busybox_adjtimex (empty)"; FAIL=$((FAIL+1)); fi +if [ -n "$_t" ]; then echo "PASS: busybox_adjtimex"; bb_case_pass; else echo "FAIL_DETAIL: busybox_adjtimex (empty)"; bb_case_fail; fi +bb_case_start "busybox_arch" _t=$({ timeout 10 sh -c "busybox arch 2>&1"; } 2>&1) -if [ -n "$_t" ] && echo "$_t" | grep -qE "x86_64|riscv|aarch64|arm|loongarch|mips|powerpc|s390"; then echo "PASS: busybox_arch"; PASS=$((PASS+1)); else echo "FAIL: busybox_arch"; FAIL=$((FAIL+1)); fi +if [ -n "$_t" ] && echo "$_t" | grep -qE "x86_64|riscv|aarch64|arm|loongarch|mips|powerpc|s390"; then echo "PASS: busybox_arch"; bb_case_pass; else echo "FAIL_DETAIL: busybox_arch"; bb_case_fail; fi +bb_case_start "busybox_arp" _t=$({ timeout 10 sh -c "busybox arp 2>&1"; echo "BUSYBOX_ARP_STATUS:$?"; } 2>&1) _status=$(printf '%s\n' "$_t" | sed -n 's/^BUSYBOX_ARP_STATUS://p') _t=$(printf '%s\n' "$_t" | sed '/^BUSYBOX_ARP_STATUS:/d') -if [ "$_status" = 0 ] && { [ -z "$_t" ] || echo "$_t" | grep -qF "HWtype" || echo "$_t" | grep -qF "[ether]"; }; then echo "PASS: busybox_arp"; PASS=$((PASS+1)); else echo "FAIL: busybox_arp"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_status" = 0 ] && { [ -z "$_t" ] || echo "$_t" | grep -qF "HWtype" || echo "$_t" | grep -qF "[ether]"; }; then echo "PASS: busybox_arp"; bb_case_pass; else echo "FAIL_DETAIL: busybox_arp"; echo "$_t"; bb_case_fail; fi -_t=$({ timeout 10 sh -c "busybox arping -c 1 10.0.2.2 2>&1"; echo "ARPING_STATUS:$?"; } 2>&1) -if echo "$_t" | grep -qF "ARPING_STATUS:0" && echo "$_t" | grep -Eq "Received [1-9][0-9]* response[(]s[)]|Unicast reply"; then echo "PASS: busybox_arping"; PASS=$((PASS+1)); else echo "FAIL: busybox_arping"; echo "$_t"; FAIL=$((FAIL+1)); fi +bb_case_start "busybox_arping" +_t=$({ timeout 3 sh -c "busybox arping -f -c 1 -w 1 10.0.2.2 2>&1"; echo "ARPING_STATUS:$?"; } 2>&1) +if echo "$_t" | grep -qF "ARPING_STATUS:0" && echo "$_t" | grep -Eq "Received [1-9][0-9]* response[(]s[)]|Unicast reply"; then echo "PASS: busybox_arping"; bb_case_pass; else echo "FAIL_DETAIL: busybox_arping"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_arping_loopback_negative" _t=$({ timeout 5 sh -c "busybox arping -c 1 -w 1 127.0.0.1 2>&1"; echo "ARPING_LOOPBACK_STATUS:$?"; } 2>&1) -if ! echo "$_t" | grep -Eq "Received [1-9][0-9]* response[(]s[)]|Unicast reply"; then echo "PASS: busybox_arping_loopback_negative"; PASS=$((PASS+1)); else echo "FAIL: busybox_arping_loopback_negative"; echo "$_t"; FAIL=$((FAIL+1)); fi +if ! echo "$_t" | grep -Eq "Received [1-9][0-9]* response[(]s[)]|Unicast reply"; then echo "PASS: busybox_arping_loopback_negative"; bb_case_pass; else echo "FAIL_DETAIL: busybox_arping_loopback_negative"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_ash" _t=$({ timeout 10 sh -c "busybox ash -c 'echo ash_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ash_ok"; then echo "PASS: busybox_ash"; PASS=$((PASS+1)); else echo "FAIL: busybox_ash"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ash_ok"; then echo "PASS: busybox_ash"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ash"; bb_case_fail; fi +bb_case_start "busybox_awk" _t=$({ timeout 10 sh -c "busybox awk 'BEGIN{print \"awk_ok\"}' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "awk_ok"; then echo "PASS: busybox_awk"; PASS=$((PASS+1)); else echo "FAIL: busybox_awk"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "awk_ok"; then echo "PASS: busybox_awk"; bb_case_pass; else echo "FAIL_DETAIL: busybox_awk"; bb_case_fail; fi +bb_case_start "busybox_base64" _t=$({ timeout 10 sh -c "busybox echo test | busybox base64"; } 2>&1) -if echo "$_t" | grep -qF "dGVzdAo="; then echo "PASS: busybox_base64"; PASS=$((PASS+1)); else echo "FAIL: busybox_base64"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "dGVzdAo="; then echo "PASS: busybox_base64"; bb_case_pass; else echo "FAIL_DETAIL: busybox_base64"; bb_case_fail; fi +bb_case_start "busybox_basename" _t=$({ timeout 10 sh -c "busybox basename /usr/bin/foo 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "foo"; then echo "PASS: busybox_basename"; PASS=$((PASS+1)); else echo "FAIL: busybox_basename"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "foo"; then echo "PASS: busybox_basename"; bb_case_pass; else echo "FAIL_DETAIL: busybox_basename"; bb_case_fail; fi +bb_case_start "busybox_bbconfig" _t=$({ timeout 10 sh -c "busybox bbconfig 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "CONFIG_BUSYBOX=y"; then echo "PASS: busybox_bbconfig"; PASS=$((PASS+1)); else echo "FAIL: busybox_bbconfig"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "CONFIG_BUSYBOX=y"; then echo "PASS: busybox_bbconfig"; bb_case_pass; else echo "FAIL_DETAIL: busybox_bbconfig"; bb_case_fail; fi +bb_case_start "busybox_bc" _t=$({ timeout 10 sh -c "busybox echo '2+2' | busybox bc 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "4"; then echo "PASS: busybox_bc"; PASS=$((PASS+1)); else echo "FAIL: busybox_bc"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "4"; then echo "PASS: busybox_bc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_bc"; bb_case_fail; fi +bb_case_start "busybox_beep" _t=$({ timeout 10 sh -c "busybox beep 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "can't open console"; then echo "PASS: busybox_beep"; PASS=$((PASS+1)); else echo "FAIL: busybox_beep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "can't open console"; then echo "PASS: busybox_beep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_beep"; bb_case_fail; fi +bb_case_start "busybox_brctl" _t=$({ timeout 10 sh -c "busybox brctl -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "/sys/class/net"; then echo "PASS: busybox_brctl"; PASS=$((PASS+1)); else echo "FAIL: busybox_brctl"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "/sys/class/net"; then echo "PASS: busybox_brctl"; bb_case_pass; else echo "FAIL_DETAIL: busybox_brctl"; bb_case_fail; fi +bb_case_start "busybox_bunzip2" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo bunzip_ok > /tmp/bb_bunzip_t && busybox bzip2 -f /tmp/bb_bunzip_t && busybox bunzip2 -f /tmp/bb_bunzip_t.bz2 && busybox cat /tmp/bb_bunzip_t' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "bunzip_ok"; then echo "PASS: busybox_bunzip2"; PASS=$((PASS+1)); else echo "FAIL: busybox_bunzip2"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bunzip_ok"; then echo "PASS: busybox_bunzip2"; bb_case_pass; else echo "FAIL_DETAIL: busybox_bunzip2"; bb_case_fail; fi +bb_case_start "busybox_bzcat" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo bzcat_ok > /tmp/bb_bzcat_t && busybox bzip2 -f /tmp/bb_bzcat_t && busybox bzcat /tmp/bb_bzcat_t.bz2' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "bzcat_ok"; then echo "PASS: busybox_bzcat"; PASS=$((PASS+1)); else echo "FAIL: busybox_bzcat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bzcat_ok"; then echo "PASS: busybox_bzcat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_bzcat"; bb_case_fail; fi +bb_case_start "busybox_bzip2" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo bz2_ok > /tmp/bb_bzip2_t && busybox bzip2 -kf /tmp/bb_bzip2_t && busybox test -f /tmp/bb_bzip2_t.bz2 && busybox echo bzip2_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "bzip2_ok"; then echo "PASS: busybox_bzip2"; PASS=$((PASS+1)); else echo "FAIL: busybox_bzip2"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bzip2_ok"; then echo "PASS: busybox_bzip2"; bb_case_pass; else echo "FAIL_DETAIL: busybox_bzip2"; bb_case_fail; fi +bb_case_start "busybox_cal" _t=$({ timeout 10 sh -c "busybox cal 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Su Mo Tu We Th Fr Sa"; then echo "PASS: busybox_cal"; PASS=$((PASS+1)); else echo "FAIL: busybox_cal"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Su Mo Tu We Th Fr Sa"; then echo "PASS: busybox_cal"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cal"; bb_case_fail; fi +bb_case_start "busybox_cat" _t=$({ timeout 10 sh -c "busybox cat /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_cat"; PASS=$((PASS+1)); else echo "FAIL: busybox_cat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_cat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cat"; bb_case_fail; fi +bb_case_start "busybox_chattr" _t=$({ timeout 10 sh -c "busybox chattr -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: chattr"; then echo "PASS: busybox_chattr"; PASS=$((PASS+1)); else echo "FAIL: busybox_chattr"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: chattr"; then echo "PASS: busybox_chattr"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chattr"; bb_case_fail; fi +bb_case_start "busybox_chgrp" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo g > /tmp/bb_chgrp_t && G=\$(busybox id -g) && busybox chgrp \"\$G\" /tmp/bb_chgrp_t && busybox ls -ln /tmp/bb_chgrp_t | busybox grep -q \" \$G \" && busybox echo chgrp_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "chgrp_ok"; then echo "PASS: busybox_chgrp"; PASS=$((PASS+1)); else echo "FAIL: busybox_chgrp"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "chgrp_ok"; then echo "PASS: busybox_chgrp"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chgrp"; bb_case_fail; fi +bb_case_start "busybox_chmod" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo m > /tmp/bb_chmod_t && busybox chmod 600 /tmp/bb_chmod_t && busybox ls -l /tmp/bb_chmod_t | busybox grep -q \"^-rw-------\" && busybox echo chmod_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "chmod_ok"; then echo "PASS: busybox_chmod"; PASS=$((PASS+1)); else echo "FAIL: busybox_chmod"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "chmod_ok"; then echo "PASS: busybox_chmod"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chmod"; bb_case_fail; fi +bb_case_start "busybox_chpasswd" _t=$({ timeout 10 sh -c "busybox chpasswd -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: chpasswd"; then echo "PASS: busybox_chpasswd"; PASS=$((PASS+1)); else echo "FAIL: busybox_chpasswd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: chpasswd"; then echo "PASS: busybox_chpasswd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chpasswd"; bb_case_fail; fi +bb_case_start "busybox_chroot" _t=$({ timeout 10 sh -c "busybox chroot / /bin/busybox echo chroot_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "chroot_ok"; then echo "PASS: busybox_chroot"; PASS=$((PASS+1)); else echo "FAIL: busybox_chroot"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "chroot_ok"; then echo "PASS: busybox_chroot"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chroot"; bb_case_fail; fi +bb_case_start "busybox_chvt" _t=$({ timeout 10 sh -c "busybox chvt -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "invalid number"; then echo "PASS: busybox_chvt"; PASS=$((PASS+1)); else echo "FAIL: busybox_chvt"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "invalid number"; then echo "PASS: busybox_chvt"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chvt"; bb_case_fail; fi +bb_case_start "busybox_cksum" _t=$({ timeout 10 sh -c "busybox cksum /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "/etc/passwd"; then echo "PASS: busybox_cksum"; PASS=$((PASS+1)); else echo "FAIL: busybox_cksum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "/etc/passwd"; then echo "PASS: busybox_cksum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cksum"; bb_case_fail; fi +bb_case_start "busybox_clear" _t=$({ timeout 10 sh -c "busybox clear 2>&1; busybox echo clear_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "clear_ok"; then echo "PASS: busybox_clear"; PASS=$((PASS+1)); else echo "FAIL: busybox_clear"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "clear_ok"; then echo "PASS: busybox_clear"; bb_case_pass; else echo "FAIL_DETAIL: busybox_clear"; bb_case_fail; fi +bb_case_start "busybox_cmp" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo cmp_ok > /tmp/bb_cmp_a && busybox cp /tmp/bb_cmp_a /tmp/bb_cmp_b && busybox cmp /tmp/bb_cmp_a /tmp/bb_cmp_b && busybox echo cmp_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "cmp_ok"; then echo "PASS: busybox_cmp"; PASS=$((PASS+1)); else echo "FAIL: busybox_cmp"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "cmp_ok"; then echo "PASS: busybox_cmp"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cmp"; bb_case_fail; fi +bb_case_start "busybox_comm" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox printf \"a\\nb\\n\" > /tmp/bb_comm_1 && busybox printf \"b\\nc\\n\" > /tmp/bb_comm_2 && busybox comm /tmp/bb_comm_1 /tmp/bb_comm_2' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "c"; then echo "PASS: busybox_comm"; PASS=$((PASS+1)); else echo "FAIL: busybox_comm"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "c"; then echo "PASS: busybox_comm"; bb_case_pass; else echo "FAIL_DETAIL: busybox_comm"; bb_case_fail; fi +bb_case_start "busybox_cp" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo cp_ok > /tmp/bb_cp_src && busybox cp /tmp/bb_cp_src /tmp/bb_cp_dst && busybox cat /tmp/bb_cp_dst' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "cp_ok"; then echo "PASS: busybox_cp"; PASS=$((PASS+1)); else echo "FAIL: busybox_cp"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "cp_ok"; then echo "PASS: busybox_cp"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cp"; bb_case_fail; fi +bb_case_start "busybox_cryptpw" _t=$({ timeout 10 sh -c "busybox cryptpw -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: cryptpw"; then echo "PASS: busybox_cryptpw"; PASS=$((PASS+1)); else echo "FAIL: busybox_cryptpw"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: cryptpw"; then echo "PASS: busybox_cryptpw"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cryptpw"; bb_case_fail; fi +bb_case_start "busybox_cut" _t=$({ timeout 10 sh -c "busybox echo 'a:b:c' | busybox cut -d: -f2 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "b"; then echo "PASS: busybox_cut"; PASS=$((PASS+1)); else echo "FAIL: busybox_cut"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "b"; then echo "PASS: busybox_cut"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cut"; bb_case_fail; fi +bb_case_start "busybox_date" _t=$({ timeout 10 sh -c "busybox date +%Y 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "20"; then echo "PASS: busybox_date"; PASS=$((PASS+1)); else echo "FAIL: busybox_date"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "20"; then echo "PASS: busybox_date"; bb_case_pass; else echo "FAIL_DETAIL: busybox_date"; bb_case_fail; fi +bb_case_start "busybox_dc" _t=$({ timeout 10 sh -c "busybox echo '2 2 + p' | busybox dc 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "4"; then echo "PASS: busybox_dc"; PASS=$((PASS+1)); else echo "FAIL: busybox_dc"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "4"; then echo "PASS: busybox_dc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dc"; bb_case_fail; fi +bb_case_start "busybox_dd" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo dd_ok > /tmp/bb_dd_in && busybox dd if=/tmp/bb_dd_in of=/tmp/bb_dd_out bs=1 count=6 2>/tmp/bb_dd_stat && busybox cat /tmp/bb_dd_out' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "dd_ok"; then echo "PASS: busybox_dd"; PASS=$((PASS+1)); else echo "FAIL: busybox_dd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "dd_ok"; then echo "PASS: busybox_dd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dd"; bb_case_fail; fi +bb_case_start "busybox_deallocvt" _t=$({ timeout 10 sh -c "busybox deallocvt -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "invalid number"; then echo "PASS: busybox_deallocvt"; PASS=$((PASS+1)); else echo "FAIL: busybox_deallocvt"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "invalid number"; then echo "PASS: busybox_deallocvt"; bb_case_pass; else echo "FAIL_DETAIL: busybox_deallocvt"; bb_case_fail; fi +bb_case_start "busybox_delgroup" _t=$({ timeout 10 sh -c "busybox sh -c 'G=bb_delg_t && busybox delgroup \"\$G\" 2>/dev/null || true && busybox addgroup \"\$G\" && busybox delgroup \"\$G\" && ! busybox grep -F \"\$G:\" /etc/group >/dev/null && busybox echo delgroup_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "delgroup_ok"; then echo "PASS: busybox_delgroup"; PASS=$((PASS+1)); else echo "FAIL: busybox_delgroup"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "delgroup_ok"; then echo "PASS: busybox_delgroup"; bb_case_pass; else echo "FAIL_DETAIL: busybox_delgroup"; bb_case_fail; fi +bb_case_start "busybox_deluser" _t=$({ timeout 10 sh -c "busybox sh -c 'U=bb_delu_t && busybox deluser \"\$U\" 2>/dev/null || true && busybox adduser -D -H \"\$U\" && busybox deluser \"\$U\" && ! busybox grep -F \"\$U:\" /etc/passwd >/dev/null && busybox echo deluser_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "deluser_ok"; then echo "PASS: busybox_deluser"; PASS=$((PASS+1)); else echo "FAIL: busybox_deluser"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "deluser_ok"; then echo "PASS: busybox_deluser"; bb_case_pass; else echo "FAIL_DETAIL: busybox_deluser"; bb_case_fail; fi +bb_case_start "busybox_depmod" _t=$({ timeout 10 sh -c "busybox depmod -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: depmod"; then echo "PASS: busybox_depmod"; PASS=$((PASS+1)); else echo "FAIL: busybox_depmod"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: depmod"; then echo "PASS: busybox_depmod"; bb_case_pass; else echo "FAIL_DETAIL: busybox_depmod"; bb_case_fail; fi +bb_case_start "busybox_df" _t=$({ timeout 10 sh -c "busybox df 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Filesystem"; then echo "PASS: busybox_df"; PASS=$((PASS+1)); else echo "FAIL: busybox_df"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Filesystem"; then echo "PASS: busybox_df"; bb_case_pass; else echo "FAIL_DETAIL: busybox_df"; bb_case_fail; fi +bb_case_start "busybox_diff" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo left > /tmp/bb_diff_l && busybox echo right > /tmp/bb_diff_r && busybox diff /tmp/bb_diff_l /tmp/bb_diff_r > /tmp/bb_diff_o 2>&1; busybox grep -q \"^--- /tmp/bb_diff_l\" /tmp/bb_diff_o && busybox echo diff_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "diff_ok"; then echo "PASS: busybox_diff"; PASS=$((PASS+1)); else echo "FAIL: busybox_diff"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "diff_ok"; then echo "PASS: busybox_diff"; bb_case_pass; else echo "FAIL_DETAIL: busybox_diff"; bb_case_fail; fi +bb_case_start "busybox_dirname" _t=$({ timeout 10 sh -c "busybox dirname /usr/bin/foo 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "/usr/bin"; then echo "PASS: busybox_dirname"; PASS=$((PASS+1)); else echo "FAIL: busybox_dirname"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "/usr/bin"; then echo "PASS: busybox_dirname"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dirname"; bb_case_fail; fi +bb_case_start "busybox_dmesg" _t=$({ timeout 10 sh -c "busybox dmesg -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: dmesg"; then echo "PASS: busybox_dmesg"; PASS=$((PASS+1)); else echo "FAIL: busybox_dmesg"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: dmesg"; then echo "PASS: busybox_dmesg"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dmesg"; bb_case_fail; fi +bb_case_start "busybox_dnsdomainname" _t=$({ timeout 10 sh -c "busybox dnsdomainname -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "No help available"; then echo "PASS: busybox_dnsdomainname"; PASS=$((PASS+1)); else echo "FAIL: busybox_dnsdomainname"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "No help available"; then echo "PASS: busybox_dnsdomainname"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dnsdomainname"; bb_case_fail; fi +bb_case_start "busybox_du" _t=$({ timeout 10 sh -c "busybox du -s . 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "."; then echo "PASS: busybox_du"; PASS=$((PASS+1)); else echo "FAIL: busybox_du"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "."; then echo "PASS: busybox_du"; bb_case_pass; else echo "FAIL_DETAIL: busybox_du"; bb_case_fail; fi +bb_case_start "busybox_dumpkmap" _t=$({ timeout 10 sh -c "busybox dumpkmap -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: dumpkmap"; then echo "PASS: busybox_dumpkmap"; PASS=$((PASS+1)); else echo "FAIL: busybox_dumpkmap"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: dumpkmap"; then echo "PASS: busybox_dumpkmap"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dumpkmap"; bb_case_fail; fi +bb_case_start "busybox_echo" _t=$({ timeout 10 sh -c "busybox echo echo_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "echo_ok"; then echo "PASS: busybox_echo"; PASS=$((PASS+1)); else echo "FAIL: busybox_echo"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "echo_ok"; then echo "PASS: busybox_echo"; bb_case_pass; else echo "FAIL_DETAIL: busybox_echo"; bb_case_fail; fi +bb_case_start "busybox_egrep" _t=$({ timeout 10 sh -c "busybox echo hello | busybox egrep hell 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_egrep"; PASS=$((PASS+1)); else echo "FAIL: busybox_egrep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_egrep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_egrep"; bb_case_fail; fi +bb_case_start "busybox_eject" _t=$({ timeout 10 sh -c "busybox eject 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "/dev/cdrom"; then echo "PASS: busybox_eject"; PASS=$((PASS+1)); else echo "FAIL: busybox_eject"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "/dev/cdrom"; then echo "PASS: busybox_eject"; bb_case_pass; else echo "FAIL_DETAIL: busybox_eject"; bb_case_fail; fi +bb_case_start "busybox_ether_wake" _t=$({ timeout 10 sh -c "busybox ether-wake 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: ether-wake"; then echo "PASS: busybox_ether_wake"; PASS=$((PASS+1)); else echo "FAIL: busybox_ether_wake"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: ether-wake"; then echo "PASS: busybox_ether_wake"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ether_wake"; bb_case_fail; fi +bb_case_start "busybox_expand" _t=$({ timeout 10 sh -c "busybox printf \"a\\tb\\n\" | busybox expand 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "a b"; then echo "PASS: busybox_expand"; PASS=$((PASS+1)); else echo "FAIL: busybox_expand"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "a b"; then echo "PASS: busybox_expand"; bb_case_pass; else echo "FAIL_DETAIL: busybox_expand"; bb_case_fail; fi +bb_case_start "busybox_expr" _t=$({ timeout 10 sh -c "busybox expr 3 '*' 4 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "12"; then echo "PASS: busybox_expr"; PASS=$((PASS+1)); else echo "FAIL: busybox_expr"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "12"; then echo "PASS: busybox_expr"; bb_case_pass; else echo "FAIL_DETAIL: busybox_expr"; bb_case_fail; fi +bb_case_start "busybox_factor" _t=$({ timeout 10 sh -c "busybox factor 6 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "6: 2 3"; then echo "PASS: busybox_factor"; PASS=$((PASS+1)); else echo "FAIL: busybox_factor"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "6: 2 3"; then echo "PASS: busybox_factor"; bb_case_pass; else echo "FAIL_DETAIL: busybox_factor"; bb_case_fail; fi +bb_case_start "busybox_fallocate" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_falloc_t && busybox touch /tmp/bb_falloc_t && busybox fallocate -l 1024 /tmp/bb_falloc_t && busybox ls -ln /tmp/bb_falloc_t' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF " 1024 "; then echo "PASS: busybox_fallocate"; PASS=$((PASS+1)); else echo "FAIL: busybox_fallocate"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF " 1024 "; then echo "PASS: busybox_fallocate"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fallocate"; bb_case_fail; fi +bb_case_start "busybox_false" _t=$({ timeout 10 sh -c "busybox false; busybox echo exit:\$? 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "exit:1"; then echo "PASS: busybox_false"; PASS=$((PASS+1)); else echo "FAIL: busybox_false"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "exit:1"; then echo "PASS: busybox_false"; bb_case_pass; else echo "FAIL_DETAIL: busybox_false"; bb_case_fail; fi +bb_case_start "busybox_fatattr" _t=$({ timeout 10 sh -c "busybox fatattr -v / 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "fatattr:"; then echo "PASS: busybox_fatattr"; PASS=$((PASS+1)); else echo "FAIL: busybox_fatattr"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "fatattr:"; then echo "PASS: busybox_fatattr"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fatattr"; bb_case_fail; fi +bb_case_start "busybox_fbset" _t=$({ timeout 10 sh -c "busybox fbset -i 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "option 'i' not handled"; then echo "PASS: busybox_fbset"; PASS=$((PASS+1)); else echo "FAIL: busybox_fbset"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "option 'i' not handled"; then echo "PASS: busybox_fbset"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fbset"; bb_case_fail; fi +bb_case_start "busybox_fbsplash" _t=$({ timeout 10 sh -c "busybox fbsplash -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: fbsplash"; then echo "PASS: busybox_fbsplash"; PASS=$((PASS+1)); else echo "FAIL: busybox_fbsplash"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: fbsplash"; then echo "PASS: busybox_fbsplash"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fbsplash"; bb_case_fail; fi +bb_case_start "busybox_fdisk" _t=$({ timeout 10 sh -c "busybox fdisk -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: fdisk"; then echo "PASS: busybox_fdisk"; PASS=$((PASS+1)); else echo "FAIL: busybox_fdisk"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: fdisk"; then echo "PASS: busybox_fdisk"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fdisk"; bb_case_fail; fi # busybox_blkid — list block device attributes # blkid should handle non-block-device files gracefully (exit 0, error msg to stderr) +bb_case_start "busybox_blkid" _t=$({ timeout 10 sh -c 'busybox blkid 2>&1; S=$(busybox blkid /dev/null 2>&1); R=$?; echo "$S"; echo EXIT:$R >&2'; } 2>&1) -if echo "$_t" | grep -qF "EXIT:0"; then echo "PASS: busybox_blkid"; PASS=$((PASS+1)); else echo "FAIL: busybox_blkid"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "EXIT:0"; then echo "PASS: busybox_blkid"; bb_case_pass; else echo "FAIL_DETAIL: busybox_blkid"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_fgrep" _t=$({ timeout 10 sh -c "busybox echo hello | busybox fgrep hell 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_fgrep"; PASS=$((PASS+1)); else echo "FAIL: busybox_fgrep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_fgrep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fgrep"; bb_case_fail; fi +bb_case_start "busybox_find" _t=$({ timeout 10 sh -c "busybox find / -maxdepth 1 -name proc 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "proc"; then echo "PASS: busybox_find"; PASS=$((PASS+1)); else echo "FAIL: busybox_find"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "proc"; then echo "PASS: busybox_find"; bb_case_pass; else echo "FAIL_DETAIL: busybox_find"; bb_case_fail; fi +bb_case_start "busybox_findfs" _t=$({ timeout 10 sh -c "busybox findfs -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: findfs"; then echo "PASS: busybox_findfs"; PASS=$((PASS+1)); else echo "FAIL: busybox_findfs"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: findfs"; then echo "PASS: busybox_findfs"; bb_case_pass; else echo "FAIL_DETAIL: busybox_findfs"; bb_case_fail; fi +bb_case_start "busybox_flock" _t=$({ timeout 10 sh -c "busybox rm -f /tmp/bb_flock_t && busybox touch /tmp/bb_flock_t && busybox flock -x /tmp/bb_flock_t -c 'busybox echo flock_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "flock_ok"; then echo "PASS: busybox_flock"; PASS=$((PASS+1)); else echo "FAIL: busybox_flock"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "flock_ok"; then echo "PASS: busybox_flock"; bb_case_pass; else echo "FAIL_DETAIL: busybox_flock"; bb_case_fail; fi +bb_case_start "busybox_fold" _t=$({ timeout 10 sh -c "busybox echo abcdef | busybox fold -w 2 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ab"; then echo "PASS: busybox_fold"; PASS=$((PASS+1)); else echo "FAIL: busybox_fold"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ab"; then echo "PASS: busybox_fold"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fold"; bb_case_fail; fi +bb_case_start "busybox_free" _t=$({ timeout 10 sh -c "busybox free 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Mem"; then echo "PASS: busybox_free"; PASS=$((PASS+1)); else echo "FAIL: busybox_free"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Mem"; then echo "PASS: busybox_free"; bb_case_pass; else echo "FAIL_DETAIL: busybox_free"; bb_case_fail; fi +bb_case_start "busybox_fsck" _t=$({ timeout 10 sh -c "busybox fsck -V 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "fsck"; then echo "PASS: busybox_fsck"; PASS=$((PASS+1)); else echo "FAIL: busybox_fsck"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "fsck"; then echo "PASS: busybox_fsck"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fsck"; bb_case_fail; fi +bb_case_start "busybox_fstrim" _t=$({ timeout 10 sh -c "busybox fstrim -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: fstrim"; then echo "PASS: busybox_fstrim"; PASS=$((PASS+1)); else echo "FAIL: busybox_fstrim"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: fstrim"; then echo "PASS: busybox_fstrim"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fstrim"; bb_case_fail; fi +bb_case_start "busybox_fsync" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo fsync_ok > /tmp/bb_fsync_t && busybox fsync /tmp/bb_fsync_t && busybox cat /tmp/bb_fsync_t' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "fsync_ok"; then echo "PASS: busybox_fsync"; PASS=$((PASS+1)); else echo "FAIL: busybox_fsync"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "fsync_ok"; then echo "PASS: busybox_fsync"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fsync"; bb_case_fail; fi +bb_case_start "busybox_fuser" _t=$({ timeout 10 sh -c "busybox fuser -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: fuser"; then echo "PASS: busybox_fuser"; PASS=$((PASS+1)); else echo "FAIL: busybox_fuser"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: fuser"; then echo "PASS: busybox_fuser"; bb_case_pass; else echo "FAIL_DETAIL: busybox_fuser"; bb_case_fail; fi +bb_case_start "busybox_getty" _t=$({ timeout 10 sh -c "busybox getty -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: getty"; then echo "PASS: busybox_getty"; PASS=$((PASS+1)); else echo "FAIL: busybox_getty"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: getty"; then echo "PASS: busybox_getty"; bb_case_pass; else echo "FAIL_DETAIL: busybox_getty"; bb_case_fail; fi +bb_case_start "busybox_grep" _t=$({ timeout 10 sh -c "busybox echo hello | busybox grep hell 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_grep"; PASS=$((PASS+1)); else echo "FAIL: busybox_grep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_grep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_grep"; bb_case_fail; fi +bb_case_start "busybox_groups" _t=$({ timeout 10 sh -c "busybox groups 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root"; then echo "PASS: busybox_groups"; PASS=$((PASS+1)); else echo "FAIL: busybox_groups"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root"; then echo "PASS: busybox_groups"; bb_case_pass; else echo "FAIL_DETAIL: busybox_groups"; bb_case_fail; fi # busybox_ttysize — query terminal size (outputs "rows cols") +bb_case_start "busybox_ttysize" _t=$({ timeout 10 sh -c 'busybox ttysize 2>&1'; } 2>&1) -if echo "$_t" | grep -qE '^[0-9]+ [0-9]+$'; then echo "PASS: busybox_ttysize"; PASS=$((PASS+1)); else echo "FAIL: busybox_ttysize"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qE '^[0-9]+ [0-9]+$'; then echo "PASS: busybox_ttysize"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ttysize"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_gunzip" _t=$({ timeout 10 sh -c "busybox echo -n hello | busybox gzip -c | busybox gunzip -c 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_gunzip"; PASS=$((PASS+1)); else echo "FAIL: busybox_gunzip"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_gunzip"; bb_case_pass; else echo "FAIL_DETAIL: busybox_gunzip"; bb_case_fail; fi +bb_case_start "busybox_gzip" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo -n hello > /tmp/bb_gzip_t && busybox gzip -f /tmp/bb_gzip_t && busybox test -s /tmp/bb_gzip_t.gz && busybox echo gzip_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "gzip_ok"; then echo "PASS: busybox_gzip"; PASS=$((PASS+1)); else echo "FAIL: busybox_gzip"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "gzip_ok"; then echo "PASS: busybox_gzip"; bb_case_pass; else echo "FAIL_DETAIL: busybox_gzip"; bb_case_fail; fi +bb_case_start "busybox_halt" _t=$({ timeout 10 sh -c "busybox halt -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: halt"; then echo "PASS: busybox_halt"; PASS=$((PASS+1)); else echo "FAIL: busybox_halt"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: halt"; then echo "PASS: busybox_halt"; bb_case_pass; else echo "FAIL_DETAIL: busybox_halt"; bb_case_fail; fi +bb_case_start "busybox_hd" _t=$({ timeout 10 sh -c "busybox hd -n 64 /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "00000000"; then echo "PASS: busybox_hd"; PASS=$((PASS+1)); else echo "FAIL: busybox_hd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "00000000"; then echo "PASS: busybox_hd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_hd"; bb_case_fail; fi +bb_case_start "busybox_head" _t=$({ timeout 10 sh -c "busybox head -n 1 /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_head"; PASS=$((PASS+1)); else echo "FAIL: busybox_head"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_head"; bb_case_pass; else echo "FAIL_DETAIL: busybox_head"; bb_case_fail; fi +bb_case_start "busybox_hexdump" _t=$({ timeout 10 sh -c "busybox echo -n ab | busybox hexdump -C 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "61"; then echo "PASS: busybox_hexdump"; PASS=$((PASS+1)); else echo "FAIL: busybox_hexdump"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "61"; then echo "PASS: busybox_hexdump"; bb_case_pass; else echo "FAIL_DETAIL: busybox_hexdump"; bb_case_fail; fi +bb_case_start "busybox_hostname" _t=$({ timeout 10 sh -c "busybox hostname 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "starry"; then echo "PASS: busybox_hostname"; PASS=$((PASS+1)); else echo "FAIL: busybox_hostname"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "starry"; then echo "PASS: busybox_hostname"; bb_case_pass; else echo "FAIL_DETAIL: busybox_hostname"; bb_case_fail; fi +bb_case_start "busybox_id" _t=$({ timeout 10 sh -c "busybox id 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "uid="; then echo "PASS: busybox_id"; PASS=$((PASS+1)); else echo "FAIL: busybox_id"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "uid="; then echo "PASS: busybox_id"; bb_case_pass; else echo "FAIL_DETAIL: busybox_id"; bb_case_fail; fi +bb_case_start "busybox_ifdown" _t=$({ timeout 10 sh -c "busybox ifdown 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ifdown"; then echo "PASS: busybox_ifdown"; PASS=$((PASS+1)); else echo "FAIL: busybox_ifdown"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ifdown"; then echo "PASS: busybox_ifdown"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ifdown"; bb_case_fail; fi +bb_case_start "busybox_ifup" _t=$({ timeout 10 sh -c "busybox ifup -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: ifup"; then echo "PASS: busybox_ifup"; PASS=$((PASS+1)); else echo "FAIL: busybox_ifup"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: ifup"; then echo "PASS: busybox_ifup"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ifup"; bb_case_fail; fi +bb_case_start "busybox_init" _t=$({ timeout 10 sh -c "busybox init 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "must be run as PID 1"; then echo "PASS: busybox_init"; PASS=$((PASS+1)); else echo "FAIL: busybox_init"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "must be run as PID 1"; then echo "PASS: busybox_init"; bb_case_pass; else echo "FAIL_DETAIL: busybox_init"; bb_case_fail; fi +bb_case_start "busybox_inotifyd" _t=$({ timeout 10 sh -c "busybox inotifyd -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: inotifyd"; then echo "PASS: busybox_inotifyd"; PASS=$((PASS+1)); else echo "FAIL: busybox_inotifyd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: inotifyd"; then echo "PASS: busybox_inotifyd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_inotifyd"; bb_case_fail; fi +bb_case_start "busybox_install" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_inst_dst /tmp/bb_inst_src && busybox echo ok > /tmp/bb_inst_src && busybox install -m 644 /tmp/bb_inst_src /tmp/bb_inst_dst && busybox ls -l /tmp/bb_inst_dst && busybox cat /tmp/bb_inst_dst' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ok"; then echo "PASS: busybox_install"; PASS=$((PASS+1)); else echo "FAIL: busybox_install"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ok"; then echo "PASS: busybox_install"; bb_case_pass; else echo "FAIL_DETAIL: busybox_install"; bb_case_fail; fi +bb_case_start "busybox_ionice" _t=$({ timeout 10 sh -c "busybox ionice -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: ionice"; then echo "PASS: busybox_ionice"; PASS=$((PASS+1)); else echo "FAIL: busybox_ionice"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: ionice"; then echo "PASS: busybox_ionice"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ionice"; bb_case_fail; fi +bb_case_start "busybox_ipcrm" _t=$({ timeout 10 sh -c "busybox ipcrm -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: ipcrm"; then echo "PASS: busybox_ipcrm"; PASS=$((PASS+1)); else echo "FAIL: busybox_ipcrm"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: ipcrm"; then echo "PASS: busybox_ipcrm"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ipcrm"; bb_case_fail; fi +bb_case_start "busybox_ipcs" _t=$({ timeout 10 sh -c "busybox ipcs 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Message Queues"; then echo "PASS: busybox_ipcs"; PASS=$((PASS+1)); else echo "FAIL: busybox_ipcs"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Message Queues"; then echo "PASS: busybox_ipcs"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ipcs"; bb_case_fail; fi +bb_case_start "busybox_ip" _t=$({ timeout 10 sh -c "busybox ip link 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "link/"; then echo "PASS: busybox_ip"; PASS=$((PASS+1)); else echo "FAIL: busybox_ip"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "link/"; then echo "PASS: busybox_ip"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ip"; bb_case_fail; fi +bb_case_start "busybox_iplink" _t=$({ timeout 10 sh -c "busybox iplink 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "link/"; then echo "PASS: busybox_iplink"; PASS=$((PASS+1)); else echo "FAIL: busybox_iplink"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "link/"; then echo "PASS: busybox_iplink"; bb_case_pass; else echo "FAIL_DETAIL: busybox_iplink"; bb_case_fail; fi +bb_case_start "busybox_ipaddr" _t=$({ timeout 10 sh -c "busybox ip addr 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "inet "; then echo "PASS: busybox_ipaddr"; PASS=$((PASS+1)); else echo "FAIL: busybox_ipaddr"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "inet "; then echo "PASS: busybox_ipaddr"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ipaddr"; bb_case_fail; fi +bb_case_start "busybox_ipneigh" _t=$({ timeout 10 sh -c "busybox ip neigh show 2>&1; busybox echo ipneigh_ok"; } 2>&1) -if echo "$_t" | grep -qF "ipneigh_ok"; then echo "PASS: busybox_ipneigh"; PASS=$((PASS+1)); else echo "FAIL: busybox_ipneigh"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ipneigh_ok"; then echo "PASS: busybox_ipneigh"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ipneigh"; bb_case_fail; fi +bb_case_start "busybox_iproute" _t=$({ timeout 10 sh -c "busybox ip route show 2>&1; busybox echo iproute_ok"; } 2>&1) -if echo "$_t" | grep -qF "iproute_ok"; then echo "PASS: busybox_iproute"; PASS=$((PASS+1)); else echo "FAIL: busybox_iproute"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "iproute_ok"; then echo "PASS: busybox_iproute"; bb_case_pass; else echo "FAIL_DETAIL: busybox_iproute"; bb_case_fail; fi +bb_case_start "busybox_iprule" _t=$({ timeout 10 sh -c "busybox ip rule show 2>&1; busybox echo iprule_ok"; } 2>&1) -if echo "$_t" | grep -qF "iprule_ok"; then echo "PASS: busybox_iprule"; PASS=$((PASS+1)); else echo "FAIL: busybox_iprule"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "iprule_ok"; then echo "PASS: busybox_iprule"; bb_case_pass; else echo "FAIL_DETAIL: busybox_iprule"; bb_case_fail; fi +bb_case_start "busybox_iptunnel" _t=$({ timeout 10 sh -c "busybox ip tunnel show 2>&1; busybox echo iptunnel_ok"; } 2>&1) -if echo "$_t" | grep -qF "iptunnel_ok"; then echo "PASS: busybox_iptunnel"; PASS=$((PASS+1)); else echo "FAIL: busybox_iptunnel"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "iptunnel_ok"; then echo "PASS: busybox_iptunnel"; bb_case_pass; else echo "FAIL_DETAIL: busybox_iptunnel"; bb_case_fail; fi +bb_case_start "busybox_kbd_mode" _t=$({ timeout 10 sh -c "busybox kbd_mode -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage"; then echo "PASS: busybox_kbd_mode"; PASS=$((PASS+1)); else echo "FAIL: busybox_kbd_mode"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage"; then echo "PASS: busybox_kbd_mode"; bb_case_pass; else echo "FAIL_DETAIL: busybox_kbd_mode"; bb_case_fail; fi +bb_case_start "busybox_kill" _t=$({ timeout 10 sh -c "busybox kill -l 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "HUP"; then echo "PASS: busybox_kill"; PASS=$((PASS+1)); else echo "FAIL: busybox_kill"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "HUP"; then echo "PASS: busybox_kill"; bb_case_pass; else echo "FAIL_DETAIL: busybox_kill"; bb_case_fail; fi +bb_case_start "busybox_killall" _t=$({ timeout 10 sh -c "busybox killall -l 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "HUP"; then echo "PASS: busybox_killall"; PASS=$((PASS+1)); else echo "FAIL: busybox_killall"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "HUP"; then echo "PASS: busybox_killall"; bb_case_pass; else echo "FAIL_DETAIL: busybox_killall"; bb_case_fail; fi +bb_case_start "busybox_klogd" _t=$({ timeout 10 sh -c "busybox klogd -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: klogd"; then echo "PASS: busybox_klogd"; PASS=$((PASS+1)); else echo "FAIL: busybox_klogd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: klogd"; then echo "PASS: busybox_klogd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_klogd"; bb_case_fail; fi +bb_case_start "busybox_last" _t=$({ timeout 10 sh -c "busybox last -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: last"; then echo "PASS: busybox_last"; PASS=$((PASS+1)); else echo "FAIL: busybox_last"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: last"; then echo "PASS: busybox_last"; bb_case_pass; else echo "FAIL_DETAIL: busybox_last"; bb_case_fail; fi +bb_case_start "busybox_less" _t=$({ timeout 10 sh -c "busybox less -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: less"; then echo "PASS: busybox_less"; PASS=$((PASS+1)); else echo "FAIL: busybox_less"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: less"; then echo "PASS: busybox_less"; bb_case_pass; else echo "FAIL_DETAIL: busybox_less"; bb_case_fail; fi +bb_case_start "busybox_linux32" _t=$({ timeout 10 sh -c "busybox linux32 busybox echo linux32_ok 2>&1 || busybox echo linux32_fallback"; } 2>&1) -if echo "$_t" | grep -qF "linux32_"; then echo "PASS: busybox_linux32"; PASS=$((PASS+1)); else echo "FAIL: busybox_linux32"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "linux32_"; then echo "PASS: busybox_linux32"; bb_case_pass; else echo "FAIL_DETAIL: busybox_linux32"; bb_case_fail; fi +bb_case_start "busybox_linux64" _t=$({ timeout 10 sh -c "busybox linux64 busybox echo linux64_ok 2>&1 || busybox echo linux64_fallback"; } 2>&1) -if echo "$_t" | grep -qF "linux64_"; then echo "PASS: busybox_linux64"; PASS=$((PASS+1)); else echo "FAIL: busybox_linux64"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "linux64_"; then echo "PASS: busybox_linux64"; bb_case_pass; else echo "FAIL_DETAIL: busybox_linux64"; bb_case_fail; fi +bb_case_start "busybox_list" _t=$({ timeout 10 sh -c "busybox --list"; } 2>&1) -if [ -n "$_t" ]; then echo "PASS: busybox_list"; PASS=$((PASS+1)); else echo "FAIL: busybox_list (empty)"; FAIL=$((FAIL+1)); fi +if [ -n "$_t" ]; then echo "PASS: busybox_list"; bb_case_pass; else echo "FAIL_DETAIL: busybox_list (empty)"; bb_case_fail; fi +bb_case_start "busybox_ln" _t=$({ timeout 10 sh -c "busybox rm -f /tmp/bb_ln_s && busybox echo t > /tmp/bb_ln_t && busybox ln -s /tmp/bb_ln_t /tmp/bb_ln_s && busybox readlink /tmp/bb_ln_s 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "bb_ln_t"; then echo "PASS: busybox_ln"; PASS=$((PASS+1)); else echo "FAIL: busybox_ln"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bb_ln_t"; then echo "PASS: busybox_ln"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ln"; bb_case_fail; fi +bb_case_start "busybox_loadfont" _t=$({ timeout 10 sh -c "busybox loadfont -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: loadfont"; then echo "PASS: busybox_loadfont"; PASS=$((PASS+1)); else echo "FAIL: busybox_loadfont"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: loadfont"; then echo "PASS: busybox_loadfont"; bb_case_pass; else echo "FAIL_DETAIL: busybox_loadfont"; bb_case_fail; fi +bb_case_start "busybox_loadkmap" _t=$({ timeout 10 sh -c "busybox loadkmap -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: loadkmap"; then echo "PASS: busybox_loadkmap"; PASS=$((PASS+1)); else echo "FAIL: busybox_loadkmap"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: loadkmap"; then echo "PASS: busybox_loadkmap"; bb_case_pass; else echo "FAIL_DETAIL: busybox_loadkmap"; bb_case_fail; fi +bb_case_start "busybox_logger" _t=$({ timeout 10 sh -c "busybox logger -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: logger"; then echo "PASS: busybox_logger"; PASS=$((PASS+1)); else echo "FAIL: busybox_logger"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: logger"; then echo "PASS: busybox_logger"; bb_case_pass; else echo "FAIL_DETAIL: busybox_logger"; bb_case_fail; fi +bb_case_start "busybox_login" _t=$({ timeout 10 sh -c "busybox login -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: login"; then echo "PASS: busybox_login"; PASS=$((PASS+1)); else echo "FAIL: busybox_login"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: login"; then echo "PASS: busybox_login"; bb_case_pass; else echo "FAIL_DETAIL: busybox_login"; bb_case_fail; fi +bb_case_start "busybox_logread" _t=$({ timeout 10 sh -c "busybox logread 2>&1; busybox echo logread_ok"; } 2>&1) -if echo "$_t" | grep -qF "logread_ok"; then echo "PASS: busybox_logread"; PASS=$((PASS+1)); else echo "FAIL: busybox_logread"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "logread_ok"; then echo "PASS: busybox_logread"; bb_case_pass; else echo "FAIL_DETAIL: busybox_logread"; bb_case_fail; fi +bb_case_start "busybox_losetup" _t=$({ timeout 10 sh -c "busybox losetup -a 2>&1; busybox echo losetup_ok"; } 2>&1) -if echo "$_t" | grep -qF "losetup_ok"; then echo "PASS: busybox_losetup"; PASS=$((PASS+1)); else echo "FAIL: busybox_losetup"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "losetup_ok"; then echo "PASS: busybox_losetup"; bb_case_pass; else echo "FAIL_DETAIL: busybox_losetup"; bb_case_fail; fi +bb_case_start "busybox_ls_bb" _t=$({ timeout 10 sh -c "busybox ls / 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "bin"; then echo "PASS: busybox_ls_bb"; PASS=$((PASS+1)); else echo "FAIL: busybox_ls_bb"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bin"; then echo "PASS: busybox_ls_bb"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ls_bb"; bb_case_fail; fi +bb_case_start "busybox_lsattr" _t=$({ timeout 10 sh -c "busybox lsattr -d /tmp 2>&1; busybox echo lsattr_ok"; } 2>&1) -if echo "$_t" | grep -qF "lsattr_ok"; then echo "PASS: busybox_lsattr"; PASS=$((PASS+1)); else echo "FAIL: busybox_lsattr"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "lsattr_ok"; then echo "PASS: busybox_lsattr"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lsattr"; bb_case_fail; fi +bb_case_start "busybox_lsmod" _t=$({ timeout 10 sh -c "busybox lsmod 2>&1; busybox echo lsmod_ok"; } 2>&1) -if echo "$_t" | grep -qF "lsmod_ok"; then echo "PASS: busybox_lsmod"; PASS=$((PASS+1)); else echo "FAIL: busybox_lsmod"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "lsmod_ok"; then echo "PASS: busybox_lsmod"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lsmod"; bb_case_fail; fi +bb_case_start "busybox_lsof" _t=$({ timeout 10 sh -c "busybox lsof 2>&1; busybox echo lsof_ok"; } 2>&1) -if echo "$_t" | grep -qF "lsof_ok"; then echo "PASS: busybox_lsof"; PASS=$((PASS+1)); else echo "FAIL: busybox_lsof"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "lsof_ok"; then echo "PASS: busybox_lsof"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lsof"; bb_case_fail; fi +bb_case_start "busybox_lsusb" _t=$({ timeout 10 sh -c "busybox lsusb 2>&1; busybox echo lsusb_ok"; } 2>&1) -if echo "$_t" | grep -qF "lsusb_ok"; then echo "PASS: busybox_lsusb"; PASS=$((PASS+1)); else echo "FAIL: busybox_lsusb"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "lsusb_ok"; then echo "PASS: busybox_lsusb"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lsusb"; bb_case_fail; fi +bb_case_start "busybox_lzop" _t=$({ timeout 10 sh -c "busybox rm -f /tmp/bb_lzop.txt /tmp/bb_lzop.txt.lzo && busybox echo -n lzop_t > /tmp/bb_lzop.txt && busybox lzop -f /tmp/bb_lzop.txt && busybox lzop -dc /tmp/bb_lzop.txt.lzo 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "lzop_t"; then echo "PASS: busybox_lzop"; PASS=$((PASS+1)); else echo "FAIL: busybox_lzop"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "lzop_t"; then echo "PASS: busybox_lzop"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lzop"; bb_case_fail; fi +bb_case_start "busybox_lzopcat" _t=$({ timeout 10 sh -c "busybox echo -n round | busybox lzop -c 2>/dev/null | busybox lzopcat 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "round"; then echo "PASS: busybox_lzopcat"; PASS=$((PASS+1)); else echo "FAIL: busybox_lzopcat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "round"; then echo "PASS: busybox_lzopcat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lzopcat"; bb_case_fail; fi +bb_case_start "busybox_makemime" _t=$({ timeout 10 sh -c "busybox makemime -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: makemime"; then echo "PASS: busybox_makemime"; PASS=$((PASS+1)); else echo "FAIL: busybox_makemime"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: makemime"; then echo "PASS: busybox_makemime"; bb_case_pass; else echo "FAIL_DETAIL: busybox_makemime"; bb_case_fail; fi +bb_case_start "busybox_md5sum" _t=$({ timeout 10 sh -c "busybox echo -n md5_t | busybox md5sum 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "-"; then echo "PASS: busybox_md5sum"; PASS=$((PASS+1)); else echo "FAIL: busybox_md5sum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "-"; then echo "PASS: busybox_md5sum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_md5sum"; bb_case_fail; fi +bb_case_start "busybox_mdev" _t=$({ timeout 10 sh -c "busybox mdev -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: mdev"; then echo "PASS: busybox_mdev"; PASS=$((PASS+1)); else echo "FAIL: busybox_mdev"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: mdev"; then echo "PASS: busybox_mdev"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mdev"; bb_case_fail; fi +bb_case_start "busybox_mesg" _t=$({ timeout 10 sh -c "busybox mesg 2>&1; busybox echo mesg_ok"; } 2>&1) -if echo "$_t" | grep -qF "mesg_ok"; then echo "PASS: busybox_mesg"; PASS=$((PASS+1)); else echo "FAIL: busybox_mesg"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "mesg_ok"; then echo "PASS: busybox_mesg"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mesg"; bb_case_fail; fi +bb_case_start "busybox_microcom" _t=$({ timeout 10 sh -c "busybox microcom -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: microcom"; then echo "PASS: busybox_microcom"; PASS=$((PASS+1)); else echo "FAIL: busybox_microcom"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: microcom"; then echo "PASS: busybox_microcom"; bb_case_pass; else echo "FAIL_DETAIL: busybox_microcom"; bb_case_fail; fi +bb_case_start "busybox_mkdosfs" _t=$({ timeout 10 sh -c "busybox mkdosfs -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: mkdosfs"; then echo "PASS: busybox_mkdosfs"; PASS=$((PASS+1)); else echo "FAIL: busybox_mkdosfs"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: mkdosfs"; then echo "PASS: busybox_mkdosfs"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mkdosfs"; bb_case_fail; fi +bb_case_start "busybox_mkfifo" _t=$({ timeout 10 sh -c "busybox rm -f /tmp/bb_fifo_t && busybox mkfifo /tmp/bb_fifo_t && busybox ls -l /tmp/bb_fifo_t 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "bb_fifo_t"; then echo "PASS: busybox_mkfifo"; PASS=$((PASS+1)); else echo "FAIL: busybox_mkfifo"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bb_fifo_t"; then echo "PASS: busybox_mkfifo"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mkfifo"; bb_case_fail; fi +bb_case_start "busybox_mkfs_vfat" _t=$({ timeout 10 sh -c "busybox mkfs.vfat -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: mkfs.vfat"; then echo "PASS: busybox_mkfs_vfat"; PASS=$((PASS+1)); else echo "FAIL: busybox_mkfs_vfat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: mkfs.vfat"; then echo "PASS: busybox_mkfs_vfat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mkfs_vfat"; bb_case_fail; fi +bb_case_start "busybox_mknod" _t=$({ timeout 10 sh -c "busybox mknod -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: mknod"; then echo "PASS: busybox_mknod"; PASS=$((PASS+1)); else echo "FAIL: busybox_mknod"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: mknod"; then echo "PASS: busybox_mknod"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mknod"; bb_case_fail; fi +bb_case_start "busybox_mkpasswd" _t=$({ timeout 10 sh -c "busybox mkpasswd -m md5 testpass 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "\$1\$"; then echo "PASS: busybox_mkpasswd"; PASS=$((PASS+1)); else echo "FAIL: busybox_mkpasswd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "\$1\$"; then echo "PASS: busybox_mkpasswd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mkpasswd"; bb_case_fail; fi +bb_case_start "busybox_mkswap" _t=$({ timeout 10 sh -c "busybox mkswap -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: mkswap"; then echo "PASS: busybox_mkswap"; PASS=$((PASS+1)); else echo "FAIL: busybox_mkswap"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: mkswap"; then echo "PASS: busybox_mkswap"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mkswap"; bb_case_fail; fi +bb_case_start "busybox_mktemp" _t=$({ timeout 10 sh -c "busybox sh -c 'd=\$(busybox mktemp -d -t bbXXXXXX) && busybox test -d \"\$d\" && busybox echo mktemp_ok && busybox rm -rf \"\$d\"' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "mktemp_ok"; then echo "PASS: busybox_mktemp"; PASS=$((PASS+1)); else echo "FAIL: busybox_mktemp"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "mktemp_ok"; then echo "PASS: busybox_mktemp"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mktemp"; bb_case_fail; fi +bb_case_start "busybox_modinfo" _t=$({ timeout 10 sh -c "busybox modinfo -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: modinfo"; then echo "PASS: busybox_modinfo"; PASS=$((PASS+1)); else echo "FAIL: busybox_modinfo"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: modinfo"; then echo "PASS: busybox_modinfo"; bb_case_pass; else echo "FAIL_DETAIL: busybox_modinfo"; bb_case_fail; fi +bb_case_start "busybox_modprobe" _t=$({ timeout 10 sh -c "busybox modprobe -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: modprobe"; then echo "PASS: busybox_modprobe"; PASS=$((PASS+1)); else echo "FAIL: busybox_modprobe"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: modprobe"; then echo "PASS: busybox_modprobe"; bb_case_pass; else echo "FAIL_DETAIL: busybox_modprobe"; bb_case_fail; fi +bb_case_start "busybox_more" _t=$({ timeout 10 sh -c "busybox more /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_more"; PASS=$((PASS+1)); else echo "FAIL: busybox_more"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_more"; bb_case_pass; else echo "FAIL_DETAIL: busybox_more"; bb_case_fail; fi +bb_case_start "busybox_mount" _t=$({ timeout 10 sh -c "busybox mount 2>&1; busybox echo mount_ok"; } 2>&1) -if echo "$_t" | grep -qF "mount_ok"; then echo "PASS: busybox_mount"; PASS=$((PASS+1)); else echo "FAIL: busybox_mount"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "mount_ok"; then echo "PASS: busybox_mount"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mount"; bb_case_fail; fi +bb_case_start "busybox_mountpoint" _t=$({ timeout 10 sh -c "busybox mountpoint / 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "is a mountpoint"; then echo "PASS: busybox_mountpoint"; PASS=$((PASS+1)); else echo "FAIL: busybox_mountpoint"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "is a mountpoint"; then echo "PASS: busybox_mountpoint"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mountpoint"; bb_case_fail; fi +bb_case_start "busybox_mpstat" _t=$({ timeout 5 sh -c "busybox mpstat 2>&1; busybox echo mpstat_ok"; } 2>&1) -if echo "$_t" | grep -qF "mpstat_ok"; then echo "PASS: busybox_mpstat"; PASS=$((PASS+1)); else echo "FAIL: busybox_mpstat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "mpstat_ok"; then echo "PASS: busybox_mpstat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mpstat"; bb_case_fail; fi +bb_case_start "busybox_nameif" _t=$({ timeout 10 sh -c "busybox nameif -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nameif"; then echo "PASS: busybox_nameif"; PASS=$((PASS+1)); else echo "FAIL: busybox_nameif"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nameif"; then echo "PASS: busybox_nameif"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nameif"; bb_case_fail; fi +bb_case_start "busybox_nanddump" _t=$({ timeout 10 sh -c "busybox nanddump -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nanddump"; then echo "PASS: busybox_nanddump"; PASS=$((PASS+1)); else echo "FAIL: busybox_nanddump"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nanddump"; then echo "PASS: busybox_nanddump"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nanddump"; bb_case_fail; fi +bb_case_start "busybox_nandwrite" _t=$({ timeout 10 sh -c "busybox nandwrite -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nandwrite"; then echo "PASS: busybox_nandwrite"; PASS=$((PASS+1)); else echo "FAIL: busybox_nandwrite"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nandwrite"; then echo "PASS: busybox_nandwrite"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nandwrite"; bb_case_fail; fi +bb_case_start "busybox_nbd_client" _t=$({ timeout 10 sh -c "busybox nbd-client -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nbd-client"; then echo "PASS: busybox_nbd_client"; PASS=$((PASS+1)); else echo "FAIL: busybox_nbd_client"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nbd-client"; then echo "PASS: busybox_nbd_client"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nbd_client"; bb_case_fail; fi +bb_case_start "busybox_nc" _t=$({ timeout 10 sh -c "busybox nc -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nc"; then echo "PASS: busybox_nc"; PASS=$((PASS+1)); else echo "FAIL: busybox_nc"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nc"; then echo "PASS: busybox_nc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nc"; bb_case_fail; fi +bb_case_start "busybox_netstat" _t=$({ timeout 10 sh -c "busybox netstat -a 2>&1; busybox echo netstat_ok"; } 2>&1) -if echo "$_t" | grep -qF "netstat_ok"; then echo "PASS: busybox_netstat"; PASS=$((PASS+1)); else echo "FAIL: busybox_netstat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "netstat_ok"; then echo "PASS: busybox_netstat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_netstat"; bb_case_fail; fi +bb_case_start "busybox_nice" _t=$({ timeout 10 sh -c "busybox nice -n 10 busybox echo nice_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "nice_ok"; then echo "PASS: busybox_nice"; PASS=$((PASS+1)); else echo "FAIL: busybox_nice"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "nice_ok"; then echo "PASS: busybox_nice"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nice"; bb_case_fail; fi +bb_case_start "busybox_nl" _t=$({ timeout 10 sh -c "busybox nl -ba /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_nl"; PASS=$((PASS+1)); else echo "FAIL: busybox_nl"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_nl"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nl"; bb_case_fail; fi +bb_case_start "busybox_nmeter" _t=$({ timeout 10 sh -c "busybox nmeter -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nmeter"; then echo "PASS: busybox_nmeter"; PASS=$((PASS+1)); else echo "FAIL: busybox_nmeter"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nmeter"; then echo "PASS: busybox_nmeter"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nmeter"; bb_case_fail; fi -_t=$({ timeout 10 sh -c "busybox nologin 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "This account is not available"; then echo "PASS: busybox_nologin"; PASS=$((PASS+1)); else echo "FAIL: busybox_nologin"; FAIL=$((FAIL+1)); fi +bb_case_start "busybox_nologin" +_t=$({ timeout 2 sh -c 'busybox rm -f /tmp/bb_nologin.out; busybox nologin >/tmp/bb_nologin.out 2>&1 & _pid=$!; busybox usleep 200000; busybox kill -9 "$_pid" 2>/dev/null || true; busybox cat /tmp/bb_nologin.out 2>/dev/null; busybox rm -f /tmp/bb_nologin.out'; } 2>&1) +if echo "$_t" | grep -qF "This account is not available"; then echo "PASS: busybox_nologin"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nologin"; bb_case_fail; fi +bb_case_start "busybox_nproc" _t=$({ timeout 10 sh -c "busybox sh -c 'n=\$(busybox nproc) && busybox test -n \"\$n\" && busybox echo nproc_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "nproc_ok"; then echo "PASS: busybox_nproc"; PASS=$((PASS+1)); else echo "FAIL: busybox_nproc"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "nproc_ok"; then echo "PASS: busybox_nproc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nproc"; bb_case_fail; fi +bb_case_start "busybox_nsenter" _t=$({ timeout 10 sh -c "busybox nsenter -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: nsenter"; then echo "PASS: busybox_nsenter"; PASS=$((PASS+1)); else echo "FAIL: busybox_nsenter"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: nsenter"; then echo "PASS: busybox_nsenter"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nsenter"; bb_case_fail; fi +bb_case_start "busybox_nslookup" _t=$({ timeout 10 sh -c "busybox nslookup 127.0.0.1 2>&1; busybox echo nslookup_ok"; } 2>&1) -if echo "$_t" | grep -qF "nslookup_ok"; then echo "PASS: busybox_nslookup"; PASS=$((PASS+1)); else echo "FAIL: busybox_nslookup"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "nslookup_ok"; then echo "PASS: busybox_nslookup"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nslookup"; bb_case_fail; fi +bb_case_start "busybox_ntpd" _t=$({ timeout 10 sh -c "busybox ntpd -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: ntpd"; then echo "PASS: busybox_ntpd"; PASS=$((PASS+1)); else echo "FAIL: busybox_ntpd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: ntpd"; then echo "PASS: busybox_ntpd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ntpd"; bb_case_fail; fi +bb_case_start "busybox_od" _t=$({ timeout 10 sh -c "busybox echo test | busybox od -An -tx1 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "74"; then echo "PASS: busybox_od"; PASS=$((PASS+1)); else echo "FAIL: busybox_od"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "74"; then echo "PASS: busybox_od"; bb_case_pass; else echo "FAIL_DETAIL: busybox_od"; bb_case_fail; fi +bb_case_start "busybox_openvt" _t=$({ timeout 10 sh -c "busybox openvt -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: openvt"; then echo "PASS: busybox_openvt"; PASS=$((PASS+1)); else echo "FAIL: busybox_openvt"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: openvt"; then echo "PASS: busybox_openvt"; bb_case_pass; else echo "FAIL_DETAIL: busybox_openvt"; bb_case_fail; fi +bb_case_start "busybox_partprobe" _t=$({ timeout 10 sh -c "busybox partprobe -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: partprobe"; then echo "PASS: busybox_partprobe"; PASS=$((PASS+1)); else echo "FAIL: busybox_partprobe"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: partprobe"; then echo "PASS: busybox_partprobe"; bb_case_pass; else echo "FAIL_DETAIL: busybox_partprobe"; bb_case_fail; fi +bb_case_start "busybox_passwd" _t=$({ timeout 10 sh -c "busybox passwd -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: passwd"; then echo "PASS: busybox_passwd"; PASS=$((PASS+1)); else echo "FAIL: busybox_passwd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: passwd"; then echo "PASS: busybox_passwd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_passwd"; bb_case_fail; fi +bb_case_start "busybox_paste" _t=$({ timeout 10 sh -c "busybox echo a > /tmp/bb_p1 && busybox echo b > /tmp/bb_p2 && busybox paste /tmp/bb_p1 /tmp/bb_p2 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "a b"; then echo "PASS: busybox_paste"; PASS=$((PASS+1)); else echo "FAIL: busybox_paste"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "a b"; then echo "PASS: busybox_paste"; bb_case_pass; else echo "FAIL_DETAIL: busybox_paste"; bb_case_fail; fi +bb_case_start "busybox_pgrep" _t=$({ timeout 10 sh -c "busybox pgrep -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: pgrep"; then echo "PASS: busybox_pgrep"; PASS=$((PASS+1)); else echo "FAIL: busybox_pgrep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: pgrep"; then echo "PASS: busybox_pgrep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pgrep"; bb_case_fail; fi +bb_case_start "busybox_pidof" _t=$({ busybox pidof -s init 2>&1 || busybox pidof -s sh 2>&1; } 2>&1) -if echo "$_t" | grep -qF "1"; then echo "PASS: busybox_pidof"; PASS=$((PASS+1)); else echo "FAIL: busybox_pidof"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "1"; then echo "PASS: busybox_pidof"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pidof"; bb_case_fail; fi +bb_case_start "busybox_ping6" _t=$({ timeout 10 sh -c "busybox ping6 -c 1 ::1 2>&1 || busybox echo ping6_fallback"; } 2>&1) -if echo "$_t" | grep -qF "ping6_"; then echo "PASS: busybox_ping6"; PASS=$((PASS+1)); else echo "FAIL: busybox_ping6"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ping6_"; then echo "PASS: busybox_ping6"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ping6"; bb_case_fail; fi +bb_case_start "busybox_pivot_root" _t=$({ timeout 10 sh -c "busybox pivot_root -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: pivot_root"; then echo "PASS: busybox_pivot_root"; PASS=$((PASS+1)); else echo "FAIL: busybox_pivot_root"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: pivot_root"; then echo "PASS: busybox_pivot_root"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pivot_root"; bb_case_fail; fi +bb_case_start "busybox_pkill" _t=$({ timeout 10 sh -c "busybox pkill -l 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "HUP"; then echo "PASS: busybox_pkill"; PASS=$((PASS+1)); else echo "FAIL: busybox_pkill"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "HUP"; then echo "PASS: busybox_pkill"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pkill"; bb_case_fail; fi +bb_case_start "busybox_pmap" _t=$({ timeout 10 sh -c "busybox pmap 1 2>&1; busybox echo pmap_ok"; } 2>&1) -if echo "$_t" | grep -qF "pmap_ok"; then echo "PASS: busybox_pmap"; PASS=$((PASS+1)); else echo "FAIL: busybox_pmap"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "pmap_ok"; then echo "PASS: busybox_pmap"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pmap"; bb_case_fail; fi +bb_case_start "busybox_poweroff" _t=$({ timeout 10 sh -c "busybox poweroff -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: poweroff"; then echo "PASS: busybox_poweroff"; PASS=$((PASS+1)); else echo "FAIL: busybox_poweroff"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: poweroff"; then echo "PASS: busybox_poweroff"; bb_case_pass; else echo "FAIL_DETAIL: busybox_poweroff"; bb_case_fail; fi +bb_case_start "busybox_printenv" _t=$({ timeout 10 sh -c "busybox printenv PATH 2>&1; busybox echo printenv_ok"; } 2>&1) -if echo "$_t" | grep -qF "printenv_ok"; then echo "PASS: busybox_printenv"; PASS=$((PASS+1)); else echo "FAIL: busybox_printenv"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "printenv_ok"; then echo "PASS: busybox_printenv"; bb_case_pass; else echo "FAIL_DETAIL: busybox_printenv"; bb_case_fail; fi +bb_case_start "busybox_printf" _t=$({ timeout 10 sh -c "busybox printf 'pf_%s_ok\\n' bb 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "pf_bb_ok"; then echo "PASS: busybox_printf"; PASS=$((PASS+1)); else echo "FAIL: busybox_printf"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "pf_bb_ok"; then echo "PASS: busybox_printf"; bb_case_pass; else echo "FAIL_DETAIL: busybox_printf"; bb_case_fail; fi +bb_case_start "busybox_ps" _t=$({ timeout 10 sh -c "busybox ps 2>&1; busybox echo ps_ok"; } 2>&1) -if echo "$_t" | grep -qF "ps_ok"; then echo "PASS: busybox_ps"; PASS=$((PASS+1)); else echo "FAIL: busybox_ps"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ps_ok"; then echo "PASS: busybox_ps"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ps"; bb_case_fail; fi +bb_case_start "busybox_pscan" _t=$({ timeout 10 sh -c "busybox pscan -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: pscan"; then echo "PASS: busybox_pscan"; PASS=$((PASS+1)); else echo "FAIL: busybox_pscan"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: pscan"; then echo "PASS: busybox_pscan"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pscan"; bb_case_fail; fi +bb_case_start "busybox_pstree" _t=$({ timeout 10 sh -c "busybox pstree 2>&1; busybox echo pstree_ok"; } 2>&1) -if echo "$_t" | grep -qF "pstree_ok"; then echo "PASS: busybox_pstree"; PASS=$((PASS+1)); else echo "FAIL: busybox_pstree"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "pstree_ok"; then echo "PASS: busybox_pstree"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pstree"; bb_case_fail; fi +bb_case_start "busybox_pwd" _t=$({ timeout 10 sh -c "busybox pwd 2>&1; busybox echo pwd_ok"; } 2>&1) -if echo "$_t" | grep -qF "pwd_ok"; then echo "PASS: busybox_pwd"; PASS=$((PASS+1)); else echo "FAIL: busybox_pwd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "pwd_ok"; then echo "PASS: busybox_pwd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pwd"; bb_case_fail; fi +bb_case_start "busybox_pwdx" _t=$({ timeout 10 sh -c "busybox pwdx 1 2>&1; busybox echo pwdx_ok"; } 2>&1) -if echo "$_t" | grep -qF "pwdx_ok"; then echo "PASS: busybox_pwdx"; PASS=$((PASS+1)); else echo "FAIL: busybox_pwdx"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "pwdx_ok"; then echo "PASS: busybox_pwdx"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pwdx"; bb_case_fail; fi +bb_case_start "busybox_rdate" _t=$({ timeout 10 sh -c "busybox rdate -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: rdate"; then echo "PASS: busybox_rdate"; PASS=$((PASS+1)); else echo "FAIL: busybox_rdate"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: rdate"; then echo "PASS: busybox_rdate"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rdate"; bb_case_fail; fi +bb_case_start "busybox_readahead" _t=$({ timeout 10 sh -c "busybox echo ra > /tmp/bb_ra_f && busybox readahead /tmp/bb_ra_f 2>/dev/null; busybox echo ra_done 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ra_done"; then echo "PASS: busybox_readahead"; PASS=$((PASS+1)); else echo "FAIL: busybox_readahead"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ra_done"; then echo "PASS: busybox_readahead"; bb_case_pass; else echo "FAIL_DETAIL: busybox_readahead"; bb_case_fail; fi +bb_case_start "busybox_readlink" _t=$({ timeout 10 sh -c "busybox readlink -f /proc/self/exe 2>&1; busybox echo readlink_ok"; } 2>&1) -if echo "$_t" | grep -qF "readlink_ok"; then echo "PASS: busybox_readlink"; PASS=$((PASS+1)); else echo "FAIL: busybox_readlink"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "readlink_ok"; then echo "PASS: busybox_readlink"; bb_case_pass; else echo "FAIL_DETAIL: busybox_readlink"; bb_case_fail; fi +bb_case_start "busybox_realpath" _t=$({ timeout 10 sh -c "busybox realpath /tmp 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "/tmp"; then echo "PASS: busybox_realpath"; PASS=$((PASS+1)); else echo "FAIL: busybox_realpath"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "/tmp"; then echo "PASS: busybox_realpath"; bb_case_pass; else echo "FAIL_DETAIL: busybox_realpath"; bb_case_fail; fi +bb_case_start "busybox_reboot" _t=$({ timeout 10 sh -c "busybox reboot -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: reboot"; then echo "PASS: busybox_reboot"; PASS=$((PASS+1)); else echo "FAIL: busybox_reboot"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: reboot"; then echo "PASS: busybox_reboot"; bb_case_pass; else echo "FAIL_DETAIL: busybox_reboot"; bb_case_fail; fi +bb_case_start "busybox_reformime" _t=$({ timeout 10 sh -c "busybox reformime -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: reformime"; then echo "PASS: busybox_reformime"; PASS=$((PASS+1)); else echo "FAIL: busybox_reformime"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: reformime"; then echo "PASS: busybox_reformime"; bb_case_pass; else echo "FAIL_DETAIL: busybox_reformime"; bb_case_fail; fi +bb_case_start "busybox_renice" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox renice +0 -p \$\$; busybox echo renice_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "renice_ok"; then echo "PASS: busybox_renice"; PASS=$((PASS+1)); else echo "FAIL: busybox_renice"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "renice_ok"; then echo "PASS: busybox_renice"; bb_case_pass; else echo "FAIL_DETAIL: busybox_renice"; bb_case_fail; fi +bb_case_start "busybox_reset" _t=$({ timeout 10 sh -c "busybox reset 2>/dev/null; busybox echo reset_done 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "reset_done"; then echo "PASS: busybox_reset"; PASS=$((PASS+1)); else echo "FAIL: busybox_reset"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "reset_done"; then echo "PASS: busybox_reset"; bb_case_pass; else echo "FAIL_DETAIL: busybox_reset"; bb_case_fail; fi +bb_case_start "busybox_rev" _t=$({ timeout 10 sh -c "busybox echo abcd | busybox rev 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "dcba"; then echo "PASS: busybox_rev"; PASS=$((PASS+1)); else echo "FAIL: busybox_rev"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "dcba"; then echo "PASS: busybox_rev"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rev"; bb_case_fail; fi +bb_case_start "busybox_rfkill" _t=$({ timeout 10 sh -c "busybox rfkill list 2>&1; busybox echo rfkill_ok"; } 2>&1) -if echo "$_t" | grep -qF "rfkill_ok"; then echo "PASS: busybox_rfkill"; PASS=$((PASS+1)); else echo "FAIL: busybox_rfkill"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "rfkill_ok"; then echo "PASS: busybox_rfkill"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rfkill"; bb_case_fail; fi +bb_case_start "busybox_rm" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox touch /tmp/bb_rm_x && busybox rm /tmp/bb_rm_x && busybox test ! -e /tmp/bb_rm_x && busybox echo rm_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "rm_ok"; then echo "PASS: busybox_rm"; PASS=$((PASS+1)); else echo "FAIL: busybox_rm"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "rm_ok"; then echo "PASS: busybox_rm"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rm"; bb_case_fail; fi +bb_case_start "busybox_rmmod" _t=$({ timeout 10 sh -c "busybox rmmod -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: rmmod"; then echo "PASS: busybox_rmmod"; PASS=$((PASS+1)); else echo "FAIL: busybox_rmmod"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: rmmod"; then echo "PASS: busybox_rmmod"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rmmod"; bb_case_fail; fi +bb_case_start "busybox_route" _t=$({ timeout 10 sh -c "busybox route -n 2>&1; busybox echo route_ok"; } 2>&1) -if echo "$_t" | grep -qF "route_ok"; then echo "PASS: busybox_route"; PASS=$((PASS+1)); else echo "FAIL: busybox_route"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "route_ok"; then echo "PASS: busybox_route"; bb_case_pass; else echo "FAIL_DETAIL: busybox_route"; bb_case_fail; fi +bb_case_start "busybox_sed" _t=$({ timeout 10 sh -c "busybox echo hello | busybox sed 's/hello/sed_ok/' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "sed_ok"; then echo "PASS: busybox_sed"; PASS=$((PASS+1)); else echo "FAIL: busybox_sed"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "sed_ok"; then echo "PASS: busybox_sed"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sed"; bb_case_fail; fi +bb_case_start "busybox_sendmail" _t=$({ timeout 10 sh -c "busybox sendmail -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: sendmail"; then echo "PASS: busybox_sendmail"; PASS=$((PASS+1)); else echo "FAIL: busybox_sendmail"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: sendmail"; then echo "PASS: busybox_sendmail"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sendmail"; bb_case_fail; fi +bb_case_start "busybox_seq" _t=$({ timeout 10 sh -c "busybox seq 1 3 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "3"; then echo "PASS: busybox_seq"; PASS=$((PASS+1)); else echo "FAIL: busybox_seq"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "3"; then echo "PASS: busybox_seq"; bb_case_pass; else echo "FAIL_DETAIL: busybox_seq"; bb_case_fail; fi +bb_case_start "busybox_setconsole" _t=$({ timeout 10 sh -c "busybox setconsole -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: setconsole"; then echo "PASS: busybox_setconsole"; PASS=$((PASS+1)); else echo "FAIL: busybox_setconsole"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: setconsole"; then echo "PASS: busybox_setconsole"; bb_case_pass; else echo "FAIL_DETAIL: busybox_setconsole"; bb_case_fail; fi +bb_case_start "busybox_setfont" _t=$({ timeout 10 sh -c "busybox setfont -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: setfont"; then echo "PASS: busybox_setfont"; PASS=$((PASS+1)); else echo "FAIL: busybox_setfont"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: setfont"; then echo "PASS: busybox_setfont"; bb_case_pass; else echo "FAIL_DETAIL: busybox_setfont"; bb_case_fail; fi +bb_case_start "busybox_setkeycodes" _t=$({ timeout 10 sh -c "busybox setkeycodes -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: setkeycodes"; then echo "PASS: busybox_setkeycodes"; PASS=$((PASS+1)); else echo "FAIL: busybox_setkeycodes"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: setkeycodes"; then echo "PASS: busybox_setkeycodes"; bb_case_pass; else echo "FAIL_DETAIL: busybox_setkeycodes"; bb_case_fail; fi +bb_case_start "busybox_setpriv" _t=$({ timeout 10 sh -c "busybox setpriv -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: setpriv"; then echo "PASS: busybox_setpriv"; PASS=$((PASS+1)); else echo "FAIL: busybox_setpriv"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: setpriv"; then echo "PASS: busybox_setpriv"; bb_case_pass; else echo "FAIL_DETAIL: busybox_setpriv"; bb_case_fail; fi +bb_case_start "busybox_setserial" _t=$({ timeout 10 sh -c "busybox setserial -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: setserial"; then echo "PASS: busybox_setserial"; PASS=$((PASS+1)); else echo "FAIL: busybox_setserial"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: setserial"; then echo "PASS: busybox_setserial"; bb_case_pass; else echo "FAIL_DETAIL: busybox_setserial"; bb_case_fail; fi +bb_case_start "busybox_setsid" _t=$({ timeout 10 sh -c "busybox setsid busybox echo setsid_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "setsid_ok"; then echo "PASS: busybox_setsid"; PASS=$((PASS+1)); else echo "FAIL: busybox_setsid"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "setsid_ok"; then echo "PASS: busybox_setsid"; bb_case_pass; else echo "FAIL_DETAIL: busybox_setsid"; bb_case_fail; fi +bb_case_start "busybox_sh" _t=$({ timeout 10 sh -c "busybox sh -c 'echo sh_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "sh_ok"; then echo "PASS: busybox_sh"; PASS=$((PASS+1)); else echo "FAIL: busybox_sh"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "sh_ok"; then echo "PASS: busybox_sh"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sh"; bb_case_fail; fi +bb_case_start "busybox_sha1sum" _t=$({ timeout 10 sh -c "busybox echo -n sha1_t | busybox sha1sum 2>&1"; } 2>&1) -if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha1sum"; PASS=$((PASS+1)); else echo "FAIL: busybox_sha1sum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha1sum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sha1sum"; bb_case_fail; fi +bb_case_start "busybox_sha256sum" _t=$({ timeout 10 sh -c "busybox echo -n s256 | busybox sha256sum 2>&1"; } 2>&1) -if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha256sum"; PASS=$((PASS+1)); else echo "FAIL: busybox_sha256sum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha256sum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sha256sum"; bb_case_fail; fi +bb_case_start "busybox_sha3sum" _t=$({ timeout 10 sh -c "busybox echo -n s3 | busybox sha3sum 2>&1"; } 2>&1) -if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha3sum"; PASS=$((PASS+1)); else echo "FAIL: busybox_sha3sum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha3sum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sha3sum"; bb_case_fail; fi +bb_case_start "busybox_sha512sum" _t=$({ timeout 10 sh -c "busybox echo -n s512 | busybox sha512sum 2>&1"; } 2>&1) -if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha512sum"; PASS=$((PASS+1)); else echo "FAIL: busybox_sha512sum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF " -"; then echo "PASS: busybox_sha512sum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sha512sum"; bb_case_fail; fi +bb_case_start "busybox_showkey" _t=$({ timeout 10 sh -c "busybox showkey -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: showkey"; then echo "PASS: busybox_showkey"; PASS=$((PASS+1)); else echo "FAIL: busybox_showkey"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: showkey"; then echo "PASS: busybox_showkey"; bb_case_pass; else echo "FAIL_DETAIL: busybox_showkey"; bb_case_fail; fi +bb_case_start "busybox_shred" _t=$({ timeout 10 sh -c "busybox sh -c 'echo x > /tmp/bb_shred_t && busybox shred -n 1 -u /tmp/bb_shred_t 2>&1; busybox test ! -f /tmp/bb_shred_t && busybox echo shred_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "shred_ok"; then echo "PASS: busybox_shred"; PASS=$((PASS+1)); else echo "FAIL: busybox_shred"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "shred_ok"; then echo "PASS: busybox_shred"; bb_case_pass; else echo "FAIL_DETAIL: busybox_shred"; bb_case_fail; fi +bb_case_start "busybox_shuf" _t=$({ timeout 10 sh -c "busybox printf 'a b c ' | busybox shuf 2>&1; busybox echo shuf_ok"; } 2>&1) -if echo "$_t" | grep -qF "shuf_ok"; then echo "PASS: busybox_shuf"; PASS=$((PASS+1)); else echo "FAIL: busybox_shuf"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "shuf_ok"; then echo "PASS: busybox_shuf"; bb_case_pass; else echo "FAIL_DETAIL: busybox_shuf"; bb_case_fail; fi +bb_case_start "busybox_slattach" _t=$({ timeout 10 sh -c "busybox slattach -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: slattach"; then echo "PASS: busybox_slattach"; PASS=$((PASS+1)); else echo "FAIL: busybox_slattach"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: slattach"; then echo "PASS: busybox_slattach"; bb_case_pass; else echo "FAIL_DETAIL: busybox_slattach"; bb_case_fail; fi +bb_case_start "busybox_sleep" _t=$({ timeout 10 sh -c "busybox sleep 0 && busybox echo sleep_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "sleep_ok"; then echo "PASS: busybox_sleep"; PASS=$((PASS+1)); else echo "FAIL: busybox_sleep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "sleep_ok"; then echo "PASS: busybox_sleep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sleep"; bb_case_fail; fi +bb_case_start "busybox_sort" _t=$({ timeout 10 sh -c "busybox printf 'c a b ' | busybox sort 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "a"; then echo "PASS: busybox_sort"; PASS=$((PASS+1)); else echo "FAIL: busybox_sort"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "a"; then echo "PASS: busybox_sort"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sort"; bb_case_fail; fi +bb_case_start "busybox_stat" _t=$({ timeout 10 sh -c "busybox stat /etc/passwd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "File: /etc/passwd"; then echo "PASS: busybox_stat"; PASS=$((PASS+1)); else echo "FAIL: busybox_stat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "File: /etc/passwd"; then echo "PASS: busybox_stat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_stat"; bb_case_fail; fi +bb_case_start "busybox_strings" _t=$({ timeout 10 sh -c "busybox strings /bin/busybox 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "BusyBox"; then echo "PASS: busybox_strings"; PASS=$((PASS+1)); else echo "FAIL: busybox_strings"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "BusyBox"; then echo "PASS: busybox_strings"; bb_case_pass; else echo "FAIL_DETAIL: busybox_strings"; bb_case_fail; fi +bb_case_start "busybox_stty" _t=$({ timeout 10 sh -c "busybox stty -a 2>&1; busybox echo stty_ok"; } 2>&1) -if echo "$_t" | grep -qF "stty_ok"; then echo "PASS: busybox_stty"; PASS=$((PASS+1)); else echo "FAIL: busybox_stty"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "stty_ok"; then echo "PASS: busybox_stty"; bb_case_pass; else echo "FAIL_DETAIL: busybox_stty"; bb_case_fail; fi +bb_case_start "busybox_su" _t=$({ timeout 10 sh -c "busybox su -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: su"; then echo "PASS: busybox_su"; PASS=$((PASS+1)); else echo "FAIL: busybox_su"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: su"; then echo "PASS: busybox_su"; bb_case_pass; else echo "FAIL_DETAIL: busybox_su"; bb_case_fail; fi +bb_case_start "busybox_sum" _t=$({ timeout 10 sh -c "busybox echo sum_t | busybox sum 2>&1; busybox echo sum_ok"; } 2>&1) -if echo "$_t" | grep -qF "sum_ok"; then echo "PASS: busybox_sum"; PASS=$((PASS+1)); else echo "FAIL: busybox_sum"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "sum_ok"; then echo "PASS: busybox_sum"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sum"; bb_case_fail; fi +bb_case_start "busybox_swapoff" _t=$({ timeout 10 sh -c "busybox swapoff -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: swapoff"; then echo "PASS: busybox_swapoff"; PASS=$((PASS+1)); else echo "FAIL: busybox_swapoff"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: swapoff"; then echo "PASS: busybox_swapoff"; bb_case_pass; else echo "FAIL_DETAIL: busybox_swapoff"; bb_case_fail; fi +bb_case_start "busybox_swapon" _t=$({ timeout 10 sh -c "busybox swapon -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: swapon"; then echo "PASS: busybox_swapon"; PASS=$((PASS+1)); else echo "FAIL: busybox_swapon"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: swapon"; then echo "PASS: busybox_swapon"; bb_case_pass; else echo "FAIL_DETAIL: busybox_swapon"; bb_case_fail; fi +bb_case_start "busybox_switch_root" _t=$({ timeout 10 sh -c "busybox switch_root -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: switch_root"; then echo "PASS: busybox_switch_root"; PASS=$((PASS+1)); else echo "FAIL: busybox_switch_root"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: switch_root"; then echo "PASS: busybox_switch_root"; bb_case_pass; else echo "FAIL_DETAIL: busybox_switch_root"; bb_case_fail; fi +bb_case_start "busybox_sync" _t=$({ timeout 10 sh -c "busybox sync && busybox echo sync_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "sync_ok"; then echo "PASS: busybox_sync"; PASS=$((PASS+1)); else echo "FAIL: busybox_sync"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "sync_ok"; then echo "PASS: busybox_sync"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sync"; bb_case_fail; fi +bb_case_start "busybox_sysctl" _t=$({ timeout 10 sh -c "busybox sysctl kernel.hostname 2>&1 || busybox sysctl -h 2>&1; busybox echo sysctl_ok"; } 2>&1) -if echo "$_t" | grep -qF "sysctl_ok"; then echo "PASS: busybox_sysctl"; PASS=$((PASS+1)); else echo "FAIL: busybox_sysctl"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "sysctl_ok"; then echo "PASS: busybox_sysctl"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sysctl"; bb_case_fail; fi +bb_case_start "busybox_syslogd" _t=$({ timeout 10 sh -c "busybox syslogd -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: syslogd"; then echo "PASS: busybox_syslogd"; PASS=$((PASS+1)); else echo "FAIL: busybox_syslogd"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: syslogd"; then echo "PASS: busybox_syslogd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_syslogd"; bb_case_fail; fi +bb_case_start "busybox_tac" _t=$({ timeout 10 sh -c "busybox printf 'a b ' | busybox tac 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "b"; then echo "PASS: busybox_tac"; PASS=$((PASS+1)); else echo "FAIL: busybox_tac"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "b"; then echo "PASS: busybox_tac"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tac"; bb_case_fail; fi +bb_case_start "busybox_tee" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo tee_line | busybox tee /tmp/bb_tee_f >/dev/null && busybox cat /tmp/bb_tee_f' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "tee_line"; then echo "PASS: busybox_tee"; PASS=$((PASS+1)); else echo "FAIL: busybox_tee"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "tee_line"; then echo "PASS: busybox_tee"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tee"; bb_case_fail; fi +bb_case_start "busybox_test" _t=$({ timeout 10 sh -c "busybox test 1 -eq 1 && busybox echo test_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "test_ok"; then echo "PASS: busybox_test"; PASS=$((PASS+1)); else echo "FAIL: busybox_test"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "test_ok"; then echo "PASS: busybox_test"; bb_case_pass; else echo "FAIL_DETAIL: busybox_test"; bb_case_fail; fi +bb_case_start "busybox_time" _t=$({ timeout 10 sh -c "busybox time busybox echo time_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "time_ok"; then echo "PASS: busybox_time"; PASS=$((PASS+1)); else echo "FAIL: busybox_time"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "time_ok"; then echo "PASS: busybox_time"; bb_case_pass; else echo "FAIL_DETAIL: busybox_time"; bb_case_fail; fi +bb_case_start "busybox_timeout" _t=$({ timeout 10 sh -c "busybox timeout 2 busybox echo timeout_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "timeout_ok"; then echo "PASS: busybox_timeout"; PASS=$((PASS+1)); else echo "FAIL: busybox_timeout"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "timeout_ok"; then echo "PASS: busybox_timeout"; bb_case_pass; else echo "FAIL_DETAIL: busybox_timeout"; bb_case_fail; fi +bb_case_start "busybox_top" _t=$({ timeout 10 sh -c "busybox top -b -n 1 2>&1; busybox echo top_ok"; } 2>&1) -if echo "$_t" | grep -qF "top_ok"; then echo "PASS: busybox_top"; PASS=$((PASS+1)); else echo "FAIL: busybox_top"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "top_ok"; then echo "PASS: busybox_top"; bb_case_pass; else echo "FAIL_DETAIL: busybox_top"; bb_case_fail; fi +bb_case_start "busybox_touch" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox touch /tmp/bb_touch_f && busybox test -f /tmp/bb_touch_f && busybox echo touch_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "touch_ok"; then echo "PASS: busybox_touch"; PASS=$((PASS+1)); else echo "FAIL: busybox_touch"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "touch_ok"; then echo "PASS: busybox_touch"; bb_case_pass; else echo "FAIL_DETAIL: busybox_touch"; bb_case_fail; fi +bb_case_start "busybox_tr" _t=$({ timeout 10 sh -c "busybox echo abc | busybox tr 'a-z' 'A-Z' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ABC"; then echo "PASS: busybox_tr"; PASS=$((PASS+1)); else echo "FAIL: busybox_tr"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ABC"; then echo "PASS: busybox_tr"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tr"; bb_case_fail; fi +bb_case_start "busybox_traceroute" _t=$({ timeout 10 sh -c "busybox traceroute -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: traceroute"; then echo "PASS: busybox_traceroute"; PASS=$((PASS+1)); else echo "FAIL: busybox_traceroute"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: traceroute"; then echo "PASS: busybox_traceroute"; bb_case_pass; else echo "FAIL_DETAIL: busybox_traceroute"; bb_case_fail; fi +bb_case_start "busybox_traceroute6" _t=$({ timeout 10 sh -c "busybox traceroute6 -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: traceroute6"; then echo "PASS: busybox_traceroute6"; PASS=$((PASS+1)); else echo "FAIL: busybox_traceroute6"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: traceroute6"; then echo "PASS: busybox_traceroute6"; bb_case_pass; else echo "FAIL_DETAIL: busybox_traceroute6"; bb_case_fail; fi +bb_case_start "busybox_tree" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox mkdir -p /tmp/bb_tree/d && busybox echo x > /tmp/bb_tree/d/a && busybox tree /tmp/bb_tree 2>&1'"; } 2>&1) -if echo "$_t" | grep -qF "a"; then echo "PASS: busybox_tree"; PASS=$((PASS+1)); else echo "FAIL: busybox_tree"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "a"; then echo "PASS: busybox_tree"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tree"; bb_case_fail; fi +bb_case_start "busybox_true" _t=$({ timeout 10 sh -c "busybox true && busybox echo true_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "true_ok"; then echo "PASS: busybox_true"; PASS=$((PASS+1)); else echo "FAIL: busybox_true"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "true_ok"; then echo "PASS: busybox_true"; bb_case_pass; else echo "FAIL_DETAIL: busybox_true"; bb_case_fail; fi +bb_case_start "busybox_truncate" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo abcd > /tmp/bb_trunc_f && busybox truncate -s 2 /tmp/bb_trunc_f && busybox cat /tmp/bb_trunc_f' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ab"; then echo "PASS: busybox_truncate"; PASS=$((PASS+1)); else echo "FAIL: busybox_truncate"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ab"; then echo "PASS: busybox_truncate"; bb_case_pass; else echo "FAIL_DETAIL: busybox_truncate"; bb_case_fail; fi +bb_case_start "busybox_tty" _t=$({ timeout 10 sh -c "busybox tty 2>&1; busybox echo tty_ok"; } 2>&1) -if echo "$_t" | grep -qF "tty_ok"; then echo "PASS: busybox_tty"; PASS=$((PASS+1)); else echo "FAIL: busybox_tty"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "tty_ok"; then echo "PASS: busybox_tty"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tty"; bb_case_fail; fi +bb_case_start "busybox_tunctl" _t=$({ timeout 10 sh -c "busybox tunctl -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: tunctl"; then echo "PASS: busybox_tunctl"; PASS=$((PASS+1)); else echo "FAIL: busybox_tunctl"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: tunctl"; then echo "PASS: busybox_tunctl"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tunctl"; bb_case_fail; fi +bb_case_start "busybox_udhcpc" _t=$({ timeout 10 sh -c "busybox udhcpc -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: udhcpc"; then echo "PASS: busybox_udhcpc"; PASS=$((PASS+1)); else echo "FAIL: busybox_udhcpc"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: udhcpc"; then echo "PASS: busybox_udhcpc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_udhcpc"; bb_case_fail; fi +bb_case_start "busybox_udhcpc6" _t=$({ timeout 10 sh -c "busybox udhcpc6 -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: udhcpc6"; then echo "PASS: busybox_udhcpc6"; PASS=$((PASS+1)); else echo "FAIL: busybox_udhcpc6"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: udhcpc6"; then echo "PASS: busybox_udhcpc6"; bb_case_pass; else echo "FAIL_DETAIL: busybox_udhcpc6"; bb_case_fail; fi +bb_case_start "busybox_umount" _t=$({ timeout 10 sh -c "busybox umount -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: umount"; then echo "PASS: busybox_umount"; PASS=$((PASS+1)); else echo "FAIL: busybox_umount"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: umount"; then echo "PASS: busybox_umount"; bb_case_pass; else echo "FAIL_DETAIL: busybox_umount"; bb_case_fail; fi +bb_case_start "busybox_uname" _t=$({ timeout 10 sh -c "busybox uname -a 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Linux"; then echo "PASS: busybox_uname"; PASS=$((PASS+1)); else echo "FAIL: busybox_uname"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Linux"; then echo "PASS: busybox_uname"; bb_case_pass; else echo "FAIL_DETAIL: busybox_uname"; bb_case_fail; fi +bb_case_start "busybox_unexpand" _t=$({ timeout 10 sh -c "busybox printf 'x y ' | busybox unexpand -a 2>&1; busybox echo unexpand_ok"; } 2>&1) -if echo "$_t" | grep -qF "unexpand_ok"; then echo "PASS: busybox_unexpand"; PASS=$((PASS+1)); else echo "FAIL: busybox_unexpand"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "unexpand_ok"; then echo "PASS: busybox_unexpand"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unexpand"; bb_case_fail; fi +bb_case_start "busybox_uniq" _t=$({ timeout 10 sh -c "busybox printf 'a a b ' | busybox uniq 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "b"; then echo "PASS: busybox_uniq"; PASS=$((PASS+1)); else echo "FAIL: busybox_uniq"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "b"; then echo "PASS: busybox_uniq"; bb_case_pass; else echo "FAIL_DETAIL: busybox_uniq"; bb_case_fail; fi +bb_case_start "busybox_unix2dos" _t=$({ timeout 10 sh -c "busybox printf 'u2d ' | busybox unix2dos 2>&1; busybox echo unix2dos_ok"; } 2>&1) -if echo "$_t" | grep -qF "unix2dos_ok"; then echo "PASS: busybox_unix2dos"; PASS=$((PASS+1)); else echo "FAIL: busybox_unix2dos"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "unix2dos_ok"; then echo "PASS: busybox_unix2dos"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unix2dos"; bb_case_fail; fi +bb_case_start "busybox_unlink" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo u > /tmp/bb_unl && busybox unlink /tmp/bb_unl && busybox test ! -e /tmp/bb_unl && busybox echo unlink_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "unlink_ok"; then echo "PASS: busybox_unlink"; PASS=$((PASS+1)); else echo "FAIL: busybox_unlink"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "unlink_ok"; then echo "PASS: busybox_unlink"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unlink"; bb_case_fail; fi +bb_case_start "busybox_unlzma" _t=$({ timeout 10 sh -c "busybox unlzma -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: unlzma"; then echo "PASS: busybox_unlzma"; PASS=$((PASS+1)); else echo "FAIL: busybox_unlzma"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: unlzma"; then echo "PASS: busybox_unlzma"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unlzma"; bb_case_fail; fi +bb_case_start "busybox_unlzop" _t=$({ timeout 10 sh -c "busybox unlzop -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: unlzop"; then echo "PASS: busybox_unlzop"; PASS=$((PASS+1)); else echo "FAIL: busybox_unlzop"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: unlzop"; then echo "PASS: busybox_unlzop"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unlzop"; bb_case_fail; fi +bb_case_start "busybox_unshare" _t=$({ timeout 10 sh -c "busybox unshare -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: unshare"; then echo "PASS: busybox_unshare"; PASS=$((PASS+1)); else echo "FAIL: busybox_unshare"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: unshare"; then echo "PASS: busybox_unshare"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unshare"; bb_case_fail; fi +bb_case_start "busybox_unxz" _t=$({ timeout 10 sh -c "busybox unxz -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: unxz"; then echo "PASS: busybox_unxz"; PASS=$((PASS+1)); else echo "FAIL: busybox_unxz"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: unxz"; then echo "PASS: busybox_unxz"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unxz"; bb_case_fail; fi +bb_case_start "busybox_unzip" _t=$({ timeout 10 sh -c "busybox unzip -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: unzip"; then echo "PASS: busybox_unzip"; PASS=$((PASS+1)); else echo "FAIL: busybox_unzip"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: unzip"; then echo "PASS: busybox_unzip"; bb_case_pass; else echo "FAIL_DETAIL: busybox_unzip"; bb_case_fail; fi +bb_case_start "busybox_uptime" _t=$({ timeout 10 sh -c "busybox uptime 2>&1; busybox echo uptime_ok"; } 2>&1) -if echo "$_t" | grep -qF "uptime_ok"; then echo "PASS: busybox_uptime"; PASS=$((PASS+1)); else echo "FAIL: busybox_uptime"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "uptime_ok"; then echo "PASS: busybox_uptime"; bb_case_pass; else echo "FAIL_DETAIL: busybox_uptime"; bb_case_fail; fi +bb_case_start "busybox_usleep" _t=$({ timeout 10 sh -c "busybox usleep 1 && busybox echo usleep_ok 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "usleep_ok"; then echo "PASS: busybox_usleep"; PASS=$((PASS+1)); else echo "FAIL: busybox_usleep"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "usleep_ok"; then echo "PASS: busybox_usleep"; bb_case_pass; else echo "FAIL_DETAIL: busybox_usleep"; bb_case_fail; fi +bb_case_start "busybox_uudecode" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox echo hi | busybox uuencode out | busybox uudecode -o /tmp/bb_uudec 2>&1 && busybox cat /tmp/bb_uudec' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hi"; then echo "PASS: busybox_uudecode"; PASS=$((PASS+1)); else echo "FAIL: busybox_uudecode"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hi"; then echo "PASS: busybox_uudecode"; bb_case_pass; else echo "FAIL_DETAIL: busybox_uudecode"; bb_case_fail; fi +bb_case_start "busybox_uuencode" _t=$({ timeout 10 sh -c "busybox echo enc | busybox uuencode out 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "begin"; then echo "PASS: busybox_uuencode"; PASS=$((PASS+1)); else echo "FAIL: busybox_uuencode"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "begin"; then echo "PASS: busybox_uuencode"; bb_case_pass; else echo "FAIL_DETAIL: busybox_uuencode"; bb_case_fail; fi +bb_case_start "busybox_vconfig" _t=$({ timeout 10 sh -c "busybox vconfig -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: vconfig"; then echo "PASS: busybox_vconfig"; PASS=$((PASS+1)); else echo "FAIL: busybox_vconfig"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: vconfig"; then echo "PASS: busybox_vconfig"; bb_case_pass; else echo "FAIL_DETAIL: busybox_vconfig"; bb_case_fail; fi +bb_case_start "busybox_vi" _t=$({ timeout 10 sh -c "busybox vi -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: vi"; then echo "PASS: busybox_vi"; PASS=$((PASS+1)); else echo "FAIL: busybox_vi"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: vi"; then echo "PASS: busybox_vi"; bb_case_pass; else echo "FAIL_DETAIL: busybox_vi"; bb_case_fail; fi +bb_case_start "busybox_vlock" _t=$({ timeout 10 sh -c "busybox vlock -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: vlock" || echo "$_t" | grep -qF "vlock:"; then echo "PASS: busybox_vlock"; PASS=$((PASS+1)); else echo "FAIL: busybox_vlock"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: vlock" || echo "$_t" | grep -qF "vlock:"; then echo "PASS: busybox_vlock"; bb_case_pass; else echo "FAIL_DETAIL: busybox_vlock"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_volname" _t=$({ timeout 10 sh -c "busybox volname /dev/null 2>&1; busybox echo volname_ok"; } 2>&1) -if echo "$_t" | grep -qF "volname_ok"; then echo "PASS: busybox_volname"; PASS=$((PASS+1)); else echo "FAIL: busybox_volname"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "volname_ok"; then echo "PASS: busybox_volname"; bb_case_pass; else echo "FAIL_DETAIL: busybox_volname"; bb_case_fail; fi +bb_case_start "busybox_watch" _t=$({ timeout 10 sh -c "busybox watch -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: watch"; then echo "PASS: busybox_watch"; PASS=$((PASS+1)); else echo "FAIL: busybox_watch"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: watch"; then echo "PASS: busybox_watch"; bb_case_pass; else echo "FAIL_DETAIL: busybox_watch"; bb_case_fail; fi +bb_case_start "busybox_watchdog" _t=$({ timeout 10 sh -c "busybox watchdog -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: watchdog"; then echo "PASS: busybox_watchdog"; PASS=$((PASS+1)); else echo "FAIL: busybox_watchdog"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: watchdog"; then echo "PASS: busybox_watchdog"; bb_case_pass; else echo "FAIL_DETAIL: busybox_watchdog"; bb_case_fail; fi +bb_case_start "busybox_wc" _t=$({ timeout 10 sh -c "busybox printf 'a b c ' | busybox wc -l 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "3"; then echo "PASS: busybox_wc"; PASS=$((PASS+1)); else echo "FAIL: busybox_wc"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "3"; then echo "PASS: busybox_wc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_wc"; bb_case_fail; fi +bb_case_start "busybox_wget" _t=$({ timeout 30 sh -c "busybox rm -f /tmp/bb_wget.html && busybox wget -O /tmp/bb_wget.html http://example.com/ 2>&1 && busybox test -s /tmp/bb_wget.html && busybox grep -qi example /tmp/bb_wget.html && busybox echo wget_download_ok"; } 2>&1) -if echo "$_t" | grep -qF "wget_download_ok"; then echo "PASS: busybox_wget"; PASS=$((PASS+1)); else echo "FAIL: busybox_wget"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "wget_download_ok"; then echo "PASS: busybox_wget"; bb_case_pass; else echo "FAIL_DETAIL: busybox_wget"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_which" _t=$({ timeout 10 sh -c "busybox which busybox 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "busybox"; then echo "PASS: busybox_which"; PASS=$((PASS+1)); else echo "FAIL: busybox_which"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "busybox"; then echo "PASS: busybox_which"; bb_case_pass; else echo "FAIL_DETAIL: busybox_which"; bb_case_fail; fi +bb_case_start "busybox_who" _t=$({ timeout 10 sh -c "busybox who 2>&1 | busybox wc -l 2>&1; busybox echo who_ok"; } 2>&1) -if echo "$_t" | grep -qF "who_ok"; then echo "PASS: busybox_who"; PASS=$((PASS+1)); else echo "FAIL: busybox_who"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "who_ok"; then echo "PASS: busybox_who"; bb_case_pass; else echo "FAIL_DETAIL: busybox_who"; bb_case_fail; fi +bb_case_start "busybox_whoami" _t=$({ timeout 10 sh -c "busybox whoami 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root"; then echo "PASS: busybox_whoami"; PASS=$((PASS+1)); else echo "FAIL: busybox_whoami"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root"; then echo "PASS: busybox_whoami"; bb_case_pass; else echo "FAIL_DETAIL: busybox_whoami"; bb_case_fail; fi +bb_case_start "busybox_whois" _t=$({ timeout 10 sh -c "busybox whois -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: whois"; then echo "PASS: busybox_whois"; PASS=$((PASS+1)); else echo "FAIL: busybox_whois"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: whois"; then echo "PASS: busybox_whois"; bb_case_pass; else echo "FAIL_DETAIL: busybox_whois"; bb_case_fail; fi +bb_case_start "busybox_xargs" _t=$({ timeout 10 sh -c "busybox echo a b | busybox xargs busybox echo X 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "X a b"; then echo "PASS: busybox_xargs"; PASS=$((PASS+1)); else echo "FAIL: busybox_xargs"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "X a b"; then echo "PASS: busybox_xargs"; bb_case_pass; else echo "FAIL_DETAIL: busybox_xargs"; bb_case_fail; fi +bb_case_start "busybox_xzcat" _t=$({ timeout 10 sh -c "busybox xzcat -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: xzcat"; then echo "PASS: busybox_xzcat"; PASS=$((PASS+1)); else echo "FAIL: busybox_xzcat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: xzcat"; then echo "PASS: busybox_xzcat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_xzcat"; bb_case_fail; fi +bb_case_start "busybox_yes" _t=$({ timeout 10 sh -c "busybox yes y | busybox head -n 1 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "y"; then echo "PASS: busybox_yes"; PASS=$((PASS+1)); else echo "FAIL: busybox_yes"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "y"; then echo "PASS: busybox_yes"; bb_case_pass; else echo "FAIL_DETAIL: busybox_yes"; bb_case_fail; fi +bb_case_start "busybox_zcat" _t=$({ timeout 10 sh -c "busybox echo -n hello | busybox gzip -c | busybox zcat 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_zcat"; PASS=$((PASS+1)); else echo "FAIL: busybox_zcat"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hello"; then echo "PASS: busybox_zcat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_zcat"; bb_case_fail; fi +bb_case_start "busybox_zcip" _t=$({ timeout 10 sh -c "busybox zcip -h 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "Usage: zcip"; then echo "PASS: busybox_zcip"; PASS=$((PASS+1)); else echo "FAIL: busybox_zcip"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "Usage: zcip"; then echo "PASS: busybox_zcip"; bb_case_pass; else echo "FAIL_DETAIL: busybox_zcip"; bb_case_fail; fi +bb_case_start "ls_root" _t=$({ timeout 10 sh -c "ls /"; } 2>&1) -if echo "$_t" | grep -qF "bin"; then echo "PASS: ls_root"; PASS=$((PASS+1)); else echo "FAIL: ls_root"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bin"; then echo "PASS: ls_root"; bb_case_pass; else echo "FAIL_DETAIL: ls_root"; bb_case_fail; fi # Custom test: addgroup +bb_case_start "addgroup" _t=$({ timeout 10 sh -c "G=\$(date +%s); busybox delgroup \"gg_\$G\" 2>/dev/null; busybox addgroup \"gg_\$G\" 2>&1 && busybox grep -F \"gg_\$G:\" /etc/group 2>&1; busybox delgroup \"gg_\$G\" 2>/dev/null"; } 2>&1) -if echo "$_t" | grep -qF "gg_"; then echo "PASS: addgroup"; PASS=$((PASS+1)); else echo "FAIL: addgroup"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "gg_"; then echo "PASS: addgroup"; bb_case_pass; else echo "FAIL_DETAIL: addgroup"; bb_case_fail; fi # Custom test: adduser +bb_case_start "adduser" _t=$({ timeout 10 sh -c "U=\$(date +%s); busybox deluser \"uu_\$U\" 2>/dev/null; busybox adduser -D -H \"uu_\$U\" 2>&1 && busybox grep -F \"uu_\$U:\" /etc/passwd 2>&1; busybox deluser \"uu_\$U\" 2>/dev/null"; } 2>&1) -if echo "$_t" | grep -qF "uu_"; then echo "PASS: adduser"; PASS=$((PASS+1)); else echo "FAIL: adduser"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "uu_"; then echo "PASS: adduser"; bb_case_pass; else echo "FAIL_DETAIL: adduser"; bb_case_fail; fi # Restored batch: PostgreSQL bring-up applets (see rcore-os/tgoskits#349). +bb_case_start "busybox_chown" _t=$({ timeout 10 sh -c "busybox sh -c 'U=\$(busybox id -u); G=\$(busybox id -g); busybox echo c > /tmp/bb_chown_t && busybox chown \"\$U:\$G\" /tmp/bb_chown_t && [ \"\$(busybox stat -c \"%u:%g\" /tmp/bb_chown_t)\" = \"\$U:\$G\" ] && busybox echo chown_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "chown_ok"; then echo "PASS: busybox_chown"; PASS=$((PASS+1)); else echo "FAIL: busybox_chown"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "chown_ok"; then echo "PASS: busybox_chown"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chown"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_cpio" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_cpio.arc; busybox rm -rf /tmp/bb_cpio_src /tmp/bb_cpio_out; busybox mkdir -p /tmp/bb_cpio_src /tmp/bb_cpio_out && busybox echo cpio_payload > /tmp/bb_cpio_src/in && cd /tmp/bb_cpio_src && busybox echo in | busybox cpio -o -H newc > /tmp/bb_cpio.arc && cd /tmp/bb_cpio_out && busybox cpio -i < /tmp/bb_cpio.arc && busybox cat in && busybox echo cpio_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "cpio_ok"; then echo "PASS: busybox_cpio"; PASS=$((PASS+1)); else echo "FAIL: busybox_cpio"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "cpio_ok"; then echo "PASS: busybox_cpio"; bb_case_pass; else echo "FAIL_DETAIL: busybox_cpio"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_dos2unix" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox printf \"a\\r\\nb\\r\\n\" > /tmp/bb_d2u && busybox dos2unix /tmp/bb_d2u && busybox od -An -tx1 /tmp/bb_d2u' 2>&1"; } 2>&1) _d2u=$(echo "$_t" | tr -d '\n' | tr -s ' ') -if echo "$_d2u" | grep -qF "61 0a 62 0a" && ! echo "$_d2u" | grep -qF "0d"; then echo "PASS: busybox_dos2unix"; PASS=$((PASS+1)); else echo "FAIL: busybox_dos2unix"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_d2u" | grep -qF "61 0a 62 0a" && ! echo "$_d2u" | grep -qF "0d"; then echo "PASS: busybox_dos2unix"; bb_case_pass; else echo "FAIL_DETAIL: busybox_dos2unix"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_env" _t=$({ timeout 10 sh -c "busybox env 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "PATH="; then echo "PASS: busybox_env"; PASS=$((PASS+1)); else echo "FAIL: busybox_env"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "PATH="; then echo "PASS: busybox_env"; bb_case_pass; else echo "FAIL_DETAIL: busybox_env"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_getopt" _t=$({ timeout 10 sh -c "busybox getopt -o ab: -- -a -b bar 2>&1"; } 2>&1) -if echo "$_t" | grep -qF -- "-a -b 'bar' --"; then echo "PASS: busybox_getopt"; PASS=$((PASS+1)); else echo "FAIL: busybox_getopt"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF -- "-a -b 'bar' --"; then echo "PASS: busybox_getopt"; bb_case_pass; else echo "FAIL_DETAIL: busybox_getopt"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_hostid" _t=$({ timeout 10 sh -c "busybox hostid 2>&1"; } 2>&1) -if echo "$_t" | grep -qE '^(0x)?[0-9a-fA-F]+$'; then echo "PASS: busybox_hostid"; PASS=$((PASS+1)); else echo "FAIL: busybox_hostid"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qE '^(0x)?[0-9a-fA-F]+$'; then echo "PASS: busybox_hostid"; bb_case_pass; else echo "FAIL_DETAIL: busybox_hostid"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_ipcalc" _t=$({ timeout 10 sh -c "busybox ipcalc -m 192.168.1.1/24 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "NETMASK="; then echo "PASS: busybox_ipcalc"; PASS=$((PASS+1)); else echo "FAIL: busybox_ipcalc"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "NETMASK="; then echo "PASS: busybox_ipcalc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ipcalc"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_lzcat" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox printf %s XQAAgAD//////////wA6GUrOJnKDn//7E4AA | busybox base64 -d > /tmp/bb_lzcat.lzma && busybox lzcat /tmp/bb_lzcat.lzma' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "test"; then echo "PASS: busybox_lzcat"; PASS=$((PASS+1)); else echo "FAIL: busybox_lzcat"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "test"; then echo "PASS: busybox_lzcat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lzcat"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_lzma" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox printf %s XQAAgAD//////////wA2Hondf+Fbcap///6gWAA= | busybox base64 -d > /tmp/bb_lzma.lzma && busybox lzma -dc /tmp/bb_lzma.lzma' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "lzma_t"; then echo "PASS: busybox_lzma"; PASS=$((PASS+1)); else echo "FAIL: busybox_lzma"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "lzma_t"; then echo "PASS: busybox_lzma"; bb_case_pass; else echo "FAIL_DETAIL: busybox_lzma"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_ifconfig" _t=$({ timeout 10 sh -c "busybox ifconfig 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "eth0"; then echo "PASS: busybox_ifconfig"; PASS=$((PASS+1)); else echo "FAIL: busybox_ifconfig"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "eth0"; then echo "PASS: busybox_ifconfig"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ifconfig"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_ifenslave" _t=$({ timeout 10 sh -c "busybox ifenslave 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "eth0" && echo "$_t" | grep -qF "lo"; then echo "PASS: busybox_ifenslave"; PASS=$((PASS+1)); else echo "FAIL: busybox_ifenslave"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "eth0" && echo "$_t" | grep -qF "lo"; then echo "PASS: busybox_ifenslave"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ifenslave"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_ping" _t=$({ timeout 10 sh -c "busybox ping -c 1 127.0.0.1 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "1 packets transmitted" && echo "$_t" | grep -qE "1 packets? received|1 received" && echo "$_t" | grep -qF "0% packet loss"; then echo "PASS: busybox_ping"; PASS=$((PASS+1)); else echo "FAIL: busybox_ping"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "1 packets transmitted" && echo "$_t" | grep -qE "1 packets? received|1 received" && echo "$_t" | grep -qF "0% packet loss"; then echo "PASS: busybox_ping"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ping"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_pipe_progress" _t=$({ timeout 10 sh -c "busybox printf abc | busybox pipe_progress 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "abc"; then echo "PASS: busybox_pipe_progress"; PASS=$((PASS+1)); else echo "FAIL: busybox_pipe_progress"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "abc"; then echo "PASS: busybox_pipe_progress"; bb_case_pass; else echo "FAIL_DETAIL: busybox_pipe_progress"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_iostat" _t=$({ timeout 10 sh -c "busybox iostat 1 1 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "avg-cpu"; then echo "PASS: busybox_iostat"; PASS=$((PASS+1)); else echo "FAIL: busybox_iostat"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "avg-cpu"; then echo "PASS: busybox_iostat"; bb_case_pass; else echo "FAIL_DETAIL: busybox_iostat"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_split" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -rf /tmp/bb_spl && busybox mkdir -p /tmp/bb_spl && busybox printf abcdef > /tmp/bb_spl/in && busybox split -b2 /tmp/bb_spl/in /tmp/bb_spl/o && busybox cat /tmp/bb_spl/oaa' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "ab"; then echo "PASS: busybox_split"; PASS=$((PASS+1)); else echo "FAIL: busybox_split"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "ab"; then echo "PASS: busybox_split"; bb_case_pass; else echo "FAIL_DETAIL: busybox_split"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_nohup" _t=$({ timeout 10 sh -c "busybox sh -c 'cd /tmp && busybox rm -f nohup.out && busybox nohup busybox sh -c \"busybox echo nohup_ok > nohup.out\" >/dev/null 2>&1 && busybox cat nohup.out' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "nohup_ok"; then echo "PASS: busybox_nohup"; PASS=$((PASS+1)); else echo "FAIL: busybox_nohup"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "nohup_ok"; then echo "PASS: busybox_nohup"; bb_case_pass; else echo "FAIL_DETAIL: busybox_nohup"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_run_parts" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -rf /tmp/bb_rp && busybox mkdir -p /tmp/bb_rp/d && busybox printf \"#!/bin/sh\\necho rp_ok\\n\" > /tmp/bb_rp/d/00t && busybox chmod +x /tmp/bb_rp/d/00t && busybox run-parts /tmp/bb_rp/d' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "rp_ok"; then echo "PASS: busybox_run_parts"; PASS=$((PASS+1)); else echo "FAIL: busybox_run_parts"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "rp_ok"; then echo "PASS: busybox_run_parts"; bb_case_pass; else echo "FAIL_DETAIL: busybox_run_parts"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_tail" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox printf \"first\\nroot:\\n\" > /tmp/bb_tail_t && busybox tail -n 1 /tmp/bb_tail_t' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_tail"; PASS=$((PASS+1)); else echo "FAIL: busybox_tail"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "root:"; then echo "PASS: busybox_tail"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tail"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_tar" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -rf /tmp/bb_tar && busybox mkdir -p /tmp/bb_tar && busybox echo one > /tmp/bb_tar/one && busybox tar -cf /tmp/bb_tar.tar -C /tmp/bb_tar one && busybox tar -tf /tmp/bb_tar.tar' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "one"; then echo "PASS: busybox_tar"; PASS=$((PASS+1)); else echo "FAIL: busybox_tar"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "one"; then echo "PASS: busybox_tar"; bb_case_pass; else echo "FAIL_DETAIL: busybox_tar"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_xxd" _t=$({ timeout 10 sh -c "busybox printf 'Hi' | busybox xxd 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "4869"; then echo "PASS: busybox_xxd"; PASS=$((PASS+1)); else echo "FAIL: busybox_xxd"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "4869"; then echo "PASS: busybox_xxd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_xxd"; echo "$_t"; bb_case_fail; fi # busybox_mkdir (-p on a new path) +bb_case_start "busybox_mkdir" _t=$({ timeout 10 sh -c 'busybox rm -rf /tmp/bb_mkd_one 2>/dev/null && busybox mkdir -p /tmp/bb_mkd_one && busybox ls -d /tmp/bb_mkd_one'; } 2>&1) -if echo "$_t" | grep -qF "bb_mkd_one"; then echo "PASS: busybox_mkdir"; PASS=$((PASS+1)); else echo "FAIL: busybox_mkdir"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "bb_mkd_one"; then echo "PASS: busybox_mkdir"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mkdir"; bb_case_fail; fi # busybox_mv +bb_case_start "busybox_mv" _t=$({ timeout 10 sh -c 'busybox echo mv_ok > /tmp/bb_mv_from && busybox mv /tmp/bb_mv_from /tmp/bb_mv_to && busybox cat /tmp/bb_mv_to'; } 2>&1) -if echo "$_t" | grep -qF "mv_ok"; then echo "PASS: busybox_mv"; PASS=$((PASS+1)); else echo "FAIL: busybox_mv"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "mv_ok"; then echo "PASS: busybox_mv"; bb_case_pass; else echo "FAIL_DETAIL: busybox_mv"; bb_case_fail; fi # tmpfs_rename_exec_elf - write an ELF via a temporary tmpfs path, rename it # to the final executable name, verify the renamed file still reads back the # ELF magic, then execute the final path. This regresses page-cache/user_data # loss across tmpfs rename, which surfaces as Exec format error. +bb_case_start "tmpfs_rename_exec_elf" _t=$({ timeout 15 sh -c 'busybox rm -rf /tmp/bb_rename_elf && busybox mkdir -p /tmp/bb_rename_elf && busybox cp /bin/busybox /tmp/bb_rename_elf/busybox.tmp && busybox mv /tmp/bb_rename_elf/busybox.tmp /tmp/bb_rename_elf/busybox && [ "$(busybox head -c 4 /tmp/bb_rename_elf/busybox | busybox xxd -p)" = "7f454c46" ] && /tmp/bb_rename_elf/busybox echo rename_exec_ok'; } 2>&1) -if echo "$_t" | grep -qF "rename_exec_ok"; then echo "PASS: tmpfs_rename_exec_elf"; PASS=$((PASS+1)); else echo "FAIL: tmpfs_rename_exec_elf"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "rename_exec_ok"; then echo "PASS: tmpfs_rename_exec_elf"; bb_case_pass; else echo "FAIL_DETAIL: tmpfs_rename_exec_elf"; echo "$_t"; bb_case_fail; fi # busybox_rmdir +bb_case_start "busybox_rmdir" _t=$({ timeout 10 sh -c 'busybox mkdir -p /tmp/bb_rmd && busybox rmdir /tmp/bb_rmd && busybox echo rmdir_ok'; } 2>&1) -if echo "$_t" | grep -qF "rmdir_ok"; then echo "PASS: busybox_rmdir"; PASS=$((PASS+1)); else echo "FAIL: busybox_rmdir"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "rmdir_ok"; then echo "PASS: busybox_rmdir"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rmdir"; bb_case_fail; fi # busybox_link — create hard link on tmpfs and verify content is shared +bb_case_start "busybox_link" _t=$({ timeout 10 sh -c 'busybox rm -f /tmp/bb_link_a /tmp/bb_link_b; busybox echo link_data > /tmp/bb_link_a && busybox link /tmp/bb_link_a /tmp/bb_link_b && busybox cat /tmp/bb_link_b'; } 2>&1) -if echo "$_t" | grep -qF "link_data"; then echo "PASS: busybox_link"; PASS=$((PASS+1)); else echo "FAIL: busybox_link"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "link_data"; then echo "PASS: busybox_link"; bb_case_pass; else echo "FAIL_DETAIL: busybox_link"; bb_case_fail; fi # blkid — identify block device metadata +bb_case_start "blkid" _t=$({ timeout 10 sh -c "busybox blkid /dev/null 2>&1"; } 2>&1) _rc=$? -if echo "$_t" | grep -qE "/dev/null|Usage|not a block|No such|ioctl" || { [ -z "$_t" ] && [ "$_rc" -eq 0 ]; }; then echo "PASS: blkid"; PASS=$((PASS+1)); else echo "FAIL: blkid"; echo "$_t (rc=$_rc)"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qE "/dev/null|Usage|not a block|No such|ioctl" || { [ -z "$_t" ] && [ "$_rc" -eq 0 ]; }; then echo "PASS: blkid"; bb_case_pass; else echo "FAIL_DETAIL: blkid"; echo "$_t (rc=$_rc)"; bb_case_fail; fi # blkdiscard — unbound loop device must fail with non-zero exit and a # device-level error (ENXIO → "No such device"). Accepting rc=0 or # generic busybox prefix matches would let the old no-op-success bug pass. +bb_case_start "blkdiscard" _t=$({ timeout 10 sh -c "busybox blkdiscard /dev/loop0 2>&1"; } 2>&1) _rc=$? -if [ "$_rc" -ne 0 ] && echo "$_t" | grep -qiE "No such device|ENXIO"; then echo "PASS: blkdiscard"; PASS=$((PASS+1)); else echo "FAIL: blkdiscard"; echo "$_t (rc=$_rc)"; FAIL=$((FAIL+1)); fi +if [ "$_rc" -ne 0 ] && echo "$_t" | grep -qiE "No such device|ENXIO"; then echo "PASS: blkdiscard"; bb_case_pass; else echo "FAIL_DETAIL: blkdiscard"; echo "$_t (rc=$_rc)"; bb_case_fail; fi # blockdev — get sector size of block device +bb_case_start "blockdev" _t=$({ timeout 10 sh -c "busybox blockdev --getss /dev/loop0 2>&1"; } 2>&1) -_rc=$?; if [ "$_rc" -eq 0 ] && echo "$_t" | grep -q "[0-9]"; then echo "PASS: blockdev"; PASS=$((PASS+1)); else echo "FAIL: blockdev"; echo "$_t (rc=$_rc)"; FAIL=$((FAIL+1)); fi +_rc=$?; if [ "$_rc" -eq 0 ] && echo "$_t" | grep -q "[0-9]"; then echo "PASS: blockdev"; bb_case_pass; else echo "FAIL_DETAIL: blockdev"; echo "$_t (rc=$_rc)"; bb_case_fail; fi # hwclock — read hardware clock +bb_case_start "busybox_hwclock" _t=$({ timeout 10 sh -c "busybox hwclock -r 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "hwclock"; then echo "PASS: busybox_hwclock"; PASS=$((PASS+1)); else echo "FAIL: busybox_hwclock"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "hwclock"; then echo "PASS: busybox_hwclock"; bb_case_pass; else echo "FAIL_DETAIL: busybox_hwclock"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_run_parts" _t=$({ timeout 10 sh -c "busybox sh -c 'mkdir -p /tmp/bb_rp/d && busybox echo rp_ok > /tmp/bb_rp/d/00t && chmod +x /tmp/bb_rp/d/00t && busybox run-parts /tmp/bb_rp/d' 2>&1"; } 2>&1) -if echo "$_t" | grep -qF "rp_ok"; then echo "PASS: busybox_run_parts"; PASS=$((PASS+1)); else echo "FAIL: busybox_run_parts"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qF "rp_ok"; then echo "PASS: busybox_run_parts"; bb_case_pass; else echo "FAIL_DETAIL: busybox_run_parts"; bb_case_fail; fi # busybox_add_shell — exercise the real /etc/shells rewrite path (NOT --help). # add-shell opens /etc/shells O_RDONLY, opens /etc/shells.tmp @@ -936,6 +1277,7 @@ if echo "$_t" | grep -qF "rp_ok"; then echo "PASS: busybox_run_parts"; PASS=$((P # /etc/shells.tmp over /etc/shells. Probe a unique path each run, verify # it lands in /etc/shells, verify the .tmp file did NOT leak, and restore # the original file so re-runs stay idempotent. +bb_case_start "busybox_add_shell" _addshell_probe="/tmp/bb_addshell_probe_$$" _t=$(timeout 15 sh -c ' busybox cp /etc/shells /tmp/bb_addshell_backup @@ -957,10 +1299,10 @@ _t=$(timeout 15 sh -c ' busybox rm -f /tmp/bb_addshell_backup ' 2>&1) if echo "$_t" | grep -qF "add_shell_ok"; then - echo "PASS: busybox_add_shell"; PASS=$((PASS+1)) + echo "PASS: busybox_add_shell"; bb_case_pass else - echo "FAIL: busybox_add_shell"; echo "$_t" - FAIL=$((FAIL+1)) + echo "FAIL_DETAIL: busybox_add_shell"; echo "$_t" + bb_case_fail fi # busybox_crontab — install a crontab into a private spool dir (-c), list it @@ -971,6 +1313,7 @@ fi # so the test reverse-falsifies "crontab silently dropped the install", # "crontab -l can't reopen the installed file", and "crontab -r left the # file behind". +bb_case_start "busybox_crontab" _t=$(timeout 20 sh -c ' busybox rm -rf /tmp/bb_crontab_tabs /tmp/bb_crontab_in /tmp/bb_crontab_out busybox mkdir -p /tmp/bb_crontab_tabs @@ -995,52 +1338,64 @@ _t=$(timeout 20 sh -c ' fi ' 2>&1) if echo "$_t" | grep -qF "cron_tab_ok"; then - echo "PASS: busybox_crontab"; PASS=$((PASS+1)) + echo "PASS: busybox_crontab"; bb_case_pass else - echo "FAIL: busybox_crontab"; echo "$_t" - FAIL=$((FAIL+1)) + echo "FAIL_DETAIL: busybox_crontab"; echo "$_t" + bb_case_fail fi # Additional stable BusyBox semantics for shell-script compatibility. +bb_case_start "busybox_touch_no_create" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_sem_touch_missing && busybox touch -c /tmp/bb_sem_touch_missing && busybox test ! -e /tmp/bb_sem_touch_missing && busybox echo touch_no_create_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qxF "touch_no_create_ok"; then echo "PASS: busybox_touch_no_create"; PASS=$((PASS+1)); else echo "FAIL: busybox_touch_no_create"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qxF "touch_no_create_ok"; then echo "PASS: busybox_touch_no_create"; bb_case_pass; else echo "FAIL_DETAIL: busybox_touch_no_create"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_rm_recursive" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -rf /tmp/bb_sem_rm && busybox mkdir -p /tmp/bb_sem_rm/a/b && busybox printf data > /tmp/bb_sem_rm/a/b/file && busybox rm -rf /tmp/bb_sem_rm && busybox test ! -e /tmp/bb_sem_rm && busybox echo rm_recursive_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qxF "rm_recursive_ok"; then echo "PASS: busybox_rm_recursive"; PASS=$((PASS+1)); else echo "FAIL: busybox_rm_recursive"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qxF "rm_recursive_ok"; then echo "PASS: busybox_rm_recursive"; bb_case_pass; else echo "FAIL_DETAIL: busybox_rm_recursive"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_ln_hardlink" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_sem_ln_a /tmp/bb_sem_ln_b && busybox printf linkdata > /tmp/bb_sem_ln_a && busybox ln /tmp/bb_sem_ln_a /tmp/bb_sem_ln_b && [ \"\$(busybox stat -c %h /tmp/bb_sem_ln_a)\" = 2 ] && [ \"\$(busybox cat /tmp/bb_sem_ln_b)\" = linkdata ] && busybox echo ln_hardlink_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qxF "ln_hardlink_ok"; then echo "PASS: busybox_ln_hardlink"; PASS=$((PASS+1)); else echo "FAIL: busybox_ln_hardlink"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qxF "ln_hardlink_ok"; then echo "PASS: busybox_ln_hardlink"; bb_case_pass; else echo "FAIL_DETAIL: busybox_ln_hardlink"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_readlink_exact" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_sem_rl_link /tmp/bb_sem_rl_target && busybox printf x > /tmp/bb_sem_rl_target && busybox ln -s /tmp/bb_sem_rl_target /tmp/bb_sem_rl_link && busybox readlink /tmp/bb_sem_rl_link' 2>&1"; } 2>&1) -if [ "$_t" = "/tmp/bb_sem_rl_target" ]; then echo "PASS: busybox_readlink_exact"; PASS=$((PASS+1)); else echo "FAIL: busybox_readlink_exact"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_t" = "/tmp/bb_sem_rl_target" ]; then echo "PASS: busybox_readlink_exact"; bb_case_pass; else echo "FAIL_DETAIL: busybox_readlink_exact"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_realpath_dotdot" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -rf /tmp/bb_sem_real && busybox mkdir -p /tmp/bb_sem_real/d && busybox realpath /tmp/bb_sem_real/./d/..//d' 2>&1"; } 2>&1) -if [ "$_t" = "/tmp/bb_sem_real/d" ]; then echo "PASS: busybox_realpath_dotdot"; PASS=$((PASS+1)); else echo "FAIL: busybox_realpath_dotdot"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_t" = "/tmp/bb_sem_real/d" ]; then echo "PASS: busybox_realpath_dotdot"; bb_case_pass; else echo "FAIL_DETAIL: busybox_realpath_dotdot"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_stat_mode_size" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_sem_stat && busybox printf abc > /tmp/bb_sem_stat && busybox chmod 640 /tmp/bb_sem_stat && busybox stat -c \"%s %a %F\" /tmp/bb_sem_stat' 2>&1"; } 2>&1) -if [ "$_t" = "3 640 regular file" ]; then echo "PASS: busybox_stat_mode_size"; PASS=$((PASS+1)); else echo "FAIL: busybox_stat_mode_size"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_t" = "3 640 regular file" ]; then echo "PASS: busybox_stat_mode_size"; bb_case_pass; else echo "FAIL_DETAIL: busybox_stat_mode_size"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_chmod_symbolic" _t=$({ timeout 10 sh -c "busybox sh -c 'busybox rm -f /tmp/bb_sem_chmod && busybox printf x > /tmp/bb_sem_chmod && busybox chmod u=rw,g=r,o= /tmp/bb_sem_chmod && busybox stat -c %a /tmp/bb_sem_chmod' 2>&1"; } 2>&1) -if [ "$_t" = "640" ]; then echo "PASS: busybox_chmod_symbolic"; PASS=$((PASS+1)); else echo "FAIL: busybox_chmod_symbolic"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_t" = "640" ]; then echo "PASS: busybox_chmod_symbolic"; bb_case_pass; else echo "FAIL_DETAIL: busybox_chmod_symbolic"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_sort_unique" _t=$({ timeout 10 sh -c "busybox printf 'b\nA\nb\n' | busybox sort -u 2>&1"; } 2>&1) _sort=$(echo "$_t" | tr '\n' '|') -if [ "$_sort" = "A|b|" ]; then echo "PASS: busybox_sort_unique"; PASS=$((PASS+1)); else echo "FAIL: busybox_sort_unique"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_sort" = "A|b|" ]; then echo "PASS: busybox_sort_unique"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sort_unique"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_uniq_counts" _t=$({ timeout 10 sh -c "busybox printf 'a\na\nb\n' | busybox uniq -c | busybox sed 's/^ *//' 2>&1"; } 2>&1) _uniq=$(echo "$_t" | tr '\n' '|') -if [ "$_uniq" = "2 a|1 b|" ]; then echo "PASS: busybox_uniq_counts"; PASS=$((PASS+1)); else echo "FAIL: busybox_uniq_counts"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_uniq" = "2 a|1 b|" ]; then echo "PASS: busybox_uniq_counts"; bb_case_pass; else echo "FAIL_DETAIL: busybox_uniq_counts"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_xargs_n1" _t=$({ timeout 10 sh -c "busybox printf 'aa\nbb\n' | busybox xargs -n1 busybox printf '<%s>\n' 2>&1"; } 2>&1) _xargs=$(echo "$_t" | tr '\n' '|') -if [ "$_xargs" = "||" ]; then echo "PASS: busybox_xargs_n1"; PASS=$((PASS+1)); else echo "FAIL: busybox_xargs_n1"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_xargs" = "||" ]; then echo "PASS: busybox_xargs_n1"; bb_case_pass; else echo "FAIL_DETAIL: busybox_xargs_n1"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_printf_escape" _t=$({ timeout 10 sh -c "busybox printf '%b' 'a\012b' | busybox od -An -tx1 2>&1"; } 2>&1) _printf=$(echo "$_t" | tr -d '\n' | tr -s ' ' | busybox sed 's/^ //; s/ $//') -if [ "$_printf" = "61 0a 62" ]; then echo "PASS: busybox_printf_escape"; PASS=$((PASS+1)); else echo "FAIL: busybox_printf_escape"; echo "$_t"; FAIL=$((FAIL+1)); fi +if [ "$_printf" = "61 0a 62" ]; then echo "PASS: busybox_printf_escape"; bb_case_pass; else echo "FAIL_DETAIL: busybox_printf_escape"; echo "$_t"; bb_case_fail; fi +bb_case_start "busybox_sh_env_cd" _t=$({ timeout 10 sh -c "busybox sh -c 'export BB_SEM_ENV=ok; cd /tmp && [ \"\$BB_SEM_ENV:\$PWD\" = \"ok:/tmp\" ] && command -v busybox >/dev/null && busybox echo sh_env_cd_ok' 2>&1"; } 2>&1) -if echo "$_t" | grep -qxF "sh_env_cd_ok"; then echo "PASS: busybox_sh_env_cd"; PASS=$((PASS+1)); else echo "FAIL: busybox_sh_env_cd"; echo "$_t"; FAIL=$((FAIL+1)); fi +if echo "$_t" | grep -qxF "sh_env_cd_ok"; then echo "PASS: busybox_sh_env_cd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sh_env_cd"; echo "$_t"; bb_case_fail; fi # busybox_crond — actually exercise bb_daemonize_or_rexec: launch crond in # background mode (no -f), confirm the parent returns rc=0 immediately, find @@ -1053,6 +1408,7 @@ if echo "$_t" | grep -qxF "sh_env_cd_ok"; then echo "PASS: busybox_sh_env_cd"; P # argv[0] is "busybox", and pidof-by-name returns empty — the same way it # would on Linux given the same invocation. Matching by argv tail via # `ps | grep` is the reliable identification path. +bb_case_start "busybox_crond" _t=$(timeout 20 sh -c ' _cleanup_crond() { _crond_lines=$(busybox ps 2>&1 | busybox grep "crond -c /tmp/bb_crond_tabs" | busybox grep -v grep) @@ -1071,10 +1427,10 @@ _t=$(timeout 20 sh -c ' _drc=$? _i=0 _line="" - while [ "$_i" -lt 5 ] && [ -z "$_line" ]; do + while [ "$_i" -lt 50 ] && [ -z "$_line" ]; do _line=$(busybox ps 2>&1 | busybox grep "crond -c /tmp/bb_crond_tabs" | busybox grep -v grep | busybox head -n 1) if [ -z "$_line" ]; then - busybox sleep 1 + busybox usleep 100000 _i=$((_i + 1)) fi done @@ -1084,9 +1440,11 @@ _t=$(timeout 20 sh -c ' busybox kill "$_pid" _i=0 _still="x" - while [ "$_i" -lt 5 ] && [ -n "$_still" ]; do - busybox sleep 1 + while [ "$_i" -lt 50 ] && [ -n "$_still" ]; do _still=$(busybox ps 2>&1 | busybox grep "crond -c /tmp/bb_crond_tabs" | busybox grep -v grep) + if [ -n "$_still" ]; then + busybox usleep 100000 + fi _i=$((_i + 1)) done if [ -z "$_still" ]; then @@ -1100,10 +1458,10 @@ _t=$(timeout 20 sh -c ' fi ' 2>&1) if echo "$_t" | grep -qF "crond_ok"; then - echo "PASS: busybox_crond"; PASS=$((PASS+1)) + echo "PASS: busybox_crond"; bb_case_pass else - echo "FAIL: busybox_crond"; echo "$_t" - FAIL=$((FAIL+1)) + echo "FAIL_DETAIL: busybox_crond"; echo "$_t" + bb_case_fail fi # busybox_acpid — applet wiring sanity check. @@ -1111,14 +1469,15 @@ fi # only succeeds if something is printed before fork). We pass an unknown flag # `-h` so getopt32 reaches bb_show_usage, which writes the applet banner to # stderr — confirming the applet table contains acpid and busybox can run it. +bb_case_start "busybox_acpid" _t=$({ timeout 10 sh -c "busybox acpid -h 2>&1"; echo "EXIT:$?"; } 2>&1) _rc=$(printf '%s\n' "$_t" | sed -n 's/^EXIT://p') _t=$(printf '%s\n' "$_t" | sed '/^EXIT:/d') if echo "$_t" | grep -qF "Usage:" && echo "$_t" | grep -qF "acpid"; then - echo "PASS: busybox_acpid"; PASS=$((PASS+1)) + echo "PASS: busybox_acpid"; bb_case_pass else - echo "FAIL: busybox_acpid (rc=$_rc)"; echo "$_t" - FAIL=$((FAIL+1)) + echo "FAIL_DETAIL: busybox_acpid (rc=$_rc)"; echo "$_t" + bb_case_fail fi echo "=== BusyBox Test Summary ===" From a44b6bd543982436dea92d6049dc16d610849fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Tue, 26 May 2026 12:02:05 +0800 Subject: [PATCH 36/71] Remove spin lock implementations: RelaxStrategy and RwLock (#955) This commit deletes the `RelaxStrategy` trait and its implementations (`Spin`, `Yield`, `Loop`), as well as the `RwLock` structure and its associated methods and guards. The removal of these components simplifies the codebase and eliminates the spinning lock mechanism, which may lead to priority inversion issues. This change is aimed at improving overall performance and maintainability. --- Cargo.lock | 5 +- Cargo.toml | 6 +- components/spin/CHANGELOG.md | 162 ---- components/spin/Cargo.toml | 75 -- components/spin/LICENSE | 21 - components/spin/README.md | 134 --- components/spin/SECURITY.md | 13 - components/spin/src/barrier.rs | 242 ------ components/spin/src/lazy.rs | 125 --- components/spin/src/lib.rs | 234 ------ components/spin/src/mutex.rs | 344 -------- components/spin/src/mutex/fair.rs | 739 ----------------- components/spin/src/mutex/spin.rs | 561 ------------- components/spin/src/mutex/ticket.rs | 551 ------------- components/spin/src/once.rs | 795 ------------------ components/spin/src/relax.rs | 61 -- components/spin/src/rwlock.rs | 1190 --------------------------- 17 files changed, 4 insertions(+), 5254 deletions(-) delete mode 100644 components/spin/CHANGELOG.md delete mode 100644 components/spin/Cargo.toml delete mode 100644 components/spin/LICENSE delete mode 100644 components/spin/README.md delete mode 100644 components/spin/SECURITY.md delete mode 100644 components/spin/src/barrier.rs delete mode 100644 components/spin/src/lazy.rs delete mode 100644 components/spin/src/lib.rs delete mode 100644 components/spin/src/mutex.rs delete mode 100644 components/spin/src/mutex/fair.rs delete mode 100644 components/spin/src/mutex/spin.rs delete mode 100644 components/spin/src/mutex/ticket.rs delete mode 100644 components/spin/src/once.rs delete mode 100644 components/spin/src/relax.rs delete mode 100644 components/spin/src/rwlock.rs diff --git a/Cargo.lock b/Cargo.lock index 7e4f41f15e..7a6fc4e9ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aarch64-cpu" @@ -7805,9 +7805,10 @@ dependencies = [ [[package]] name = "spin" version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" dependencies = [ "lock_api", - "portable-atomic", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e5f46a6136..6f5a38e290 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,6 @@ members = [ "components/scope-local", "components/someboot", "components/somehal-macros", - "components/spin", "components/starry-process", "components/starry-signal", "components/starry-vm", @@ -386,7 +385,7 @@ dma-api = { version = "0.7.3", path = "components/dma-api" } mmio-api = { version = "0.2.2", path = "components/mmio-api" } lock_api = { version = "0.4", default-features = false } log = "0.4" -spin = { version = "0.10", path = "components/spin" } +spin = "0.10" ostool = { version = "0.19" } uefi = "0.36" fdt-edit = "0.2.3" @@ -398,6 +397,3 @@ simple-ahci = "0.1.1-preview.1" bcm2835-sdhci = "0.1.1-preview.1" ixgbe-driver = "0.1.1-preview.1" x86 = "0.52" - -[patch.crates-io] -spin = { path = "components/spin" } diff --git a/components/spin/CHANGELOG.md b/components/spin/CHANGELOG.md deleted file mode 100644 index 19986ce6b5..0000000000 --- a/components/spin/CHANGELOG.md +++ /dev/null @@ -1,162 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -# Unreleased - -### Added - -### Changed - -### Fixed - -# [0.10.0] - 2025-03-26 - -### Added - -- `Mutex::try_lock_weak` -- `RwLock::try_write_weak` -- `RwLock::try_upgrade_weak` - -### Changed - -- Updated MSRV to 1.60 -- Use `dep:` syntax in Cargo.toml -- `portable_atomic` feature has been renamed to `portable-atomic`, for consistency. - -### Fixed - -# [0.9.8] - 2023-04-03 - -### Fixed - -- Unsoundness in `Once::try_call_once` caused by an `Err(_)` result - -# [0.9.7] - 2023-03-27 - -### Fixed - -- Relaxed accidentally restricted `Send`/`Sync` bounds for `Mutex` guards - -# [0.9.6] - 2023-03-13 - -### Fixed - -- Relaxed accidentally restricted `Send`/`Sync` bounds for `RwLock` guards - -# [0.9.5] - 2023-02-07 - -### Added - -- `FairMutex`, a new mutex implementation that reduces writer starvation. -- A MSRV policy: Rust 1.38 is currently required - -### Changed - -- The crate's CI now has full MIRI integration, further improving the confidence you can have in the implementation. - -### Fixed - -- Ensured that the crate's abstractions comply with stacked borrows rules. -- Unsoundness in the `RwLock` that could be triggered via a reader overflow -- Relaxed various `Send`/`Sync` bound requirements to make the crate more flexible - -# [0.9.4] - 2022-07-14 - -### Fixed - -- Fixed unsoundness in `RwLock` on reader overflow -- Relaxed `Send`/`Sync` bounds for `SpinMutex` and `TicketMutex` (doesn't affect `Mutex` itself) - -# [0.9.3] - 2022-04-17 - -### Added - -- Implemented `Default` for `Once` -- `Once::try_call_once` - -### Fixed - -- Fixed bug that caused `Once::call_once` to incorrectly fail - -# [0.9.2] - 2021-07-09 - -### Changed - -- Improved `Once` performance by reducing the memory footprint of internal state to one byte - -### Fixed - -- Improved performance of `Once` by relaxing ordering guarantees and removing redundant checks - -# [0.9.1] - 2021-06-21 - -### Added - -- Default type parameter on `Once` for better ergonomics - -# [0.9.0] - 2021-03-18 - -### Changed - -- Placed all major API features behind feature flags - -### Fixed - -- A compilation bug with the `lock_api` feature - -# [0.8.0] - 2021-03-15 - -### Added - -- `Once::get_unchecked` -- `RelaxStrategy` trait with type parameter on all locks to support switching between relax strategies - -### Changed - -- `lock_api1` feature is now named `lock_api` - -# [0.7.1] - 2021-01-12 - -### Fixed - -- Prevented `Once` leaking the inner value upon drop - -# [0.7.0] - 2020-10-18 - -### Added - -- `Once::initialized` -- `Once::get_mut` -- `Once::try_into_inner` -- `Once::poll` -- `RwLock`, `Mutex` and `Once` now implement `From` -- `Lazy` type for lazy initialization -- `TicketMutex`, an alternative mutex implementation -- `std` feature flag to enable thread yielding instead of spinning -- `Mutex::is_locked`/`SpinMutex::is_locked`/`TicketMutex::is_locked` -- `Barrier` - -### Changed - -- `Once::wait` now spins even if initialization has not yet started -- `Guard::leak` is now an associated function instead of a method -- Improved the performance of `SpinMutex` by relaxing unnecessarily conservative - ordering requirements - -# [0.6.0] - 2020-10-08 - -### Added - -- More dynamic `Send`/`Sync` bounds for lock guards -- `lock_api` compatibility -- `Guard::leak` methods -- `RwLock::reader_count` and `RwLock::writer_count` -- `Display` implementation for guard types - -### Changed - -- Made `Debug` impls of lock guards just show the inner type like `std` diff --git a/components/spin/Cargo.toml b/components/spin/Cargo.toml deleted file mode 100644 index 6e6a30f344..0000000000 --- a/components/spin/Cargo.toml +++ /dev/null @@ -1,75 +0,0 @@ -[package] -name = "spin" -version = "0.10.0" -edition = "2021" -authors = [ - "Mathijs van de Nes ", - "John Ericson ", - "Joshua Barretto ", -] -license = "MIT" -repository = "https://github.com/mvdnes/spin-rs.git" -keywords = ["spinlock", "mutex", "rwlock"] -description = "Spin-based synchronization primitives" -rust-version = "1.60" - -[dependencies] -lock_api_crate = { package = "lock_api", version = "0.4", optional = true } -# Enable require-cas feature to provide a better error message if the end user forgets to use the cfg or feature. -portable-atomic = { version = "1.3", optional = true, default-features = false, features = ["require-cas"] } - -[features] -# TGOSKits keeps this crate for `RwLock`, `Once`, and `Lazy` compatibility. -# Non-sleeping mutex users should use `ax-kspin` instead. -# Note: mutex is enabled here for external crates like buddy-slab-allocator -# that still depend on spin::Mutex. This feature should not be used in new code. -default = ["lock_api", "rwlock", "once", "lazy", "mutex"] - -# Enables `Mutex`. By default this uses `SpinMutex`; enable `use_ticket_mutex` -# to make the root `Mutex` alias use `TicketMutex`. -mutex = [] - -# Enables `SpinMutex` and the default spin mutex implementation for `Mutex`. -spin_mutex = ["mutex"] - -# Enables `TicketMutex`. -ticket_mutex = ["mutex"] - -# Enables `FairMutex`. -fair_mutex = ["mutex"] - -# Enables the non-default ticket mutex implementation for `Mutex`. -use_ticket_mutex = ["mutex", "ticket_mutex"] - -# Enables `RwLock`. -rwlock = [] - -# Enables `Once`. -once = [] - -# Enables `Lazy`. -lazy = ["once"] - -# Enables `Barrier`. Because this feature uses `mutex`, either `spin_mutex` or `use_ticket_mutex` must be enabled. -barrier = ["mutex"] - -# Enables `lock_api`-compatible types that use the primitives in this crate internally. -lock_api = ["dep:lock_api_crate"] - -# Enables std-only features such as yield-relaxing. -std = [] - -# Use the `portable-atomic` crate to support platforms without native atomic operations. -# The `portable_atomic_unsafe_assume_single_core` cfg or `critical-section` feature -# of `portable-atomic` crate must also be set by the final binary crate. -# See the documentation for the `portable-atomic` crate for more information -# with some requirements for no-std build: -# https://github.com/taiki-e/portable-atomic#optional-features -portable-atomic = ["dep:portable-atomic"] - -# Deprecated alias: -portable_atomic = ["portable-atomic"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/components/spin/LICENSE b/components/spin/LICENSE deleted file mode 100644 index b2d7f7bbdc..0000000000 --- a/components/spin/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Mathijs van de Nes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/components/spin/README.md b/components/spin/README.md deleted file mode 100644 index 80c226fec4..0000000000 --- a/components/spin/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# spin-rs - -[![Crates.io version](https://img.shields.io/crates/v/spin.svg)](https://crates.io/crates/spin) -[![docs.rs](https://docs.rs/spin/badge.svg)](https://docs.rs/spin/) -[![Build Status](https://travis-ci.org/mvdnes/spin-rs.svg)](https://travis-ci.org/mvdnes/spin-rs) - -Spin-based synchronization primitives. - -This crate provides [spin-based](https://en.wikipedia.org/wiki/Spinlock) -versions of the primitives in `std::sync`. Because synchronization is done -through spinning, the primitives are suitable for use in `no_std` environments. - -Before deciding to use `spin`, we recommend reading -[this superb blog post](https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html) -by [@matklad](https://github.com/matklad/) that discusses the pros and cons of -spinlocks. If you have access to `std`, it's likely that the primitives in -`std::sync` will serve you better except in very specific circumstances. - -## Features - -- `Mutex`, `RwLock`, `Once`, `Lazy` and `Barrier` equivalents -- Support for `no_std` environments -- [`lock_api`](https://crates.io/crates/lock_api) compatibility -- Upgradeable `RwLock` guards -- Guards can be sent and shared between threads -- Guard leaking -- Ticket locks -- Different strategies for dealing with contention - -## Usage - -Include the following under the `[dependencies]` section in your `Cargo.toml` file. - -```toml -spin = "x.y" -``` - -## Example - -When calling `lock` on a `Mutex` you will get a guard value that provides access -to the data. When this guard is dropped, the mutex will become available again. - -```rust -extern crate spin; -use std::{sync::Arc, thread}; - -fn main() { - let counter = Arc::new(spin::Mutex::new(0)); - - let thread = thread::spawn({ - let counter = counter.clone(); - move || { - for _ in 0..100 { - *counter.lock() += 1; - } - } - }); - - for _ in 0..100 { - *counter.lock() += 1; - } - - thread.join().unwrap(); - - assert_eq!(*counter.lock(), 200); -} -``` - -## Feature flags - -The crate comes with a few feature flags that you may wish to use. - -- `mutex` enables the `Mutex` type. - -- `spin_mutex` enables the `SpinMutex` type. - -- `ticket_mutex` enables the `TicketMutex` type. - -- `use_ticket_mutex` switches to a ticket lock for the implementation of `Mutex`. This - is recommended only on targets for which ordinary spinning locks perform very badly - because it will change the implementation used by other crates that depend on `spin`. - -- `rwlock` enables the `RwLock` type. - -- `once` enables the `Once` type. - -- `lazy` enables the `Lazy` type. - -- `barrier` enables the `Barrier` type. - -- `lock_api` enables support for [`lock_api`](https://crates.io/crates/lock_api) - -- `std` enables support for thread yielding instead of spinning. - -- `portable-atomic` enables usage of the `portable-atomic` crate - to support platforms without native atomic operations (Cortex-M0, etc.). - The `portable_atomic_unsafe_assume_single_core` or `critical-section` feature - of `portable-atomic` crate must also be set by the final binary crate. - See the documentation for the `portable-atomic` crate for more information - with some requirements for no-std build: - https://github.com/taiki-e/portable-atomic#optional-features - -## Remarks - -It is often desirable to have a lock shared between threads. Wrapping the lock in an -`std::sync::Arc` is route through which this might be achieved. - -Locks provide zero-overhead access to their data when accessed through a mutable -reference by using their `get_mut` methods. - -The behaviour of these lock is similar to their namesakes in `std::sync`. they -differ on the following: - -- Locks will not be poisoned in case of failure. -- Threads will not yield to the OS scheduler when encounter a lock that cannot be - accessed. Instead, they will 'spin' in a busy loop until the lock becomes available. - -Many of the feature flags listed above are enabled by default. If you're writing a -library, we recommend disabling those that you don't use to avoid increasing compilation -time for your crate's users. You can do this like so: - -``` -[dependencies] -spin = { version = "x.y", default-features = false, features = [...] } -``` - -## Minimum Safe Rust Version (MSRV) - -This crate is guaranteed to compile on a Minimum Safe Rust Version (MSRV) of 1.60.0 and above. -This version will not be changed without a minor version bump. - -## License - -`spin` is distributed under the MIT License, (See `LICENSE`). diff --git a/components/spin/SECURITY.md b/components/spin/SECURITY.md deleted file mode 100644 index 0dd66d578e..0000000000 --- a/components/spin/SECURITY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Security Policy - -## Supported Versions - -Security updates are applied only to the latest release. - -## Reporting a Vulnerability - -If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. - -Please disclose it at our [security advisory](https://github.com/mvdnes/spin-rs/security/advisories/new). - -This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerabilities will be disclosed in a best effort base. diff --git a/components/spin/src/barrier.rs b/components/spin/src/barrier.rs deleted file mode 100644 index 49b3c99166..0000000000 --- a/components/spin/src/barrier.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Synchronization primitive allowing multiple threads to synchronize the -//! beginning of some computation. -//! -//! Implementation adapted from the 'Barrier' type of the standard library. See: -//! -//! -//! Copyright 2014 The Rust Project Developers. See the COPYRIGHT -//! file at the top-level directory of this distribution and at -//! . -//! -//! Licensed under the Apache License, Version 2.0 > or the MIT license -//! >, at your -//! option. This file may not be copied, modified, or distributed -//! except according to those terms. - -use crate::{RelaxStrategy, Spin, mutex::Mutex}; - -/// A primitive that synchronizes the execution of multiple threads. -/// -/// # Example -/// -/// ``` -/// use std::{sync::Arc, thread}; -/// -/// use spin; -/// -/// let mut handles = Vec::with_capacity(10); -/// let barrier = Arc::new(spin::Barrier::new(10)); -/// for _ in 0..10 { -/// let c = barrier.clone(); -/// // The same messages will be printed together. -/// // You will NOT see any interleaving. -/// handles.push(thread::spawn(move || { -/// println!("before wait"); -/// c.wait(); -/// println!("after wait"); -/// })); -/// } -/// // Wait for other threads to finish. -/// for handle in handles { -/// handle.join().unwrap(); -/// } -/// ``` -pub struct Barrier { - lock: Mutex, - num_threads: usize, -} - -// The inner state of a double barrier -struct BarrierState { - count: usize, - generation_id: usize, -} - -/// A `BarrierWaitResult` is returned by [`wait`] when all threads in the [`Barrier`] -/// have rendezvoused. -/// -/// [`wait`]: struct.Barrier.html#method.wait -/// [`Barrier`]: struct.Barrier.html -/// -/// # Examples -/// -/// ``` -/// use spin; -/// -/// let barrier = spin::Barrier::new(1); -/// let barrier_wait_result = barrier.wait(); -/// ``` -pub struct BarrierWaitResult(bool); - -impl Barrier { - /// Blocks the current thread until all threads have rendezvoused here. - /// - /// Barriers are re-usable after all threads have rendezvoused once, and can - /// be used continuously. - /// - /// A single (arbitrary) thread will receive a [`BarrierWaitResult`] that - /// returns `true` from [`is_leader`] when returning from this function, and - /// all other threads will receive a result that will return `false` from - /// [`is_leader`]. - /// - /// [`BarrierWaitResult`]: struct.BarrierWaitResult.html - /// [`is_leader`]: struct.BarrierWaitResult.html#method.is_leader - /// - /// # Examples - /// - /// ``` - /// use std::{sync::Arc, thread}; - /// - /// use spin; - /// - /// let mut handles = Vec::with_capacity(10); - /// let barrier = Arc::new(spin::Barrier::new(10)); - /// for _ in 0..10 { - /// let c = barrier.clone(); - /// // The same messages will be printed together. - /// // You will NOT see any interleaving. - /// handles.push(thread::spawn(move || { - /// println!("before wait"); - /// c.wait(); - /// println!("after wait"); - /// })); - /// } - /// // Wait for other threads to finish. - /// for handle in handles { - /// handle.join().unwrap(); - /// } - /// ``` - pub fn wait(&self) -> BarrierWaitResult { - let mut lock = self.lock.lock(); - lock.count += 1; - - if lock.count < self.num_threads { - // not the leader - let local_gen = lock.generation_id; - - while local_gen == lock.generation_id && lock.count < self.num_threads { - drop(lock); - R::relax(); - lock = self.lock.lock(); - } - BarrierWaitResult(false) - } else { - // this thread is the leader, - // and is responsible for incrementing the generation - lock.count = 0; - lock.generation_id = lock.generation_id.wrapping_add(1); - BarrierWaitResult(true) - } - } -} - -impl Barrier { - /// Creates a new barrier that can block a given number of threads. - /// - /// A barrier will block `n`-1 threads which call [`wait`] and then wake up - /// all threads at once when the `n`th thread calls [`wait`]. A Barrier created - /// with n = 0 will behave identically to one created with n = 1. - /// - /// [`wait`]: #method.wait - /// - /// # Examples - /// - /// ``` - /// use spin; - /// - /// let barrier = spin::Barrier::new(10); - /// ``` - pub const fn new(n: usize) -> Self { - Self { - lock: Mutex::new(BarrierState { - count: 0, - generation_id: 0, - }), - num_threads: n, - } - } -} - -impl BarrierWaitResult { - /// Returns whether this thread from [`wait`] is the "leader thread". - /// - /// Only one thread will have `true` returned from their result, all other - /// threads will have `false` returned. - /// - /// [`wait`]: struct.Barrier.html#method.wait - /// - /// # Examples - /// - /// ``` - /// use spin; - /// - /// let barrier = spin::Barrier::new(1); - /// let barrier_wait_result = barrier.wait(); - /// println!("{:?}", barrier_wait_result.is_leader()); - /// ``` - pub fn is_leader(&self) -> bool { - self.0 - } -} - -#[cfg(test)] -mod tests { - use std::{ - prelude::v1::*, - sync::{ - Arc, - mpsc::{TryRecvError, channel}, - }, - thread, - }; - - type Barrier = super::Barrier; - - fn use_barrier(n: usize, barrier: Arc) { - let (tx, rx) = channel(); - - let mut ts = Vec::new(); - for _ in 0..n - 1 { - let c = barrier.clone(); - let tx = tx.clone(); - ts.push(thread::spawn(move || { - tx.send(c.wait().is_leader()).unwrap(); - })); - } - - // At this point, all spawned threads should be blocked, - // so we shouldn't get anything from the port - assert!(match rx.try_recv() { - Err(TryRecvError::Empty) => true, - _ => false, - }); - - let mut leader_found = barrier.wait().is_leader(); - - // Now, the barrier is cleared and we should get data. - for _ in 0..n - 1 { - if rx.recv().unwrap() { - assert!(!leader_found); - leader_found = true; - } - } - assert!(leader_found); - - for t in ts { - t.join().unwrap(); - } - } - - #[test] - fn test_barrier() { - const N: usize = 10; - - let barrier = Arc::new(Barrier::new(N)); - - use_barrier(N, barrier.clone()); - - // use barrier twice to ensure it is reusable - use_barrier(N, barrier.clone()); - } -} diff --git a/components/spin/src/lazy.rs b/components/spin/src/lazy.rs deleted file mode 100644 index ca19777c48..0000000000 --- a/components/spin/src/lazy.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! Synchronization primitives for lazy evaluation. -//! -//! Implementation adapted from the `SyncLazy` type of the standard library. See: -//! - -use core::{cell::Cell, fmt, ops::Deref}; - -use crate::{RelaxStrategy, Spin, once::Once}; - -/// A value which is initialized on the first access. -/// -/// This type is a thread-safe `Lazy`, and can be used in statics. -/// -/// # Examples -/// -/// ``` -/// use std::collections::HashMap; -/// -/// use spin::Lazy; -/// -/// static HASHMAP: Lazy> = Lazy::new(|| { -/// println!("initializing"); -/// let mut m = HashMap::new(); -/// m.insert(13, "Spica".to_string()); -/// m.insert(74, "Hoyten".to_string()); -/// m -/// }); -/// -/// fn main() { -/// println!("ready"); -/// std::thread::spawn(|| { -/// println!("{:?}", HASHMAP.get(&13)); -/// }) -/// .join() -/// .unwrap(); -/// println!("{:?}", HASHMAP.get(&74)); -/// -/// // Prints: -/// // ready -/// // initializing -/// // Some("Spica") -/// // Some("Hoyten") -/// } -/// ``` -pub struct Lazy T, R = Spin> { - cell: Once, - init: Cell>, -} - -impl fmt::Debug for Lazy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_tuple("Lazy"); - let d = if let Some(x) = self.cell.get() { - d.field(&x) - } else { - d.field(&format_args!("")) - }; - d.finish() - } -} - -// We never create a `&F` from a `&Lazy` so it is fine -// to not impl `Sync` for `F` -// we do create a `&mut Option` in `force`, but this is -// properly synchronized, so it only happens once -// so it also does not contribute to this impl. -unsafe impl Sync for Lazy where Once: Sync {} -// auto-derived `Send` impl is OK. - -impl Lazy { - /// Creates a new lazy value with the given initializing - /// function. - pub const fn new(f: F) -> Self { - Self { - cell: Once::new(), - init: Cell::new(Some(f)), - } - } - /// Retrieves a mutable pointer to the inner data. - /// - /// This is especially useful when interfacing with low level code or FFI where the caller - /// explicitly knows that it has exclusive access to the inner data. Note that reading from - /// this pointer is UB until initialized or directly written to. - pub fn as_mut_ptr(&self) -> *mut T { - self.cell.as_mut_ptr() - } -} - -impl T, R: RelaxStrategy> Lazy { - /// Forces the evaluation of this lazy value and - /// returns a reference to result. This is equivalent - /// to the `Deref` impl, but is explicit. - /// - /// # Examples - /// - /// ``` - /// use spin::Lazy; - /// - /// let lazy = Lazy::new(|| 92); - /// - /// assert_eq!(Lazy::force(&lazy), &92); - /// assert_eq!(&*lazy, &92); - /// ``` - pub fn force(this: &Self) -> &T { - this.cell.call_once(|| match this.init.take() { - Some(f) => f(), - None => panic!("Lazy instance has previously been poisoned"), - }) - } -} - -impl T, R: RelaxStrategy> Deref for Lazy { - type Target = T; - - fn deref(&self) -> &T { - Self::force(self) - } -} - -impl Default for Lazy T, R> { - /// Creates a new lazy value using `Default` as the initializing function. - fn default() -> Self { - Self::new(T::default) - } -} diff --git a/components/spin/src/lib.rs b/components/spin/src/lib.rs deleted file mode 100644 index 354e5b028a..0000000000 --- a/components/spin/src/lib.rs +++ /dev/null @@ -1,234 +0,0 @@ -#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![deny(missing_docs)] - -//! This crate provides [spin-based](https://en.wikipedia.org/wiki/Spinlock) versions of the -//! primitives in `std::sync` and `std::lazy`. Because synchronization is done through spinning, -//! the primitives are suitable for use in `no_std` environments. -//! -//! # Features -//! -//! - `Mutex`, `RwLock`, `Once`/`SyncOnceCell`, and `SyncLazy` equivalents -//! -//! - Support for `no_std` environments -//! -//! - [`lock_api`](https://crates.io/crates/lock_api) compatibility -//! -//! - Upgradeable `RwLock` guards -//! -//! - Guards can be sent and shared between threads -//! -//! - Guard leaking -//! -//! - Ticket locks -//! -//! - Different strategies for dealing with contention -//! -//! # Relationship with `std::sync` -//! -//! While `spin` is not a drop-in replacement for `std::sync` (and -//! [should not be considered as such](https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html)) -//! an effort is made to keep this crate reasonably consistent with `std::sync`. -//! -//! Many of the types defined in this crate have 'additional capabilities' when compared to `std::sync`: -//! -//! - Because spinning does not depend on the thread-driven model of `std::sync`, guards ([`MutexGuard`], -//! [`RwLockReadGuard`], [`RwLockWriteGuard`], etc.) may be sent and shared between threads. -//! -//! - [`RwLockUpgradableGuard`] supports being upgraded into a [`RwLockWriteGuard`]. -//! -//! - Guards support [leaking](https://doc.rust-lang.org/nomicon/leaking.html). -//! -//! - [`Once`] owns the value returned by its `call_once` initializer. -//! -//! - [`RwLock`] supports counting readers and writers. -//! -//! Conversely, the types in this crate do not have some of the features `std::sync` has: -//! -//! - Locks do not track [panic poisoning](https://doc.rust-lang.org/nomicon/poisoning.html). -//! -//! ## Feature flags -//! -//! The crate comes with a few feature flags that you may wish to use. -//! -//! - `lock_api` enables support for [`lock_api`](https://crates.io/crates/lock_api) -//! -//! - `ticket_mutex` uses a ticket lock for the implementation of `Mutex` -//! -//! - `fair_mutex` enables a fairer implementation of `Mutex` that uses eventual fairness to avoid -//! starvation -//! -//! - `std` enables support for thread yielding instead of spinning -//! -//! - `portable-atomic` enables usage of the `portable-atomic` crate -//! to support platforms without native atomic operations (Cortex-M0, etc.). -//! See the documentation for the `portable-atomic` crate for more information -//! with some requirements for no-std build: -//! https://github.com/taiki-e/portable-atomic#optional-features - -#[cfg(any(test, feature = "std"))] -extern crate core; - -#[cfg(feature = "portable-atomic")] -extern crate portable_atomic; - -#[cfg(all( - not(feature = "portable-atomic"), - any(feature = "mutex", feature = "rwlock", feature = "once") -))] -use core::sync::atomic; - -#[cfg(all( - feature = "portable-atomic", - any(feature = "mutex", feature = "rwlock", feature = "once") -))] -use portable_atomic as atomic; - -#[cfg(feature = "barrier")] -#[cfg_attr(docsrs, doc(cfg(feature = "barrier")))] -pub mod barrier; -#[cfg(feature = "lazy")] -#[cfg_attr(docsrs, doc(cfg(feature = "lazy")))] -pub mod lazy; -#[cfg(feature = "mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "mutex")))] -pub mod mutex; -#[cfg(feature = "once")] -#[cfg_attr(docsrs, doc(cfg(feature = "once")))] -pub mod once; -pub mod relax; -#[cfg(feature = "rwlock")] -#[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] -pub mod rwlock; - -#[cfg(feature = "mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "mutex")))] -pub use mutex::MutexGuard; -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -pub use relax::Yield; -pub use relax::{RelaxStrategy, Spin}; -#[cfg(feature = "rwlock")] -#[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] -pub use rwlock::RwLockReadGuard; - -// Avoid confusing inference errors by aliasing away the relax strategy parameter. Users that need to use a different -// relax strategy can do so by accessing the types through their fully-qualified path. This is a little bit horrible -// but sadly adding a default type parameter is *still* a breaking change in Rust (for understandable reasons). - -/// A primitive that synchronizes the execution of multiple threads. See [`barrier::Barrier`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "barrier")] -#[cfg_attr(docsrs, doc(cfg(feature = "barrier")))] -pub type Barrier = crate::barrier::Barrier; - -/// A value which is initialized on the first access. See [`lazy::Lazy`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "lazy")] -#[cfg_attr(docsrs, doc(cfg(feature = "lazy")))] -pub type Lazy T> = crate::lazy::Lazy; - -/// A primitive that synchronizes the execution of multiple threads. See [`mutex::Mutex`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "mutex")))] -pub type Mutex = crate::mutex::Mutex; - -/// A primitive that provides lazy one-time initialization. See [`once::Once`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "once")] -#[cfg_attr(docsrs, doc(cfg(feature = "once")))] -pub type Once = crate::once::Once; - -/// A lock that provides data access to either one writer or many readers. See [`rwlock::RwLock`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "rwlock")] -#[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] -pub type RwLock = crate::rwlock::RwLock; - -/// A guard that provides immutable data access but can be upgraded to [`RwLockWriteGuard`]. See -/// [`rwlock::RwLockUpgradableGuard`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "rwlock")] -#[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] -pub type RwLockUpgradableGuard<'a, T> = crate::rwlock::RwLockUpgradableGuard<'a, T>; - -/// A guard that provides mutable data access. See [`rwlock::RwLockWriteGuard`] for documentation. -/// -/// A note for advanced users: this alias exists to avoid subtle type inference errors due to the default relax -/// strategy type parameter. If you need a non-default relax strategy, use the fully-qualified path. -#[cfg(feature = "rwlock")] -#[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] -pub type RwLockWriteGuard<'a, T> = crate::rwlock::RwLockWriteGuard<'a, T>; - -/// Spin synchronisation primitives, but compatible with [`lock_api`](https://crates.io/crates/lock_api). -#[cfg(feature = "lock_api")] -#[cfg_attr(docsrs, doc(cfg(feature = "lock_api")))] -pub mod lock_api { - /// A lock that provides mutually exclusive data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)). - #[cfg(feature = "mutex")] - #[cfg_attr(docsrs, doc(cfg(feature = "mutex")))] - pub type Mutex = lock_api_crate::Mutex, T>; - - /// A guard that provides mutable data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)). - #[cfg(feature = "mutex")] - #[cfg_attr(docsrs, doc(cfg(feature = "mutex")))] - pub type MutexGuard<'a, T> = lock_api_crate::MutexGuard<'a, crate::Mutex<()>, T>; - - /// A lock that provides data access to either one writer or many readers (compatible with [`lock_api`](https://crates.io/crates/lock_api)). - #[cfg(feature = "rwlock")] - #[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] - pub type RwLock = lock_api_crate::RwLock, T>; - - /// A guard that provides immutable data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)). - #[cfg(feature = "rwlock")] - #[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] - pub type RwLockReadGuard<'a, T> = lock_api_crate::RwLockReadGuard<'a, crate::RwLock<()>, T>; - - /// A guard that provides mutable data access (compatible with [`lock_api`](https://crates.io/crates/lock_api)). - #[cfg(feature = "rwlock")] - #[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] - pub type RwLockWriteGuard<'a, T> = lock_api_crate::RwLockWriteGuard<'a, crate::RwLock<()>, T>; - - /// A guard that provides immutable data access but can be upgraded to [`RwLockWriteGuard`] (compatible with [`lock_api`](https://crates.io/crates/lock_api)). - #[cfg(feature = "rwlock")] - #[cfg_attr(docsrs, doc(cfg(feature = "rwlock")))] - pub type RwLockUpgradableReadGuard<'a, T> = - lock_api_crate::RwLockUpgradableReadGuard<'a, crate::RwLock<()>, T>; -} - -/// In the event of an invalid operation, it's best to abort the current process. -#[cfg(feature = "fair_mutex")] -fn abort() -> ! { - #[cfg(not(feature = "std"))] - { - // Panicking while panicking is defined by Rust to result in an abort. - struct Panic; - - impl Drop for Panic { - fn drop(&mut self) { - panic!("aborting due to invalid operation"); - } - } - - let _panic = Panic; - panic!("aborting due to invalid operation"); - } - - #[cfg(feature = "std")] - { - std::process::abort(); - } -} diff --git a/components/spin/src/mutex.rs b/components/spin/src/mutex.rs deleted file mode 100644 index 918de72690..0000000000 --- a/components/spin/src/mutex.rs +++ /dev/null @@ -1,344 +0,0 @@ -//! Locks that have the same behaviour as a mutex. -//! -//! The [`Mutex`] in the root of the crate, can be configured using the `ticket_mutex` feature. -//! If it's enabled, [`TicketMutex`] and [`TicketMutexGuard`] will be re-exported as [`Mutex`] -//! and [`MutexGuard`], otherwise the [`SpinMutex`] and guard will be re-exported. -//! -//! `ticket_mutex` is disabled by default. -//! -//! [`Mutex`]: ./struct.Mutex.html -//! [`MutexGuard`]: ./struct.MutexGuard.html -//! [`TicketMutex`]: ./ticket/struct.TicketMutex.html -//! [`TicketMutexGuard`]: ./ticket/struct.TicketMutexGuard.html -//! [`SpinMutex`]: ./spin/struct.SpinMutex.html -//! [`SpinMutexGuard`]: ./spin/struct.SpinMutexGuard.html - -#[cfg(any( - feature = "spin_mutex", - all(feature = "mutex", not(feature = "use_ticket_mutex")) -))] -#[cfg_attr(docsrs, doc(cfg(feature = "spin_mutex")))] -pub mod spin; -#[cfg(any( - feature = "spin_mutex", - all(feature = "mutex", not(feature = "use_ticket_mutex")) -))] -#[cfg_attr(docsrs, doc(cfg(feature = "spin_mutex")))] -pub use self::spin::{SpinMutex, SpinMutexGuard}; - -#[cfg(feature = "ticket_mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "ticket_mutex")))] -pub mod ticket; -#[cfg(feature = "ticket_mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "ticket_mutex")))] -pub use self::ticket::{TicketMutex, TicketMutexGuard}; - -#[cfg(feature = "fair_mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "fair_mutex")))] -pub mod fair; -use core::{ - fmt, - ops::{Deref, DerefMut}, -}; - -#[cfg(feature = "fair_mutex")] -#[cfg_attr(docsrs, doc(cfg(feature = "fair_mutex")))] -pub use self::fair::{FairMutex, FairMutexGuard, Starvation}; -use crate::{RelaxStrategy, Spin}; - -#[cfg(not(feature = "use_ticket_mutex"))] -type InnerMutex = self::spin::SpinMutex; -#[cfg(not(feature = "use_ticket_mutex"))] -type InnerMutexGuard<'a, T> = self::spin::SpinMutexGuard<'a, T>; - -#[cfg(feature = "use_ticket_mutex")] -type InnerMutex = self::ticket::TicketMutex; -#[cfg(feature = "use_ticket_mutex")] -type InnerMutexGuard<'a, T> = self::ticket::TicketMutexGuard<'a, T>; - -/// A spin-based lock providing mutually exclusive access to data. -/// -/// The implementation uses either a ticket mutex or a regular spin mutex depending on whether the `spin_mutex` or -/// `ticket_mutex` feature flag is enabled. -/// -/// # Example -/// -/// ``` -/// use spin; -/// -/// let lock = spin::Mutex::new(0); -/// -/// // Modify the data -/// *lock.lock() = 2; -/// -/// // Read the data -/// let answer = *lock.lock(); -/// assert_eq!(answer, 2); -/// ``` -/// -/// # Thread safety example -/// -/// ``` -/// use std::sync::{Arc, Barrier}; -/// -/// use spin; -/// -/// let thread_count = 1000; -/// let spin_mutex = Arc::new(spin::Mutex::new(0)); -/// -/// // We use a barrier to ensure the readout happens after all writing -/// let barrier = Arc::new(Barrier::new(thread_count + 1)); -/// -/// # let mut ts = Vec::new(); -/// for _ in 0..thread_count { -/// let my_barrier = barrier.clone(); -/// let my_lock = spin_mutex.clone(); -/// # let t = -/// std::thread::spawn(move || { -/// let mut guard = my_lock.lock(); -/// *guard += 1; -/// -/// // Release the lock to prevent a deadlock -/// drop(guard); -/// my_barrier.wait(); -/// }); -/// # ts.push(t); -/// } -/// -/// barrier.wait(); -/// -/// let answer = { *spin_mutex.lock() }; -/// assert_eq!(answer, thread_count); -/// -/// # for t in ts { -/// # t.join().unwrap(); -/// # } -/// ``` -pub struct Mutex { - inner: InnerMutex, -} - -unsafe impl Sync for Mutex {} -unsafe impl Send for Mutex {} - -/// A generic guard that will protect some data access and -/// uses either a ticket lock or a normal spin mutex. -/// -/// For more info see [`TicketMutexGuard`] or [`SpinMutexGuard`]. -/// -/// [`TicketMutexGuard`]: ./struct.TicketMutexGuard.html -/// [`SpinMutexGuard`]: ./struct.SpinMutexGuard.html -pub struct MutexGuard<'a, T: 'a + ?Sized> { - inner: InnerMutexGuard<'a, T>, -} - -impl Mutex { - /// Creates a new [`Mutex`] wrapping the supplied data. - /// - /// # Example - /// - /// ``` - /// use spin::Mutex; - /// - /// static MUTEX: Mutex<()> = Mutex::new(()); - /// - /// fn demo() { - /// let lock = MUTEX.lock(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - #[inline(always)] - pub const fn new(value: T) -> Self { - Self { - inner: InnerMutex::new(value), - } - } - - /// Consumes this [`Mutex`] and unwraps the underlying data. - /// - /// # Example - /// - /// ``` - /// let lock = spin::Mutex::new(42); - /// assert_eq!(42, lock.into_inner()); - /// ``` - #[inline(always)] - pub fn into_inner(self) -> T { - self.inner.into_inner() - } -} - -impl Mutex { - /// Locks the [`Mutex`] and returns a guard that permits access to the inner data. - /// - /// The returned value may be dereferenced for data access - /// and the lock will be dropped when the guard falls out of scope. - /// - /// ``` - /// let lock = spin::Mutex::new(0); - /// { - /// let mut data = lock.lock(); - /// // The lock is now locked and the data can be accessed - /// *data += 1; - /// // The lock is implicitly dropped at the end of the scope - /// } - /// ``` - #[inline(always)] - pub fn lock(&self) -> MutexGuard<'_, T> { - MutexGuard { - inner: self.inner.lock(), - } - } -} - -impl Mutex { - /// Returns `true` if the lock is currently held. - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - #[inline(always)] - pub fn is_locked(&self) -> bool { - self.inner.is_locked() - } - - /// Force unlock this [`Mutex`]. - /// - /// # Safety - /// - /// This is *extremely* unsafe if the lock is not held by the current - /// thread. However, this can be useful in some instances for exposing the - /// lock to FFI that doesn't know how to deal with RAII. - #[inline(always)] - pub unsafe fn force_unlock(&self) { - self.inner.force_unlock() - } - - /// Try to lock this [`Mutex`], returning a lock guard if successful. - /// - /// # Example - /// - /// ``` - /// let lock = spin::Mutex::new(42); - /// - /// let maybe_guard = lock.try_lock(); - /// assert!(maybe_guard.is_some()); - /// - /// // `maybe_guard` is still held, so the second call fails - /// let maybe_guard2 = lock.try_lock(); - /// assert!(maybe_guard2.is_none()); - /// ``` - #[inline(always)] - pub fn try_lock(&self) -> Option> { - self.inner - .try_lock() - .map(|guard| MutexGuard { inner: guard }) - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in Rust, - /// no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As such, - /// this is a 'zero-cost' operation. - /// - /// # Example - /// - /// ``` - /// let mut lock = spin::Mutex::new(0); - /// *lock.get_mut() = 10; - /// assert_eq!(*lock.lock(), 10); - /// ``` - #[inline(always)] - pub fn get_mut(&mut self) -> &mut T { - self.inner.get_mut() - } -} - -impl fmt::Debug for Mutex { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.inner, f) - } -} - -impl Default for Mutex { - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl From for Mutex { - fn from(data: T) -> Self { - Self::new(data) - } -} - -impl<'a, T: ?Sized> MutexGuard<'a, T> { - /// Leak the lock guard, yielding a mutable reference to the underlying data. - /// - /// Note that this function will permanently lock the original [`Mutex`]. - /// - /// ``` - /// let mylock = spin::Mutex::new(0); - /// - /// let data: &mut i32 = spin::MutexGuard::leak(mylock.lock()); - /// - /// *data = 1; - /// assert_eq!(*data, 1); - /// ``` - #[inline(always)] - pub fn leak(this: Self) -> &'a mut T { - InnerMutexGuard::leak(this.inner) - } -} - -impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized + fmt::Display> fmt::Display for MutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { - type Target = T; - fn deref(&self) -> &T { - &self.inner - } -} - -impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawMutex for Mutex<(), R> { - type GuardMarker = lock_api_crate::GuardSend; - - const INIT: Self = Self::new(()); - - fn lock(&self) { - // Prevent guard destructor running - core::mem::forget(Self::lock(self)); - } - - fn try_lock(&self) -> bool { - // Prevent guard destructor running - Self::try_lock(self).map(core::mem::forget).is_some() - } - - unsafe fn unlock(&self) { - self.force_unlock(); - } - - fn is_locked(&self) -> bool { - self.inner.is_locked() - } -} diff --git a/components/spin/src/mutex/fair.rs b/components/spin/src/mutex/fair.rs deleted file mode 100644 index d744dd2c67..0000000000 --- a/components/spin/src/mutex/fair.rs +++ /dev/null @@ -1,739 +0,0 @@ -//! A spinning mutex with a fairer unlock algorithm. -//! -//! This mutex is similar to the `SpinMutex` in that it uses spinning to avoid -//! context switches. However, it uses a fairer unlock algorithm that avoids -//! starvation of threads that are waiting for the lock. - -use core::{ - cell::UnsafeCell, - fmt, - marker::PhantomData, - mem::ManuallyDrop, - ops::{Deref, DerefMut}, -}; - -use crate::{ - RelaxStrategy, Spin, - atomic::{AtomicUsize, Ordering}, -}; - -// The lowest bit of `lock` is used to indicate whether the mutex is locked or not. The rest of the bits are used to -// store the number of starving threads. -const LOCKED: usize = 1; -const STARVED: usize = 2; - -/// Number chosen by fair roll of the dice, adjust as needed. -const STARVATION_SPINS: usize = 1024; - -/// A [spin lock](https://en.m.wikipedia.org/wiki/Spinlock) providing mutually exclusive access to data, but with a fairer -/// algorithm. -/// -/// # Example -/// -/// ``` -/// use spin; -/// -/// let lock = spin::mutex::FairMutex::<_>::new(0); -/// -/// // Modify the data -/// *lock.lock() = 2; -/// -/// // Read the data -/// let answer = *lock.lock(); -/// assert_eq!(answer, 2); -/// ``` -/// -/// # Thread safety example -/// -/// ``` -/// use std::sync::{Arc, Barrier}; -/// -/// use spin; -/// -/// let thread_count = 1000; -/// let spin_mutex = Arc::new(spin::mutex::FairMutex::<_>::new(0)); -/// -/// // We use a barrier to ensure the readout happens after all writing -/// let barrier = Arc::new(Barrier::new(thread_count + 1)); -/// -/// for _ in (0..thread_count) { -/// let my_barrier = barrier.clone(); -/// let my_lock = spin_mutex.clone(); -/// std::thread::spawn(move || { -/// let mut guard = my_lock.lock(); -/// *guard += 1; -/// -/// // Release the lock to prevent a deadlock -/// drop(guard); -/// my_barrier.wait(); -/// }); -/// } -/// -/// barrier.wait(); -/// -/// let answer = { *spin_mutex.lock() }; -/// assert_eq!(answer, thread_count); -/// ``` -pub struct FairMutex { - phantom: PhantomData, - pub(crate) lock: AtomicUsize, - data: UnsafeCell, -} - -/// A guard that provides mutable data access. -/// -/// When the guard falls out of scope it will release the lock. -pub struct FairMutexGuard<'a, T: ?Sized + 'a> { - lock: &'a AtomicUsize, - data: *mut T, -} - -/// A handle that indicates that we have been trying to acquire the lock for a while. -/// -/// This handle is used to prevent starvation. -pub struct Starvation<'a, T: ?Sized + 'a, R> { - lock: &'a FairMutex, -} - -/// Indicates whether a lock was rejected due to the lock being held by another thread or due to starvation. -#[derive(Debug)] -pub enum LockRejectReason { - /// The lock was rejected due to the lock being held by another thread. - Locked, - - /// The lock was rejected due to starvation. - Starved, -} - -// Same unsafe impls as `std::sync::Mutex` -unsafe impl Sync for FairMutex {} -unsafe impl Send for FairMutex {} - -unsafe impl Sync for FairMutexGuard<'_, T> {} -unsafe impl Send for FairMutexGuard<'_, T> {} - -impl FairMutex { - /// Creates a new [`FairMutex`] wrapping the supplied data. - /// - /// # Example - /// - /// ``` - /// use spin::mutex::FairMutex; - /// - /// static MUTEX: FairMutex<()> = FairMutex::<_>::new(()); - /// - /// fn demo() { - /// let lock = MUTEX.lock(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - #[inline(always)] - pub const fn new(data: T) -> Self { - FairMutex { - lock: AtomicUsize::new(0), - data: UnsafeCell::new(data), - phantom: PhantomData, - } - } - - /// Consumes this [`FairMutex`] and unwraps the underlying data. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::FairMutex::<_>::new(42); - /// assert_eq!(42, lock.into_inner()); - /// ``` - #[inline(always)] - pub fn into_inner(self) -> T { - // We know statically that there are no outstanding references to - // `self` so there's no need to lock. - let FairMutex { data, .. } = self; - data.into_inner() - } - - /// Returns a mutable pointer to the underlying data. - /// - /// This is mostly meant to be used for applications which require manual unlocking, but where - /// storing both the lock and the pointer to the inner data gets inefficient. - /// - /// # Example - /// ``` - /// let lock = spin::mutex::FairMutex::<_>::new(42); - /// - /// unsafe { - /// core::mem::forget(lock.lock()); - /// - /// assert_eq!(lock.as_mut_ptr().read(), 42); - /// lock.as_mut_ptr().write(58); - /// - /// lock.force_unlock(); - /// } - /// - /// assert_eq!(*lock.lock(), 58); - /// ``` - #[inline(always)] - pub fn as_mut_ptr(&self) -> *mut T { - self.data.get() - } -} - -impl FairMutex { - /// Locks the [`FairMutex`] and returns a guard that permits access to the inner data. - /// - /// The returned value may be dereferenced for data access - /// and the lock will be dropped when the guard falls out of scope. - /// - /// ``` - /// let lock = spin::mutex::FairMutex::<_>::new(0); - /// { - /// let mut data = lock.lock(); - /// // The lock is now locked and the data can be accessed - /// *data += 1; - /// // The lock is implicitly dropped at the end of the scope - /// } - /// ``` - #[inline(always)] - pub fn lock(&self) -> FairMutexGuard<'_, T> { - // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` - // when called in a loop. - let mut spins = 0; - while self - .lock - .compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed) - .is_err() - { - // Wait until the lock looks unlocked before retrying - while self.is_locked() { - R::relax(); - - // If we've been spinning for a while, switch to a fairer strategy that will prevent - // newer users from stealing our lock from us. - if spins > STARVATION_SPINS { - return self.starve().lock(); - } - spins += 1; - } - } - - FairMutexGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - } - } -} - -impl FairMutex { - /// Returns `true` if the lock is currently held. - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - #[inline(always)] - pub fn is_locked(&self) -> bool { - self.lock.load(Ordering::Relaxed) & LOCKED != 0 - } - - /// Force unlock this [`FairMutex`]. - /// - /// # Safety - /// - /// This is *extremely* unsafe if the lock is not held by the current - /// thread. However, this can be useful in some instances for exposing the - /// lock to FFI that doesn't know how to deal with RAII. - #[inline(always)] - pub unsafe fn force_unlock(&self) { - self.lock.fetch_and(!LOCKED, Ordering::Release); - } - - /// Try to lock this [`FairMutex`], returning a lock guard if successful. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::FairMutex::<_>::new(42); - /// - /// let maybe_guard = lock.try_lock(); - /// assert!(maybe_guard.is_some()); - /// - /// // `maybe_guard` is still held, so the second call fails - /// let maybe_guard2 = lock.try_lock(); - /// assert!(maybe_guard2.is_none()); - /// ``` - #[inline(always)] - pub fn try_lock(&self) -> Option> { - self.try_lock_starver().ok() - } - - /// Tries to lock this [`FairMutex`] and returns a result that indicates whether the lock was - /// rejected due to a starver or not. - #[inline(always)] - pub fn try_lock_starver(&self) -> Result, LockRejectReason> { - match self - .lock - .compare_exchange(0, LOCKED, Ordering::Acquire, Ordering::Relaxed) - .unwrap_or_else(|x| x) - { - 0 => Ok(FairMutexGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - }), - LOCKED => Err(LockRejectReason::Locked), - _ => Err(LockRejectReason::Starved), - } - } - - /// Indicates that the current user has been waiting for the lock for a while - /// and that the lock should yield to this thread over a newly arriving thread. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::FairMutex::<_>::new(42); - /// - /// // Lock the mutex to simulate it being used by another user. - /// let guard1 = lock.lock(); - /// - /// // Try to lock the mutex. - /// let guard2 = lock.try_lock(); - /// assert!(guard2.is_none()); - /// - /// // Wait for a while. - /// wait_for_a_while(); - /// - /// // We are now starved, indicate as such. - /// let starve = lock.starve(); - /// - /// // Once the lock is released, another user trying to lock it will - /// // fail. - /// drop(guard1); - /// let guard3 = lock.try_lock(); - /// assert!(guard3.is_none()); - /// - /// // However, we will be able to lock it. - /// let guard4 = starve.try_lock(); - /// assert!(guard4.is_ok()); - /// - /// # fn wait_for_a_while() {} - /// ``` - pub fn starve(&self) -> Starvation<'_, T, R> { - // Add a new starver to the state. - if self.lock.fetch_add(STARVED, Ordering::Relaxed) > (isize::MAX - 1) as usize { - // In the event of a potential lock overflow, abort. - crate::abort(); - } - - Starvation { lock: self } - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the [`FairMutex`] mutably, and a mutable reference is guaranteed to be exclusive in - /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As - /// such, this is a 'zero-cost' operation. - /// - /// # Example - /// - /// ``` - /// let mut lock = spin::mutex::FairMutex::<_>::new(0); - /// *lock.get_mut() = 10; - /// assert_eq!(*lock.lock(), 10); - /// ``` - #[inline(always)] - pub fn get_mut(&mut self) -> &mut T { - // We know statically that there are no other references to `self`, so - // there's no need to lock the inner mutex. - unsafe { &mut *self.data.get() } - } -} - -impl fmt::Debug for FairMutex { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - struct LockWrapper<'a, T: ?Sized + fmt::Debug>(Option>); - - impl fmt::Debug for LockWrapper<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match &self.0 { - Some(guard) => fmt::Debug::fmt(guard, f), - None => f.write_str(""), - } - } - } - - f.debug_struct("FairMutex") - .field("data", &LockWrapper(self.try_lock())) - .finish() - } -} - -impl Default for FairMutex { - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl From for FairMutex { - fn from(data: T) -> Self { - Self::new(data) - } -} - -impl<'a, T: ?Sized> FairMutexGuard<'a, T> { - /// Leak the lock guard, yielding a mutable reference to the underlying data. - /// - /// Note that this function will permanently lock the original [`FairMutex`]. - /// - /// ``` - /// let mylock = spin::mutex::FairMutex::<_>::new(0); - /// - /// let data: &mut i32 = spin::mutex::FairMutexGuard::leak(mylock.lock()); - /// - /// *data = 1; - /// assert_eq!(*data, 1); - /// ``` - #[inline(always)] - pub fn leak(this: Self) -> &'a mut T { - // Use ManuallyDrop to avoid stacked-borrow invalidation - let mut this = ManuallyDrop::new(this); - // We know statically that only we are referencing data - unsafe { &mut *this.data } - } -} - -impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for FairMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized + fmt::Display> fmt::Display for FairMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized> Deref for FairMutexGuard<'a, T> { - type Target = T; - fn deref(&self) -> &T { - // We know statically that only we are referencing data - unsafe { &*self.data } - } -} - -impl<'a, T: ?Sized> DerefMut for FairMutexGuard<'a, T> { - fn deref_mut(&mut self) -> &mut T { - // We know statically that only we are referencing data - unsafe { &mut *self.data } - } -} - -impl<'a, T: ?Sized> Drop for FairMutexGuard<'a, T> { - /// The dropping of the MutexGuard will release the lock it was created from. - fn drop(&mut self) { - self.lock.fetch_and(!LOCKED, Ordering::Release); - } -} - -impl<'a, T: ?Sized, R> Starvation<'a, T, R> { - /// Attempts the lock the mutex if we are the only starving user. - /// - /// This allows another user to lock the mutex if they are starving as well. - pub fn try_lock_fair(self) -> Result, Self> { - // Try to lock the mutex. - if self - .lock - .lock - .compare_exchange( - STARVED, - STARVED | LOCKED, - Ordering::Acquire, - Ordering::Relaxed, - ) - .is_ok() - { - // We are the only starving user, lock the mutex. - Ok(FairMutexGuard { - lock: &self.lock.lock, - data: self.lock.data.get(), - }) - } else { - // Another user is starving, fail. - Err(self) - } - } - - /// Attempts to lock the mutex. - /// - /// If the lock is currently held by another thread, this will return `None`. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::FairMutex::<_>::new(42); - /// - /// // Lock the mutex to simulate it being used by another user. - /// let guard1 = lock.lock(); - /// - /// // Try to lock the mutex. - /// let guard2 = lock.try_lock(); - /// assert!(guard2.is_none()); - /// - /// // Wait for a while. - /// wait_for_a_while(); - /// - /// // We are now starved, indicate as such. - /// let starve = lock.starve(); - /// - /// // Once the lock is released, another user trying to lock it will - /// // fail. - /// drop(guard1); - /// let guard3 = lock.try_lock(); - /// assert!(guard3.is_none()); - /// - /// // However, we will be able to lock it. - /// let guard4 = starve.try_lock(); - /// assert!(guard4.is_ok()); - /// - /// # fn wait_for_a_while() {} - /// ``` - pub fn try_lock(self) -> Result, Self> { - // Try to lock the mutex. - if self.lock.lock.fetch_or(LOCKED, Ordering::Acquire) & LOCKED == 0 { - // We have successfully locked the mutex. - // By dropping `self` here, we decrement the starvation count. - Ok(FairMutexGuard { - lock: &self.lock.lock, - data: self.lock.data.get(), - }) - } else { - Err(self) - } - } -} - -impl<'a, T: ?Sized, R: RelaxStrategy> Starvation<'a, T, R> { - /// Locks the mutex. - pub fn lock(mut self) -> FairMutexGuard<'a, T> { - // Try to lock the mutex. - loop { - match self.try_lock() { - Ok(lock) => return lock, - Err(starve) => self = starve, - } - - // Relax until the lock is released. - while self.lock.is_locked() { - R::relax(); - } - } - } -} - -impl<'a, T: ?Sized, R> Drop for Starvation<'a, T, R> { - fn drop(&mut self) { - // As there is no longer a user being starved, we decrement the starver count. - self.lock.lock.fetch_sub(STARVED, Ordering::Release); - } -} - -impl fmt::Display for LockRejectReason { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - LockRejectReason::Locked => write!(f, "locked"), - LockRejectReason::Starved => write!(f, "starved"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for LockRejectReason {} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawMutex for FairMutex<(), R> { - type GuardMarker = lock_api_crate::GuardSend; - - const INIT: Self = Self::new(()); - - fn lock(&self) { - // Prevent guard destructor running - core::mem::forget(Self::lock(self)); - } - - fn try_lock(&self) -> bool { - // Prevent guard destructor running - Self::try_lock(self).map(core::mem::forget).is_some() - } - - unsafe fn unlock(&self) { - self.force_unlock(); - } - - fn is_locked(&self) -> bool { - Self::is_locked(self) - } -} - -#[cfg(test)] -mod tests { - use std::{ - prelude::v1::*, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - mpsc::channel, - }, - thread, - }; - - type FairMutex = super::FairMutex; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn smoke() { - let m = FairMutex::<_>::new(()); - drop(m.lock()); - drop(m.lock()); - } - - #[test] - fn lots_and_lots() { - static M: FairMutex<()> = FairMutex::<_>::new(()); - static mut CNT: u32 = 0; - const J: u32 = 1000; - const K: u32 = 3; - - fn inc() { - for _ in 0..J { - unsafe { - let _g = M.lock(); - CNT += 1; - } - } - } - - let (tx, rx) = channel(); - for _ in 0..K { - let tx2 = tx.clone(); - thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - }); - let tx2 = tx.clone(); - thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - }); - } - - drop(tx); - for _ in 0..2 * K { - rx.recv().unwrap(); - } - assert_eq!(unsafe { CNT }, J * K * 2); - } - - #[test] - fn try_lock() { - let mutex = FairMutex::<_>::new(42); - - // First lock succeeds - let a = mutex.try_lock(); - assert_eq!(a.as_ref().map(|r| **r), Some(42)); - - // Additional lock fails - let b = mutex.try_lock(); - assert!(b.is_none()); - - // After dropping lock, it succeeds again - ::core::mem::drop(a); - let c = mutex.try_lock(); - assert_eq!(c.as_ref().map(|r| **r), Some(42)); - } - - #[test] - fn test_into_inner() { - let m = FairMutex::<_>::new(NonCopy(10)); - assert_eq!(m.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct Foo(Arc); - impl Drop for Foo { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let num_drops = Arc::new(AtomicUsize::new(0)); - let m = FairMutex::<_>::new(Foo(num_drops.clone())); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - { - let _inner = m.into_inner(); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - } - assert_eq!(num_drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_mutex_arc_nested() { - // Tests nested mutexes and access - // to underlying data. - let arc = Arc::new(FairMutex::<_>::new(1)); - let arc2 = Arc::new(FairMutex::<_>::new(arc)); - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let lock = arc2.lock(); - let lock2 = lock.lock(); - assert_eq!(*lock2, 1); - tx.send(()).unwrap(); - }); - rx.recv().unwrap(); - } - - #[test] - fn test_mutex_arc_access_in_unwind() { - let arc = Arc::new(FairMutex::<_>::new(1)); - let arc2 = arc.clone(); - let _ = thread::spawn(move || -> () { - struct Unwinder { - i: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - *self.i.lock() += 1; - } - } - let _u = Unwinder { i: arc2 }; - panic!(); - }) - .join(); - let lock = arc.lock(); - assert_eq!(*lock, 2); - } - - #[test] - fn test_mutex_unsized() { - let mutex: &FairMutex<[i32]> = &FairMutex::<_>::new([1, 2, 3]); - { - let b = &mut *mutex.lock(); - b[0] = 4; - b[2] = 5; - } - let comp: &[i32] = &[4, 2, 5]; - assert_eq!(&*mutex.lock(), comp); - } - - #[test] - fn test_mutex_force_lock() { - let lock = FairMutex::<_>::new(()); - ::std::mem::forget(lock.lock()); - unsafe { - lock.force_unlock(); - } - assert!(lock.try_lock().is_some()); - } -} diff --git a/components/spin/src/mutex/spin.rs b/components/spin/src/mutex/spin.rs deleted file mode 100644 index 0f67fbde04..0000000000 --- a/components/spin/src/mutex/spin.rs +++ /dev/null @@ -1,561 +0,0 @@ -//! A naïve spinning mutex. -//! -//! Waiting threads hammer an atomic variable until it becomes available. Best-case latency is low, but worst-case -//! latency is theoretically infinite. - -use core::{ - cell::UnsafeCell, - fmt, - marker::PhantomData, - mem::ManuallyDrop, - ops::{Deref, DerefMut}, -}; - -use crate::{ - RelaxStrategy, Spin, - atomic::{AtomicBool, Ordering}, -}; - -/// A [spin lock](https://en.m.wikipedia.org/wiki/Spinlock) providing mutually exclusive access to data. -/// -/// # Example -/// -/// ``` -/// use spin; -/// -/// let lock = spin::mutex::SpinMutex::<_>::new(0); -/// -/// // Modify the data -/// *lock.lock() = 2; -/// -/// // Read the data -/// let answer = *lock.lock(); -/// assert_eq!(answer, 2); -/// ``` -/// -/// # Thread safety example -/// -/// ``` -/// use std::sync::{Arc, Barrier}; -/// -/// use spin; -/// -/// let thread_count = 1000; -/// let spin_mutex = Arc::new(spin::mutex::SpinMutex::<_>::new(0)); -/// -/// // We use a barrier to ensure the readout happens after all writing -/// let barrier = Arc::new(Barrier::new(thread_count + 1)); -/// -/// # let mut ts = Vec::new(); -/// for _ in (0..thread_count) { -/// let my_barrier = barrier.clone(); -/// let my_lock = spin_mutex.clone(); -/// # let t = -/// std::thread::spawn(move || { -/// let mut guard = my_lock.lock(); -/// *guard += 1; -/// -/// // Release the lock to prevent a deadlock -/// drop(guard); -/// my_barrier.wait(); -/// }); -/// # ts.push(t); -/// } -/// -/// barrier.wait(); -/// -/// let answer = { *spin_mutex.lock() }; -/// assert_eq!(answer, thread_count); -/// -/// # for t in ts { -/// # t.join().unwrap(); -/// # } -/// ``` -pub struct SpinMutex { - phantom: PhantomData, - pub(crate) lock: AtomicBool, - data: UnsafeCell, -} - -/// A guard that provides mutable data access. -/// -/// When the guard falls out of scope it will release the lock. -pub struct SpinMutexGuard<'a, T: ?Sized + 'a> { - lock: &'a AtomicBool, - data: *mut T, -} - -// Same unsafe impls as `std::sync::Mutex` -unsafe impl Sync for SpinMutex {} -unsafe impl Send for SpinMutex {} - -unsafe impl Sync for SpinMutexGuard<'_, T> {} -unsafe impl Send for SpinMutexGuard<'_, T> {} - -impl SpinMutex { - /// Creates a new [`SpinMutex`] wrapping the supplied data. - /// - /// # Example - /// - /// ``` - /// use spin::mutex::SpinMutex; - /// - /// static MUTEX: SpinMutex<()> = SpinMutex::<_>::new(()); - /// - /// fn demo() { - /// let lock = MUTEX.lock(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - #[inline(always)] - pub const fn new(data: T) -> Self { - SpinMutex { - lock: AtomicBool::new(false), - data: UnsafeCell::new(data), - phantom: PhantomData, - } - } - - /// Consumes this [`SpinMutex`] and unwraps the underlying data. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::SpinMutex::<_>::new(42); - /// assert_eq!(42, lock.into_inner()); - /// ``` - #[inline(always)] - pub fn into_inner(self) -> T { - // We know statically that there are no outstanding references to - // `self` so there's no need to lock. - let SpinMutex { data, .. } = self; - data.into_inner() - } - - /// Returns a mutable pointer to the underlying data. - /// - /// This is mostly meant to be used for applications which require manual unlocking, but where - /// storing both the lock and the pointer to the inner data gets inefficient. - /// - /// # Example - /// ``` - /// let lock = spin::mutex::SpinMutex::<_>::new(42); - /// - /// unsafe { - /// core::mem::forget(lock.lock()); - /// - /// assert_eq!(lock.as_mut_ptr().read(), 42); - /// lock.as_mut_ptr().write(58); - /// - /// lock.force_unlock(); - /// } - /// - /// assert_eq!(*lock.lock(), 58); - /// ``` - #[inline(always)] - pub fn as_mut_ptr(&self) -> *mut T { - self.data.get() - } -} - -impl SpinMutex { - /// Locks the [`SpinMutex`] and returns a guard that permits access to the inner data. - /// - /// The returned value may be dereferenced for data access - /// and the lock will be dropped when the guard falls out of scope. - /// - /// ``` - /// let lock = spin::mutex::SpinMutex::<_>::new(0); - /// { - /// let mut data = lock.lock(); - /// // The lock is now locked and the data can be accessed - /// *data += 1; - /// // The lock is implicitly dropped at the end of the scope - /// } - /// ``` - #[inline(always)] - pub fn lock(&self) -> SpinMutexGuard<'_, T> { - // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` - // when called in a loop. - loop { - if let Some(guard) = self.try_lock_weak() { - break guard; - } - - while self.is_locked() { - R::relax(); - } - } - } -} - -impl SpinMutex { - /// Returns `true` if the lock is currently held. - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - #[inline(always)] - pub fn is_locked(&self) -> bool { - self.lock.load(Ordering::Relaxed) - } - - /// Force unlock this [`SpinMutex`]. - /// - /// # Safety - /// - /// This is *extremely* unsafe if the lock is not held by the current - /// thread. However, this can be useful in some instances for exposing the - /// lock to FFI that doesn't know how to deal with RAII. - #[inline(always)] - pub unsafe fn force_unlock(&self) { - self.lock.store(false, Ordering::Release); - } - - /// Try to lock this [`SpinMutex`], returning a lock guard if successful. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::SpinMutex::<_>::new(42); - /// - /// let maybe_guard = lock.try_lock(); - /// assert!(maybe_guard.is_some()); - /// - /// // `maybe_guard` is still held, so the second call fails - /// let maybe_guard2 = lock.try_lock(); - /// assert!(maybe_guard2.is_none()); - /// ``` - #[inline(always)] - pub fn try_lock(&self) -> Option> { - // The reason for using a strong compare_exchange is explained here: - // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 - if self - .lock - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - { - Some(SpinMutexGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - }) - } else { - None - } - } - - /// Try to lock this [`SpinMutex`], returning a lock guard if succesful. - /// - /// Unlike [`SpinMutex::try_lock`], this function is allowed to spuriously fail even when the mutex is unlocked, - /// which can result in more efficient code on some platforms. - #[inline(always)] - pub fn try_lock_weak(&self) -> Option> { - if self - .lock - .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - { - Some(SpinMutexGuard { - lock: &self.lock, - data: unsafe { &mut *self.data.get() }, - }) - } else { - None - } - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the [`SpinMutex`] mutably, and a mutable reference is guaranteed to be exclusive in - /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As - /// such, this is a 'zero-cost' operation. - /// - /// # Example - /// - /// ``` - /// let mut lock = spin::mutex::SpinMutex::<_>::new(0); - /// *lock.get_mut() = 10; - /// assert_eq!(*lock.lock(), 10); - /// ``` - #[inline(always)] - pub fn get_mut(&mut self) -> &mut T { - // We know statically that there are no other references to `self`, so - // there's no need to lock the inner mutex. - unsafe { &mut *self.data.get() } - } -} - -impl fmt::Debug for SpinMutex { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try_lock() { - Some(guard) => write!(f, "Mutex {{ data: ") - .and_then(|()| (*guard).fmt(f)) - .and_then(|()| write!(f, " }}")), - None => write!(f, "Mutex {{ }}"), - } - } -} - -impl Default for SpinMutex { - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl From for SpinMutex { - fn from(data: T) -> Self { - Self::new(data) - } -} - -impl<'a, T: ?Sized> SpinMutexGuard<'a, T> { - /// Leak the lock guard, yielding a mutable reference to the underlying data. - /// - /// Note that this function will permanently lock the original [`SpinMutex`]. - /// - /// ``` - /// let mylock = spin::mutex::SpinMutex::<_>::new(0); - /// - /// let data: &mut i32 = spin::mutex::SpinMutexGuard::leak(mylock.lock()); - /// - /// *data = 1; - /// assert_eq!(*data, 1); - /// ``` - #[inline(always)] - pub fn leak(this: Self) -> &'a mut T { - // Use ManuallyDrop to avoid stacked-borrow invalidation - let mut this = ManuallyDrop::new(this); - // We know statically that only we are referencing data - unsafe { &mut *this.data } - } -} - -impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for SpinMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized + fmt::Display> fmt::Display for SpinMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized> Deref for SpinMutexGuard<'a, T> { - type Target = T; - fn deref(&self) -> &T { - // We know statically that only we are referencing data - unsafe { &*self.data } - } -} - -impl<'a, T: ?Sized> DerefMut for SpinMutexGuard<'a, T> { - fn deref_mut(&mut self) -> &mut T { - // We know statically that only we are referencing data - unsafe { &mut *self.data } - } -} - -impl<'a, T: ?Sized> Drop for SpinMutexGuard<'a, T> { - /// The dropping of the MutexGuard will release the lock it was created from. - fn drop(&mut self) { - self.lock.store(false, Ordering::Release); - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawMutex for SpinMutex<(), R> { - type GuardMarker = lock_api_crate::GuardSend; - - const INIT: Self = Self::new(()); - - fn lock(&self) { - // Prevent guard destructor running - core::mem::forget(Self::lock(self)); - } - - fn try_lock(&self) -> bool { - // Prevent guard destructor running - Self::try_lock(self).map(core::mem::forget).is_some() - } - - unsafe fn unlock(&self) { - self.force_unlock(); - } - - fn is_locked(&self) -> bool { - Self::is_locked(self) - } -} - -#[cfg(test)] -mod tests { - use std::{ - prelude::v1::*, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - mpsc::channel, - }, - thread, - }; - - type SpinMutex = super::SpinMutex; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn smoke() { - let m = SpinMutex::<_>::new(()); - drop(m.lock()); - drop(m.lock()); - } - - #[test] - fn lots_and_lots() { - static M: SpinMutex<()> = SpinMutex::<_>::new(()); - static mut CNT: u32 = 0; - const J: u32 = 1000; - const K: u32 = 3; - - fn inc() { - for _ in 0..J { - unsafe { - let _g = M.lock(); - CNT += 1; - } - } - } - - let (tx, rx) = channel(); - let mut ts = Vec::new(); - for _ in 0..K { - let tx2 = tx.clone(); - ts.push(thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - })); - let tx2 = tx.clone(); - ts.push(thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - })); - } - - drop(tx); - for _ in 0..2 * K { - rx.recv().unwrap(); - } - assert_eq!(unsafe { CNT }, J * K * 2); - - for t in ts { - t.join().unwrap(); - } - } - - #[test] - fn try_lock() { - let mutex = SpinMutex::<_>::new(42); - - // First lock succeeds - let a = mutex.try_lock(); - assert_eq!(a.as_ref().map(|r| **r), Some(42)); - - // Additional lock fails - let b = mutex.try_lock(); - assert!(b.is_none()); - - // After dropping lock, it succeeds again - ::core::mem::drop(a); - let c = mutex.try_lock(); - assert_eq!(c.as_ref().map(|r| **r), Some(42)); - } - - #[test] - fn test_into_inner() { - let m = SpinMutex::<_>::new(NonCopy(10)); - assert_eq!(m.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct Foo(Arc); - impl Drop for Foo { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let num_drops = Arc::new(AtomicUsize::new(0)); - let m = SpinMutex::<_>::new(Foo(num_drops.clone())); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - { - let _inner = m.into_inner(); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - } - assert_eq!(num_drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_mutex_arc_nested() { - // Tests nested mutexes and access - // to underlying data. - let arc = Arc::new(SpinMutex::<_>::new(1)); - let arc2 = Arc::new(SpinMutex::<_>::new(arc)); - let (tx, rx) = channel(); - let t = thread::spawn(move || { - let lock = arc2.lock(); - let lock2 = lock.lock(); - assert_eq!(*lock2, 1); - tx.send(()).unwrap(); - }); - rx.recv().unwrap(); - t.join().unwrap(); - } - - #[test] - fn test_mutex_arc_access_in_unwind() { - let arc = Arc::new(SpinMutex::<_>::new(1)); - let arc2 = arc.clone(); - let _ = thread::spawn(move || -> () { - struct Unwinder { - i: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - *self.i.lock() += 1; - } - } - let _u = Unwinder { i: arc2 }; - panic!(); - }) - .join(); - let lock = arc.lock(); - assert_eq!(*lock, 2); - } - - #[test] - fn test_mutex_unsized() { - let mutex: &SpinMutex<[i32]> = &SpinMutex::<_>::new([1, 2, 3]); - { - let b = &mut *mutex.lock(); - b[0] = 4; - b[2] = 5; - } - let comp: &[i32] = &[4, 2, 5]; - assert_eq!(&*mutex.lock(), comp); - } - - #[test] - fn test_mutex_force_lock() { - let lock = SpinMutex::<_>::new(()); - ::std::mem::forget(lock.lock()); - unsafe { - lock.force_unlock(); - } - assert!(lock.try_lock().is_some()); - } -} diff --git a/components/spin/src/mutex/ticket.rs b/components/spin/src/mutex/ticket.rs deleted file mode 100644 index 534485df8d..0000000000 --- a/components/spin/src/mutex/ticket.rs +++ /dev/null @@ -1,551 +0,0 @@ -//! A ticket-based mutex. -//! -//! Waiting threads take a 'ticket' from the lock in the order they arrive and gain access to the lock when their -//! ticket is next in the queue. Best-case latency is slightly worse than a regular spinning mutex, but worse-case -//! latency is infinitely better. Waiting threads simply need to wait for all threads that come before them in the -//! queue to finish. - -use core::{ - cell::UnsafeCell, - fmt, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; - -use crate::{ - RelaxStrategy, Spin, - atomic::{AtomicUsize, Ordering}, -}; - -/// A spin-based [ticket lock](https://en.wikipedia.org/wiki/Ticket_lock) providing mutually exclusive access to data. -/// -/// A ticket lock is analogous to a queue management system for lock requests. When a thread tries to take a lock, it -/// is assigned a 'ticket'. It then spins until its ticket becomes next in line. When the lock guard is released, the -/// next ticket will be processed. -/// -/// Ticket locks significantly reduce the worse-case performance of locking at the cost of slightly higher average-time -/// overhead. -/// -/// # Example -/// -/// ``` -/// use spin; -/// -/// let lock = spin::mutex::TicketMutex::<_>::new(0); -/// -/// // Modify the data -/// *lock.lock() = 2; -/// -/// // Read the data -/// let answer = *lock.lock(); -/// assert_eq!(answer, 2); -/// ``` -/// -/// # Thread safety example -/// -/// ``` -/// use std::sync::{Arc, Barrier}; -/// -/// use spin; -/// -/// let thread_count = 1000; -/// let spin_mutex = Arc::new(spin::mutex::TicketMutex::<_>::new(0)); -/// -/// // We use a barrier to ensure the readout happens after all writing -/// let barrier = Arc::new(Barrier::new(thread_count + 1)); -/// -/// for _ in (0..thread_count) { -/// let my_barrier = barrier.clone(); -/// let my_lock = spin_mutex.clone(); -/// std::thread::spawn(move || { -/// let mut guard = my_lock.lock(); -/// *guard += 1; -/// -/// // Release the lock to prevent a deadlock -/// drop(guard); -/// my_barrier.wait(); -/// }); -/// } -/// -/// barrier.wait(); -/// -/// let answer = { *spin_mutex.lock() }; -/// assert_eq!(answer, thread_count); -/// ``` -pub struct TicketMutex { - phantom: PhantomData, - next_ticket: AtomicUsize, - next_serving: AtomicUsize, - data: UnsafeCell, -} - -/// A guard that protects some data. -/// -/// When the guard is dropped, the next ticket will be processed. -pub struct TicketMutexGuard<'a, T: ?Sized + 'a> { - next_serving: &'a AtomicUsize, - ticket: usize, - data: &'a mut T, -} - -unsafe impl Sync for TicketMutex {} -unsafe impl Send for TicketMutex {} - -impl TicketMutex { - /// Creates a new [`TicketMutex`] wrapping the supplied data. - /// - /// # Example - /// - /// ``` - /// use spin::mutex::TicketMutex; - /// - /// static MUTEX: TicketMutex<()> = TicketMutex::<_>::new(()); - /// - /// fn demo() { - /// let lock = MUTEX.lock(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - #[inline(always)] - pub const fn new(data: T) -> Self { - Self { - phantom: PhantomData, - next_ticket: AtomicUsize::new(0), - next_serving: AtomicUsize::new(0), - data: UnsafeCell::new(data), - } - } - - /// Consumes this [`TicketMutex`] and unwraps the underlying data. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::TicketMutex::<_>::new(42); - /// assert_eq!(42, lock.into_inner()); - /// ``` - #[inline(always)] - pub fn into_inner(self) -> T { - self.data.into_inner() - } - /// Returns a mutable pointer to the underying data. - /// - /// This is mostly meant to be used for applications which require manual unlocking, but where - /// storing both the lock and the pointer to the inner data gets inefficient. - /// - /// # Example - /// ``` - /// let lock = spin::mutex::SpinMutex::<_>::new(42); - /// - /// unsafe { - /// core::mem::forget(lock.lock()); - /// - /// assert_eq!(lock.as_mut_ptr().read(), 42); - /// lock.as_mut_ptr().write(58); - /// - /// lock.force_unlock(); - /// } - /// - /// assert_eq!(*lock.lock(), 58); - /// ``` - #[inline(always)] - pub fn as_mut_ptr(&self) -> *mut T { - self.data.get() - } -} - -impl fmt::Debug for TicketMutex { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try_lock() { - Some(guard) => write!(f, "Mutex {{ data: ") - .and_then(|()| (*guard).fmt(f)) - .and_then(|()| write!(f, " }}")), - None => write!(f, "Mutex {{ }}"), - } - } -} - -impl TicketMutex { - /// Locks the [`TicketMutex`] and returns a guard that permits access to the inner data. - /// - /// The returned data may be dereferenced for data access - /// and the lock will be dropped when the guard falls out of scope. - /// - /// ``` - /// let lock = spin::mutex::TicketMutex::<_>::new(0); - /// { - /// let mut data = lock.lock(); - /// // The lock is now locked and the data can be accessed - /// *data += 1; - /// // The lock is implicitly dropped at the end of the scope - /// } - /// ``` - #[inline(always)] - pub fn lock(&self) -> TicketMutexGuard<'_, T> { - let ticket = self.next_ticket.fetch_add(1, Ordering::Relaxed); - - while self.next_serving.load(Ordering::Acquire) != ticket { - R::relax(); - } - - TicketMutexGuard { - next_serving: &self.next_serving, - ticket, - // Safety - // We know that we are the next ticket to be served, - // so there's no other thread accessing the data. - // - // Every other thread has another ticket number so it's - // definitely stuck in the spin loop above. - data: unsafe { &mut *self.data.get() }, - } - } -} - -impl TicketMutex { - /// Returns `true` if the lock is currently held. - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - #[inline(always)] - pub fn is_locked(&self) -> bool { - let ticket = self.next_ticket.load(Ordering::Relaxed); - self.next_serving.load(Ordering::Relaxed) != ticket - } - - /// Force unlock this [`TicketMutex`], by serving the next ticket. - /// - /// # Safety - /// - /// This is *extremely* unsafe if the lock is not held by the current - /// thread. However, this can be useful in some instances for exposing the - /// lock to FFI that doesn't know how to deal with RAII. - #[inline(always)] - pub unsafe fn force_unlock(&self) { - self.next_serving.fetch_add(1, Ordering::Release); - } - - /// Try to lock this [`TicketMutex`], returning a lock guard if successful. - /// - /// # Example - /// - /// ``` - /// let lock = spin::mutex::TicketMutex::<_>::new(42); - /// - /// let maybe_guard = lock.try_lock(); - /// assert!(maybe_guard.is_some()); - /// - /// // `maybe_guard` is still held, so the second call fails - /// let maybe_guard2 = lock.try_lock(); - /// assert!(maybe_guard2.is_none()); - /// ``` - #[inline(always)] - pub fn try_lock(&self) -> Option> { - // TODO: Replace with `fetch_update` to avoid manual CAS when upgrading MSRV - let ticket = { - let mut prev = self.next_ticket.load(Ordering::SeqCst); - loop { - if self.next_serving.load(Ordering::Acquire) == prev { - match self.next_ticket.compare_exchange_weak( - prev, - prev + 1, - Ordering::SeqCst, - Ordering::SeqCst, - ) { - Ok(x) => break Some(x), - Err(next_prev) => prev = next_prev, - } - } else { - break None; - } - } - }; - - ticket.map(|ticket| TicketMutexGuard { - next_serving: &self.next_serving, - ticket, - // Safety - // We have a ticket that is equal to the next_serving ticket, so we know: - // - that no other thread can have the same ticket id as this thread - // - that we are the next one to be served so we have exclusive access to the data - data: unsafe { &mut *self.data.get() }, - }) - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the [`TicketMutex`] mutably, and a mutable reference is guaranteed to be exclusive in - /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As - /// such, this is a 'zero-cost' operation. - /// - /// # Example - /// - /// ``` - /// let mut lock = spin::mutex::TicketMutex::<_>::new(0); - /// *lock.get_mut() = 10; - /// assert_eq!(*lock.lock(), 10); - /// ``` - #[inline(always)] - pub fn get_mut(&mut self) -> &mut T { - // Safety: - // We know that there are no other references to `self`, - // so it's safe to return a exclusive reference to the data. - unsafe { &mut *self.data.get() } - } -} - -impl Default for TicketMutex { - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl From for TicketMutex { - fn from(data: T) -> Self { - Self::new(data) - } -} - -impl<'a, T: ?Sized> TicketMutexGuard<'a, T> { - /// Leak the lock guard, yielding a mutable reference to the underlying data. - /// - /// Note that this function will permanently lock the original [`TicketMutex`]. - /// - /// ``` - /// let mylock = spin::mutex::TicketMutex::<_>::new(0); - /// - /// let data: &mut i32 = spin::mutex::TicketMutexGuard::leak(mylock.lock()); - /// - /// *data = 1; - /// assert_eq!(*data, 1); - /// ``` - #[inline(always)] - pub fn leak(this: Self) -> &'a mut T { - let data = this.data as *mut _; // Keep it in pointer form temporarily to avoid double-aliasing - core::mem::forget(this); - unsafe { &mut *data } - } -} - -impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for TicketMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized + fmt::Display> fmt::Display for TicketMutexGuard<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'a, T: ?Sized> Deref for TicketMutexGuard<'a, T> { - type Target = T; - fn deref(&self) -> &T { - self.data - } -} - -impl<'a, T: ?Sized> DerefMut for TicketMutexGuard<'a, T> { - fn deref_mut(&mut self) -> &mut T { - self.data - } -} - -impl<'a, T: ?Sized> Drop for TicketMutexGuard<'a, T> { - fn drop(&mut self) { - let new_ticket = self.ticket + 1; - self.next_serving.store(new_ticket, Ordering::Release); - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawMutex for TicketMutex<(), R> { - type GuardMarker = lock_api_crate::GuardSend; - - const INIT: Self = Self::new(()); - - fn lock(&self) { - // Prevent guard destructor running - core::mem::forget(Self::lock(self)); - } - - fn try_lock(&self) -> bool { - // Prevent guard destructor running - Self::try_lock(self).map(core::mem::forget).is_some() - } - - unsafe fn unlock(&self) { - self.force_unlock(); - } - - fn is_locked(&self) -> bool { - Self::is_locked(self) - } -} - -#[cfg(test)] -mod tests { - use std::{ - prelude::v1::*, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - mpsc::channel, - }, - thread, - }; - - type TicketMutex = super::TicketMutex; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn smoke() { - let m = TicketMutex::<_>::new(()); - drop(m.lock()); - drop(m.lock()); - } - - #[test] - fn lots_and_lots() { - static M: TicketMutex<()> = TicketMutex::<_>::new(()); - static mut CNT: u32 = 0; - const J: u32 = 1000; - const K: u32 = 3; - - fn inc() { - for _ in 0..J { - unsafe { - let _g = M.lock(); - CNT += 1; - } - } - } - - let (tx, rx) = channel(); - for _ in 0..K { - let tx2 = tx.clone(); - thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - }); - let tx2 = tx.clone(); - thread::spawn(move || { - inc(); - tx2.send(()).unwrap(); - }); - } - - drop(tx); - for _ in 0..2 * K { - rx.recv().unwrap(); - } - assert_eq!(unsafe { CNT }, J * K * 2); - } - - #[test] - fn try_lock() { - let mutex = TicketMutex::<_>::new(42); - - // First lock succeeds - let a = mutex.try_lock(); - assert_eq!(a.as_ref().map(|r| **r), Some(42)); - - // Additional lock fails - let b = mutex.try_lock(); - assert!(b.is_none()); - - // After dropping lock, it succeeds again - ::core::mem::drop(a); - let c = mutex.try_lock(); - assert_eq!(c.as_ref().map(|r| **r), Some(42)); - } - - #[test] - fn test_into_inner() { - let m = TicketMutex::<_>::new(NonCopy(10)); - assert_eq!(m.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct Foo(Arc); - impl Drop for Foo { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let num_drops = Arc::new(AtomicUsize::new(0)); - let m = TicketMutex::<_>::new(Foo(num_drops.clone())); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - { - let _inner = m.into_inner(); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - } - assert_eq!(num_drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_mutex_arc_nested() { - // Tests nested mutexes and access - // to underlying data. - let arc = Arc::new(TicketMutex::<_>::new(1)); - let arc2 = Arc::new(TicketMutex::<_>::new(arc)); - let (tx, rx) = channel(); - let _t = thread::spawn(move || { - let lock = arc2.lock(); - let lock2 = lock.lock(); - assert_eq!(*lock2, 1); - tx.send(()).unwrap(); - }); - rx.recv().unwrap(); - } - - #[test] - fn test_mutex_arc_access_in_unwind() { - let arc = Arc::new(TicketMutex::<_>::new(1)); - let arc2 = arc.clone(); - let _ = thread::spawn(move || -> () { - struct Unwinder { - i: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - *self.i.lock() += 1; - } - } - let _u = Unwinder { i: arc2 }; - panic!(); - }) - .join(); - let lock = arc.lock(); - assert_eq!(*lock, 2); - } - - #[test] - fn test_mutex_unsized() { - let mutex: &TicketMutex<[i32]> = &TicketMutex::<_>::new([1, 2, 3]); - { - let b = &mut *mutex.lock(); - b[0] = 4; - b[2] = 5; - } - let comp: &[i32] = &[4, 2, 5]; - assert_eq!(&*mutex.lock(), comp); - } - - #[test] - fn is_locked() { - let mutex = TicketMutex::<_>::new(()); - assert!(!mutex.is_locked()); - let lock = mutex.lock(); - assert!(mutex.is_locked()); - drop(lock); - assert!(!mutex.is_locked()); - } -} diff --git a/components/spin/src/once.rs b/components/spin/src/once.rs deleted file mode 100644 index 4c54b7e12d..0000000000 --- a/components/spin/src/once.rs +++ /dev/null @@ -1,795 +0,0 @@ -//! Synchronization primitives for one-time evaluation. - -use core::{cell::UnsafeCell, fmt, marker::PhantomData, mem::MaybeUninit}; - -use crate::{ - RelaxStrategy, Spin, - atomic::{AtomicU8, Ordering}, -}; - -/// A primitive that provides lazy one-time initialization. -/// -/// Unlike its `std::sync` equivalent, this is generalized such that the closure returns a -/// value to be stored by the [`Once`] (`std::sync::Once` can be trivially emulated with -/// `Once`). -/// -/// Because [`Once::new`] is `const`, this primitive may be used to safely initialize statics. -/// -/// # Examples -/// -/// ``` -/// use spin; -/// -/// static START: spin::Once = spin::Once::new(); -/// -/// START.call_once(|| { -/// // run initialization here -/// }); -/// ``` -pub struct Once { - phantom: PhantomData, - status: AtomicStatus, - data: UnsafeCell>, -} - -impl Default for Once { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for Once { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut d = f.debug_tuple("Once"); - let d = if let Some(x) = self.get() { - d.field(&x) - } else { - d.field(&format_args!("")) - }; - d.finish() - } -} - -// Same unsafe impls as `std::sync::RwLock`, because this also allows for -// concurrent reads. -unsafe impl Sync for Once {} -unsafe impl Send for Once {} - -mod status { - use super::*; - - // SAFETY: This structure has an invariant, namely that the inner atomic u8 must *always* have - // a value for which there exists a valid Status. This means that users of this API must only - // be allowed to load and store `Status`es. - #[repr(transparent)] - pub struct AtomicStatus(AtomicU8); - - // Four states that a Once can be in, encoded into the lower bits of `status` in - // the Once structure. - #[repr(u8)] - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum Status { - Incomplete = 0x00, - Running = 0x01, - Complete = 0x02, - Panicked = 0x03, - } - impl Status { - // Construct a status from an inner u8 integer. - // - // # Safety - // - // For this to be safe, the inner number must have a valid corresponding enum variant. - unsafe fn new_unchecked(inner: u8) -> Self { - core::mem::transmute(inner) - } - } - - impl AtomicStatus { - #[inline(always)] - pub const fn new(status: Status) -> Self { - // SAFETY: We got the value directly from status, so transmuting back is fine. - Self(AtomicU8::new(status as u8)) - } - #[inline(always)] - pub fn load(&self, ordering: Ordering) -> Status { - // SAFETY: We know that the inner integer must have been constructed from a Status in - // the first place. - unsafe { Status::new_unchecked(self.0.load(ordering)) } - } - #[inline(always)] - pub fn store(&self, status: Status, ordering: Ordering) { - // SAFETY: While not directly unsafe, this is safe because the value was retrieved from - // a status, thus making transmutation safe. - self.0.store(status as u8, ordering); - } - #[inline(always)] - pub fn compare_exchange( - &self, - old: Status, - new: Status, - success: Ordering, - failure: Ordering, - ) -> Result { - match self - .0 - .compare_exchange(old as u8, new as u8, success, failure) - { - // SAFETY: A compare exchange will always return a value that was later stored into - // the atomic u8, but due to the invariant that it must be a valid Status, we know - // that both Ok(_) and Err(_) will be safely transmutable. - Ok(ok) => Ok(unsafe { Status::new_unchecked(ok) }), - Err(err) => Err(unsafe { Status::new_unchecked(err) }), - } - } - #[inline(always)] - pub fn get_mut(&mut self) -> &mut Status { - // SAFETY: Since we know that the u8 inside must be a valid Status, we can safely cast - // it to a &mut Status. - unsafe { &mut *((self.0.get_mut() as *mut u8).cast::()) } - } - } -} -use self::status::{AtomicStatus, Status}; - -impl Once { - /// Performs an initialization routine once and only once. The given closure - /// will be executed if this is the first time `call_once` has been called, - /// and otherwise the routine will *not* be invoked. - /// - /// This method will block the calling thread if another initialization - /// routine is currently running. - /// - /// When this function returns, it is guaranteed that some initialization - /// has run and completed (it may not be the closure specified). The - /// returned pointer will point to the result from the closure that was - /// run. - /// - /// # Panics - /// - /// This function will panic if the [`Once`] previously panicked while attempting - /// to initialize. This is similar to the poisoning behaviour of `std::sync`'s - /// primitives. - /// - /// # Examples - /// - /// ``` - /// use spin; - /// - /// static INIT: spin::Once = spin::Once::new(); - /// - /// fn get_cached_val() -> usize { - /// *INIT.call_once(expensive_computation) - /// } - /// - /// fn expensive_computation() -> usize { - /// // ... - /// # 2 - /// } - /// ``` - pub fn call_once T>(&self, f: F) -> &T { - match self.try_call_once(|| Ok::(f())) { - Ok(x) => x, - Err(void) => match void {}, - } - } - - /// This method is similar to `call_once`, but allows the given closure to - /// fail, and lets the `Once` in a uninitialized state if it does. - /// - /// This method will block the calling thread if another initialization - /// routine is currently running. - /// - /// When this function returns without error, it is guaranteed that some - /// initialization has run and completed (it may not be the closure - /// specified). The returned reference will point to the result from the - /// closure that was run. - /// - /// # Panics - /// - /// This function will panic if the [`Once`] previously panicked while attempting - /// to initialize. This is similar to the poisoning behaviour of `std::sync`'s - /// primitives. - /// - /// # Examples - /// - /// ``` - /// use spin; - /// - /// static INIT: spin::Once = spin::Once::new(); - /// - /// fn get_cached_val() -> Result { - /// INIT.try_call_once(expensive_fallible_computation) - /// .map(|x| *x) - /// } - /// - /// fn expensive_fallible_computation() -> Result { - /// // ... - /// # Ok(2) - /// } - /// ``` - pub fn try_call_once Result, E>(&self, f: F) -> Result<&T, E> { - if let Some(value) = self.get() { - Ok(value) - } else { - self.try_call_once_slow(f) - } - } - - #[cold] - fn try_call_once_slow Result, E>(&self, f: F) -> Result<&T, E> { - loop { - let xchg = self.status.compare_exchange( - Status::Incomplete, - Status::Running, - Ordering::Acquire, - Ordering::Acquire, - ); - - match xchg { - Ok(_must_be_state_incomplete) => { - // Impl is defined after the match for readability - } - Err(Status::Panicked) => panic!("Once panicked"), - Err(Status::Running) => match self.poll() { - Some(v) => return Ok(v), - None => continue, - }, - Err(Status::Complete) => { - return Ok(unsafe { - // SAFETY: The status is Complete - self.force_get() - }); - } - Err(Status::Incomplete) => { - // The compare_exchange failed, so this shouldn't ever be reached, - // however if we decide to switch to compare_exchange_weak it will - // be safer to leave this here than hit an unreachable - continue; - } - } - - // The compare-exchange succeeded, so we shall initialize it. - - // We use a guard (Finish) to catch panics caused by builder - let finish = Finish { - status: &self.status, - }; - let val = match f() { - Ok(val) => val, - Err(err) => { - // If an error occurs, clean up everything and leave. - core::mem::forget(finish); - self.status.store(Status::Incomplete, Ordering::Release); - return Err(err); - } - }; - unsafe { - // SAFETY: - // `UnsafeCell`/deref: currently the only accessor, mutably - // and immutably by cas exclusion. - // `write`: pointer comes from `MaybeUninit`. - (*self.data.get()).as_mut_ptr().write(val); - }; - // If there were to be a panic with unwind enabled, the code would - // short-circuit and never reach the point where it writes the inner data. - // The destructor for Finish will run, and poison the Once to ensure that other - // threads accessing it do not exhibit unwanted behavior, if there were to be - // any inconsistency in data structures caused by the panicking thread. - // - // However, f() is expected in the general case not to panic. In that case, we - // simply forget the guard, bypassing its destructor. We could theoretically - // clear a flag instead, but this eliminates the call to the destructor at - // compile time, and unconditionally poisons during an eventual panic, if - // unwinding is enabled. - core::mem::forget(finish); - - // SAFETY: Release is required here, so that all memory accesses done in the - // closure when initializing, become visible to other threads that perform Acquire - // loads. - // - // And, we also know that the changes this thread has done will not magically - // disappear from our cache, so it does not need to be AcqRel. - self.status.store(Status::Complete, Ordering::Release); - - // This next line is mainly an optimization. - return unsafe { Ok(self.force_get()) }; - } - } - - /// Spins until the [`Once`] contains a value. - /// - /// Note that in releases prior to `0.7`, this function had the behaviour of [`Once::poll`]. - /// - /// # Panics - /// - /// This function will panic if the [`Once`] previously panicked while attempting - /// to initialize. This is similar to the poisoning behaviour of `std::sync`'s - /// primitives. - pub fn wait(&self) -> &T { - loop { - match self.poll() { - Some(x) => break x, - None => R::relax(), - } - } - } - - /// Like [`Once::get`], but will spin if the [`Once`] is in the process of being - /// initialized. If initialization has not even begun, `None` will be returned. - /// - /// Note that in releases prior to `0.7`, this function was named `wait`. - /// - /// # Panics - /// - /// This function will panic if the [`Once`] previously panicked while attempting - /// to initialize. This is similar to the poisoning behaviour of `std::sync`'s - /// primitives. - pub fn poll(&self) -> Option<&T> { - loop { - // SAFETY: Acquire is safe here, because if the status is COMPLETE, then we want to make - // sure that all memory accessed done while initializing that value, are visible when - // we return a reference to the inner data after this load. - match self.status.load(Ordering::Acquire) { - Status::Incomplete => return None, - Status::Running => R::relax(), // We spin - Status::Complete => return Some(unsafe { self.force_get() }), - Status::Panicked => panic!("Once previously poisoned by a panicked"), - } - } - } -} - -impl Once { - /// Initialization constant of [`Once`]. - #[allow(clippy::declare_interior_mutable_const)] - pub const INIT: Self = Self { - phantom: PhantomData, - status: AtomicStatus::new(Status::Incomplete), - data: UnsafeCell::new(MaybeUninit::uninit()), - }; - - /// Creates a new [`Once`]. - pub const fn new() -> Self { - Self::INIT - } - - /// Creates a new initialized [`Once`]. - pub const fn initialized(data: T) -> Self { - Self { - phantom: PhantomData, - status: AtomicStatus::new(Status::Complete), - data: UnsafeCell::new(MaybeUninit::new(data)), - } - } - - /// Retrieve a pointer to the inner data. - /// - /// While this method itself is safe, accessing the pointer before the [`Once`] has been - /// initialized is UB, unless this method has already been written to from a pointer coming - /// from this method. - pub fn as_mut_ptr(&self) -> *mut T { - // SAFETY: - // * MaybeUninit always has exactly the same layout as T - self.data.get().cast::() - } - - /// Get a reference to the initialized instance. Must only be called once COMPLETE. - unsafe fn force_get(&self) -> &T { - // SAFETY: - // * `UnsafeCell`/inner deref: data never changes again - // * `MaybeUninit`/outer deref: data was initialized - &*(*self.data.get()).as_ptr() - } - - /// Get a reference to the initialized instance. Must only be called once COMPLETE. - unsafe fn force_get_mut(&mut self) -> &mut T { - // SAFETY: - // * `UnsafeCell`/inner deref: data never changes again - // * `MaybeUninit`/outer deref: data was initialized - &mut *(*self.data.get()).as_mut_ptr() - } - - /// Get a reference to the initialized instance. Must only be called once COMPLETE. - unsafe fn force_into_inner(self) -> T { - // SAFETY: - // * `UnsafeCell`/inner deref: data never changes again - // * `MaybeUninit`/outer deref: data was initialized - (*self.data.get()).as_ptr().read() - } - - /// Returns a reference to the inner value if the [`Once`] has been initialized. - pub fn get(&self) -> Option<&T> { - // SAFETY: Just as with `poll`, Acquire is safe here because we want to be able to see the - // nonatomic stores done when initializing, once we have loaded and checked the status. - match self.status.load(Ordering::Acquire) { - Status::Complete => Some(unsafe { self.force_get() }), - _ => None, - } - } - - /// Returns a reference to the inner value on the unchecked assumption that the [`Once`] has been initialized. - /// - /// # Safety - /// - /// This is *extremely* unsafe if the `Once` has not already been initialized because a reference to uninitialized - /// memory will be returned, immediately triggering undefined behaviour (even if the reference goes unused). - /// However, this can be useful in some instances for exposing the `Once` to FFI or when the overhead of atomically - /// checking initialization is unacceptable and the `Once` has already been initialized. - pub unsafe fn get_unchecked(&self) -> &T { - debug_assert_eq!( - self.status.load(Ordering::SeqCst), - Status::Complete, - "Attempted to access an uninitialized Once. If this was run without debug checks, \ - this would be undefined behaviour. This is a serious bug and you must fix it.", - ); - self.force_get() - } - - /// Returns a mutable reference to the inner value if the [`Once`] has been initialized. - /// - /// Because this method requires a mutable reference to the [`Once`], no synchronization - /// overhead is required to access the inner value. In effect, it is zero-cost. - pub fn get_mut(&mut self) -> Option<&mut T> { - match *self.status.get_mut() { - Status::Complete => Some(unsafe { self.force_get_mut() }), - _ => None, - } - } - - /// Returns a mutable reference to the inner value - /// - /// # Safety - /// - /// This is *extremely* unsafe if the `Once` has not already been initialized because a reference to uninitialized - /// memory will be returned, immediately triggering undefined behaviour (even if the reference goes unused). - /// However, this can be useful in some instances for exposing the `Once` to FFI or when the overhead of atomically - /// checking initialization is unacceptable and the `Once` has already been initialized. - pub unsafe fn get_mut_unchecked(&mut self) -> &mut T { - debug_assert_eq!( - self.status.load(Ordering::SeqCst), - Status::Complete, - "Attempted to access an unintialized Once. If this was to run without debug checks, \ - this would be undefined behavior. This is a serious bug and you must fix it.", - ); - self.force_get_mut() - } - - /// Returns a the inner value if the [`Once`] has been initialized. - /// - /// Because this method requires ownership of the [`Once`], no synchronization overhead - /// is required to access the inner value. In effect, it is zero-cost. - pub fn try_into_inner(mut self) -> Option { - match *self.status.get_mut() { - Status::Complete => Some(unsafe { self.force_into_inner() }), - _ => None, - } - } - - /// Returns a the inner value if the [`Once`] has been initialized. - /// # Safety - /// - /// This is *extremely* unsafe if the `Once` has not already been initialized because a reference to uninitialized - /// memory will be returned, immediately triggering undefined behaviour (even if the reference goes unused) - /// This can be useful, if `Once` has already been initialized, and you want to bypass an - /// option check. - pub unsafe fn into_inner_unchecked(self) -> T { - debug_assert_eq!( - self.status.load(Ordering::SeqCst), - Status::Complete, - "Attempted to access an unintialized Once. If this was to run without debug checks, \ - this would be undefined behavior. This is a serious bug and you must fix it.", - ); - self.force_into_inner() - } - - /// Checks whether the value has been initialized. - /// - /// This is done using [`Acquire`](core::sync::atomic::Ordering::Acquire) ordering, and - /// therefore it is safe to access the value directly via - /// [`get_unchecked`](Self::get_unchecked) if this returns true. - pub fn is_completed(&self) -> bool { - // TODO: Add a similar variant for Relaxed? - self.status.load(Ordering::Acquire) == Status::Complete - } -} - -impl From for Once { - fn from(data: T) -> Self { - Self::initialized(data) - } -} - -impl Drop for Once { - fn drop(&mut self) { - // No need to do any atomic access here, we have &mut! - if *self.status.get_mut() == Status::Complete { - unsafe { - // TODO: Use MaybeUninit::assume_init_drop once stabilised - core::ptr::drop_in_place((*self.data.get()).as_mut_ptr()); - } - } - } -} - -struct Finish<'a> { - status: &'a AtomicStatus, -} - -impl<'a> Drop for Finish<'a> { - fn drop(&mut self) { - // While using Relaxed here would most likely not be an issue, we use SeqCst anyway. - // This is mainly because panics are not meant to be fast at all, but also because if - // there were to be a compiler bug which reorders accesses within the same thread, - // where it should not, we want to be sure that the panic really is handled, and does - // not cause additional problems. SeqCst will therefore help guarding against such - // bugs. - self.status.store(Status::Panicked, Ordering::SeqCst); - } -} - -#[cfg(test)] -mod tests { - use std::{ - prelude::v1::*, - sync::{Arc, atomic::AtomicU32, mpsc::channel}, - thread, - }; - - use super::*; - - #[test] - fn smoke_once() { - static O: Once = Once::new(); - let mut a = 0; - O.call_once(|| a += 1); - assert_eq!(a, 1); - O.call_once(|| a += 1); - assert_eq!(a, 1); - } - - #[test] - fn smoke_once_value() { - static O: Once = Once::new(); - let a = O.call_once(|| 1); - assert_eq!(*a, 1); - let b = O.call_once(|| 2); - assert_eq!(*b, 1); - } - - #[test] - fn stampede_once() { - static O: Once = Once::new(); - static mut RUN: bool = false; - - let (tx, rx) = channel(); - let mut ts = Vec::new(); - for _ in 0..10 { - let tx = tx.clone(); - ts.push(thread::spawn(move || { - for _ in 0..4 { - thread::yield_now() - } - unsafe { - O.call_once(|| { - assert!(!RUN); - RUN = true; - }); - assert!(RUN); - } - tx.send(()).unwrap(); - })); - } - - unsafe { - O.call_once(|| { - assert!(!RUN); - RUN = true; - }); - assert!(RUN); - } - - for _ in 0..10 { - rx.recv().unwrap(); - } - - for t in ts { - t.join().unwrap(); - } - } - - #[test] - fn get() { - static INIT: Once = Once::new(); - - assert!(INIT.get().is_none()); - INIT.call_once(|| 2); - assert_eq!(INIT.get().map(|r| *r), Some(2)); - } - - #[test] - fn get_no_wait() { - static INIT: Once = Once::new(); - - assert!(INIT.get().is_none()); - let t = thread::spawn(move || { - INIT.call_once(|| { - thread::sleep(std::time::Duration::from_secs(3)); - 42 - }); - }); - assert!(INIT.get().is_none()); - - t.join().unwrap(); - } - - #[test] - fn poll() { - static INIT: Once = Once::new(); - - assert!(INIT.poll().is_none()); - INIT.call_once(|| 3); - assert_eq!(INIT.poll().map(|r| *r), Some(3)); - } - - #[test] - fn wait() { - static INIT: Once = Once::new(); - - let t = std::thread::spawn(|| { - assert_eq!(*INIT.wait(), 3); - assert!(INIT.is_completed()); - }); - - for _ in 0..4 { - thread::yield_now() - } - - assert!(INIT.poll().is_none()); - INIT.call_once(|| 3); - - t.join().unwrap(); - } - - #[test] - fn panic() { - use std::panic; - - static INIT: Once = Once::new(); - - // poison the once - let t = panic::catch_unwind(|| { - INIT.call_once(|| panic!()); - }); - assert!(t.is_err()); - - // poisoning propagates - let t = panic::catch_unwind(|| { - INIT.call_once(|| {}); - }); - assert!(t.is_err()); - } - - #[test] - fn init_constant() { - static O: Once = Once::INIT; - let mut a = 0; - O.call_once(|| a += 1); - assert_eq!(a, 1); - O.call_once(|| a += 1); - assert_eq!(a, 1); - } - - static mut CALLED: bool = false; - - struct DropTest {} - - impl Drop for DropTest { - fn drop(&mut self) { - unsafe { - CALLED = true; - } - } - } - - #[test] - fn try_call_once_err() { - let once = Once::<_, Spin>::new(); - let shared = Arc::new((once, AtomicU32::new(0))); - - let (tx, rx) = channel(); - - let t0 = { - let shared = shared.clone(); - thread::spawn(move || { - let (once, called) = &*shared; - - once.try_call_once(|| { - called.fetch_add(1, Ordering::AcqRel); - tx.send(()).unwrap(); - thread::sleep(std::time::Duration::from_millis(50)); - Err(()) - }) - .ok(); - }) - }; - - let t1 = { - let shared = shared.clone(); - thread::spawn(move || { - rx.recv().unwrap(); - let (once, called) = &*shared; - assert_eq!( - called.load(Ordering::Acquire), - 1, - "leader thread did not run first" - ); - - once.call_once(|| { - called.fetch_add(1, Ordering::AcqRel); - }); - }) - }; - - t0.join().unwrap(); - t1.join().unwrap(); - - assert_eq!(shared.1.load(Ordering::Acquire), 2); - } - - // This is sort of two test cases, but if we write them as separate test methods - // they can be executed concurrently and then fail some small fraction of the - // time. - #[test] - fn drop_occurs_and_skip_uninit_drop() { - unsafe { - CALLED = false; - } - - { - let once = Once::<_>::new(); - once.call_once(|| DropTest {}); - } - - assert!(unsafe { CALLED }); - // Now test that we skip drops for the uninitialized case. - unsafe { - CALLED = false; - } - - let once = Once::::new(); - drop(once); - - assert!(unsafe { !CALLED }); - } - - #[test] - fn call_once_test() { - for _ in 0..20 { - use std::{ - sync::{Arc, atomic::AtomicUsize}, - time::Duration, - }; - let share = Arc::new(AtomicUsize::new(0)); - let once = Arc::new(Once::<_, Spin>::new()); - let mut hs = Vec::new(); - for _ in 0..8 { - let h = thread::spawn({ - let share = share.clone(); - let once = once.clone(); - move || { - thread::sleep(Duration::from_millis(10)); - once.call_once(|| { - share.fetch_add(1, Ordering::SeqCst); - }); - } - }); - hs.push(h); - } - for h in hs { - h.join().unwrap(); - } - assert_eq!(1, share.load(Ordering::SeqCst)); - } - } -} diff --git a/components/spin/src/relax.rs b/components/spin/src/relax.rs deleted file mode 100644 index 8842f80bcd..0000000000 --- a/components/spin/src/relax.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Strategies that determine the behaviour of locks when encountering contention. - -/// A trait implemented by spinning relax strategies. -pub trait RelaxStrategy { - /// Perform the relaxing operation during a period of contention. - fn relax(); -} - -/// A strategy that rapidly spins while informing the CPU that it should power down non-essential components via -/// [`core::hint::spin_loop`]. -/// -/// Note that spinning is a 'dumb' strategy and most schedulers cannot correctly differentiate it from useful work, -/// thereby misallocating even more CPU time to the spinning process. This is known as -/// ['priority inversion'](https://matklad.github.io/2020/01/02/spinlocks-considered-harmful.html). -/// -/// If you see signs that priority inversion is occurring, consider switching to [`Yield`] or, even better, not using a -/// spinlock at all and opting for a proper scheduler-aware lock. Remember also that different targets, operating -/// systems, schedulers, and even the same scheduler with different workloads will exhibit different behaviour. Just -/// because priority inversion isn't occurring in your tests does not mean that it will not occur. Use a scheduler- -/// aware lock if at all possible. -pub struct Spin; - -impl RelaxStrategy for Spin { - #[inline(always)] - fn relax() { - // Use the deprecated spin_loop_hint() to ensure that we don't get - // a higher MSRV than we need to. - #[allow(deprecated)] - core::sync::atomic::spin_loop_hint(); - } -} - -/// A strategy that yields the current time slice to the scheduler in favour of other threads or processes. -/// -/// This is generally used as a strategy for minimising power consumption and priority inversion on targets that have a -/// standard library available. Note that such targets have scheduler-integrated concurrency primitives available, and -/// you should generally use these instead, except in rare circumstances. -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -pub struct Yield; - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl RelaxStrategy for Yield { - #[inline(always)] - fn relax() { - std::thread::yield_now(); - } -} - -/// A strategy that rapidly spins, without telling the CPU to do any powering down. -/// -/// You almost certainly do not want to use this. Use [`Spin`] instead. It exists for completeness and for targets -/// that, for some reason, miscompile or do not support spin hint intrinsics despite attempting to generate code for -/// them (i.e: this is a workaround for possible compiler bugs). -pub struct Loop; - -impl RelaxStrategy for Loop { - #[inline(always)] - fn relax() {} -} diff --git a/components/spin/src/rwlock.rs b/components/spin/src/rwlock.rs deleted file mode 100644 index 80504c257e..0000000000 --- a/components/spin/src/rwlock.rs +++ /dev/null @@ -1,1190 +0,0 @@ -//! A lock that provides data access to either one writer or many readers. - -use core::{ - cell::UnsafeCell, - fmt, - marker::PhantomData, - mem, - mem::ManuallyDrop, - ops::{Deref, DerefMut}, -}; - -use crate::{ - RelaxStrategy, Spin, - atomic::{AtomicUsize, Ordering}, -}; - -/// A lock that provides data access to either one writer or many readers. -/// -/// This lock behaves in a similar manner to its namesake `std::sync::RwLock` but uses -/// spinning for synchronisation instead. Unlike its namespace, this lock does not -/// track lock poisoning. -/// -/// This type of lock allows a number of readers or at most one writer at any -/// point in time. The write portion of this lock typically allows modification -/// of the underlying data (exclusive access) and the read portion of this lock -/// typically allows for read-only access (shared access). -/// -/// The type parameter `T` represents the data that this lock protects. It is -/// required that `T` satisfies `Send` to be shared across tasks and `Sync` to -/// allow concurrent access through readers. The RAII guards returned from the -/// locking methods implement `Deref` (and `DerefMut` for the `write` methods) -/// to allow access to the contained of the lock. -/// -/// An [`RwLockUpgradableGuard`](RwLockUpgradableGuard) can be upgraded to a -/// writable guard through the [`RwLockUpgradableGuard::upgrade`](RwLockUpgradableGuard::upgrade) -/// [`RwLockUpgradableGuard::try_upgrade`](RwLockUpgradableGuard::try_upgrade) functions. -/// Writable or upgradeable guards can be downgraded through their respective `downgrade` -/// functions. -/// -/// Based on Facebook's -/// [`folly/RWSpinLock.h`](https://github.com/facebook/folly/blob/a0394d84f2d5c3e50ebfd0566f9d3acb52cfab5a/folly/synchronization/RWSpinLock.h). -/// This implementation is unfair to writers - if the lock always has readers, then no writers will -/// ever get a chance. Using an upgradeable lock guard can *somewhat* alleviate this issue as no -/// new readers are allowed when an upgradeable guard is held, but upgradeable guards can be taken -/// when there are existing readers. However if the lock is that highly contended and writes are -/// crucial then this implementation may be a poor choice. -/// -/// # Examples -/// -/// ``` -/// use spin; -/// -/// let lock = spin::RwLock::new(5); -/// -/// // many reader locks can be held at once -/// { -/// let r1 = lock.read(); -/// let r2 = lock.read(); -/// assert_eq!(*r1, 5); -/// assert_eq!(*r2, 5); -/// } // read locks are dropped at this point -/// -/// // only one write lock may be held, however -/// { -/// let mut w = lock.write(); -/// *w += 1; -/// assert_eq!(*w, 6); -/// } // write lock is dropped here -/// ``` -pub struct RwLock { - phantom: PhantomData, - lock: AtomicUsize, - data: UnsafeCell, -} - -const READER: usize = 1 << 2; -const UPGRADED: usize = 1 << 1; -const WRITER: usize = 1; - -/// A guard that provides immutable data access. -/// -/// When the guard falls out of scope it will decrement the read count, -/// potentially releasing the lock. -pub struct RwLockReadGuard<'a, T: 'a + ?Sized> { - lock: &'a AtomicUsize, - data: *const T, -} - -/// A guard that provides mutable data access. -/// -/// When the guard falls out of scope it will release the lock. -pub struct RwLockWriteGuard<'a, T: 'a + ?Sized, R = Spin> { - phantom: PhantomData, - inner: &'a RwLock, - data: *mut T, -} - -/// A guard that provides immutable data access but can be upgraded to [`RwLockWriteGuard`]. -/// -/// No writers or other upgradeable guards can exist while this is in scope. New reader -/// creation is prevented (to alleviate writer starvation) but there may be existing readers -/// when the lock is acquired. -/// -/// When the guard falls out of scope it will release the lock. -pub struct RwLockUpgradableGuard<'a, T: 'a + ?Sized, R = Spin> { - phantom: PhantomData, - inner: &'a RwLock, - data: *const T, -} - -// Same unsafe impls as `std::sync::RwLock` -unsafe impl Send for RwLock {} -unsafe impl Sync for RwLock {} - -unsafe impl Send for RwLockWriteGuard<'_, T, R> {} -unsafe impl Sync for RwLockWriteGuard<'_, T, R> {} - -unsafe impl Send for RwLockReadGuard<'_, T> {} -unsafe impl Sync for RwLockReadGuard<'_, T> {} - -unsafe impl Send for RwLockUpgradableGuard<'_, T, R> {} -unsafe impl Sync for RwLockUpgradableGuard<'_, T, R> {} - -impl RwLock { - /// Creates a new spinlock wrapping the supplied data. - /// - /// May be used statically: - /// - /// ``` - /// use spin; - /// - /// static RW_LOCK: spin::RwLock<()> = spin::RwLock::new(()); - /// - /// fn demo() { - /// let lock = RW_LOCK.read(); - /// // do something with lock - /// drop(lock); - /// } - /// ``` - #[inline] - pub const fn new(data: T) -> Self { - RwLock { - phantom: PhantomData, - lock: AtomicUsize::new(0), - data: UnsafeCell::new(data), - } - } - - /// Consumes this `RwLock`, returning the underlying data. - #[inline] - pub fn into_inner(self) -> T { - // We know statically that there are no outstanding references to - // `self` so there's no need to lock. - let RwLock { data, .. } = self; - data.into_inner() - } - /// Returns a mutable pointer to the underying data. - /// - /// This is mostly meant to be used for applications which require manual unlocking, but where - /// storing both the lock and the pointer to the inner data gets inefficient. - /// - /// While this is safe, writing to the data is undefined behavior unless the current thread has - /// acquired a write lock, and reading requires either a read or write lock. - /// - /// # Example - /// ``` - /// let lock = spin::RwLock::new(42); - /// - /// unsafe { - /// core::mem::forget(lock.write()); - /// - /// assert_eq!(lock.as_mut_ptr().read(), 42); - /// lock.as_mut_ptr().write(58); - /// - /// lock.force_write_unlock(); - /// } - /// - /// assert_eq!(*lock.read(), 58); - /// ``` - #[inline(always)] - pub fn as_mut_ptr(&self) -> *mut T { - self.data.get() - } -} - -impl RwLock { - /// Locks this rwlock with shared read access, blocking the current thread - /// until it can be acquired. - /// - /// The calling thread will be blocked until there are no more writers which - /// hold the lock. There may be other readers currently inside the lock when - /// this method returns. This method does not provide any guarantees with - /// respect to the ordering of whether contentious readers or writers will - /// acquire the lock first. - /// - /// Returns an RAII guard which will release this thread's shared access - /// once it is dropped. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// let mut data = mylock.read(); - /// // The lock is now locked and the data can be read - /// println!("{}", *data); - /// // The lock is dropped - /// } - /// ``` - #[inline] - pub fn read(&self) -> RwLockReadGuard<'_, T> { - loop { - match self.try_read() { - Some(guard) => return guard, - None => R::relax(), - } - } - } - - /// Lock this rwlock with exclusive write access, blocking the current - /// thread until it can be acquired. - /// - /// This function will not return while other writers or other readers - /// currently have access to the lock. - /// - /// Returns an RAII guard which will drop the write access of this rwlock - /// when dropped. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// let mut data = mylock.write(); - /// // The lock is now locked and the data can be written - /// *data += 1; - /// // The lock is dropped - /// } - /// ``` - #[inline] - pub fn write(&self) -> RwLockWriteGuard<'_, T, R> { - loop { - match self.try_write_internal(false) { - Some(guard) => return guard, - None => R::relax(), - } - } - } - - /// Obtain a readable lock guard that can later be upgraded to a writable lock guard. - /// Upgrades can be done through the [`RwLockUpgradableGuard::upgrade`](RwLockUpgradableGuard::upgrade) method. - #[inline] - pub fn upgradeable_read(&self) -> RwLockUpgradableGuard<'_, T, R> { - loop { - match self.try_upgradeable_read() { - Some(guard) => return guard, - None => R::relax(), - } - } - } -} - -impl RwLock { - // Acquire a read lock, returning the new lock value. - fn acquire_reader(&self) -> usize { - // An arbitrary cap that allows us to catch overflows long before they happen - const MAX_READERS: usize = usize::MAX / READER / 2; - - let value = self.lock.fetch_add(READER, Ordering::Acquire); - - if value > MAX_READERS * READER { - self.lock.fetch_sub(READER, Ordering::Relaxed); - panic!("Too many lock readers, cannot safely proceed"); - } else { - value - } - } - - /// Attempt to acquire this lock with shared read access. - /// - /// This function will never block and will return immediately if `read` - /// would otherwise succeed. Returns `Some` of an RAII guard which will - /// release the shared access of this thread when dropped, or `None` if the - /// access could not be granted. This method does not provide any - /// guarantees with respect to the ordering of whether contentious readers - /// or writers will acquire the lock first. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// match mylock.try_read() { - /// Some(data) => { - /// // The lock is now locked and the data can be read - /// println!("{}", *data); - /// // The lock is dropped - /// } - /// None => (), // no cigar - /// }; - /// } - /// ``` - #[inline] - pub fn try_read(&self) -> Option> { - let value = self.acquire_reader(); - - // We check the UPGRADED bit here so that new readers are prevented when an UPGRADED lock is held. - // This helps reduce writer starvation. - if value & (WRITER | UPGRADED) != 0 { - // Lock is taken, undo. - self.lock.fetch_sub(READER, Ordering::Release); - None - } else { - Some(RwLockReadGuard { - lock: &self.lock, - data: unsafe { &*self.data.get() }, - }) - } - } - - /// Return the number of readers that currently hold the lock (including upgradable readers). - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - pub fn reader_count(&self) -> usize { - let state = self.lock.load(Ordering::Relaxed); - state / READER + (state & UPGRADED) / UPGRADED - } - - /// Return the number of writers that currently hold the lock. - /// - /// Because [`RwLock`] guarantees exclusive mutable access, this function may only return either `0` or `1`. - /// - /// # Safety - /// - /// This function provides no synchronization guarantees and so its result should be considered 'out of date' - /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. - pub fn writer_count(&self) -> usize { - (self.lock.load(Ordering::Relaxed) & WRITER) / WRITER - } - - /// Force decrement the reader count. - /// - /// # Safety - /// - /// This is *extremely* unsafe if there are outstanding `RwLockReadGuard`s - /// live, or if called more times than `read` has been called, but can be - /// useful in FFI contexts where the caller doesn't know how to deal with - /// RAII. The underlying atomic operation uses `Ordering::Release`. - #[inline] - pub unsafe fn force_read_decrement(&self) { - debug_assert!(self.lock.load(Ordering::Relaxed) & !WRITER > 0); - self.lock.fetch_sub(READER, Ordering::Release); - } - - /// Force unlock exclusive write access. - /// - /// # Safety - /// - /// This is *extremely* unsafe if there are outstanding `RwLockWriteGuard`s - /// live, or if called when there are current readers, but can be useful in - /// FFI contexts where the caller doesn't know how to deal with RAII. The - /// underlying atomic operation uses `Ordering::Release`. - #[inline] - pub unsafe fn force_write_unlock(&self) { - debug_assert_eq!(self.lock.load(Ordering::Relaxed) & !(WRITER | UPGRADED), 0); - self.lock.fetch_and(!(WRITER | UPGRADED), Ordering::Release); - } - - #[inline(always)] - fn try_write_internal(&self, strong: bool) -> Option> { - if compare_exchange( - &self.lock, - 0, - WRITER, - Ordering::Acquire, - Ordering::Relaxed, - strong, - ) - .is_ok() - { - Some(RwLockWriteGuard { - phantom: PhantomData, - inner: self, - data: unsafe { &mut *self.data.get() }, - }) - } else { - None - } - } - - /// Attempt to lock this rwlock with exclusive write access. - /// - /// This function does not ever block, and it will return `None` if a call - /// to `write` would otherwise block. If successful, an RAII guard is - /// returned. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// { - /// match mylock.try_write() { - /// Some(mut data) => { - /// // The lock is now locked and the data can be written - /// *data += 1; - /// // The lock is implicitly dropped - /// } - /// None => (), // no cigar - /// }; - /// } - /// ``` - #[inline] - pub fn try_write(&self) -> Option> { - self.try_write_internal(true) - } - - /// Attempt to lock this rwlock with exclusive write access. - /// - /// Unlike [`RwLock::try_write`], this function is allowed to spuriously fail even when acquiring exclusive write access - /// would otherwise succeed, which can result in more efficient code on some platforms. - #[inline] - pub fn try_write_weak(&self) -> Option> { - self.try_write_internal(false) - } - - /// Tries to obtain an upgradeable lock guard. - #[inline] - pub fn try_upgradeable_read(&self) -> Option> { - if self.lock.fetch_or(UPGRADED, Ordering::Acquire) & (WRITER | UPGRADED) == 0 { - Some(RwLockUpgradableGuard { - phantom: PhantomData, - inner: self, - data: unsafe { &*self.data.get() }, - }) - } else { - // We can't unflip the UPGRADED bit back just yet as there is another upgradeable or write lock. - // When they unlock, they will clear the bit. - None - } - } - - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the `RwLock` mutably, no actual locking needs to - /// take place -- the mutable borrow statically guarantees no locks exist. - /// - /// # Examples - /// - /// ``` - /// let mut lock = spin::RwLock::new(0); - /// *lock.get_mut() = 10; - /// assert_eq!(*lock.read(), 10); - /// ``` - pub fn get_mut(&mut self) -> &mut T { - // We know statically that there are no other references to `self`, so - // there's no need to lock the inner lock. - unsafe { &mut *self.data.get() } - } -} - -impl fmt::Debug for RwLock { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.try_read() { - Some(guard) => write!(f, "RwLock {{ data: ") - .and_then(|()| (*guard).fmt(f)) - .and_then(|()| write!(f, " }}")), - None => write!(f, "RwLock {{ }}"), - } - } -} - -impl Default for RwLock { - fn default() -> Self { - Self::new(Default::default()) - } -} - -impl From for RwLock { - fn from(data: T) -> Self { - Self::new(data) - } -} - -impl<'rwlock, T: ?Sized> RwLockReadGuard<'rwlock, T> { - /// Leak the lock guard, yielding a reference to the underlying data. - /// - /// Note that this function will permanently lock the original lock for all but reading locks. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// - /// let data: &i32 = spin::RwLockReadGuard::leak(mylock.read()); - /// - /// assert_eq!(*data, 0); - /// ``` - #[inline] - pub fn leak(this: Self) -> &'rwlock T { - let this = ManuallyDrop::new(this); - // Safety: We know statically that only we are referencing data - unsafe { &*this.data } - } -} - -impl<'rwlock, T: ?Sized + fmt::Debug> fmt::Debug for RwLockReadGuard<'rwlock, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'rwlock, T: ?Sized + fmt::Display> fmt::Display for RwLockReadGuard<'rwlock, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'rwlock, T: ?Sized, R: RelaxStrategy> RwLockUpgradableGuard<'rwlock, T, R> { - /// Upgrades an upgradeable lock guard to a writable lock guard. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// - /// let upgradeable = mylock.upgradeable_read(); // Readable, but not yet writable - /// let writable = upgradeable.upgrade(); - /// ``` - #[inline] - pub fn upgrade(mut self) -> RwLockWriteGuard<'rwlock, T, R> { - loop { - self = match self.try_upgrade_internal(false) { - Ok(guard) => return guard, - Err(e) => e, - }; - - R::relax(); - } - } -} - -impl<'rwlock, T: ?Sized, R> RwLockUpgradableGuard<'rwlock, T, R> { - #[inline(always)] - fn try_upgrade_internal(self, strong: bool) -> Result, Self> { - if compare_exchange( - &self.inner.lock, - UPGRADED, - WRITER, - Ordering::Acquire, - Ordering::Relaxed, - strong, - ) - .is_ok() - { - let inner = self.inner; - - // Forget the old guard so its destructor doesn't run (before mutably aliasing data below) - mem::forget(self); - - // Upgrade successful - Ok(RwLockWriteGuard { - phantom: PhantomData, - inner, - data: unsafe { &mut *inner.data.get() }, - }) - } else { - Err(self) - } - } - - /// Tries to upgrade an upgradeable lock guard to a writable lock guard. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// let upgradeable = mylock.upgradeable_read(); // Readable, but not yet writable - /// - /// match upgradeable.try_upgrade() { - /// Ok(writable) => - /// // upgrade successful - use writable lock guard - /// { - /// () - /// } - /// Err(upgradeable) => - /// // upgrade unsuccessful - /// { - /// () - /// } - /// }; - /// ``` - #[inline] - pub fn try_upgrade(self) -> Result, Self> { - self.try_upgrade_internal(true) - } - - /// Tries to upgrade an upgradeable lock guard to a writable lock guard. - /// - /// Unlike [`RwLockUpgradableGuard::try_upgrade`], this function is allowed to spuriously fail even when upgrading - /// would otherwise succeed, which can result in more efficient code on some platforms. - #[inline] - pub fn try_upgrade_weak(self) -> Result, Self> { - self.try_upgrade_internal(false) - } - - #[inline] - /// Downgrades the upgradeable lock guard to a readable, shared lock guard. Cannot fail and is guaranteed not to spin. - /// - /// ``` - /// let mylock = spin::RwLock::new(1); - /// - /// let upgradeable = mylock.upgradeable_read(); - /// assert!(mylock.try_read().is_none()); - /// assert_eq!(*upgradeable, 1); - /// - /// let readable = upgradeable.downgrade(); // This is guaranteed not to spin - /// assert!(mylock.try_read().is_some()); - /// assert_eq!(*readable, 1); - /// ``` - pub fn downgrade(self) -> RwLockReadGuard<'rwlock, T> { - // Reserve the read guard for ourselves - self.inner.acquire_reader(); - - let inner = self.inner; - - // Dropping self removes the UPGRADED bit - mem::drop(self); - - RwLockReadGuard { - lock: &inner.lock, - data: unsafe { &*inner.data.get() }, - } - } - - /// Leak the lock guard, yielding a reference to the underlying data. - /// - /// Note that this function will permanently lock the original lock. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// - /// let data: &i32 = spin::RwLockUpgradableGuard::leak(mylock.upgradeable_read()); - /// - /// assert_eq!(*data, 0); - /// ``` - #[inline] - pub fn leak(this: Self) -> &'rwlock T { - let this = ManuallyDrop::new(this); - // Safety: We know statically that only we are referencing data - unsafe { &*this.data } - } -} - -impl<'rwlock, T: ?Sized + fmt::Debug, R> fmt::Debug for RwLockUpgradableGuard<'rwlock, T, R> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'rwlock, T: ?Sized + fmt::Display, R> fmt::Display for RwLockUpgradableGuard<'rwlock, T, R> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'rwlock, T: ?Sized, R> RwLockWriteGuard<'rwlock, T, R> { - /// Downgrades the writable lock guard to a readable, shared lock guard. Cannot fail and is guaranteed not to spin. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// - /// let mut writable = mylock.write(); - /// *writable = 1; - /// - /// let readable = writable.downgrade(); // This is guaranteed not to spin - /// // - /// # let readable_2 = mylock.try_read().unwrap(); - /// assert_eq!(*readable, 1); - /// ``` - #[inline] - pub fn downgrade(self) -> RwLockReadGuard<'rwlock, T> { - // Reserve the read guard for ourselves - self.inner.acquire_reader(); - - let inner = self.inner; - - // Dropping self removes the UPGRADED bit - mem::drop(self); - - RwLockReadGuard { - lock: &inner.lock, - data: unsafe { &*inner.data.get() }, - } - } - - /// Downgrades the writable lock guard to an upgradable, shared lock guard. Cannot fail and is guaranteed not to spin. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// - /// let mut writable = mylock.write(); - /// *writable = 1; - /// - /// let readable = writable.downgrade_to_upgradeable(); // This is guaranteed not to spin - /// assert_eq!(*readable, 1); - /// ``` - #[inline] - pub fn downgrade_to_upgradeable(self) -> RwLockUpgradableGuard<'rwlock, T, R> { - debug_assert_eq!( - self.inner.lock.load(Ordering::Acquire) & (WRITER | UPGRADED), - WRITER - ); - - // Reserve the read guard for ourselves - self.inner.lock.store(UPGRADED, Ordering::Release); - - let inner = self.inner; - - // Dropping self removes the UPGRADED bit - mem::forget(self); - - RwLockUpgradableGuard { - phantom: PhantomData, - inner, - data: unsafe { &*inner.data.get() }, - } - } - - /// Leak the lock guard, yielding a mutable reference to the underlying data. - /// - /// Note that this function will permanently lock the original lock. - /// - /// ``` - /// let mylock = spin::RwLock::new(0); - /// - /// let data: &mut i32 = spin::RwLockWriteGuard::leak(mylock.write()); - /// - /// *data = 1; - /// assert_eq!(*data, 1); - /// ``` - #[inline] - pub fn leak(this: Self) -> &'rwlock mut T { - let mut this = ManuallyDrop::new(this); - // Safety: We know statically that only we are referencing data - unsafe { &mut *this.data } - } -} - -impl<'rwlock, T: ?Sized + fmt::Debug, R> fmt::Debug for RwLockWriteGuard<'rwlock, T, R> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<'rwlock, T: ?Sized + fmt::Display, R> fmt::Display for RwLockWriteGuard<'rwlock, T, R> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<'rwlock, T: ?Sized> Deref for RwLockReadGuard<'rwlock, T> { - type Target = T; - - fn deref(&self) -> &T { - // Safety: We know statically that only we are referencing data - unsafe { &*self.data } - } -} - -impl<'rwlock, T: ?Sized, R> Deref for RwLockUpgradableGuard<'rwlock, T, R> { - type Target = T; - - fn deref(&self) -> &T { - // Safety: We know statically that only we are referencing data - unsafe { &*self.data } - } -} - -impl<'rwlock, T: ?Sized, R> Deref for RwLockWriteGuard<'rwlock, T, R> { - type Target = T; - - fn deref(&self) -> &T { - // Safety: We know statically that only we are referencing data - unsafe { &*self.data } - } -} - -impl<'rwlock, T: ?Sized, R> DerefMut for RwLockWriteGuard<'rwlock, T, R> { - fn deref_mut(&mut self) -> &mut T { - // Safety: We know statically that only we are referencing data - unsafe { &mut *self.data } - } -} - -impl<'rwlock, T: ?Sized> Drop for RwLockReadGuard<'rwlock, T> { - fn drop(&mut self) { - debug_assert!(self.lock.load(Ordering::Relaxed) & !(WRITER | UPGRADED) > 0); - self.lock.fetch_sub(READER, Ordering::Release); - } -} - -impl<'rwlock, T: ?Sized, R> Drop for RwLockUpgradableGuard<'rwlock, T, R> { - fn drop(&mut self) { - debug_assert_eq!( - self.inner.lock.load(Ordering::Relaxed) & (WRITER | UPGRADED), - UPGRADED - ); - self.inner.lock.fetch_sub(UPGRADED, Ordering::AcqRel); - } -} - -impl<'rwlock, T: ?Sized, R> Drop for RwLockWriteGuard<'rwlock, T, R> { - fn drop(&mut self) { - debug_assert_eq!(self.inner.lock.load(Ordering::Relaxed) & WRITER, WRITER); - - // Writer is responsible for clearing both WRITER and UPGRADED bits. - // The UPGRADED bit may be set if an upgradeable lock attempts an upgrade while this lock is held. - self.inner - .lock - .fetch_and(!(WRITER | UPGRADED), Ordering::Release); - } -} - -#[inline(always)] -fn compare_exchange( - atomic: &AtomicUsize, - current: usize, - new: usize, - success: Ordering, - failure: Ordering, - strong: bool, -) -> Result { - if strong { - atomic.compare_exchange(current, new, success, failure) - } else { - atomic.compare_exchange_weak(current, new, success, failure) - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawRwLock for RwLock<(), R> { - type GuardMarker = lock_api_crate::GuardSend; - - const INIT: Self = Self::new(()); - - #[inline(always)] - fn lock_exclusive(&self) { - // Prevent guard destructor running - core::mem::forget(self.write()); - } - - #[inline(always)] - fn try_lock_exclusive(&self) -> bool { - // Prevent guard destructor running - self.try_write().map(core::mem::forget).is_some() - } - - #[inline(always)] - unsafe fn unlock_exclusive(&self) { - drop(RwLockWriteGuard { - inner: self, - data: &mut (), - phantom: PhantomData, - }); - } - - #[inline(always)] - fn lock_shared(&self) { - // Prevent guard destructor running - core::mem::forget(self.read()); - } - - #[inline(always)] - fn try_lock_shared(&self) -> bool { - // Prevent guard destructor running - self.try_read().map(core::mem::forget).is_some() - } - - #[inline(always)] - unsafe fn unlock_shared(&self) { - drop(RwLockReadGuard { - lock: &self.lock, - data: &(), - }); - } - - #[inline(always)] - fn is_locked(&self) -> bool { - self.lock.load(Ordering::Relaxed) != 0 - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawRwLockUpgrade for RwLock<(), R> { - #[inline(always)] - fn lock_upgradable(&self) { - // Prevent guard destructor running - core::mem::forget(self.upgradeable_read()); - } - - #[inline(always)] - fn try_lock_upgradable(&self) -> bool { - // Prevent guard destructor running - self.try_upgradeable_read().map(core::mem::forget).is_some() - } - - #[inline(always)] - unsafe fn unlock_upgradable(&self) { - drop(RwLockUpgradableGuard { - inner: self, - data: &(), - phantom: PhantomData, - }); - } - - #[inline(always)] - unsafe fn upgrade(&self) { - let tmp_guard = RwLockUpgradableGuard { - inner: self, - data: &(), - phantom: PhantomData, - }; - core::mem::forget(tmp_guard.upgrade()); - } - - #[inline(always)] - unsafe fn try_upgrade(&self) -> bool { - let tmp_guard = RwLockUpgradableGuard { - inner: self, - data: &(), - phantom: PhantomData, - }; - tmp_guard.try_upgrade().map(core::mem::forget).is_ok() - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawRwLockDowngrade for RwLock<(), R> { - unsafe fn downgrade(&self) { - let tmp_guard = RwLockWriteGuard { - inner: self, - data: &mut (), - phantom: PhantomData, - }; - core::mem::forget(tmp_guard.downgrade()); - } -} - -#[cfg(feature = "lock_api")] -unsafe impl lock_api_crate::RawRwLockUpgradeDowngrade for RwLock<(), R> { - unsafe fn downgrade_upgradable(&self) { - let tmp_guard = RwLockUpgradableGuard { - inner: self, - data: &(), - phantom: PhantomData, - }; - core::mem::forget(tmp_guard.downgrade()); - } - - unsafe fn downgrade_to_upgradable(&self) { - let tmp_guard = RwLockWriteGuard { - inner: self, - data: &mut (), - phantom: PhantomData, - }; - core::mem::forget(tmp_guard.downgrade_to_upgradeable()); - } -} - -#[cfg(test)] -mod tests { - use std::{ - prelude::v1::*, - sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, - mpsc::channel, - }, - thread, - }; - - type RwLock = super::RwLock; - - #[derive(Eq, PartialEq, Debug)] - struct NonCopy(i32); - - #[test] - fn smoke() { - let l = RwLock::new(()); - drop(l.read()); - drop(l.write()); - drop((l.read(), l.read())); - drop(l.write()); - } - - // TODO: needs RNG - //#[test] - // fn frob() { - // static R: RwLock = RwLock::new(); - // const N: usize = 10; - // const M: usize = 1000; - // - // let (tx, rx) = channel::<()>(); - // for _ in 0..N { - // let tx = tx.clone(); - // thread::spawn(move|| { - // let mut rng = rand::thread_rng(); - // for _ in 0..M { - // if rng.gen_weighted_bool(N) { - // drop(R.write()); - // } else { - // drop(R.read()); - // } - // } - // drop(tx); - // }); - // } - // drop(tx); - // let _ = rx.recv(); - // unsafe { R.destroy(); } - //} - - #[test] - fn test_rw_arc() { - let arc = Arc::new(RwLock::new(0)); - let arc2 = arc.clone(); - let (tx, rx) = channel(); - - let t = thread::spawn(move || { - let mut lock = arc2.write(); - for _ in 0..10 { - let tmp = *lock; - *lock = -1; - thread::yield_now(); - *lock = tmp + 1; - } - tx.send(()).unwrap(); - }); - - // Readers try to catch the writer in the act - let mut children = Vec::new(); - for _ in 0..5 { - let arc3 = arc.clone(); - children.push(thread::spawn(move || { - let lock = arc3.read(); - assert!(*lock >= 0); - })); - } - - // Wait for children to pass their asserts - for r in children { - assert!(r.join().is_ok()); - } - - // Wait for writer to finish - rx.recv().unwrap(); - let lock = arc.read(); - assert_eq!(*lock, 10); - - assert!(t.join().is_ok()); - } - - #[test] - fn test_rw_access_in_unwind() { - let arc = Arc::new(RwLock::new(1)); - let arc2 = arc.clone(); - let _ = thread::spawn(move || -> () { - struct Unwinder { - i: Arc>, - } - impl Drop for Unwinder { - fn drop(&mut self) { - let mut lock = self.i.write(); - *lock += 1; - } - } - let _u = Unwinder { i: arc2 }; - panic!(); - }) - .join(); - let lock = arc.read(); - assert_eq!(*lock, 2); - } - - #[test] - fn test_rwlock_unsized() { - let rw: &RwLock<[i32]> = &RwLock::new([1, 2, 3]); - { - let b = &mut *rw.write(); - b[0] = 4; - b[2] = 5; - } - let comp: &[i32] = &[4, 2, 5]; - assert_eq!(&*rw.read(), comp); - } - - #[test] - fn test_rwlock_try_write() { - use std::mem::drop; - - let lock = RwLock::new(0isize); - let read_guard = lock.read(); - - let write_result = lock.try_write(); - match write_result { - None => (), - Some(_) => assert!( - false, - "try_write should not succeed while read_guard is in scope" - ), - } - - drop(read_guard); - } - - #[test] - fn test_rw_try_read() { - let m = RwLock::new(0); - ::std::mem::forget(m.write()); - assert!(m.try_read().is_none()); - } - - #[test] - fn test_into_inner() { - let m = RwLock::new(NonCopy(10)); - assert_eq!(m.into_inner(), NonCopy(10)); - } - - #[test] - fn test_into_inner_drop() { - struct Foo(Arc); - impl Drop for Foo { - fn drop(&mut self) { - self.0.fetch_add(1, Ordering::SeqCst); - } - } - let num_drops = Arc::new(AtomicUsize::new(0)); - let m = RwLock::new(Foo(num_drops.clone())); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - { - let _inner = m.into_inner(); - assert_eq!(num_drops.load(Ordering::SeqCst), 0); - } - assert_eq!(num_drops.load(Ordering::SeqCst), 1); - } - - #[test] - fn test_force_read_decrement() { - let m = RwLock::new(()); - ::std::mem::forget(m.read()); - ::std::mem::forget(m.read()); - ::std::mem::forget(m.read()); - assert!(m.try_write().is_none()); - unsafe { - m.force_read_decrement(); - m.force_read_decrement(); - } - assert!(m.try_write().is_none()); - unsafe { - m.force_read_decrement(); - } - assert!(m.try_write().is_some()); - } - - #[test] - fn test_force_write_unlock() { - let m = RwLock::new(()); - ::std::mem::forget(m.write()); - assert!(m.try_read().is_none()); - unsafe { - m.force_write_unlock(); - } - assert!(m.try_read().is_some()); - } - - #[test] - fn test_upgrade_downgrade() { - let m = RwLock::new(()); - { - let _r = m.read(); - let upg = m.try_upgradeable_read().unwrap(); - assert!(m.try_read().is_none()); - assert!(m.try_write().is_none()); - assert!(upg.try_upgrade().is_err()); - } - { - let w = m.write(); - assert!(m.try_upgradeable_read().is_none()); - let _r = w.downgrade(); - assert!(m.try_upgradeable_read().is_some()); - assert!(m.try_read().is_some()); - assert!(m.try_write().is_none()); - } - { - let _u = m.upgradeable_read(); - assert!(m.try_upgradeable_read().is_none()); - } - - assert!(m.try_upgradeable_read().unwrap().try_upgrade().is_ok()); - } -} From 054a2121a54625053e48bfc7bb7374efe5af6a6b Mon Sep 17 00:00:00 2001 From: CharlieV <134790819+CharlieVinnie@users.noreply.github.com> Date: Tue, 26 May 2026 12:40:52 +0800 Subject: [PATCH 37/71] feat(starry): add DeepSeek TUI example app with Docker-based build (#907) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(starry): add DeepSeek TUI example app with Docker-based build 在 StarryOS 上添加 DeepSeek TUI 示例应用,支持在 x86_64 QEMU 中离线 smoke 测试和在线 C 素数测试。 - prepare_deepseek_assets.sh: 在 Alpine Docker 容器中编译 musl 动态链接的 deepseek + deepseek-tui 二进制,处理 libdbus 交叉编译依赖 - prepare_deepseek_rootfs.sh: 构建 Alpine rootfs ext4 镜像,注入二进制、 运行时共享库、CA 证书和 API Key 环境变量 - Dockerfile.build: Alpine + Rust nightly 构建环境 - run_me.sh: 总控脚本,支持 --build/--smoke/--test/--shell 模式 - qemu-x86_64.toml: 离线 smoke 测试(验证 --version 和 model list) - qemu-x86_64-deepseek-prime-test.toml: 在线素数测试 * fix(deepseek-tui): address review comments - Dockerfile.build: use wildcard libdbus-1.so.3* to avoid version-specific path - prepare_deepseek_rootfs.sh: implement --env-file option that was parsed but unused - qemu-x86_64.toml: tighten "No such process" -> "No such process \(os error 3\)" - prepare_deepseek_assets.sh: remove unused $tmp_dir dead code --- apps/starry/deepseek-tui/Dockerfile.build | 37 +++ apps/starry/deepseek-tui/README.md | 118 +++++++++ .../build-x86_64-unknown-none.toml | 5 + .../deepseek-tui/prepare_deepseek_assets.sh | 93 +++++++ .../deepseek-tui/prepare_deepseek_rootfs.sh | 235 ++++++++++++++++++ .../qemu-x86_64-deepseek-prime-test.toml | 124 +++++++++ apps/starry/deepseek-tui/qemu-x86_64.toml | 49 ++++ apps/starry/deepseek-tui/run_me.sh | 130 ++++++++++ 8 files changed, 791 insertions(+) create mode 100644 apps/starry/deepseek-tui/Dockerfile.build create mode 100644 apps/starry/deepseek-tui/README.md create mode 100644 apps/starry/deepseek-tui/build-x86_64-unknown-none.toml create mode 100755 apps/starry/deepseek-tui/prepare_deepseek_assets.sh create mode 100755 apps/starry/deepseek-tui/prepare_deepseek_rootfs.sh create mode 100644 apps/starry/deepseek-tui/qemu-x86_64-deepseek-prime-test.toml create mode 100644 apps/starry/deepseek-tui/qemu-x86_64.toml create mode 100755 apps/starry/deepseek-tui/run_me.sh diff --git a/apps/starry/deepseek-tui/Dockerfile.build b/apps/starry/deepseek-tui/Dockerfile.build new file mode 100644 index 0000000000..e6fb02a490 --- /dev/null +++ b/apps/starry/deepseek-tui/Dockerfile.build @@ -0,0 +1,37 @@ +FROM alpine:latest AS builder + +RUN apk add --no-cache \ + dbus-dev \ + pkgconfig \ + gcc \ + musl-dev \ + git \ + rustup + +ENV RUSTUP_HOME=/root/.rustup \ + CARGO_HOME=/root/.cargo + +RUN rustup-init -y --default-toolchain nightly-2026-04-27 +ENV PATH=/root/.cargo/bin:$PATH + +RUN rustup target add x86_64-unknown-linux-musl + +ARG TAG=v0.8.18 +RUN git clone --depth 1 --branch "$TAG" https://github.com/Hmbown/DeepSeek-TUI.git /src + +WORKDIR /src + +# Dynamic linking so we can link against Alpine's libdbus-1.so +ENV RUSTFLAGS="-C target-feature=-crt-static" +RUN cargo build --release --locked --target x86_64-unknown-linux-musl --bin deepseek +RUN cargo build --release --locked --target x86_64-unknown-linux-musl --bin deepseek-tui + +RUN strip /src/target/x86_64-unknown-linux-musl/release/deepseek \ + /src/target/x86_64-unknown-linux-musl/release/deepseek-tui + +FROM alpine:latest AS export +RUN apk add --no-cache tar +COPY --from=builder /src/target/x86_64-unknown-linux-musl/release/deepseek / +COPY --from=builder /src/target/x86_64-unknown-linux-musl/release/deepseek-tui / +COPY --from=builder /usr/lib/libdbus-1.so.3* /usr/lib/ +COPY --from=builder /usr/lib/libgcc_s.so.1 /usr/lib/ diff --git a/apps/starry/deepseek-tui/README.md b/apps/starry/deepseek-tui/README.md new file mode 100644 index 0000000000..cafbf39b30 --- /dev/null +++ b/apps/starry/deepseek-tui/README.md @@ -0,0 +1,118 @@ +# StarryOS DeepSeek TUI 示例 + +本目录提供在 StarryOS x86_64 QEMU 虚拟机中运行 [DeepSeek TUI](https://github.com/Hmbown/DeepSeek-TUI) 的手动示例。不参与 CI 测试套件。 + +## 前置依赖 + +- **Docker** — 用于在 Alpine 容器中编译 musl 动态链接的 deepseek 二进制(`libdbus-1.so.3` 在 musl 目标下需容器环境构建) +- **Rust 工具链** — 用于编译 StarryOS 内核(`cargo xtask starry qemu`) +- **e2fsprogs** — 提供 `debugfs`,用于向 rootfs ext4 镜像注入文件 + +## 文件清单 + +| 文件 | 功能 | +|---|---| +| `run_me.sh` | 总控脚本,支持 `--build`、`--smoke`、`--test`、`--shell`、`--api-key`、`--proxy` | +| `prepare_deepseek_assets.sh` | 编译 deepseek + deepseek-tui 二进制,提取至 `target/deepseek/assets/`" +| `prepare_deepseek_rootfs.sh` | 构建 Alpine rootfs,注入二进制、共享库、API Key、CA 证书 | +| `Dockerfile.build` | Alpine Docker 构建环境,用于编译 musl 动态链接的 deepseek 二进制 | +| `build-x86_64-unknown-none.toml` | StarryOS 内核构建配置 | +| `qemu-x86_64.toml` | 离线 smoke 测试 QEMU 配置(无网络需求) | +| `qemu-x86_64-deepseek-prime-test.toml` | 在线 C 素数测试 QEMU 配置(需要 API Key + 网络) | + +## 构建流程说明 + +1. `prepare_deepseek_assets.sh` 优先使用 Docker(检测到 docker 命令时),在 Alpine 容器中用 musl 工具链从源码编译 deepseek + deepseek-tui,并进行动态链接(依赖 `libdbus-1.so.3` 和 `libgcc_s.so.1`)。构建产物存放在 `target/deepseek/assets/`。 +2. `prepare_deepseek_rootfs.sh` 将上述二进制和共享库注入 Alpine rootfs ext4 镜像中。额外注入 CA 证书和 `DEEPSEEK_API_KEY` 环境变量(在线测试时)。 +3. `cargo xtask starry qemu` 编译 StarryOS 内核(`x86_64-unknown-none`),挂载 rootfs,启动 QEMU 虚拟机。 + +## 使用方法 + +### 构建二进制 + +```bash +bash apps/starry/deepseek-tui/run_me.sh --build +``` + +或直接运行: + +```bash +bash apps/starry/deepseek-tui/prepare_deepseek_assets.sh +``` + +### 离线 smoke 测试 + +验证 `deepseek --version`、`deepseek-tui --version`、`deepseek model list` 等基本命令,无需 API Key。 + +```bash +bash apps/starry/deepseek-tui/run_me.sh --smoke +``` + +预期输出包含: + +``` +STARRY_DEEPSEEK_STAGE_G_PASSED +``` + +### 在线 C 素数测试 + +向 DeepSeek 发送提示词,要求其编写 C 程序找出大于 998244352 的最小素数,编译并运行,验证输出是否为 998244353。 + +```bash +bash apps/starry/deepseek-tui/run_me.sh --test --api-key sk-your-key-here +``` + +如需代理: + +```bash +bash apps/starry/deepseek-tui/run_me.sh --test --api-key sk-your-key-here --proxy http://10.0.2.2:7890 +``` + +预期输出包含: + +``` +STARRY_DEEPSEEK_PRIME_TEST_DONE +STARRY_DEEPSEEK_PRIME_TEST_PASSED +``` + +### 交互式 shell + +```bash +bash apps/starry/deepseek-tui/run_me.sh --shell +``` + +或带 API Key: + +```bash +bash apps/starry/deepseek-tui/run_me.sh --shell --api-key sk-your-key-here +``` + +### 仅构建 rootfs(不启动 QEMU) + +离线版: + +```bash +bash apps/starry/deepseek-tui/run_me.sh --rootfs +``` + +在线版: + +```bash +bash apps/starry/deepseek-tui/run_me.sh --rootfs --api-key sk-your-key-here +``` + +## 本地产物 + +- `target/deepseek/assets/` — deepseek + deepseek-tui 二进制及运行时共享库 +- `target/deepseek/build/` — DeepSeek-TUI 源码克隆 +- `tmp/axbuild/rootfs/rootfs-x86_64-deepseek*.img` — 构建好的 rootfs 镜像 + +以上均为不受版本控制的本地构建产物。 + +## 注意事项 + +- 仅支持 x86_64 QEMU,不支持其他架构 +- 不覆盖 DeepSeek TUI 交互模式、默认 Linux Sandbox、CI 在线请求等场景 +- 在线测试需要 `--api-key` 参数 +- 二进制通过动态链接 `libdbus-1.so.3` 和 `libgcc_s.so.1`,rootfs 注入脚本会自动处理这些依赖 +- **构建依赖 Docker**:`prepare_deepseek_assets.sh` 需要 Docker 运行时,在 Alpine 容器中编译 musl 动态链接的 deepseek 二进制。当前脚本在检测到 docker 命令时自动使用容器方式;若宿主机没有 Docker,会回退到本地构建(但可能因 dbus 交叉编译问题失败) diff --git a/apps/starry/deepseek-tui/build-x86_64-unknown-none.toml b/apps/starry/deepseek-tui/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..1c8a2eeca2 --- /dev/null +++ b/apps/starry/deepseek-tui/build-x86_64-unknown-none.toml @@ -0,0 +1,5 @@ +target = "x86_64-unknown-none" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = ["qemu"] +plat_dyn = false diff --git a/apps/starry/deepseek-tui/prepare_deepseek_assets.sh b/apps/starry/deepseek-tui/prepare_deepseek_assets.sh new file mode 100755 index 0000000000..87448d1886 --- /dev/null +++ b/apps/starry/deepseek-tui/prepare_deepseek_assets.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -euo pipefail + +workspace="${STARRY_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}" +asset_dir="$workspace/target/deepseek/assets" +src_dir="$workspace/target/deepseek/build/deepseek-tui" +repo="https://github.com/Hmbown/DeepSeek-TUI.git" +tag="v0.8.18" + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "error: required command not found: $1" >&2 + exit 1 + fi +} + +need_cmd install + +if [ -x "$asset_dir/deepseek" ] && [ -x "$asset_dir/deepseek-tui" ]; then + echo "DeepSeek TUI assets already staged in $asset_dir" + file "$asset_dir/deepseek" 2>/dev/null | head -1 + ls -lh "$asset_dir/deepseek" + exit 0 +fi + +mkdir -p "$asset_dir" "$src_dir" + +docker_build() { + local script_dir + script_dir="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" + echo "Building deepseek $tag in Docker (Alpine + musl)..." + docker build --network host \ + -f "$script_dir/Dockerfile.build" \ + -t deepseek-tui-builder \ + "$workspace" 2>&1 + echo "" + echo "Extracting binaries and shared libraries..." + docker run --rm deepseek-tui-builder tar -cO /deepseek /deepseek-tui | tar -xv -C "$asset_dir" + mkdir -p "$asset_dir/lib" + docker run --rm deepseek-tui-builder tar -cO /usr/lib/ | tar -xv --strip-components=2 -C "$asset_dir/lib/" 2>/dev/null || true + echo "" + file "$asset_dir/deepseek" 2>/dev/null || true + docker run --rm deepseek-tui-builder /deepseek --version 2>/dev/null || echo "(version check skipped — musl binary needs musl host)" + echo "" + echo "DeepSeek TUI assets ready in $asset_dir" + ls -lh "$asset_dir/" + exit 0 +} + +direct_build() { + need_cmd git + need_cmd cargo + need_cmd strip + + echo "Cloning DeepSeek TUI $tag..." + if [ -d "$src_dir/.git" ]; then + echo " Updating existing clone..." + cd "$src_dir" && git fetch --tags --depth 1 origin "$tag" && git checkout -f "$tag" + else + git clone --depth 1 --branch "$tag" "$repo" "$src_dir" + fi + + echo "" + echo "Building deepseek ($tag)..." + cd "$src_dir" + + echo " Building deepseek (crates/cli) with musl target..." + cargo build --release --locked --target x86_64-unknown-linux-musl --bin deepseek + + echo " Building deepseek-tui (crates/tui) with musl target..." + cargo build --release --locked --target x86_64-unknown-linux-musl --bin deepseek-tui + + echo "" + echo "Stripping and staging binaries..." + install -m 0755 "$src_dir/target/x86_64-unknown-linux-musl/release/deepseek" "$asset_dir/deepseek" + install -m 0755 "$src_dir/target/x86_64-unknown-linux-musl/release/deepseek-tui" "$asset_dir/deepseek-tui" + strip "$asset_dir/deepseek" "$asset_dir/deepseek-tui" + + echo "" + file "$asset_dir/deepseek" + "$asset_dir/deepseek" --version + "$asset_dir/deepseek-tui" --version + + echo "" + echo "DeepSeek TUI assets ready in $asset_dir" + ls -lh "$asset_dir/" +} + +if command -v docker >/dev/null 2>&1; then + docker_build +else + direct_build +fi diff --git a/apps/starry/deepseek-tui/prepare_deepseek_rootfs.sh b/apps/starry/deepseek-tui/prepare_deepseek_rootfs.sh new file mode 100755 index 0000000000..306ac294c6 --- /dev/null +++ b/apps/starry/deepseek-tui/prepare_deepseek_rootfs.sh @@ -0,0 +1,235 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +workspace="$(cd "$script_dir/../../.." && pwd)" +base_rootfs="$workspace/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img" +output_rootfs="$workspace/tmp/axbuild/rootfs/rootfs-x86_64-deepseek.img" +api_key="" +proxy_url="${DEEPSEEK_ONLINE_PROXY:-}" +env_file="" +ca_cert="${SSL_CERT_FILE:-/etc/ssl/certs/ca-certificates.crt}" + +usage() { + cat </dev/null 2>&1; then + echo "error: required command not found: $1" >&2 + exit 1 + fi +} + +shell_quote() { + local value="$1" + printf "'%s'" "${value//\'/\'\\\'\'}" +} + +workspace_path() { + local path="$1" + if [[ "$path" = /* ]]; then + printf '%s\n' "$path" + else + printf '%s/%s\n' "$workspace" "$path" + fi +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --api-key) + api_key="$2" + shift 2 + ;; + --proxy) + proxy_url="$2" + shift 2 + ;; + --env-file) + env_file="$2" + shift 2 + ;; + --base-rootfs) + base_rootfs="$2" + shift 2 + ;; + --output-rootfs) + output_rootfs="$2" + shift 2 + ;; + --ca-cert) + ca_cert="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "error: unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +base_rootfs="$(workspace_path "$base_rootfs")" +output_rootfs="$(workspace_path "$output_rootfs")" +ca_cert="$(workspace_path "$ca_cert")" +if [[ -n "$env_file" ]]; then + env_file="$(workspace_path "$env_file")" +fi + +need_cmd cp +need_cmd debugfs +need_cmd install +need_cmd mktemp +need_cmd stat + +if [[ ! -f "$base_rootfs" ]]; then + if [[ "$base_rootfs" == "$workspace/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img" ]]; then + echo "Base rootfs not found; preparing the default x86_64 Alpine rootfs..." + (cd "$workspace" && cargo xtask starry rootfs --arch x86_64) + fi +fi + +if [[ ! -f "$base_rootfs" ]]; then + echo "error: base rootfs not found: $base_rootfs" >&2 + exit 1 +fi + +"$script_dir/prepare_deepseek_assets.sh" + +deepseek_bin="$workspace/target/deepseek/assets/deepseek" +deepseek_tui_bin="$workspace/target/deepseek/assets/deepseek-tui" +if [[ ! -x "$deepseek_bin" || ! -x "$deepseek_tui_bin" ]]; then + echo "error: DeepSeek TUI assets are missing after prepare_deepseek_assets.sh" >&2 + exit 1 +fi + +mkdir -p "$(dirname "$output_rootfs")" +tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/starry-deepseek-rootfs.XXXXXX")" +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT + +overlay="$tmp_dir/overlay" +mkdir -p "$overlay/usr/local/bin" "$overlay/usr/lib" "$overlay/root/.deepseek" + +# Inject shared libraries needed by the dynamically-linked binaries +lib_dir="$workspace/target/deepseek/assets/lib" +if [ -d "$lib_dir" ]; then + for lib in "$lib_dir"/*.so*; do + [ -f "$lib" ] && install -m 0644 "$lib" "$overlay/usr/lib/" + done +fi +install -m 0755 "$deepseek_bin" "$overlay/usr/local/bin/deepseek" +install -m 0755 "$deepseek_tui_bin" "$overlay/usr/local/bin/deepseek-tui" + +# Write env file with DEEPSEEK_API_KEY, optional proxy settings, or injected env file +if [[ -n "$env_file" ]]; then + install -m 0644 "$env_file" "$overlay/root/.deepseek/starry-online-env" +else + { + if [[ -n "$api_key" ]]; then + quoted_key="$(shell_quote "$api_key")" + echo "export DEEPSEEK_API_KEY=$quoted_key" + fi + if [[ -n "$proxy_url" ]]; then + quoted_proxy="$(shell_quote "$proxy_url")" + cat < "$overlay/root/.deepseek/starry-online-env" +fi + +if [[ -f "$ca_cert" ]]; then + mkdir -p "$overlay/etc/ssl/certs" + install -m 0644 "$ca_cert" "$overlay/etc/ssl/certs/ca-certificates.crt" +else + echo "warning: CA bundle not found at $ca_cert; relying on the base rootfs CA bundle" >&2 +fi + +tmp_rootfs="$tmp_dir/rootfs.img" +cp --reflink=auto "$base_rootfs" "$tmp_rootfs" 2>/dev/null || cp "$base_rootfs" "$tmp_rootfs" + +debugfs_script="$tmp_dir/inject.debugfs" +{ + find "$overlay" -type d | sort | while IFS= read -r dir; do + rel="${dir#"$overlay"}" + [[ -z "$rel" ]] && continue + printf 'mkdir %s\n' "$rel" + done + find "$overlay" -type f | sort | while IFS= read -r file; do + rel="${file#"$overlay"}" + mode="$(stat -c '%a' "$file")" + printf 'rm %s\n' "$rel" + printf 'write %s %s\n' "$file" "$rel" + printf 'sif %s mode 0100%s\n' "$rel" "$mode" + done + printf 'quit\n' +} > "$debugfs_script" + +debugfs_log="$tmp_dir/debugfs.log" +if ! debugfs -w -f "$debugfs_script" "$tmp_rootfs" >"$debugfs_log" 2>&1; then + cat "$debugfs_log" >&2 + exit 1 +fi +mv "$tmp_rootfs" "$output_rootfs" + +display_rootfs="$output_rootfs" +case "$display_rootfs" in + "$workspace"/*) + display_rootfs="${display_rootfs#"$workspace"/}" + ;; +esac + +echo "DeepSeek TUI example rootfs ready:" +echo " $output_rootfs" +echo +echo "Run the offline smoke test with:" +echo " cargo xtask starry qemu --arch x86_64 \\" +echo " --qemu-config apps/starry/deepseek-tui/qemu-x86_64.toml \\" +echo " --rootfs $display_rootfs" +if [[ -n "$api_key" ]]; then + echo + echo "Run the online prime demo with:" + echo " cargo xtask starry qemu --arch x86_64 \\" + echo " --qemu-config apps/starry/deepseek-tui/qemu-x86_64-deepseek-prime-test.toml \\" + echo " --rootfs $display_rootfs" +fi diff --git a/apps/starry/deepseek-tui/qemu-x86_64-deepseek-prime-test.toml b/apps/starry/deepseek-tui/qemu-x86_64-deepseek-prime-test.toml new file mode 100644 index 0000000000..a5e3dceef7 --- /dev/null +++ b/apps/starry/deepseek-tui/qemu-x86_64-deepseek-prime-test.toml @@ -0,0 +1,124 @@ +# Example only: this online flow is opt-in and is not part of test-suit or CI. +# Prepare tmp/axbuild/rootfs/rootfs-x86_64-deepseek-online.img with +# prepare_deepseek_rootfs.sh --api-key ... before running it. +args = [ + "-machine", + "q35", + "-nographic", + "-m", + "2G", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-deepseek-online.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", + "-snapshot", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = ''' +export HOME=/root +export USER=root +export SHELL=/bin/sh +export TERM=xterm-256color +export PATH=/usr/local/bin:/usr/bin:/bin:/sbin +export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt +if [ -f "$HOME/.deepseek/starry-online-env" ]; then + . "$HOME/.deepseek/starry-online-env" +fi +set -e + +echo "STARRY_DEEPSEEK_PRIME_TEST_ENV_READY" + +command -v deepseek +command -v deepseek-tui +command -v gcc + +deepseek --version + +if [ -z "${DEEPSEEK_API_KEY:-}" ]; then + echo "STARRY_DEEPSEEK_PRIME_TEST_MISSING_API_KEY" + echo "Run prepare_deepseek_rootfs.sh with --api-key first." + exit 1 +fi + +echo "DEEPSEEK_API_KEY is set" | head -c 30 +echo "" + +mkdir -p /tmp/deepseek-prime-test +cd /tmp/deepseek-prime-test + +cat > /tmp/deepseek-prime-prompt.txt <<'PROMPT_EOF' +Write a C program to /tmp/deepseek-prime-test/a.c using your write_file tool, then compile and run it. + +Requirements for a.c: +1. Define a function is_prime(int n) that checks if n is prime. +2. Find the smallest prime number strictly greater than 998244352. +3. Print only the result number (nothing else). + +Steps: +1. Use write_file to create /tmp/deepseek-prime-test/a.c with the program. +2. Use exec_shell to compile: gcc -O2 -o /tmp/deepseek-prime-test/a /tmp/deepseek-prime-test/a.c +3. Use exec_shell to run it: /tmp/deepseek-prime-test/a + +Do not output the code in your reply — just create, compile, and run. +PROMPT_EOF + +deepseek \ + --approval-policy never \ + --sandbox-mode workspace-write \ + --model deepseek-v4-flash \ + exec \ + --auto \ + "$(cat /tmp/deepseek-prime-prompt.txt)" 2>&1 | tee /tmp/deepseek-exec-output.txt || true + +echo "===== CHECKING OUTPUT FILES =====" +ls -la /tmp/deepseek-prime-test/ 2>/dev/null || echo "No files in test directory" + +if [ -f /tmp/deepseek-prime-test/a ]; then + echo "===== RUNNING COMPILED BINARY =====" + result=$(/tmp/deepseek-prime-test/a 2>&1) + echo "Result: $result" + + if [ "$result" = "998244353" ]; then + echo "STARRY_DEEPSEEK_PRIME_TEST_DONE" + echo "STARRY_DEEPSEEK_PRIME_TEST_PASSED" + else + echo "STARRY_DEEPSEEK_PRIME_TEST_WRONG_OUTPUT" + echo "Expected: 998244353, Got: $result" + fi +elif [ -f /tmp/deepseek-prime-test/a.c ]; then + echo "===== COMPILING AND RUNNING A.C =====" + cat /tmp/deepseek-prime-test/a.c + gcc -O2 -o /tmp/deepseek-prime-test/a /tmp/deepseek-prime-test/a.c && \ + result=$(/tmp/deepseek-prime-test/a) && \ + echo "Result: $result" + if [ "$result" = "998244353" ]; then + echo "STARRY_DEEPSEEK_PRIME_TEST_DONE" + echo "STARRY_DEEPSEEK_PRIME_TEST_PASSED" + else + echo "STARRY_DEEPSEEK_PRIME_TEST_WRONG_OUTPUT" + echo "Expected: 998244353, Got: $result" + fi +else + echo "STARRY_DEEPSEEK_PRIME_TEST_NO_FILE" + echo "deepseek exec output file:" + cat /tmp/deepseek-exec-output.txt 2>/dev/null || true +fi +''' +success_regex = ["(?m)^STARRY_DEEPSEEK_PRIME_TEST_PASSED\\s*$"] +fail_regex = [ + "(?i)\\bpanic(?:ked)?\\b", + "(?i)page fault", + "(?i)segmentation fault", + "No such process \\(os error 3\\)", + "sys_prctl: unsupported option 23", + "(?m)^STARRY_DEEPSEEK_PRIME_TEST_MISSING_API_KEY\\s*$", + "(?m)^STARRY_DEEPSEEK_PRIME_TEST_WRONG_OUTPUT\\s*$", + "(?m)^STARRY_DEEPSEEK_PRIME_TEST_NO_FILE\\s*$", +] +timeout = 2400 diff --git a/apps/starry/deepseek-tui/qemu-x86_64.toml b/apps/starry/deepseek-tui/qemu-x86_64.toml new file mode 100644 index 0000000000..b9a2b6d235 --- /dev/null +++ b/apps/starry/deepseek-tui/qemu-x86_64.toml @@ -0,0 +1,49 @@ +# Example only: this is not part of the StarryOS test-suit or CI. +args = [ + "-machine", + "q35", + "-nographic", + "-m", + "2G", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-deepseek.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", + "-snapshot", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = ''' +export HOME=/root +export USER=root +export SHELL=/bin/sh +export TERM=xterm-256color +export PATH=/usr/local/bin:/usr/bin:/bin:/sbin +set -e + +deepseek --version +deepseek-tui --version + +deepseek --help > /tmp/deepseek-help.txt 2>&1 +grep -F "Usage:" /tmp/deepseek-help.txt + +deepseek model list > /tmp/deepseek-model-list.txt 2>&1 +head -3 /tmp/deepseek-model-list.txt +grep -q "deepseek" /tmp/deepseek-model-list.txt + +echo "STARRY_DEEPSEEK_STAGE_G_PASSED" +''' +success_regex = ["(?m)^STARRY_DEEPSEEK_STAGE_G_PASSED\\s*$"] +fail_regex = [ + "(?i)\\bpanic(?:ked)?\\b", + "(?i)page fault", + "(?i)segmentation fault", + "No such process \\(os error 3\\)", + "sys_prctl: unsupported option 23", +] +timeout = 120 diff --git a/apps/starry/deepseek-tui/run_me.sh b/apps/starry/deepseek-tui/run_me.sh new file mode 100755 index 0000000000..1e2480c7f8 --- /dev/null +++ b/apps/starry/deepseek-tui/run_me.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +workspace="$(cd "$script_dir/../../.." && pwd)" + +usage() { + cat <&2 + exit 1 + fi + build + rootfs_online + echo "=== Run online C prime test ===" + cd "$workspace" && cargo xtask starry qemu \ + --arch x86_64 \ + --qemu-config apps/starry/deepseek-tui/qemu-x86_64-deepseek-prime-test.toml \ + --rootfs tmp/axbuild/rootfs/rootfs-x86_64-deepseek-online.img +} + +shell() { + build + if [[ -n "$api_key" ]]; then + rootfs_online + local rootfs="tmp/axbuild/rootfs/rootfs-x86_64-deepseek-online.img" + else + rootfs_offline + local rootfs="tmp/axbuild/rootfs/rootfs-x86_64-deepseek.img" + fi + echo "=== Interactive QEMU shell ===" + cd "$workspace" && cargo xtask starry qemu \ + --arch x86_64 \ + --rootfs "$rootfs" +} + +api_key="" +proxy="" +mode="" + +if [[ $# -eq 0 ]]; then + usage + exit 0 +fi + +while [[ $# -gt 0 ]]; do + case "$1" in + --build) mode="build" ;; + --rootfs) mode="rootfs" ;; + --smoke) mode="smoke" ;; + --test) mode="test" ;; + --shell) mode="shell" ;; + --api-key) api_key="$2"; shift ;; + --proxy) proxy="$2"; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;; + esac + shift +done + +case "$mode" in + build) build ;; + rootfs) + if [[ -n "$api_key" ]]; then + rootfs_online + else + rootfs_offline + fi + ;; + smoke) smoke ;; + test) test_online ;; + shell) shell ;; + *) usage; exit 1 ;; +esac From b39029775103d81a4ec87936dabe3257e28e1a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Tue, 26 May 2026 14:24:07 +0800 Subject: [PATCH 38/71] fix(axbuild): skip disabled grouped C subcases (#942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor and remove deprecated test files and configurations - Deleted the test framework header file used for syscall testing in the qemu-kcov stress tests. - Removed the CMakeLists.txt and main.c files for the syzkaller-emulation tests, along with its test framework. - Eliminated the threading test files including CMakeLists.txt, main.c, and test_framework.h. - Updated build configurations for aarch64 targets in multiple toml files to enable new features and set platform dynamics. - Simplified the prebuild script for the raw message peek syscall test by directly using apk to install dependencies. - Adjusted shell prompt in the qemu-aarch64 configuration for stress-ng to reflect the root user. * fix(ax-task): preempt on async wake, guard wait queue against double-enqueue (#912) * fix(ax-task): preempt on async wake, guard wait queue against double-enqueue Two small but related scheduler fixes that together remove a latency spike and a panic seen on async I/O paths. 1. AxWaker::wake_by_ref calls unblock_task with resched=true The previous resched=false meant any future woken by an interrupt or another task only became runnable after the currently running task reached its next voluntary yield point. With the default 10 ms timer tick, the visible latency on a two-hop wake (interrupt -> tty reader -> TUI consumer) was up to 20 ms before the consumer started running. Passing resched=true triggers set_preempt_pending() inside unblock_task so the woken task can take over at the next safe preemption point, collapsing the latency to a few microseconds. 2. blocked_resched skips push_back when the task is already queued Enabling preemptive wakes exposes a race in WaitQueue::wait_until. wait_until acquires the queue lock, sets the task state to Blocked and calls blocked_resched. blocked_resched used to unconditionally set in_wait_queue and push the task into the queue. If an AxWaker firing on an unrelated future preempts the task between the previous push_back and the resched call (which is now possible because the AxWaker arms set_preempt_pending), the task can re-enter wait_until and reach blocked_resched a second time while the previous entry is still in the queue. notify_one_with later pops the stale entry and transfers mutex ownership to it; the task subsequently observes the mutex as already owned and panics with "Thread(N) tried to acquire mutex it already owns" on the next aspace().lock(). Guard the push_back with `if !curr.in_wait_queue()`. The flag is still cleared by notify_one_with after a successful pop, so the skip only ever fires when the previous wait is still pending and the task is genuinely already queued. * test(ax-task): add blocked_resched double-enqueue regression test; enable ax-task in CI std test suite 补充审查意见要求的回归测试,验证 blocked_resched 中 `if !curr.in_wait_queue()` 守护能防止 AxWaker::wake_by_ref 直接唤醒路径导致的双重入队。 测试场景: 1. T1 阻塞在 WaitQueue::wait_until(WQ 有 1 条 stale 条目,in_wait_queue=true) 2. 主任务用 select_run_queue::unblock_task 直接唤醒 T1(不清除 in_wait_queue), 模拟 AxWaker::wake_by_ref with resched=true 的执行路径 3. T1 重跑,条件仍为 false,再次进入 blocked_resched 4. 断言 WQ 最终为空——有幽灵条目则说明双重入队 bug 未被修复 同时修复 ax-task 测试长期未在 CI 运行的问题: - axbuild std 测试 runner 扩展 CSV 格式支持每包 feature 列表 (`package,feat1,feat2,...`,向后兼容旧格式) - std_crates.csv 中 ax-task 改为 `ax-task,multitask,sched-fifo,test`, 使 cargo xtask test 能正常编译并执行调度器测试 Co-Authored-By: Claude Sonnet 4.6 (1M context) --------- Co-authored-by: StarryOS Fix Co-authored-by: Claude Sonnet 4.6 (1M context) * fix(starry-kernel,x86-qemu-q35): probe terminal size, deliver SIGWINCH, batch ONLCR writes (#913) Four serial-console / TTY improvements that together make TUI applications (ratatui-based clients, anything that reacts to SIGWINCH or that draws a full frame per render) usable on the QEMU serial console. 1. NTTY probes the connected terminal's real dimensions On NTTY initialisation (synchronously, before the input poll task is spawned) the kernel sends the standard cursor-position-report sequence (`\x1b7\x1b[9999;9999H\x1b[6n\x1b8`) and parses the `\x1b[rows;colsR` reply. TIOCGWINSZ then reports the real host terminal size instead of a stale fallback, which is what TUI applications rely on to lay out panels and to translate mouse coordinates to widgets. The wait is bounded to ~100 ms of wall time via `ax_hal::time::wall_time()` so a host that ignores CPR does not delay boot. 2. Default window size 28x110 -> 24x80 24x80 is the standard VT100 fallback that applications expect when TIOCGWINSZ reports a "default" terminal. The previous value was an arbitrary serial-friendly size that confused applications on first paint. 3. SIGWINCH on TIOCSWINSZ Match Linux tty_do_resize(): when an ioctl changes the recorded window size, send SIGWINCH to the foreground process group so TUI applications can re-layout immediately when the user resizes the host terminal. 4. Batched ONLCR output and atomic UART writes write_output_bytes() now collects the `\n -> \r\n` translation into a single Vec and issues exactly one writer.write() call. The previous per-newline issue path produced O(newlines) separate writes; a typical TUI frame contains many cursor-movement newlines, so the COM1 driver acquired/released its lock dozens of times for a single frame and terminal emulators rendered the frame line-by-line (visible flicker). On the x86-qemu-q35 platform the ConsoleIfImpl::write_bytes implementation also holds the COM1 lock for the whole call so that one logical write is never interleaved with output from another task and pays the lock-acquire cost once per write_bytes rather than once per byte. Co-authored-by: StarryOS Fix * fix(busybox-tests): enhance wget test with local server simulation and improve crond cleanup * Revert "fix(ax-task): preempt on async wake, guard wait queue against double-…" (#939) This reverts commit d26c7ddb92269eac5e1e9fa7be60088f7b7a97a9. * fix(axbuild): skip disabled grouped C subcases --------- Co-authored-by: Feiran Qin <161046517+jakeuibn@users.noreply.github.com> Co-authored-by: StarryOS Fix Co-authored-by: Claude Sonnet 4.6 (1M context) --- .../someboot/src/arch/aarch64/el1/mod.rs | 10 +- .../may-2026-retrospective.md | 2 +- docs/docs/development/starryos.md | 6 + drivers/ax-driver/src/net/binding.rs | 15 + drivers/ax-driver/src/pci/fdt.rs | 12 +- drivers/ax-driver/src/pci/mod.rs | 2 +- os/StarryOS/kernel/Cargo.toml | 1 - os/StarryOS/kernel/src/file/fs.rs | 46 -- os/StarryOS/kernel/src/kcov/mod.rs | 418 ----------------- os/StarryOS/kernel/src/lib.rs | 2 - .../kernel/src/mm/aspace/backend/linear.rs | 12 +- .../kernel/src/mm/aspace/backend/shared.rs | 8 +- os/StarryOS/kernel/src/pseudofs/dev/mod.rs | 11 - os/StarryOS/kernel/src/pseudofs/device.rs | 11 - os/StarryOS/kernel/src/pseudofs/mod.rs | 6 +- os/StarryOS/kernel/src/pseudofs/sysfs.rs | 8 +- os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs | 4 +- .../kernel/src/pseudofs/usbfs/sysfs.rs | 166 +------ os/StarryOS/kernel/src/syscall/mm/mmap.rs | 14 - os/StarryOS/kernel/src/syscall/task/clone.rs | 7 - os/StarryOS/kernel/src/task/mod.rs | 28 -- os/StarryOS/kernel/src/task/ops.rs | 3 - os/StarryOS/starryos/Cargo.toml | 1 - os/arceos/modules/axhal/Cargo.toml | 2 - os/arceos/modules/axhal/src/kcov.rs | 218 --------- os/arceos/modules/axhal/src/lib.rs | 3 - scripts/axbuild/src/build.rs | 7 +- scripts/axbuild/src/test/build.rs | 201 +++++++- .../build-aarch64-unknown-none-softfloat.toml | 13 +- .../build-aarch64-unknown-none-softfloat.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 17 - ...ld-loongarch64-unknown-none-softfloat.toml | 17 - .../build-riscv64gc-unknown-none-elf.toml | 17 - .../build-x86_64-unknown-none.toml | 17 - .../qemu-kcov-smp/concurrent/c/CMakeLists.txt | 10 - .../qemu-kcov-smp/concurrent/c/src/main.c | 223 --------- .../concurrent/c/src/test_framework.h | 35 -- .../qemu-kcov/basic-easy/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/basic-easy/c/src/main.c | 128 ------ .../basic-easy/c/src/test_framework.h | 85 ---- .../buffer-boundary/c/CMakeLists.txt | 9 - .../qemu-kcov/buffer-boundary/c/src/main.c | 132 ------ .../buffer-boundary/c/src/test_framework.h | 66 --- .../build-aarch64-unknown-none-softfloat.toml | 15 - ...ld-loongarch64-unknown-none-softfloat.toml | 15 - .../build-riscv64gc-unknown-none-elf.toml | 15 - .../qemu-kcov/build-x86_64-unknown-none.toml | 15 - .../disable-thread-check/c/CMakeLists.txt | 9 - .../disable-thread-check/c/src/main.c | 356 --------------- .../c/src/test_framework.h | 31 -- .../qemu-kcov/enable-disable/c/CMakeLists.txt | 9 - .../qemu-kcov/enable-disable/c/src/main.c | 67 --- .../enable-disable/c/src/test_framework.h | 35 -- .../qemu-kcov/guard-test/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/guard-test/c/src/main.c | 151 ------ .../guard-test/c/src/test_framework.h | 35 -- .../qemu-kcov/init-trace/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/init-trace/c/src/main.c | 38 -- .../init-trace/c/src/test_framework.h | 35 -- .../qemu-kcov/integrity/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/integrity/c/src/main.c | 112 ----- .../integrity/c/src/test_framework.h | 35 -- .../qemu-kcov/linux-doc/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/linux-doc/c/src/main.c | 64 --- .../qemu-kcov/mmap-buffer/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/mmap-buffer/c/src/main.c | 117 ----- .../mmap-buffer/c/src/test_framework.h | 35 -- .../normal/qemu-kcov/open/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/open/c/src/main.c | 17 - .../qemu-kcov/open/c/src/test_framework.h | 35 -- .../qemu-kcov/pc-values/c/CMakeLists.txt | 9 - .../normal/qemu-kcov/pc-values/c/src/main.c | 85 ---- .../pc-values/c/src/test_framework.h | 35 -- .../qemu-kcov/per-fd-state/c/CMakeLists.txt | 9 - .../qemu-kcov/per-fd-state/c/src/main.c | 137 ------ .../per-fd-state/c/src/test_framework.h | 35 -- .../pre-init-reject/c/CMakeLists.txt | 9 - .../qemu-kcov/pre-init-reject/c/src/main.c | 42 -- .../pre-init-reject/c/src/test_framework.h | 35 -- .../normal/qemu-kcov/stress/c/CMakeLists.txt | 10 - .../normal/qemu-kcov/stress/c/src/main.c | 293 ------------ .../qemu-kcov/stress/c/src/test_framework.h | 100 ---- .../syzkaller-emulation/c/CMakeLists.txt | 9 - .../syzkaller-emulation/c/src/main.c | 431 ------------------ .../c/src/test_framework.h | 35 -- .../qemu-kcov/threading/c/CMakeLists.txt | 10 - .../normal/qemu-kcov/threading/c/src/main.c | 90 ---- .../threading/c/src/test_framework.h | 35 -- .../build-aarch64-unknown-none-softfloat.toml | 11 +- .../qemu-smp1/busybox/sh/busybox-tests.sh | 92 ++-- .../syscall/test-raw-msg-peek/c/prebuild.sh | 30 +- .../build-aarch64-unknown-none-softfloat.toml | 11 +- .../build-aarch64-unknown-none-softfloat.toml | 11 +- .../build-aarch64-unknown-none-softfloat.toml | 11 +- .../stress-ng-0/stress-ng-0/qemu-aarch64.toml | 2 +- 95 files changed, 345 insertions(+), 4531 deletions(-) delete mode 100644 os/StarryOS/kernel/src/kcov/mod.rs delete mode 100644 os/arceos/modules/axhal/src/kcov.rs delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/basic-easy/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/basic-easy/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/basic-easy/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml delete mode 100644 test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/enable-disable/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/guard-test/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/guard-test/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/guard-test/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/init-trace/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/init-trace/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/init-trace/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/integrity/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/integrity/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/integrity/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/linux-doc/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/linux-doc/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/open/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/open/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/open/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/pc-values/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/pc-values/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/pc-values/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/per-fd-state/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/stress/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/stress/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/stress/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/test_framework.h delete mode 100644 test-suit/starryos/normal/qemu-kcov/threading/c/CMakeLists.txt delete mode 100644 test-suit/starryos/normal/qemu-kcov/threading/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-kcov/threading/c/src/test_framework.h diff --git a/components/someboot/src/arch/aarch64/el1/mod.rs b/components/someboot/src/arch/aarch64/el1/mod.rs index 3d1ff70a0b..657b53177a 100644 --- a/components/someboot/src/arch/aarch64/el1/mod.rs +++ b/components/someboot/src/arch/aarch64/el1/mod.rs @@ -193,7 +193,15 @@ pub fn is_mmu_enabled() -> bool { #[inline(always)] pub fn setup_sctlr() { - SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable); + SCTLR_EL1.modify( + SCTLR_EL1::M::Enable + + SCTLR_EL1::C::Cacheable + + SCTLR_EL1::I::Cacheable + + SCTLR_EL1::UCT::DontTrap + + SCTLR_EL1::DZE::DontTrap + + SCTLR_EL1::UCI::DontTrap, + ); + SCTLR_EL1.set(SCTLR_EL1.get() | (1 << 23)); flush_tlb(None); barrier::dsb(barrier::SY); barrier::isb(barrier::SY); diff --git a/docs/blog/2026/05-12-may-2026-retrospective/may-2026-retrospective.md b/docs/blog/2026/05-12-may-2026-retrospective/may-2026-retrospective.md index 1065b33861..48c00659dc 100644 --- a/docs/blog/2026/05-12-may-2026-retrospective/may-2026-retrospective.md +++ b/docs/blog/2026/05-12-may-2026-retrospective/may-2026-retrospective.md @@ -212,7 +212,7 @@ SD/MMC 方向开始抽象可复用 host backend,相关工作已经有部分提 ### 运行时覆盖率与 SG2002 适配 -运行时覆盖率方面,Linux 兼容 KCOV 接口仍在 PR 中尝试。SG2002 方向目前主要体现为 PR #554 中的开发板支持草案,尚未进入当前 dev/doc 分支。 +运行时覆盖率方面,Linux 兼容 KCOV 接口已有尝试,但由于改动范围和多核语义成本较高,当前暂不纳入 StarryOS。SG2002 方向目前主要体现为 PR #554 中的开发板支持草案,尚未进入当前 dev/doc 分支。 - [PR #425](https://github.com/rcore-os/tgoskits/pull/425) — StarryOS Linux 兼容 KCOV 尝试(flying-mice987,未完成) - [PR #554](https://github.com/rcore-os/tgoskits/pull/554) — SG2002 平台支持(周睿,未完成) diff --git a/docs/docs/development/starryos.md b/docs/docs/development/starryos.md index eca8a05190..d7e8084768 100644 --- a/docs/docs/development/starryos.md +++ b/docs/docs/development/starryos.md @@ -119,6 +119,12 @@ StarryOS 专用组件(位于 `components/`): 内核默认启用:`fp-simd`, `irq`, `uspace`, `page-alloc-4g`, `alloc-slab`, `multitask`, `task-ext`, `sched-rr`, `rtc`, `fs-ng-ext4`, `net-ng`。 +### 3.3 KCOV 暂不引入 + +StarryOS 目前不暴露 `kcov` feature,也不注册 `/dev/kcov`。此前尝试引入 Linux KCOV 兼容接口时,需要同时改动编译插桩参数、`ax-hal` 架构 trampoline、文件描述符状态、设备 mmap、任务生命周期和测试矩阵,侵入面过大。 + +另一个阻塞点是多核语义:KCOV 的 per-fd / per-task 状态需要和调度、抢占、线程退出、fork 以及中断上下文保持一致,当前实现还不能稳定覆盖 SMP 场景。因此在形成更小的边界和可靠的多核方案前,暂不把 KCOV 纳入 StarryOS。 + --- ## 4. Syscall 开发 diff --git a/drivers/ax-driver/src/net/binding.rs b/drivers/ax-driver/src/net/binding.rs index d03cec41ff..32d4b0eda0 100644 --- a/drivers/ax-driver/src/net/binding.rs +++ b/drivers/ax-driver/src/net/binding.rs @@ -42,6 +42,21 @@ impl DriverGeneric for PlatformNetDevice { } pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { + #[cfg(all(feature = "pci-fdt", target_os = "none"))] + { + let interrupt_pin = endpoint.interrupt_pin(); + if interrupt_pin != 0 { + match crate::pci::fdt_irq_for_endpoint(endpoint.address(), interrupt_pin) { + Ok(Some(irq)) => return Some(irq), + Ok(None) => {} + Err(err) => log::warn!( + "failed to resolve FDT IRQ for net endpoint {}: {err}", + endpoint.address() + ), + } + } + } + #[cfg(feature = "pci")] if let Some(irq) = crate::pci::legacy_irq_for_endpoint(endpoint.address(), endpoint.interrupt_pin()) diff --git a/drivers/ax-driver/src/pci/fdt.rs b/drivers/ax-driver/src/pci/fdt.rs index f1919a8f30..b3788fe574 100644 --- a/drivers/ax-driver/src/pci/fdt.rs +++ b/drivers/ax-driver/src/pci/fdt.rs @@ -1,14 +1,14 @@ extern crate alloc; use alloc::format; -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(target_os = "none")] use alloc::vec::Vec; -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(target_os = "none")] use fdt_edit::Fdt; use fdt_edit::{NodeType, PciRange, PciSpace}; use log::{debug, trace, warn}; -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(target_os = "none")] use rdrive::probe::pci::PciAddress; use rdrive::{ PlatformDevice, @@ -131,7 +131,7 @@ pub(super) fn register_fdt_legacy_irq(info: &FdtInfo<'_>, logical_bus_end: u8) { super::register_legacy_irq_route(0, logical_bus_end, irq); } -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(target_os = "none")] pub fn fdt_irq_for_endpoint( address: PciAddress, interrupt_pin: u8, @@ -144,7 +144,7 @@ pub fn fdt_irq_for_endpoint( result.map(Some) } -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(target_os = "none")] fn resolve_pci_irq_from_fdt( fdt: &Fdt, address: PciAddress, @@ -214,7 +214,7 @@ fn resolve_pci_irq_from_fdt( }) } -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(target_os = "none")] fn decode_irq_cells(specifier: &[u32]) -> Option { match specifier { [irq] => Some(*irq as usize), diff --git a/drivers/ax-driver/src/pci/mod.rs b/drivers/ax-driver/src/pci/mod.rs index 05d31b2fdc..c6d063badb 100644 --- a/drivers/ax-driver/src/pci/mod.rs +++ b/drivers/ax-driver/src/pci/mod.rs @@ -31,7 +31,7 @@ use crate::virtio::VirtIoHalImpl; #[cfg(feature = "pci-fdt")] mod fdt; -#[cfg(all(feature = "pci-fdt", feature = "xhci-pci", target_os = "none"))] +#[cfg(all(feature = "pci-fdt", target_os = "none"))] pub(crate) use fdt::fdt_irq_for_endpoint; const MAX_PCIE_LEGACY_IRQS: usize = 8; diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 64ac6c5bf1..fbcf3b9aa4 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -18,7 +18,6 @@ default = ["dynamic_debug"] dev-log = [] ext4 = ["ax-fs/ext4"] input = ["dep:ax-input", "ax-feat/input"] -kcov = ["ax-hal/starry-kcov"] memtrack = ["ax-feat/dwarf", "ax-alloc/tracking", "dep:gimli"] rknpu = ["dep:ax-driver", "ax-driver/rknpu"] plat-dyn = [ diff --git a/os/StarryOS/kernel/src/file/fs.rs b/os/StarryOS/kernel/src/file/fs.rs index 5c3ae9163d..214a1aa8b0 100644 --- a/os/StarryOS/kernel/src/file/fs.rs +++ b/os/StarryOS/kernel/src/file/fs.rs @@ -16,8 +16,6 @@ use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::{AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, O_APPEND, O_EXCL}; use super::{FileLike, Kstat, get_file_like}; -#[cfg(feature = "kcov")] -use crate::pseudofs::DeviceMmap; use crate::{ file::{IoDst, IoSrc}, pseudofs::Device, @@ -119,57 +117,25 @@ pub struct File { open_flags: u32, nonblock: AtomicBool, append: AtomicBool, - /// Per-fd kcov state, created when opening `/dev/kcov`. - #[cfg(feature = "kcov")] - kcov_state: Option>, } impl File { pub fn new(inner: ax_fs::File, open_flags: u32) -> Self { - #[cfg(feature = "kcov")] - let kcov_state = Self::detect_kcov(&inner); Self { inner, open_flags, nonblock: AtomicBool::new(false), append: AtomicBool::new(open_flags & O_APPEND != 0), - #[cfg(feature = "kcov")] - kcov_state, } } pub fn inner(&self) -> &ax_fs::File { &self.inner } - - /// Detect if this file is backed by the kcov device and create per-fd state. - #[cfg(feature = "kcov")] - fn detect_kcov(inner: &ax_fs::File) -> Option> { - let backend = inner.backend().ok()?; - let FileBackend::Direct(loc) = backend else { - return None; - }; - let device = loc.entry().downcast::().ok()?; - if device - .inner() - .as_any() - .downcast_ref::() - .is_some() - { - Some(Arc::new(crate::kcov::KcovFdState::new())) - } else { - None - } - } } impl Drop for File { fn drop(&mut self) { - #[cfg(feature = "kcov")] - if let Some(ref kcov_state) = self.kcov_state { - kcov_state.on_close(); - } - if let Ok(device) = self.inner.location().entry().downcast::() { device.inner().close(self.open_flags & O_EXCL != 0); } @@ -230,21 +196,9 @@ impl FileLike for File { } fn ioctl(&self, cmd: u32, arg: usize) -> AxResult { - #[cfg(feature = "kcov")] - if let Some(ref kcov_state) = self.kcov_state { - return kcov_state.ioctl(cmd, arg); - } self.inner().backend()?.location().ioctl(cmd, arg) } - #[cfg(feature = "kcov")] - fn device_mmap(&self, offset: u64) -> AxResult { - if let Some(ref kcov_state) = self.kcov_state { - return Ok(kcov_state.mmap(offset)); - } - Err(AxError::BadFileDescriptor) - } - fn file_mmap(&self) -> AxResult<(FileBackend, FileFlags)> { Ok((self.inner().backend()?.clone(), self.inner().flags())) } diff --git a/os/StarryOS/kernel/src/kcov/mod.rs b/os/StarryOS/kernel/src/kcov/mod.rs deleted file mode 100644 index 556c1ce65a..0000000000 --- a/os/StarryOS/kernel/src/kcov/mod.rs +++ /dev/null @@ -1,418 +0,0 @@ -//! KCOV (Kernel Code Coverage) support for fuzzing tools like syzkaller. -//! -//! Exposes `/dev/kcov` as a character device. Userspace opens it, initializes -//! a trace buffer via `KCOV_INIT_TRACE`, mmap's the buffer, enables coverage -//! via `KCOV_ENABLE`, runs the workload, and disables via `KCOV_DISABLE`. -//! Currently only KCOV_TRACE_PC is implemented, KCOV_TRACE_CMP is not yet implemented. - -use alloc::sync::Arc; -use core::any::Any; - -use ax_errno::AxError; -use ax_hal::{kcov::KCOV_GLOBAL_GATE, mem::phys_to_virt, paging::PageSize}; -use ax_memory_addr::PAGE_SIZE_4K; -use ax_sync::Mutex; -use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult}; - -use crate::{ - mm::SharedPages, - pseudofs::{DeviceMmap, DeviceOps}, - task::AsThread, -}; - -// ---- ioctl command encoding (Linux uapi) ---- - -const fn _ioc(dir: u32, ty: u8, nr: u32, size: usize) -> u32 { - (dir << 30) | ((ty as u32) << 8) | nr | ((size as u32) << 16) -} -const fn _io(ty: u8, nr: u32) -> u32 { - _ioc(0, ty, nr, 0) -} -const fn _ior(ty: u8, nr: u32, size: usize) -> u32 { - _ioc(2, ty, nr, size) -} - -/// Initialize trace collection with the given buffer size (in u64 words). -pub const KCOV_INIT_TRACE: u32 = _ior(b'c', 1, core::mem::size_of::()); -/// Enable coverage collection for the current thread. -pub const KCOV_ENABLE: u32 = _io(b'c', 100); -/// Disable coverage collection for the current thread. -pub const KCOV_DISABLE: u32 = _io(b'c', 101); -/// Reset coverage collection (zero the count word). Used by syzkaller in -/// read-only coverage mode; for writable buffers userspace writes 0 to -/// `buf[0]` directly. -pub const KCOV_RESET_TRACE: u32 = _io(b'c', 104); - -// Userspace ABI constants (ioctl arguments — match Linux uapi). -/// Trace program counters (PCs). -pub const KCOV_TRACE_PC: u32 = 0; -/// Trace comparison operations (not yet implemented). -pub const KCOV_TRACE_CMP: u32 = 1; - -// Internal mode constants. -/// After open — no buffer allocated (per-fd initial state). -pub const KCOV_MODE_DISABLED: u32 = 0; -/// After INIT_TRACE — buffer allocated, waiting for ENABLE. -pub const KCOV_MODE_INIT: u32 = 1; -/// After ENABLE(KCOV_TRACE_PC) — actively recording PCs. -pub const KCOV_MODE_TRACE_PC: u32 = 2; -/// After ENABLE(KCOV_TRACE_CMP) — reserved, not yet implemented. -pub const KCOV_MODE_TRACE_CMP: u32 = 3; - -/// Maximum number of coverage entries in the buffer. -/// -/// Must be at least 512K (syzkaller's default `kCoverSize`). The Linux -/// kernel defines `KCOV_MAX_ENTRIES = 1 << 24` on 64-bit; 1M gives us -/// an 8 MB buffer ceiling with headroom for fuzzing workloads. -pub const KCOV_MAX_ENTRIES: usize = 1024 * 1024; - -// ---- Types ---- - -/// Per-thread KCOV state, stored on the `Thread` struct for lock-free access -/// from the hot path (`kcov_trace_pc_impl`). -#[derive(Clone)] -pub struct KcovThreadState { - /// Physical pages backing the shared coverage buffer. - pub buf_pages: Arc, - /// Maximum count value: when `buf[0]` reaches this, the buffer is full. - /// Equals `cover_size - 1` (i.e. the number of available PC slots). - /// Matches Linux `kcov->size` semantics where the count stops at `size - 1`. - pub buf_entries: usize, - /// Current trace mode (`KCOV_TRACE_PC`, etc.). - pub mode: u32, -} - -/// Per-fd KCOV state, stored in the kernel `File` struct. -/// -/// Each `open("/dev/kcov")` creates its own `KcovFdState`, matching Linux -/// behavior where each file description has an independent kcov instance. -pub struct KcovFdState { - inner: Mutex, -} - -struct KcovFdInner { - mode: u32, - buf_pages: Option>, - buf_entries: usize, - /// TID of the thread that enabled kcov on this fd. - /// Linux stores `kcov->t = current` and verifies on DISABLE. - tracer_tid: Option, -} - -impl KcovFdState { - /// Create a new per-fd kcov instance in DISABLED mode. - pub fn new() -> Self { - Self { - inner: Mutex::new(KcovFdInner { - mode: KCOV_MODE_DISABLED, - buf_pages: None, - buf_entries: 0, - tracer_tid: None, - }), - } - } - - /// Handle ioctl commands for this kcov instance. - pub fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult { - match cmd { - KCOV_INIT_TRACE => { - let cover_size = arg; - if !(2..=KCOV_MAX_ENTRIES).contains(&cover_size) { - return Err(VfsError::InvalidInput); - } - - let mut inner = self.inner.lock(); - // Only one INIT_TRACE per fd (Linux: EBUSY on second call). - if inner.mode != KCOV_MODE_DISABLED { - return Err(VfsError::ResourceBusy); - } - - // Buffer layout: [count: u64 | pc[0]: u64 | ... | pc[N-1]: u64] - let total_entries = cover_size; - let buf_byte_size = total_entries * core::mem::size_of::(); - let num_pages = buf_byte_size.div_ceil(PAGE_SIZE_4K); - let aligned_size = num_pages * PAGE_SIZE_4K; - let pages = Arc::new( - SharedPages::new(aligned_size, PageSize::Size4K) - .map_err(|_| VfsError::InvalidInput)?, - ); - - // Zero the count word at the start of the buffer. - let base_vaddr = phys_to_virt(pages.phys_pages[0]); - unsafe { - core::ptr::write_volatile(base_vaddr.as_mut_ptr_of::(), 0u64); - } - - inner.mode = KCOV_MODE_INIT; - inner.buf_pages = Some(pages); - inner.buf_entries = total_entries - 1; - Ok(0) - } - - KCOV_ENABLE => { - let mode_arg = arg as u32; - let internal_mode = match mode_arg { - KCOV_TRACE_PC => KCOV_MODE_TRACE_PC, - KCOV_TRACE_CMP => return Err(VfsError::InvalidInput), - _ => return Err(VfsError::InvalidInput), - }; - - let task = ax_task::current(); - let mut inner = self.inner.lock(); - if inner.mode != KCOV_MODE_INIT { - // Linux: ENABLE before INIT_TRACE (or double ENABLE) → EINVAL. - return Err(VfsError::InvalidInput); - } - - // Check thread is not already tracing with another fd instance. - // Linux: a thread can have at most one kcov instance enabled. - if let Some(thr) = task.try_as_thread() - && thr.with_kcov(|k| k.is_some()) - { - return Err(VfsError::ResourceBusy); - } - - inner.mode = internal_mode; - inner.tracer_tid = Some(task.id().as_u64()); - - // Let the hot path know at least one thread is tracing. - unsafe { - KCOV_GLOBAL_GATE = 1; - } - - // Store snapshot on Thread for lock-free hot path access. - if let Some(thr) = task.try_as_thread() { - let thread_state = KcovThreadState { - buf_pages: inner.buf_pages.clone().unwrap(), - buf_entries: inner.buf_entries, - mode: internal_mode, - }; - thr.set_kcov(Some(thread_state)); - } - - Ok(0) - } - - KCOV_DISABLE => { - // Linux: arg must be 0, else EINVAL. - if arg != 0 { - return Err(VfsError::InvalidInput); - } - - let task = ax_task::current(); - let mut inner = self.inner.lock(); - - // Linux: current->kcov != kcov → EINVAL. - // Catches DISABLE before ENABLE, DISABLE from wrong thread, - // and DISABLE after DISABLE (all non-tracing states). - if inner.tracer_tid != Some(task.id().as_u64()) { - return Err(VfsError::InvalidInput); - } - - inner.mode = KCOV_MODE_INIT; - inner.tracer_tid = None; - - if let Some(thr) = task.try_as_thread() { - thr.set_kcov(None); - } - - Ok(0) - } - - KCOV_RESET_TRACE => { - // Linux: arg must be 0, fd must be active, and caller must be tracer. - if arg != 0 { - return Err(VfsError::InvalidInput); - } - let task = ax_task::current(); - let inner = self.inner.lock(); - if inner.mode != KCOV_MODE_TRACE_PC && inner.mode != KCOV_MODE_TRACE_CMP { - return Err(VfsError::InvalidInput); - } - // Upcoming Linux KCOV_RESET_TRACE patch: only the tracer may reset. - if inner.tracer_tid != Some(task.id().as_u64()) { - return Err(VfsError::InvalidInput); - } - if let Some(ref pages) = inner.buf_pages { - let count_vaddr = phys_to_virt(pages.phys_pages[0]); - unsafe { - core::ptr::write_volatile(count_vaddr.as_mut_ptr_of::(), 0u64); - } - } - Ok(0) - } - - _ => Err(VfsError::NotATty), - } - } - - /// Handle mmap for this kcov instance. - pub fn mmap(&self, offset: u64) -> DeviceMmap { - // Linux kcov requires vm_pgoff == 0; non-zero offset is rejected. - if offset != 0 { - return DeviceMmap::NotConfigured; - } - let inner = self.inner.lock(); - match inner.buf_pages { - Some(ref pages) => DeviceMmap::SharedPages(pages.clone()), - None => DeviceMmap::NotConfigured, - } - } - - /// Called when the last `File` reference to this fd is dropped. - /// - /// The shared fd state (buf_pages, mode, tracer_tid) is always torn - /// down — this is the final close. Per-thread kcov state is cleared - /// *only* when the calling thread is the tracer, preventing a non- - /// tracer final close (e.g. a child after fork that outlives the - /// parent) from writing to an unreachable buffer. Matches Linux - /// close semantics where a task's `kcov` reference keeps coverage - /// alive independently of the fd's lifetime. - pub fn on_close(&self) { - let mut inner = self.inner.lock(); - if inner.mode == KCOV_MODE_TRACE_PC || inner.mode == KCOV_MODE_TRACE_CMP { - // Clear per-thread state only if the closing thread is the tracer. - // If a non-tracer drops the last File ref (e.g. a forked child - // outliving its parent), the tracer's thread still holds its own - // Arc and will keep writing — but userspace can no - // longer read the buffer since the fd is gone. - if inner.tracer_tid == Some(ax_task::current().id().as_u64()) - && let Some(thr) = ax_task::current().try_as_thread() - { - thr.set_kcov(None); - } - inner.mode = KCOV_MODE_INIT; - inner.buf_pages = None; - inner.buf_entries = 0; - inner.tracer_tid = None; - } else { - inner.mode = KCOV_MODE_DISABLED; - inner.buf_pages = None; - inner.buf_entries = 0; - inner.tracer_tid = None; - } - } -} - -// ---- /dev/kcov device (no-op singleton) ---- - -/// The `/dev/kcov` character device. -/// -/// Per-fd ioctl and mmap are handled by `KcovFdState` in the kernel `File` -/// struct, not by this shared `DeviceOps` singleton. All operations are no-ops -/// — they are intercepted before reaching here. -pub struct KcovDevice; - -impl DeviceOps for KcovDevice { - fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult { - Err(AxError::InvalidInput) - } - - fn write_at(&self, _buf: &[u8], _offset: u64) -> VfsResult { - Err(AxError::InvalidInput) - } - - fn close(&self, _exclusive: bool) {} - - fn ioctl(&self, _cmd: u32, _arg: usize) -> VfsResult { - Err(VfsError::InvalidInput) - } - - fn mmap(&self, _offset: u64, _length: u64) -> DeviceMmap { - DeviceMmap::NotConfigured - } - - fn as_any(&self) -> &dyn Any { - self - } - - fn flags(&self) -> NodeFlags { - NodeFlags::NON_CACHEABLE - } -} - -/// Clean up kcov for the given thread (called on thread exit). -/// -/// With per-fd state, the `KcovFdState::on_close` handles cleanup when the -/// `File` is dropped. This is a safety net for thread exit — if any kcov -/// reference remains on the `Thread`, it is cleared here. -pub fn disable_for_thread(_tid: u32) { - if let Some(thr) = ax_task::current().try_as_thread() { - thr.set_kcov(None); - } -} - -/// Ensure the child starts with kcov disabled after fork. -/// -/// Linux kcov(1): "Coverage collection is disabled in the child after fork()." -/// With per-fd state the child's `Thread` is created fresh with no kcov, so -/// this is a no-op safety net. -pub fn on_fork(_child_tid: u32) {} - -// ---- Hot path ---- - -/// Records `pc` (the caller's return address) into the current thread's KCOV -/// coverage buffer. Called from the per-arch `__sanitizer_cov_trace_pc` -/// assembly trampoline (now in `axhal::kcov`). -/// -/// This runs in the hot path of every instrumented basic block — it must be -/// lock-free and fast. -/// -/// `no_mangle` + `extern "C"` so that the axhal trampoline can resolve this -/// symbol at link time. -#[unsafe(no_mangle)] -extern "C" fn kcov_trace_pc_impl(pc: u64) { - // Fast bail-out: skip all task/thread lookups when no thread has - // enabled kcov (e.g. during boot, before the test starts tracing). - if unsafe { KCOV_GLOBAL_GATE == 0 } { - return; - } - - let task = ax_task::current(); - let Some(thr) = task.try_as_thread() else { - return; - }; - - // Borrow the KCOV state through a closure to avoid cloning - // Arc on every traced basic block. - thr.with_kcov(|kcov| { - let Some(kcov) = kcov else { - return; - }; - if kcov.mode != KCOV_MODE_TRACE_PC { - return; - } - - let pages = &kcov.buf_pages.phys_pages; - let entries = kcov.buf_entries; - - // Buffer layout: page 0 starts with [count: u64 | pc[0]: u64 | ...] - let count_vaddr = phys_to_virt(pages[0]); - let count_ptr = count_vaddr.as_mut_ptr_of::(); - - // Read current count (userspace may be reading concurrently) - let idx = unsafe { core::ptr::read_volatile(count_ptr) }; - if idx >= entries as u64 { - return; // buffer full - } - - // Write PC to buffer at offset (1 + idx) - let target_byte_offset = (1 + idx as usize) * core::mem::size_of::(); - let page_idx = target_byte_offset / PAGE_SIZE_4K; - let page_off = target_byte_offset % PAGE_SIZE_4K; - - if page_idx < pages.len() { - let entry_vaddr = phys_to_virt(pages[page_idx]); - unsafe { - let entry_ptr = entry_vaddr.as_mut_ptr().add(page_off) as *mut u64; - // Ordering: write PC first, then smp_wmb(), then publish the - // count. Without the barrier a reader on another core sees - // the new count but stale PC data on weakly-ordered - // architectures (aarch64, riscv64, loongarch64). - core::ptr::write_volatile(entry_ptr, pc); - core::sync::atomic::fence(core::sync::atomic::Ordering::Release); - core::ptr::write_volatile(count_ptr, idx + 1); - } - } - }); -} diff --git a/os/StarryOS/kernel/src/lib.rs b/os/StarryOS/kernel/src/lib.rs index 10295f3eaf..2b88e0a84f 100644 --- a/os/StarryOS/kernel/src/lib.rs +++ b/os/StarryOS/kernel/src/lib.rs @@ -19,8 +19,6 @@ pub mod entry; mod config; mod file; -#[cfg(feature = "kcov")] -mod kcov; mod mm; mod pseudofs; mod stop_machine; diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs b/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs index 416e334084..f9954f359c 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs @@ -1,11 +1,11 @@ use alloc::sync::Arc; use ax_errno::AxResult; -use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor}; +use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_memory_addr::{PhysAddr, PhysAddrRange, VirtAddr, VirtAddrRange}; use ax_sync::Mutex; -use super::{AddrSpace, Backend, BackendOps}; +use super::{AddrSpace, Backend, BackendOps, pages_in}; /// Linear mapping backend. /// @@ -59,7 +59,13 @@ impl BackendOps for LinearBackend { fn unmap(&self, range: VirtAddrRange, pt: &mut PageTableCursor) -> AxResult { let pa_range = PhysAddrRange::from_start_size(self.pa(range.start), range.size()); debug!("Linear::unmap: {range:?} -> {pa_range:?}"); - pt.unmap_region(range.start, range.size())?; + for vaddr in pages_in(range, PageSize::Size4K)? { + match pt.unmap(vaddr) { + Ok((_, _, page_size)) => debug_assert_eq!(page_size, PageSize::Size4K), + Err(PagingError::NotMapped) => {} + Err(err) => return Err(err.into()), + } + } Ok(()) } diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs b/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs index f00425c662..5ab9d670af 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs @@ -2,7 +2,7 @@ use alloc::{sync::Arc, vec::Vec}; use core::ops::Deref; use ax_errno::AxResult; -use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor}; +use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_memory_addr::{MemoryAddr, PhysAddr, VirtAddr, VirtAddrRange}; use ax_sync::Mutex; @@ -96,7 +96,11 @@ impl BackendOps for SharedBackend { fn unmap(&self, range: VirtAddrRange, pt: &mut PageTableCursor) -> AxResult { debug!("Shared::unmap: {:?}", range); for vaddr in pages_in(range, self.pages.size)? { - pt.unmap(vaddr)?; + match pt.unmap(vaddr) { + Ok((_, _, page_size)) => debug_assert_eq!(page_size, self.pages.size), + Err(PagingError::NotMapped) => {} + Err(err) => return Err(err.into()), + } } Ok(()) } diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index 8c2ab6448e..459af3989a 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -293,17 +293,6 @@ fn builder(fs: Arc) -> DirMaker { ), ); - #[cfg(feature = "kcov")] - root.add( - "kcov", - Device::new( - fs.clone(), - NodeType::CharacterDevice, - DeviceId::new(10, 57), - Arc::new(crate::kcov::KcovDevice), - ), - ); - // This is mounted to a tmpfs in `new_procfs` root.add( "shm", diff --git a/os/StarryOS/kernel/src/pseudofs/device.rs b/os/StarryOS/kernel/src/pseudofs/device.rs index bfe3c02522..6a3bdc5dc0 100644 --- a/os/StarryOS/kernel/src/pseudofs/device.rs +++ b/os/StarryOS/kernel/src/pseudofs/device.rs @@ -11,8 +11,6 @@ use axpoll::{IoEvents, Pollable}; use inherit_methods_macro::inherit_methods; use super::{SimpleFs, SimpleFsNode}; -#[cfg(feature = "kcov")] -use crate::mm::SharedPages; /// Mmap behavior for devices. #[derive(Clone)] @@ -25,15 +23,6 @@ pub enum DeviceMmap { /// Maps to a cached file. Cache(CachedFile), - - #[cfg(feature = "kcov")] - /// The device supports mmap but is not yet configured - /// (→ EINVAL, matches Linux kcov semantics). - NotConfigured, - - #[cfg(feature = "kcov")] - /// Maps to a pre-allocated set of shared physical pages (kernel↔userspace). - SharedPages(Arc), } /// Trait for device operations. diff --git a/os/StarryOS/kernel/src/pseudofs/mod.rs b/os/StarryOS/kernel/src/pseudofs/mod.rs index cb04e1ba5c..7c62b472bb 100644 --- a/os/StarryOS/kernel/src/pseudofs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/mod.rs @@ -8,7 +8,6 @@ mod dyn_debug; mod file; mod fs; mod proc; -#[cfg(not(feature = "plat-dyn"))] mod sysfs; mod tmp; #[cfg(feature = "plat-dyn")] @@ -96,10 +95,9 @@ pub fn mount_all() -> LinuxResult<()> { mount_at(&fs, "/proc", proc::new_procfs())?; - #[cfg(feature = "plat-dyn")] - mount_at(&fs, "/sys", usbfs::new_sysfs())?; - #[cfg(not(feature = "plat-dyn"))] mount_at(&fs, "/sys", sysfs::new_sysfs())?; + #[cfg(feature = "plat-dyn")] + mount_at(&fs, "/sys/bus/usb", usbfs::new_bus_usb_sysfs())?; mount_at(&fs, "/sys/kernel/debug", debug::new_debugfs())?; diff --git a/os/StarryOS/kernel/src/pseudofs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/sysfs.rs index b22a5c38d9..dd41bacff0 100644 --- a/os/StarryOS/kernel/src/pseudofs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/sysfs.rs @@ -303,13 +303,19 @@ struct BusDir { impl SimpleDirOps for BusDir { fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["platform"].into_iter().map(Cow::Borrowed)) + #[cfg(feature = "plat-dyn")] + let names: &'static [&'static str] = &["platform", "usb"]; + #[cfg(not(feature = "plat-dyn"))] + let names: &'static [&'static str] = &["platform"]; + Box::new(names.iter().copied().map(Cow::Borrowed)) } fn lookup_child(&self, name: &str) -> VfsResult { let fs = self.fs.clone(); Ok(NodeOpsMux::Dir(match name { "platform" => SimpleDir::new_maker(fs.clone(), Arc::new(PlatformBusClassDir)), + #[cfg(feature = "plat-dyn")] + "usb" => SimpleDir::new_maker(fs.clone(), Arc::new(DirMapping::new())), _ => return Err(VfsError::NotFound), })) } diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs index c14cd77dc6..66d5ae56fb 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs @@ -69,8 +69,8 @@ pub(crate) fn new_usbfs() -> LinuxResult { Ok(create_filesystem(manager)) } -pub(crate) fn new_sysfs() -> Filesystem { - sysfs::new_sysfs() +pub(crate) fn new_bus_usb_sysfs() -> Filesystem { + sysfs::new_bus_usb_sysfs() } pub(crate) fn is_usbfs_device(inner: &dyn Any) -> bool { diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs index 6b672a95d8..78850eb3b6 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs @@ -7,15 +7,15 @@ use super::{ irq, manager::UsbFsManager, }; -use crate::pseudofs::{DirMapping, NodeOpsMux, SimpleDir, SimpleDirOps, SimpleFile, SimpleFs}; +use crate::pseudofs::{NodeOpsMux, SimpleDir, SimpleDirOps, SimpleFile, SimpleFs}; const SYSFS_MAGIC: u32 = 0x6265_6572; -pub(super) fn new_sysfs() -> Filesystem { +pub(super) fn new_bus_usb_sysfs() -> Filesystem { SimpleFs::new_with("sysfs".into(), SYSFS_MAGIC, |fs| { SimpleDir::new_maker( fs.clone(), - Arc::new(SysRootDir { + Arc::new(SysUsbDir { fs, manager: irq::manager(), }), @@ -39,166 +39,6 @@ fn symlink(fs: Arc, target: &'static str) -> NodeOpsMux { .into() } -struct SysRootDir { - fs: Arc, - manager: Option>, -} - -impl SimpleDirOps for SysRootDir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["bus", "class", "kernel"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "bus" => Ok(dir( - self.fs.clone(), - SysBusDir { - fs: self.fs.clone(), - manager: self.manager.clone(), - }, - )), - "class" => Ok(dir( - self.fs.clone(), - SysClassDir { - fs: self.fs.clone(), - }, - )), - "kernel" => Ok(dir( - self.fs.clone(), - SysKernelDir { - fs: self.fs.clone(), - }, - )), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - -struct SysKernelDir { - fs: Arc, -} - -impl SimpleDirOps for SysKernelDir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["debug"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "debug" => Ok(dir(self.fs.clone(), DirMapping::new())), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - -struct SysClassDir { - fs: Arc, -} - -impl SimpleDirOps for SysClassDir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["graphics"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "graphics" => Ok(dir( - self.fs.clone(), - SysGraphicsDir { - fs: self.fs.clone(), - }, - )), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - -struct SysGraphicsDir { - fs: Arc, -} - -impl SimpleDirOps for SysGraphicsDir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["fb0"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "fb0" => Ok(dir( - self.fs.clone(), - SysFb0Dir { - fs: self.fs.clone(), - }, - )), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - -struct SysFb0Dir { - fs: Arc, -} - -impl SimpleDirOps for SysFb0Dir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["device"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "device" => Ok(dir( - self.fs.clone(), - SysFb0DeviceDir { - fs: self.fs.clone(), - }, - )), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - -struct SysFb0DeviceDir { - fs: Arc, -} - -impl SimpleDirOps for SysFb0DeviceDir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["subsystem"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "subsystem" => Ok(symlink(self.fs.clone(), "whatever")), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - -struct SysBusDir { - fs: Arc, - manager: Option>, -} - -impl SimpleDirOps for SysBusDir { - fn child_names<'a>(&'a self) -> Box> + 'a> { - Box::new(["usb"].into_iter().map(Cow::Borrowed)) - } - - fn lookup_child(&self, name: &str) -> VfsResult { - match name { - "usb" => Ok(dir( - self.fs.clone(), - SysUsbDir { - fs: self.fs.clone(), - manager: self.manager.clone(), - }, - )), - _ => Err(ax_errno::AxError::NotFound), - } - } -} - struct SysUsbDir { fs: Arc, manager: Option>, diff --git a/os/StarryOS/kernel/src/syscall/mm/mmap.rs b/os/StarryOS/kernel/src/syscall/mm/mmap.rs index 976447b064..0be64134f7 100644 --- a/os/StarryOS/kernel/src/syscall/mm/mmap.rs +++ b/os/StarryOS/kernel/src/syscall/mm/mmap.rs @@ -190,8 +190,6 @@ pub fn sys_mmap( .expect("file-backed mmap has cached device_mmap") { Ok(DeviceMmap::Physical(_)) | Ok(DeviceMmap::Cache(_)) => false, - #[cfg(feature = "kcov")] - Ok(DeviceMmap::SharedPages(_)) | Ok(DeviceMmap::NotConfigured) => false, Ok(DeviceMmap::None) | Err(_) => true, } } @@ -311,10 +309,6 @@ pub fn sys_mmap( ) } Ok(DeviceMmap::None) => return Err(AxError::NoSuchDevice), - #[cfg(feature = "kcov")] - Ok(DeviceMmap::NotConfigured) => return Err(AxError::InvalidInput), - #[cfg(feature = "kcov")] - Ok(DeviceMmap::SharedPages(pages)) => Backend::new_shared(start, pages), Ok(_) => return Err(AxError::InvalidInput), Err(_) => { // Fall through to file-backed mmap @@ -352,10 +346,6 @@ pub fn sys_mmap( DeviceMmap::None => { return Err(AxError::NoSuchDevice); } - #[cfg(feature = "kcov")] - DeviceMmap::NotConfigured => { - return Err(AxError::InvalidInput); - } DeviceMmap::Physical(range) => { mapping_flags |= MappingFlags::UNCACHED; if range.is_empty() { @@ -378,10 +368,6 @@ pub fn sys_mmap( &curr.as_thread().proc_data.aspace(), true, ), - #[cfg(feature = "kcov")] - DeviceMmap::SharedPages(pages) => { - Backend::new_shared(start, pages) - } } } } diff --git a/os/StarryOS/kernel/src/syscall/task/clone.rs b/os/StarryOS/kernel/src/syscall/task/clone.rs index 8637b15474..e17de3264e 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone.rs @@ -303,13 +303,6 @@ impl CloneArgs { let task = spawn_task(new_task); add_task_to_table(&task); - // Linux kcov(1): coverage collection is disabled in the child after - // fork(). The child's Thread is always created with kcov: None and a - // new TID not present in the KCOV state table, but we clean up - // explicitly for consistency and future-proofing. - #[cfg(feature = "kcov")] - crate::kcov::on_fork(tid); - // Block the parent until the child exec's or exits. if needs_vfork_block { new_proc_data.wait_vfork_done(); diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index 336cef3d57..6d61abe40b 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -34,8 +34,6 @@ pub use self::{ cred::*, futex::*, ops::*, posix_timer::PosixTimerTable, resources::*, signal::*, stat::*, timer::*, user::*, }; -#[cfg(feature = "kcov")] -use crate::kcov::KcovThreadState; use crate::mm::AddrSpace; /// Size of the syscall instruction for the current architecture. @@ -151,10 +149,6 @@ pub struct Thread { /// Process credentials (uid, gid, etc.). cred: SpinNoIrq>, - /// KCOV coverage state for this thread. - #[cfg(feature = "kcov")] - kcov: AssumeSync>>, - /// Signo (as u8) of the synchronous user-mode fault that /// [`raise_signal_fatal`] last force-delivered to this thread, or 0 /// for "no fault dump owed". [`check_signals`] only emits the @@ -192,8 +186,6 @@ impl Thread { pdeathsig: AtomicU32::new(0), no_new_privs: AtomicBool::new(false), cred: SpinNoIrq::new(cred), - #[cfg(feature = "kcov")] - kcov: AssumeSync(RefCell::new(None)), fault_dump_signo: AtomicU8::new(0), }) @@ -307,26 +299,6 @@ impl Thread { self.no_new_privs.store(true, Ordering::Relaxed); } - /// Run a closure with a borrow of the current KCOV state for this thread. - /// - /// Uses `try_borrow` so that a trace call inside `set_kcov`'s - /// `borrow_mut` does not panic when the instrumented hot path - /// re-enters here. Avoids cloning the `Arc` on every - /// hot-path invocation. - #[cfg(feature = "kcov")] - pub fn with_kcov(&self, f: impl FnOnce(Option<&KcovThreadState>) -> R) -> R { - match self.kcov.0.try_borrow() { - Ok(borrow) => f(borrow.as_ref()), - Err(_) => f(None), - } - } - - /// Set the KCOV state for this thread. - #[cfg(feature = "kcov")] - pub fn set_kcov(&self, state: Option) { - *self.kcov.0.borrow_mut() = state; - } - /// Get a snapshot of the current credentials (clones the `Arc`). pub fn cred(&self) -> Arc { self.cred.lock().clone() diff --git a/os/StarryOS/kernel/src/task/ops.rs b/os/StarryOS/kernel/src/task/ops.rs index 68cfda83c9..34d4d974fd 100644 --- a/os/StarryOS/kernel/src/task/ops.rs +++ b/os/StarryOS/kernel/src/task/ops.rs @@ -468,9 +468,6 @@ pub fn do_exit(exit_code: i32, group_exit: bool) { ax_task::yield_now(); } - #[cfg(feature = "kcov")] - crate::kcov::disable_for_thread(curr.id().as_u64() as u32); - let process = &thr.proc_data.proc; // Use the user-visible TID (`thr.tid()`), not the scheduler ID. After // a non-leader `execve`'s de_thread the two differ, and the thread diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index 1171a16c9f..500da6e231 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -15,7 +15,6 @@ version = "0.5.12" [features] default = [] -kcov = ["starry-kernel/kcov"] myplat = ["ax-feat/myplat"] qemu = [ "ax-feat/defplat", diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 9f18b0dad6..3554652a16 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -91,8 +91,6 @@ hv = [ ] axvisor-linker = [] -starry-kcov = [] - # Custom or default platforms myplat = [] plat-dyn = ["axplat-dyn"] diff --git a/os/arceos/modules/axhal/src/kcov.rs b/os/arceos/modules/axhal/src/kcov.rs deleted file mode 100644 index e487b41b5f..0000000000 --- a/os/arceos/modules/axhal/src/kcov.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! Architecture-specific KCOV trampolines. -//! -//! These are the `__sanitizer_cov_trace_pc` entry points that the compiler -//! injects into every instrumented basic block. The recursion guard lives -//! in the per-CPU `.percpu` section so that tracing on one CPU never blocks -//! coverage collection on another under SMP. The guard is checked and set -//! entirely in naked asm *before* any C-ABI call so that LLVM KCOV -//! instrumentation of the callee cannot re-enter this function (infinite -//! recursion guard). - -#![cfg(feature = "starry-kcov")] - -use core::arch::naked_asm; - -use ax_percpu::def_percpu; - -/// Per-CPU recursion guard: nonzero while inside `kcov_trace_pc_impl` on this -/// CPU. Lives in the `.percpu` section so each CPU has its own copy. -/// -/// Checked and set by the architecture-specific naked trampolines before -/// calling `kcov_trace_pc_impl`. The underlying static is named -/// `__PERCPU_IN_KCOV_TRACE` (generated by `#[def_percpu]`) and is referenced -/// directly from asm via `sym __PERCPU_IN_KCOV_TRACE`. -#[def_percpu] -static IN_KCOV_TRACE: u8 = 0; - -/// Safety gate: blocks the trace handler until a thread explicitly enables -/// KCOV via the `KCOV_ENABLE` ioctl (which happens from userspace, long -/// after boot). Without this check, instrumented edges during early boot -/// call `kcov_trace_pc_impl` → `ax_task::current()` before the scheduler -/// has set the per-CPU task pointer (that happens at the end of -/// `primary_init`, well after the first instrumented code runs). -/// -/// `ax_task::current()` then panics with "current task is uninitialized". -/// The panic handler tries `ax_println!`, but UART is not ready yet either: -/// the platform init runs `init_trap` before `console::init_early`, so the -/// UART `LazyInit` is still empty. That is a double-panic, which rustc's -/// abort guard turns into a `ud2` → #UD (unhandled) → #DF (unhandled) → -/// triple fault → CPU reset → Seabios "Booting from ROM…" → boot again → -/// same crash → infinite reset loop. -/// -/// Setting this flag to 1 in `KCOV_ENABLE` is safe because by the time -/// userspace can issue ioctls the boot is complete and all affected -/// subsystems are live. -/// -/// This makes sure initialization of per-cpu is before IN_KCOV_TRACE is resolved. -#[used] -pub static mut KCOV_GLOBAL_GATE: u8 = 0; - -// Provided by the kernel crate (`starry_kernel::kcov`). -unsafe extern "C" { - fn kcov_trace_pc_impl(pc: u64); -} - -// --------------------------------------------------------------------------- -// x86_64 – return address at [rsp] (System V AMD64 ABI) -// per-CPU via gs-segment base -// --------------------------------------------------------------------------- - -#[cfg(target_arch = "x86_64")] -#[unsafe(no_mangle)] -#[unsafe(naked)] -/// # Safety -/// -/// Called by the compiler as a coverage instrumentation hook. The standard -/// x86_64 System V C ABI applies: the return address is on the stack. -/// Must only be called by instrumented code when KCOV is enabled. -pub unsafe extern "C" fn __sanitizer_cov_trace_pc() { - naked_asm!( - "cmp byte ptr [rip + {global}], 0", - "je 2f", - "cmp byte ptr gs:[offset {guard}], 0", - "jne 1f", - "mov byte ptr gs:[offset {guard}], 1", - "mov rdi, [rsp]", - "call {impl}", - "mov byte ptr gs:[offset {guard}], 0", - "1:", - "2:", - "ret", - global = sym KCOV_GLOBAL_GATE, - guard = sym __PERCPU_IN_KCOV_TRACE, - impl = sym kcov_trace_pc_impl, - ); -} - -// --------------------------------------------------------------------------- -// aarch64 – return address in x30 (LR) -// per-CPU via TPIDR_EL1 + symbol VMA -// --------------------------------------------------------------------------- - -#[cfg(target_arch = "aarch64")] -#[unsafe(no_mangle)] -#[unsafe(naked)] -/// # Safety -/// -/// Called by the compiler as a coverage instrumentation hook. The standard -/// AAPCS64 ABI applies: `x30` holds the return address. Must only be called -/// by instrumented code when KCOV is enabled. -pub unsafe extern "C" fn __sanitizer_cov_trace_pc() { - naked_asm!( - "adrp x16, {global}", - "ldrb w17, [x16, #:lo12:{global}]", - "cbz w17, 2f", - "mrs x16, tpidr_el1", - "movz x17, #:abs_g0_nc:{guard}", - "add x16, x16, x17", - "ldrb w17, [x16]", - "cbnz w17, 1f", - "mov w17, #1", - "strb w17, [x16]", - "str x30, [sp, #-16]!", - "mov x0, x30", - "bl {impl}", - "ldr x30, [sp], #16", - // bl {impl} clobbered x16/x17 — recalculate per-CPU address - "mrs x16, tpidr_el1", - "movz x17, #:abs_g0_nc:{guard}", - "add x16, x16, x17", - "strb wzr, [x16]", - "1:", - "2:", - "ret", - global = sym KCOV_GLOBAL_GATE, - guard = sym __PERCPU_IN_KCOV_TRACE, - impl = sym kcov_trace_pc_impl, - ); -} - -// --------------------------------------------------------------------------- -// riscv64 – return address in ra (x1) -// per-CPU via gp + symbol offset -// --------------------------------------------------------------------------- - -#[cfg(target_arch = "riscv64")] -#[unsafe(no_mangle)] -#[unsafe(naked)] -/// # Safety -/// -/// Called by the compiler as a coverage instrumentation hook. The standard -/// RISC-V calling convention applies: `ra` holds the return address. Must -/// only be called by instrumented code when KCOV is enabled. -pub unsafe extern "C" fn __sanitizer_cov_trace_pc() { - naked_asm!( - "1: auipc t0, %pcrel_hi({global})", - "addi t0, t0, %pcrel_lo(1b)", - "lb t1, 0(t0)", - "beqz t1, 2f", - "lui t0, %hi({guard})", - "add t0, t0, gp", - "addi t0, t0, %lo({guard})", - "lb t1, 0(t0)", - "bnez t1, 1f", - "li t1, 1", - "sb t1, 0(t0)", - "addi sp, sp, -16", - "sd ra, 0(sp)", - "mv a0, ra", - "call {impl}", - "ld ra, 0(sp)", - "addi sp, sp, 16", - "lui t0, %hi({guard})", - "add t0, t0, gp", - "addi t0, t0, %lo({guard})", - "sb zero, 0(t0)", - "1:", - "2:", - "ret", - global = sym KCOV_GLOBAL_GATE, - guard = sym __PERCPU_IN_KCOV_TRACE, - impl = sym kcov_trace_pc_impl, - ); -} - -// --------------------------------------------------------------------------- -// loongarch64 – return address in ra ($r1) -// per-CPU via $r21 + symbol offset -// --------------------------------------------------------------------------- - -#[cfg(target_arch = "loongarch64")] -#[unsafe(no_mangle)] -#[unsafe(naked)] -/// # Safety -/// -/// Called by the compiler as a coverage instrumentation hook. The standard -/// LoongArch calling convention applies: `ra` holds the return address. Must -/// only be called by instrumented code when KCOV is enabled. -pub unsafe extern "C" fn __sanitizer_cov_trace_pc() { - naked_asm!( - "pcalau12i $t0, %pc_hi20({global})", - "addi.d $t0, $t0, %pc_lo12({global})", - "ld.b $t1, $t0, 0", - "beqz $t1, 2f", - "lu12i.w $t0, %abs_hi20({guard})", - "ori $t0, $t0, %abs_lo12({guard})", - "add.d $t0, $t0, $r21", - "ld.b $t1, $t0, 0", - "bnez $t1, 1f", - "ori $t1, $zero, 1", - "st.b $t1, $t0, 0", - "addi.d $sp, $sp, -16", - "st.d $ra, $sp, 0", - "ori $a0, $ra, 0", - "bl {impl}", - "ld.d $ra, $sp, 0", - "addi.d $sp, $sp, 16", - "lu12i.w $t0, %abs_hi20({guard})", - "ori $t0, $t0, %abs_lo12({guard})", - "add.d $t0, $t0, $r21", - "st.b $zero, $t0, 0", - "1:", - "2:", - "jirl $zero, $ra, 0", - global = sym KCOV_GLOBAL_GATE, - guard = sym __PERCPU_IN_KCOV_TRACE, - impl = sym kcov_trace_pc_impl, - ); -} diff --git a/os/arceos/modules/axhal/src/lib.rs b/os/arceos/modules/axhal/src/lib.rs index 923397af74..2ee0cc0aa0 100644 --- a/os/arceos/modules/axhal/src/lib.rs +++ b/os/arceos/modules/axhal/src/lib.rs @@ -55,9 +55,6 @@ pub mod irq; #[cfg(feature = "paging")] pub mod paging; -#[cfg(feature = "starry-kcov")] -pub mod kcov; - /// Console input and output. pub mod console { #[cfg(feature = "irq")] diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index 58abad4fb5..e0bab7f43c 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -212,12 +212,7 @@ impl BuildInfo { self.validated_max_cpu_num()?; self.prepare_non_dynamic_platform_for(package, target, plat_dyn, metadata)?; self.resolve_features_with_metadata(package, target, plat_dyn, metadata); - let mut extra_rustflags = toolchain_rustflags(&self.env); - if self.features.iter().any(|f| f == "kcov") { - extra_rustflags.push("-Cllvm-args=-sanitizer-coverage-level=3".to_string()); - extra_rustflags.push("-Cllvm-args=-sanitizer-coverage-trace-pc".to_string()); - extra_rustflags.push("-Cpasses=sancov-module".to_string()); - } + let extra_rustflags = toolchain_rustflags(&self.env); let cargo_target = cargo_target_json_path(target, plat_dyn)?; let cargo_target = cargo_target.display().to_string(); let args = Self::build_cargo_args(&cargo_target, &extra_rustflags); diff --git a/scripts/axbuild/src/test/build.rs b/scripts/axbuild/src/test/build.rs index 1c3d85cb6c..23ec75a500 100644 --- a/scripts/axbuild/src/test/build.rs +++ b/scripts/axbuild/src/test/build.rs @@ -7,7 +7,7 @@ //! - Populate case overlays that will later be injected into the rootfs image use std::{ - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, fs, path::{Path, PathBuf}, process::Command, @@ -154,6 +154,7 @@ pub(crate) fn prepare_grouped_case_assets_sync( .iter() .filter(|subcase| subcase.kind == TestQemuSubcaseKind::C) .collect::>(); + let c_subcases = selected_grouped_c_subcases(case, c_subcases)?; if !c_subcases.is_empty() { prepare_grouped_c_subcases_sync(arch, case, &c_subcases, layout, config)?; @@ -168,6 +169,116 @@ pub(crate) fn prepare_grouped_case_assets_sync( crate::rootfs::inject::inject_overlay(case_rootfs, &layout.overlay_dir) } +fn selected_grouped_c_subcases<'a>( + case: &TestQemuCase, + subcases: Vec<&'a TestQemuSubcase>, +) -> anyhow::Result> { + let Some(command_names) = direct_usr_bin_command_names(&case.test_commands) else { + return Ok(subcases); + }; + + let mut known_names = BTreeSet::new(); + let mut selected = Vec::new(); + for subcase in subcases { + let subcase_names = grouped_c_subcase_binary_names(subcase)?; + known_names.extend(subcase_names.iter().cloned()); + if subcase_names + .iter() + .any(|name| command_names.contains(name.as_str())) + { + selected.push(subcase); + } + } + + let missing = command_names + .difference(&known_names) + .cloned() + .collect::>(); + ensure!( + missing.is_empty(), + "grouped qemu case `{}` references test command(s) without C subcases: {}", + case.qemu_config_path.display(), + missing.join(", ") + ); + + Ok(selected) +} + +fn direct_usr_bin_command_names(commands: &[String]) -> Option> { + let mut names = BTreeSet::new(); + for command in commands { + let token = command.split_ascii_whitespace().next()?; + let name = token.strip_prefix("/usr/bin/")?; + if name.is_empty() + || !name + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.')) + { + return None; + } + names.insert(name.to_string()); + } + Some(names) +} + +fn grouped_c_subcase_binary_names(subcase: &TestQemuSubcase) -> anyhow::Result> { + let mut names = BTreeSet::from([subcase.name.clone()]); + let cmake_lists = subcase + .case_dir + .join(CASE_C_DIR_NAME) + .join(CASE_CMAKE_FILE_NAME); + if cmake_lists.is_file() { + let content = fs::read_to_string(&cmake_lists) + .with_context(|| format!("failed to read {}", cmake_lists.display()))?; + names.extend(cmake_install_target_names(&content)); + } + Ok(names) +} + +fn cmake_install_target_names(content: &str) -> BTreeSet { + let mut names = BTreeSet::new(); + for line in content.lines() { + let line = line.split_once('#').map_or(line, |(code, _)| code); + if !line.contains("install") || !line.contains("TARGETS") { + continue; + } + + let mut collect_targets = false; + for token in line.split(|ch: char| ch.is_ascii_whitespace() || matches!(ch, '(' | ')')) { + if token.is_empty() { + continue; + } + + let keyword = token.to_ascii_uppercase(); + if collect_targets { + if matches!( + keyword.as_str(), + "ARCHIVE" + | "BUNDLE" + | "COMPONENT" + | "CONFIGURATIONS" + | "DESTINATION" + | "EXCLUDE_FROM_ALL" + | "FRAMEWORK" + | "LIBRARY" + | "NAMELINK_COMPONENT" + | "OBJECTS" + | "OPTIONAL" + | "PERMISSIONS" + | "RENAME" + | "RUNTIME" + ) { + break; + } + names.insert(token.to_string()); + } else if keyword == "TARGETS" { + collect_targets = true; + } + } + } + names +} + fn prepare_grouped_c_subcases_sync( arch: &str, case: &TestQemuCase, @@ -1354,6 +1465,34 @@ mod tests { } } + fn fake_c_subcase( + root: &Path, + case: &TestQemuCase, + name: &str, + install_targets: &[&str], + ) -> TestQemuSubcase { + let case_dir = case.case_dir.join(name); + let c_dir = case_dir.join("c"); + fs::create_dir_all(&c_dir).unwrap(); + fs::write( + c_dir.join(CASE_CMAKE_FILE_NAME), + format!( + "add_executable({target} src/main.c)\ninstall(TARGETS {} RUNTIME DESTINATION \ + usr/bin)\n", + install_targets.join(" "), + target = install_targets.first().unwrap_or(&name) + ), + ) + .unwrap(); + + assert!(case_dir.starts_with(root)); + TestQemuSubcase { + name: name.to_string(), + case_dir, + kind: TestQemuSubcaseKind::C, + } + } + fn command_env(command: &Command, key: &str) -> Option { command.get_envs().find_map(|(name, value)| { (name == OsStr::new(key)) @@ -1458,6 +1597,66 @@ mod tests { ); } + #[test] + fn grouped_c_subcases_keep_only_direct_usr_bin_commands() { + let root = tempdir().unwrap(); + let mut case = fake_case(root.path(), "bugfix"); + case.test_commands = vec![ + "/usr/bin/alpha".to_string(), + "/usr/bin/gamma --stress".to_string(), + ]; + + let alpha = fake_c_subcase(root.path(), &case, "alpha", &["alpha"]); + let beta = fake_c_subcase(root.path(), &case, "beta", &["beta"]); + let gamma = fake_c_subcase(root.path(), &case, "gamma-dir", &["gamma"]); + let subcases = vec![&alpha, &beta, &gamma]; + + let selected = selected_grouped_c_subcases(&case, subcases).unwrap(); + assert_eq!( + selected + .iter() + .map(|subcase| subcase.name.as_str()) + .collect::>(), + vec!["alpha", "gamma-dir"] + ); + } + + #[test] + fn grouped_c_subcases_keep_all_dynamic_shell_commands() { + let root = tempdir().unwrap(); + let mut case = fake_case(root.path(), "syscall"); + case.test_commands = + vec!["for bin in /usr/bin/starry-test-suit/*; do \"$bin\"; done".to_string()]; + + let alpha = fake_c_subcase(root.path(), &case, "alpha", &["alpha"]); + let beta = fake_c_subcase(root.path(), &case, "beta", &["beta"]); + let subcases = vec![&alpha, &beta]; + + let selected = selected_grouped_c_subcases(&case, subcases).unwrap(); + assert_eq!( + selected + .iter() + .map(|subcase| subcase.name.as_str()) + .collect::>(), + vec!["alpha", "beta"] + ); + } + + #[test] + fn grouped_c_subcases_reject_missing_direct_usr_bin_commands() { + let root = tempdir().unwrap(); + let mut case = fake_case(root.path(), "bugfix"); + case.test_commands = vec!["/usr/bin/missing".to_string()]; + + let alpha = fake_c_subcase(root.path(), &case, "alpha", &["alpha"]); + let err = selected_grouped_c_subcases(&case, vec![&alpha]).unwrap_err(); + + assert!( + err.to_string() + .contains("references test command(s) without C subcases: missing") + ); + } + #[test] fn cmake_configure_command_passes_staging_root_define() { let root = tempdir().unwrap(); diff --git a/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml index 773b7ac97c..8255f9fe7d 100644 --- a/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml @@ -1,8 +1,17 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "qemu", + "ax-hal/plat-dyn", + "ax-feat/display", + "ax-feat/rtc", + "ax-driver/pci", + "ax-driver/rtc", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", "gic-v3", "cntv-timer", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", ] # CNTV PPI 11 = IRQ 27. The kernel's `cntv-timer` path programs the # virtual-timer registers and the GIC must subscribe IRQ 27 instead @@ -10,5 +19,5 @@ features = [ # override over the platform's default axconfig. axconfig_overrides = ["devices.timer-irq=27"] log = "Warn" -plat_dyn = false +plat_dyn = true target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml index 2da23ecded..3069e0b191 100644 --- a/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,8 @@ env = {} features = [ "ax-hal/plat-dyn", + "ax-feat/rtc", + "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "starry-kernel/input", diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml deleted file mode 100644 index 6f363df452..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-aarch64-unknown-none-softfloat.toml +++ /dev/null @@ -1,17 +0,0 @@ -target = "aarch64-unknown-none-softfloat" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-hal/aarch64-qemu-virt", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", - "smp", -] -plat_dyn = false -max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml deleted file mode 100644 index a221d6be8c..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-loongarch64-unknown-none-softfloat.toml +++ /dev/null @@ -1,17 +0,0 @@ -target = "loongarch64-unknown-none-softfloat" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-hal/loongarch64-qemu-virt", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", - "smp", -] -plat_dyn = false -max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml deleted file mode 100644 index 4fb164caa3..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-riscv64gc-unknown-none-elf.toml +++ /dev/null @@ -1,17 +0,0 @@ -target = "riscv64gc-unknown-none-elf" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", - "smp", -] -plat_dyn = false -max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml deleted file mode 100644 index 04efbf58fb..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/build-x86_64-unknown-none.toml +++ /dev/null @@ -1,17 +0,0 @@ -target = "x86_64-unknown-none" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-hal/x86-pc", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", - "smp", -] -plat_dyn = false -max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/CMakeLists.txt deleted file mode 100644 index f9e956bb06..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-concurrent C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-concurrent src/main.c) -target_include_directories(test-kcov-concurrent PRIVATE src) -target_compile_options(test-kcov-concurrent PRIVATE -Wall -Wextra -Werror) -target_link_libraries(test-kcov-concurrent PRIVATE -lpthread) -install(TARGETS test-kcov-concurrent RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/main.c b/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/main.c deleted file mode 100644 index 1f311822f9..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/main.c +++ /dev/null @@ -1,223 +0,0 @@ -/* kcov: SMP concurrent coverage test — proves per-CPU guard isolation. - * - * Repeats the two-phase measurement across multiple rounds to prove the - * per-CPU guard consistently prevents cross-CPU coverage loss. - * - * Phase 1 — Sequential baselines: - * Pin to CPU 0, run do_work() (tight getpid() loop), record coverage. - * Pin to CPU 1, run do_work(), record coverage. - * Fail if buf[0] == BUF_ENTRIES (buffer overflow → baseline invalid). - * - * Phase 2 — Concurrent (pthread_barrier release): - * Thread A pinned to CPU 0, Thread B pinned to CPU 1. - * Both released simultaneously via pthread_barrier_wait(). - * Each records its coverage count. - * - * Phase 3 — Per-round assertions: - * Each concurrent count >= 70% of its sequential baseline. - * Combined concurrent >= 70% of summed baselines. - * - * The workload uses getpid() (real syscall on musl/Alpine). KCOV traces - * kernel basic blocks, so each getpid() generates several trace_pc calls. - * - * With a per-CPU guard both CPUs trace independently — cross-CPU contention - * is zero, so each thread keeps ~90%+ of baseline. A single global guard - * would cause one CPU to lose 50%+ when both trace simultaneously. - */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 - -#define ROUNDS 20 -#define WORK_LOOPS 500 -#define BUF_ENTRIES 65536 - -/* Busy-workload: tight getpid() loop that generates kernel coverage entries. - * getpid() on musl (Alpine) makes a real syscall every time — no caching. */ -static void do_work(uint64_t iterations) { - for (uint64_t i = 0; i < iterations; i++) { - getpid(); - } -} - -/* Run the workload on a specific CPU and return the coverage entry count. - * Returns UINT64_MAX on any error. */ -static uint64_t run_sequential(int cpu, uint64_t iterations) { - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - CPU_SET(cpu, &cpuset); - if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) { - return UINT64_MAX; - } - - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) return UINT64_MAX; - - if (ioctl(fd, KCOV_INIT_TRACE, BUF_ENTRIES)) { - close(fd); - return UINT64_MAX; - } - - size_t sz = BUF_ENTRIES * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (buf == MAP_FAILED) { - close(fd); - return UINT64_MAX; - } - - if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC)) { - munmap(buf, sz); - close(fd); - return UINT64_MAX; - } - - do_work(iterations); - - uint64_t count = buf[0]; - - ioctl(fd, KCOV_DISABLE, 0); - munmap(buf, sz); - close(fd); - return count; -} - -/* Per-thread data for the concurrent phase */ -typedef struct { - int cpu; - uint64_t count; /* coverage entries recorded */ - int ok; /* 1 = success */ - pthread_barrier_t *barrier; -} thr_t; - -static void *thread_concurrent(void *arg) { - thr_t *s = (thr_t *)arg; - s->ok = 0; - - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - CPU_SET(s->cpu, &cpuset); - if (sched_setaffinity(0, sizeof(cpuset), &cpuset) != 0) { - return NULL; - } - - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) return NULL; - - if (ioctl(fd, KCOV_INIT_TRACE, BUF_ENTRIES)) { - close(fd); - return NULL; - } - - size_t sz = BUF_ENTRIES * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (buf == MAP_FAILED) { - close(fd); - return NULL; - } - - if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC)) { - munmap(buf, sz); - close(fd); - return NULL; - } - - /* Both threads released simultaneously */ - pthread_barrier_wait(s->barrier); - - do_work(WORK_LOOPS); - - s->count = buf[0]; - - ioctl(fd, KCOV_DISABLE, 0); - munmap(buf, sz); - close(fd); - s->ok = 1; - return NULL; -} - -int main(void) { - TEST_START("KCOV SMP: per-CPU guard isolation (20 rounds)"); - - for (int r = 0; r < ROUNDS; r++) { - printf("\n === Round %d/%d ===\n", r + 1, ROUNDS); - - /* ---- Phase 1: sequential baselines ---- */ - uint64_t seq_a = run_sequential(0, WORK_LOOPS); - uint64_t seq_b = run_sequential(1, WORK_LOOPS); - - CHECK(seq_a != UINT64_MAX && seq_a > 100, - "CPU 0 sequential baseline > 100 (reject cached-getpid regression)"); - CHECK(seq_b != UINT64_MAX && seq_b > 100, - "CPU 1 sequential baseline > 100 (reject cached-getpid regression)"); - - /* Overflow guard: buffer must not be full, otherwise the baseline - * measurement is truncated and the 70% threshold is meaningless. */ - CHECK(seq_a < BUF_ENTRIES, - "CPU 0 baseline did not overflow buffer"); - CHECK(seq_b < BUF_ENTRIES, - "CPU 1 baseline did not overflow buffer"); - - printf(" seq[CPU0] = %lu, seq[CPU1] = %lu\n", seq_a, seq_b); - - /* ---- Phase 2: concurrent ---- */ - pthread_barrier_t barrier; - pthread_barrier_init(&barrier, NULL, 2); - - pthread_t ta, tb; - thr_t sa = { .cpu = 0, .barrier = &barrier }; - thr_t sb = { .cpu = 1, .barrier = &barrier }; - - CHECK(pthread_create(&ta, NULL, thread_concurrent, &sa) == 0, - "pthread_create CPU 0"); - CHECK(pthread_create(&tb, NULL, thread_concurrent, &sb) == 0, - "pthread_create CPU 1"); - - pthread_join(ta, NULL); - pthread_join(tb, NULL); - - CHECK(sa.ok, "thread on CPU 0 completed"); - CHECK(sb.ok, "thread on CPU 1 completed"); - - uint64_t con_a = sa.count; - uint64_t con_b = sb.count; - printf(" con[CPU0] = %lu, con[CPU1] = %lu\n", con_a, con_b); - - /* ---- Phase 3: assertions ---- */ - - /* Each concurrent run must achieve >= 70% of its sequential baseline. - * - * Small losses (10-20%) are expected from: - * - cache-line bouncing on the shared kcov buffer count word - * - scheduler jitter - * - self-recursion from the instrumented body of kcov_trace_pc_impl - * - * A global guard produces >> 50% loss on at least one CPU. */ - uint64_t thr_a = seq_a * 70 / 100; - uint64_t thr_b = seq_b * 70 / 100; - - CHECK(con_a >= thr_a, - "CPU 0 concurrent coverage >= 70% of sequential baseline"); - CHECK(con_b >= thr_b, - "CPU 1 concurrent coverage >= 70% of sequential baseline"); - - /* Combined total >= 70% of summed baselines */ - CHECK(con_a + con_b >= (seq_a + seq_b) * 70 / 100, - "combined concurrent coverage >= 70% of combined baseline"); - - pthread_barrier_destroy(&barrier); - } - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov-smp/concurrent/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/basic-easy/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/basic-easy/c/CMakeLists.txt deleted file mode 100644 index 6b0ec8a1c0..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/basic-easy/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-basic-easy C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-basic-easy src/main.c) -target_include_directories(test-kcov-basic-easy PRIVATE src) -target_compile_options(test-kcov-basic-easy PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-basic-easy RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/basic-easy/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/basic-easy/c/src/main.c deleted file mode 100644 index d6a26ffddb..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/basic-easy/c/src/main.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * test-kcov.c — KCOV device and SharedPages mmap test - * - * Tests the /dev/kcov character device, its ioctl interface, and the - * shared memory buffer exposed via mmap. Exercises: - * - * 1. /dev/kcov exists and is openable - * 2. KCOV_INIT_TRACE allocates a buffer - * 3. mmap on the kcov fd returns a writable shared mapping - * 4. KCOV_ENABLE / KCOV_DISABLE control tracing state - * 5. The mmap'd buffer records coverage data (non-zero PCs) - * - * The test requires the kernel to be built with --features kcov. - * When /dev/kcov is absent the test reports a skip (exit 0). - */ - -#define _GNU_SOURCE -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ---- KCOV ioctl constants (matching Linux uapi, not in musl headers) ---- */ - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) - -#define KCOV_TRACE_PC 0 -#define KCOV_TRACE_CMP 1 - -/* ---- Test ---- */ - -int main(void) { - TEST_START("KCOV /dev/kcov"); - - /* Open the kcov device */ - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) { - printf("SKIP: /dev/kcov not available (errno=%d: %s)\n", errno, - strerror(errno)); - printf(" The kernel must be built with --features kcov\n"); - TEST_DONE(); /* DONE: 0 pass, 0 fail → success for skip */ - } - - /* KCOV_INIT_TRACE: allocate coverage buffer (256 entries) */ - unsigned long cover_size = 256; - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, cover_size), 0, - "KCOV_INIT_TRACE (256 entries)"); - - /* mmap the kcov fd: must succeed, return writable memory */ - /* - * Buffer layout: [count: u64 | pc[0]: u64 | ... | pc[N-1]: u64] - * Total entries = cover_size (matching Linux: cover_size includes the count word). - * Available PC slots = cover_size - 1. - * mmap size = cover_size * sizeof(uint64_t). - */ - size_t buf_size = cover_size * sizeof(uint64_t); - uint64_t *buf = - mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK(buf != MAP_FAILED, "mmap(MAP_SHARED) of kcov fd succeeds"); - - /* Verify the mmap'd buffer is accessible and count starts at 0 */ - if (buf != MAP_FAILED) { - CHECK(buf[0] == 0, "initial coverage count is 0"); - } - - /* KCOV_ENABLE: start tracing (PC mode) */ - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, - "KCOV_ENABLE (KCOV_TRACE_PC)"); - - /* Exercise some syscalls to generate coverage */ - { - int tmp = open("/tmp", O_RDONLY | O_DIRECTORY); - if (tmp >= 0) - close(tmp); - getpid(); - getuid(); - struct stat st; - stat("/dev", &st); - char buf_small[64]; - getcwd(buf_small, sizeof(buf_small)); - /* stress the syscall path a bit */ - for (volatile int i = 0; i < 10; i++) { - getpid(); - } - } - - /* KCOV_DISABLE: stop tracing */ - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "KCOV_DISABLE"); - - /* Verify the buffer recorded some coverage (count > 0) */ - if (buf != MAP_FAILED) { - /* - * Whether we get coverage depends on compiler instrumentation. - * Without -fsanitize-coverage the buffer will be empty. - * - * The test still passes either way — we verify the mechanism - * works (mmap, ioctls succeed) rather than requiring actual - * coverage data unless the kernel was instrumented. - */ - uint64_t count = buf[0]; - printf(" INFO: recorded %lu coverage entries\n", count); - for (uint64_t i = 0; i <= count; i++) { - printf(" TRACE: buf[%lu]=0x%lx\n", i, buf[i]); - } - - CHECK(count > 0, "coverage count>=1"); - CHECK(count < cover_size, "coverage count 哪个调用 -> 什么结果 - * - * 用法: - * TEST_START("测试名"); - * CHECK(call == expected, "描述"); - * CHECK_ERR(call, EBADF, "描述"); - * TEST_DONE(); - */ - -#include -#include -#include -#include - -static int __pass = 0; -static int __fail = 0; - -/* ---- 核心: 带文件名+行号的检查宏 ---- */ - -/* 检查条件为真 */ -#define CHECK(cond, msg) do { \ - if (cond) { \ - printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, errno, strerror(errno)); \ - __fail++; \ - } \ -} while(0) - -/* 检查 syscall 返回特定值 */ -#define CHECK_RET(call, expected, msg) do { \ - errno = 0; \ - long _r = (long)(call); \ - long _e = (long)(expected); \ - if (_r == _e) { \ - printf(" PASS | %s:%d | %s (ret=%ld)\n", \ - __FILE__, __LINE__, msg, _r); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ - __fail++; \ - } \ -} while(0) - -/* 检查 syscall 失败且 errno 符合预期 */ -#define CHECK_ERR(call, exp_errno, msg) do { \ - errno = 0; \ - long _r = (long)(call); \ - if (_r == -1 && errno == (exp_errno)) { \ - printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ - __FILE__, __LINE__, msg, errno); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ - __fail++; \ - } \ -} while(0) - -/* ---- 测试边界 ---- */ -#define TEST_START(name) \ - printf("================================================\n"); \ - printf(" TEST: %s\n", name); \ - printf(" FILE: %s\n", __FILE__); \ - printf("================================================\n") - -#define TEST_DONE() \ - printf("------------------------------------------------\n"); \ - printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ - printf("================================================\n\n"); \ - return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/CMakeLists.txt deleted file mode 100644 index 1f3c0dd151..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-buffer-boundary C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-buffer-boundary src/main.c) -target_include_directories(test-kcov-buffer-boundary PRIVATE src) -target_compile_options(test-kcov-buffer-boundary PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-buffer-boundary RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/main.c deleted file mode 100644 index 59f2f8bb2b..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/main.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * test-kcov-buffer-boundary — regression test for buffer off-by-one. - * - * The kcov buffer layout is: [count: u64 | pc[0]: u64 | ... | pc[N-1]: u64] - * where the total number of u64 entries is the `cover_size` arg passed to - * KCOV_INIT_TRACE (matching Linux's kcov->size). - * - * Maximum number of PC entries = cover_size - 1. - * - * This test uses cover_size=512 (exactly one page of u64 entries = 4096 bytes) - * to force the buffer onto a single physical page. With the old off-by-one - * bug the kernel allocated (1+cover_size) entries, placing the last PC slot - * at byte offset cover_size*8 — the first byte beyond userspace's mmmap. - * The fix allocates exactly cover_size entries, so the last PC fits. - * - * The test fills the buffer completely, then verifies that every PC slot - * (1..cover_size-1) contains a valid kernel-space address. - */ - -#define _GNU_SOURCE -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include - -/* ---- KCOV ioctl constants ---- */ - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 - -/* Exactly one page of u64 entries = 4096 bytes. */ -#define COVER_SIZE 512 -#define MMAP_BYTES (COVER_SIZE * sizeof(uint64_t)) - -/* Architecture kernel-space PC floor. */ -#if defined(__x86_64__) || defined(__amd64__) -#define KERNEL_PC_MIN 0xffff800000000000ULL -#elif defined(__aarch64__) -#define KERNEL_PC_MIN 0xffff000000000000ULL -#elif defined(__riscv) && __riscv_xlen == 64 -#define KERNEL_PC_MIN 0xffffffc000000000ULL -#else -#define KERNEL_PC_MIN 0x8000000000000000ULL -#endif - -/* ---- helpers ---- */ - -/* Tight syscall loop to generate enough distinct PCs to fill the buffer. */ -static void heavy_burst(void) { - for (volatile int i = 0; i < 100000; i++) { - getpid(); - getuid(); - getppid(); - } -} - -/* ---- main ---- */ - -int main(void) { - TEST_START("KCOV buffer boundary — no off-by-one"); - - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) { - printf("SKIP: /dev/kcov not available (errno=%d)\n", errno); - return 0; - } - - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE), 0, - "INIT_TRACE"); - - uint64_t *buf = mmap(NULL, MMAP_BYTES, PROT_READ | PROT_WRITE, - MAP_SHARED, fd, 0); - CHECK(buf != MAP_FAILED, "mmap buffer"); - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - - /* Generate enough coverage to fill all PC slots. */ - heavy_burst(); - - uint64_t count = buf[0]; - printf(" INFO: count=%lu (expecting %d)\n", count, COVER_SIZE - 1); - - /* The count should be cover_size-1 = 511 (all PC slots filled). - * If the old off-by-one were present the kernel would allocate - * (1+cover_size) entries, and the last PC would go into an - * unmapped slot — but the count would still show cover_size. */ - CHECK(count == COVER_SIZE - 1, - "buffer completely filled"); - - /* Verify every PC slot is a valid kernel address. - * The critical regression check: buf[COVER_SIZE-1] must be - * accessible (with the old off-by-one it lived beyond the mmmap). */ - int all_valid = 1; - uint64_t first_pc = buf[1]; - for (int i = 1; i < COVER_SIZE; i++) { - uint64_t pc = buf[i]; - if (pc < KERNEL_PC_MIN) { - printf(" FAIL: buf[%d] = 0x%lx (not a kernel address)\n", - i, pc); - all_valid = 0; - } - } - CHECK(all_valid, "all PC slots valid"); - - /* Explicit check on the very last slot (the off-by-one boundary). */ - uint64_t last_pc = buf[COVER_SIZE - 1]; - CHECK(last_pc >= KERNEL_PC_MIN, - "last PC slot accessible and valid"); - - /* Verify at least some diversity in collected PCs. */ - int diverse = 0; - for (int i = 2; i < COVER_SIZE && i <= 20; i++) { - if (buf[i] != first_pc) { - diverse = 1; - break; - } - } - CHECK(diverse, "multiple distinct PCs observed"); - - ioctl(fd, KCOV_DISABLE, 0); - munmap(buf, MMAP_BYTES); - close(fd); - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/test_framework.h deleted file mode 100644 index f7978c60cf..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/buffer-boundary/c/src/test_framework.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -/* Must be defined first to enable pipe2/gettid etc. */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include -#include -#include -#include - -static int __pass = 0; -static int __fail = 0; - -#define CHECK(cond, msg) do { \ - if (cond) { \ - printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, errno, strerror(errno)); \ - __fail++; \ - } \ -} while(0) - -#define CHECK_RET(call, expected, msg) do { \ - errno = 0; \ - long _r = (long)(call); \ - long _e = (long)(expected); \ - if (_r == _e) { \ - printf(" PASS | %s:%d | %s (ret=%ld)\n", \ - __FILE__, __LINE__, msg, _r); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ - __fail++; \ - } \ -} while(0) - -#define CHECK_ERR(call, exp_errno, msg) do { \ - errno = 0; \ - long _r = (long)(call); \ - if (_r == -1 && errno == (exp_errno)) { \ - printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ - __FILE__, __LINE__, msg, errno); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ - __fail++; \ - } \ -} while(0) - -#define TEST_START(name) \ - printf("================================================\n"); \ - printf(" TEST: %s\n", name); \ - printf(" FILE: %s\n", __FILE__); \ - printf("================================================\n") - -#define TEST_DONE() \ - printf("------------------------------------------------\n"); \ - printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ - printf("================================================\n\n"); \ - return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml deleted file mode 100644 index 71696eca90..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/build-aarch64-unknown-none-softfloat.toml +++ /dev/null @@ -1,15 +0,0 @@ -target = "aarch64-unknown-none-softfloat" -env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} -log = "Warn" -features = [ - "ax-hal/aarch64-qemu-virt", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", -] -plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml deleted file mode 100644 index e2f2fc3bb9..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/build-loongarch64-unknown-none-softfloat.toml +++ /dev/null @@ -1,15 +0,0 @@ -target = "loongarch64-unknown-none-softfloat" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-hal/loongarch64-qemu-virt", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", -] -plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml deleted file mode 100644 index 6c4bb244d7..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/build-riscv64gc-unknown-none-elf.toml +++ /dev/null @@ -1,15 +0,0 @@ -target = "riscv64gc-unknown-none-elf" -env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} -log = "Warn" -features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", -] -plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml deleted file mode 100644 index 4c3d209322..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/build-x86_64-unknown-none.toml +++ /dev/null @@ -1,15 +0,0 @@ -target = "x86_64-unknown-none" -env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } -log = "Warn" -features = [ - "ax-hal/x86-pc", - "qemu", - "ax-driver/pci", - "ax-driver/virtio-blk", - "ax-driver/virtio-net", - "ax-driver/virtio-gpu", - "ax-driver/virtio-input", - "ax-driver/virtio-socket", - "kcov", -] -plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/CMakeLists.txt deleted file mode 100644 index 68cf7372c8..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-disable-thread-check C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-disable-thread-check src/main.c) -target_include_directories(test-kcov-disable-thread-check PRIVATE src) -target_compile_options(test-kcov-disable-thread-check PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-disable-thread-check RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/main.c deleted file mode 100644 index a09291aada..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/main.c +++ /dev/null @@ -1,356 +0,0 @@ -/* Test: KCOV DISABLE/RESET_TRACE thread ownership check. - * - * Verifies that DISABLE, RESET_TRACE, and close() from a thread that did NOT - * enable kcov are handled correctly, matching Linux semantics: - * - * 1. DISABLE from a non-tracing state (INIT) → EINVAL. - * 2. DISABLE from a different thread → EINVAL. - * 3. The tracer thread can still DISABLE after a rogue DISABLE attempt. - * 4. close() from the tracer thread cleans up correctly. - * 5. close() from a non-tracer thread does not clear the tracer's state. - * 6. DISABLE from INIT always returns EINVAL (idempotent in error). - * 7. RESET_TRACE from a different thread → EINVAL. - * - * These tests use fork() to create a second task that shares the fd but has - * a different TID, exercising the tracer_tid check in KCOV ioctls. - * - * Ordering note: test §5 (close from non-tracer) must run last among the - * original set because it intentionally leaves the thread's kcov state - * active. §7 (RESET_TRACE ownership) runs before §5 since it cleans up - * properly. - */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_RESET_TRACE _IO('c', 104) -#define KCOV_TRACE_PC 0 - -/* Large buffer to avoid overflow during test bursts. */ -#define BUF_ENTRIES 65536 - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - } -} - -/* ---- Helper: open + INIT_TRACE + mmap ---- */ -static int kcov_setup(uint64_t **buf, size_t *sz) { - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) return -1; - if (ioctl(fd, KCOV_INIT_TRACE, BUF_ENTRIES)) { close(fd); return -1; } - *sz = BUF_ENTRIES * sizeof(uint64_t); - *buf = mmap(NULL, *sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (*buf == MAP_FAILED) { close(fd); return -1; } - return fd; -} - -static void kcov_teardown(int fd, uint64_t *buf, size_t sz) { - munmap(buf, sz); - close(fd); -} - -/* ================================================================ - * §1: DISABLE from a non-tracing state (INIT) — no-op, no error. - * - * Even after a fork, DISABLE on an fd that was never ENABLE'd - * by this thread must succeed (the TID check only applies when - * the fd is in TRACE_PC / TRACE_CMP mode). - * ================================================================ */ -static void disable_from_init_is_noop(void) { - printf("\n --- §1: DISABLE from INIT mode returns EINVAL (Linux semantics) ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §1"); - - /* fd is in INIT mode now (never enabled). */ - pid_t pid = fork(); - if (pid == 0) { - /* Child: fd inherited, mode=INIT, tracer_tid=None. - * Linux: DISABLE before ENABLE → EINVAL (current->kcov != kcov). */ - int r = ioctl(fd, KCOV_DISABLE, 0); - _exit(r == -1 && errno == EINVAL ? 0 : 10); - } - - int wstatus; - CHECK_RET(waitpid(pid, &wstatus, 0), pid, "waitpid §1"); - CHECK(WIFEXITED(wstatus), "§1 child exited"); - CHECK_RET(WEXITSTATUS(wstatus), 0, - "§1 child DISABLE from INIT → EINVAL (matching Linux)"); - - kcov_teardown(fd, buf, sz); -} - -/* ================================================================ - * §2: DISABLE from a different thread → EINVAL. - * - * Parent ENABLEs, child inherits fd and tries to DISABLE. - * The kernel must return EINVAL because the child's TID does - * not match the tracer_tid stored at ENABLE time. - * - * After the fork test the parent DISABLEs, leaving a clean state. - * ================================================================ */ -static void disable_from_wrong_thread_returns_einval(void) { - printf("\n --- §2: DISABLE from wrong thread → EINVAL ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §2"); - - /* Enable on parent thread. */ - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "parent ENABLE"); - burst(10); - uint64_t before = buf[0]; - CHECK(before > 0, "parent recorded coverage before fork"); - - pid_t pid = fork(); - if (pid == 0) { - /* Child: inherits fd, but tracer_tid belongs to parent. - * DISABLE must fail with EINVAL. */ - int r = ioctl(fd, KCOV_DISABLE, 0); - if (r == -1 && errno == EINVAL) { - _exit(0); - } - _exit(r == 0 ? 11 : 12); - } - - int wstatus; - CHECK_RET(waitpid(pid, &wstatus, 0), pid, "waitpid §2"); - CHECK(WIFEXITED(wstatus), "§2 child exited"); - CHECK_RET(WEXITSTATUS(wstatus), 0, - "§2 child DISABLE → EINVAL (matching Linux)"); - - /* Clean up: parent DISABLEs normally. */ - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "parent DISABLE after fork test"); - kcov_teardown(fd, buf, sz); -} - -/* ================================================================ - * §3: Parent can STILL DISABLE after child's failed attempt. - * - * Because the child's DISABLE was rejected (EINVAL), the fd is - * still in TRACE_PC mode and the parent can disable normally. - * ================================================================ */ -static void parent_can_disable_after_child_fail(void) { - printf("\n --- §3: Parent DISABLE works after child's failed attempt ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §3"); - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "parent ENABLE"); - burst(10); - - pid_t pid = fork(); - if (pid == 0) { - /* Child tries (and fails) to DISABLE. */ - ioctl(fd, KCOV_DISABLE, 0); - _exit(0); - } - - int wstatus; - waitpid(pid, &wstatus, 0); - - /* Parent must still be able to DISABLE. */ - uint64_t after = buf[0]; - CHECK(after > 0, "parent still tracing after child's failed DISABLE"); - - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, - "parent DISABLE after child's failed attempt"); - - kcov_teardown(fd, buf, sz); -} - -/* ================================================================ - * §4: close() from the tracer thread stops tracing correctly. - * - * Baseline: ensure that close from the correct thread works as - * expected (the original behavior, now with TID match). - * ================================================================ */ -static void close_from_tracer_stops_tracing(void) { - printf("\n --- §4: close() from tracer stops tracing ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §4"); - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "parent ENABLE"); - burst(10); - - uint64_t before_close = buf[0]; - CHECK(before_close > 0, "coverage before close"); - - /* Tracer thread closes fd → on_close matches TID → clears thread state */ - close(fd); - - /* After close: re-open to verify the new fd works (fresh state). */ - fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "re-open after close from tracer"); - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "re-open ENABLE"); - burst(5); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "re-open DISABLE"); - - kcov_teardown(fd, buf, sz); -} - -/* ================================================================ - * §5: close() from a non-tracer thread does NOT clear the tracer's - * thread state — the tracer continues tracing. - * - * The child closes its inherited fd. Since the fd is shared via - * Arc, the child's close merely drops its Arc reference — - * on_close is NOT called. The parent's fd state (mode=TRACE_PC, - * tracer_tid=parent) remains intact, and the parent can DISABLE - * normally. - * - * This test MUST run last because it tests a corner case where - * the child holds an Arc reference to the shared File. - * ================================================================ */ -static void close_from_wrong_thread_does_not_stop_tracer(void) { - printf("\n --- §5: close() from wrong thread does not stop tracer ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §5"); - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "parent ENABLE"); - burst(10); - - pid_t pid = fork(); - if (pid == 0) { - /* Child closes the inherited fd. The child drops its Arc reference - * to the shared File, but the parent's reference keeps the File alive - * so on_close is NOT called. The fd state (mode=TRACE_PC, tracer_tid - * =parent) remains intact on the shared file description. */ - close(fd); - _exit(0); - } - - int wstatus; - waitpid(pid, &wstatus, 0); - CHECK(WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0, - "§5 child close succeeded"); - - /* The parent's fd is still in TRACE_PC mode (child's close only drops - * its Arc reference — the shared file description is not torn down - * until all references are released). Verify the thread is still - * tracing by checking that coverage continued to increase. */ - uint64_t prev = buf[0]; - burst(50); - uint64_t after = buf[0]; - CHECK(after > prev, - "§5 coverage increased after child's close (thread still tracing)"); - printf(" INFO: §5 coverage: %lu → %lu\n", prev, after); - - /* The fd is still in TRACE_PC mode (child's close drops its Arc but the - * shared file descriptor is not torn down — on_close only runs when the - * last reference is dropped). The parent can DISABLE normally. */ - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, - "§5 parent DISABLE after child's close (still tracing)"); - - /* Thread kcov is now clean — parent can exit. */ - munmap(buf, sz); - close(fd); -} - -/* ================================================================ - * §6: Double DISABLE (idempotent) — DISABLE from INIT mode is fine. - * ================================================================ */ -static void disable_from_init_is_idempotent(void) { - printf("\n --- §6: DISABLE from INIT always returns EINVAL ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §6"); - - /* DISABLE from INIT (never enabled) — both return EINVAL (Linux semantics). */ - CHECK_ERR(ioctl(fd, KCOV_DISABLE, 0), EINVAL, "first DISABLE (INIT) → EINVAL"); - CHECK_ERR(ioctl(fd, KCOV_DISABLE, 0), EINVAL, "second DISABLE (INIT) → EINVAL"); - - /* ENABLE still works after failed DISABLE (state unchanged: INIT). */ - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE after DISABLE"); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE after ENABLE (from tracer)"); - - kcov_teardown(fd, buf, sz); -} - -/* ================================================================ - * §7: RESET_TRACE from a non-tracer thread → EINVAL. - * - * Only the thread that called KCOV_ENABLE may issue KCOV_RESET_TRACE. - * A child that inherits the fd via fork must get EINVAL. - * - * Parent ENABLEs → forks → child tries RESET_TRACE → EINVAL. - * Parent can still RESET_TRACE and DISABLE normally afterwards. - * ================================================================ */ -static void reset_trace_from_wrong_thread_returns_einval(void) { - printf("\n --- §7: RESET_TRACE from wrong thread → EINVAL ---\n"); - - uint64_t *buf; - size_t sz; - int fd = kcov_setup(&buf, &sz); - CHECK(fd >= 0, "setup fd for §7"); - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "parent ENABLE"); - burst(20); - - pid_t pid = fork(); - if (pid == 0) { - /* Child: inherited fd, parent is tracer. RESET_TRACE must fail. */ - int r = ioctl(fd, KCOV_RESET_TRACE, 0); - _exit(r == -1 && errno == EINVAL ? 0 : 10); - } - - int wstatus; - CHECK_RET(waitpid(pid, &wstatus, 0), pid, "waitpid §7"); - CHECK(WIFEXITED(wstatus), "§7 child exited"); - CHECK_RET(WEXITSTATUS(wstatus), 0, - "§7 child RESET_TRACE → EINVAL (matching Linux)"); - - /* Parent's tracing is unaffected; verify and clean up. */ - burst(10); - uint64_t before = buf[0]; - CHECK(before > 0, "§7 parent still tracing after child's failed RESET"); - - CHECK_RET(ioctl(fd, KCOV_RESET_TRACE, 0), 0, - "§7 parent RESET_TRACE succeeds"); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "§7 parent DISABLE"); - kcov_teardown(fd, buf, sz); -} - -/* ---- main ---- */ - -int main(void) { - TEST_START("KCOV DISABLE thread ownership check"); - - disable_from_init_is_noop(); - disable_from_wrong_thread_returns_einval(); - parent_can_disable_after_child_fail(); - close_from_tracer_stops_tracing(); - disable_from_init_is_idempotent(); - reset_trace_from_wrong_thread_returns_einval(); - /* §5 (close from non-tracer) intentionally leaves the thread's kcov - * state active. It must run last. */ - close_from_wrong_thread_does_not_stop_tracer(); - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/test_framework.h deleted file mode 100644 index 42bac5d78c..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/disable-thread-check/c/src/test_framework.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/enable-disable/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/enable-disable/c/CMakeLists.txt deleted file mode 100644 index 3beebaae89..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/enable-disable/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-enable-disable C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-enable-disable src/main.c) -target_include_directories(test-kcov-enable-disable PRIVATE src) -target_compile_options(test-kcov-enable-disable PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-enable-disable RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/main.c deleted file mode 100644 index b844320ffb..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/main.c +++ /dev/null @@ -1,67 +0,0 @@ -/* kcov-spec §5,§6: ENABLE/DISABLE, per-task, re-enable */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 -#define KCOV_TRACE_CMP 1 - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - } -} - -int main(void) { - TEST_START("KCOV §5/§6: ENABLE/DISABLE, re-enable cycles"); - - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open"); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 256), 0, "INIT_TRACE"); - size_t sz = 256 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf == MAP_FAILED) { - close(fd); - TEST_DONE(); - } - - /* §5: TRACE_CMP is rejected with EINVAL until CMP hooks are implemented */ - CHECK_ERR(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_CMP), EINVAL, - "ENABLE TRACE_CMP rejected (not yet implemented)"); - /* After failed ENABLE, mode is still INIT. DISABLE from INIT → EINVAL. */ - CHECK_ERR(ioctl(fd, KCOV_DISABLE, 0), EINVAL, "DISABLE after failed TRACE_CMP → EINVAL"); - - /* §6: DISABLE before ENABLE → EINVAL (Linux: current->kcov != kcov) */ - CHECK_ERR(ioctl(fd, KCOV_DISABLE, 0), EINVAL, "DISABLE before ENABLE → EINVAL"); - - /* §6: "After this call coverage can be enabled" — re-enable same thread */ - for (int cycle = 1; cycle <= 3; cycle++) { - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(50); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - } - - /* §6: coverage accumulated across cycles */ - CHECK(buf[0] >= 3, "accumulated coverage across 3 cycles"); - CHECK(buf[0] < 256, "accumulated coverage across 3 cycles"); - - /* §6: after DISABLE, collection stops */ - uint64_t saved = buf[0]; - burst(100); - CHECK(buf[0] == saved, "count unchanged after DISABLE (stopped)"); - - printf(" INFO: final count=%lu\n", buf[0]); - munmap(buf, sz); - close(fd); - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/enable-disable/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/guard-test/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/guard-test/c/CMakeLists.txt deleted file mode 100644 index 5469241e97..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/guard-test/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-guard C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-guard src/main.c) -target_include_directories(test-kcov-guard PRIVATE src) -target_compile_options(test-kcov-guard PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-guard RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/guard-test/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/guard-test/c/src/main.c deleted file mode 100644 index 808a4dd19e..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/guard-test/c/src/main.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Regression test: guard address register clobbered after call. - * - * ## The bug - * - * Each architecture's naked assembly trampoline in `__sanitizer_cov_trace_pc` - * (os/StarryOS/kernel/src/kcov/mod.rs) must: - * 1. check and set IN_KCOV_TRACE (recursion guard) - * 2. call kcov_trace_pc_impl(pc) - * 3. clear IN_KCOV_TRACE - * - * On aarch64, riscv64, and loongarch64 the guard address was loaded into a - * caller-saved register (x16 / t0 / $t0) before step 2, then used again - * *after* step 2 to clear the guard. However, the C-ABI call in step 2 - * may clobber that register — the store in step 3 then writes 0 to a - * garbage address instead of clearing IN_KCOV_TRACE. - * - * Effect: IN_KCOV_TRACE stays 1 permanently. After the very first traced - * basic block, every subsequent __sanitizer_cov_trace_pc invocation skips - * the trace handler — coverage recording stops. Additionally, a random - * byte of memory is corrupted by the spurious write. - * - * x86_64 was NOT affected because it uses RIP-relative addressing (the - * address is recomputed from the instruction pointer each time, not cached - * in a register). - * - * ## The fix - * - * Re-derive the guard address after the call, before clearing the flag: - * - * aarch64: adrp x16, {guard} // added after "bl {impl}" - * riscv64: la t0, {guard} // added after "call {impl}" - * loongarch64: la.local $t0, {guard} // added after "bl {impl}" - * - * ## How this test catches the bug - * - * Staircase pattern — 10 enable→burst→disable cycles. Each cycle calls - * a small, fixed set of syscalls, then reads the coverage count from the - * mmap'd buffer and asserts it is *strictly greater* than the previous - * cycle's count. - * - * Cycle 1: enable → syscalls → disable → count = N₁ (N₁ > 0) ✓ - * Cycle 2: enable → syscalls → disable → count = N₂ - * assert N₂ > N₁ ← catches the stall - * - * If the guard was not cleared, cycle 2 records zero new edges, N₂ = N₁, - * and the assertion fails. - * - * A larger buffer size (51200 entries) is used to ensure we never hit the - * "buffer full" path, which would also cause the count to stall. - */ - -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 - -#define STAIR_STEPS 10 - -/* A modest burst: enough syscalls to generate multiple trace edges per - * step but not so many that we risk filling the buffer. */ -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - getppid(); - } -} - -int main(void) { - TEST_START("KCOV guard: staircase coverage increment"); - - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open /dev/kcov"); - if (fd < 0) { - TEST_DONE(); - } - - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 51200), 0, - "KCOV_INIT_TRACE size=51200"); - size_t sz = 51200 * sizeof(uint64_t); - uint64_t *buf = - mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap kcov buffer"); - if (buf == MAP_FAILED) { - close(fd); - TEST_DONE(); - } - - uint64_t prev = buf[0]; - CHECK(prev == 0, "initial count is 0"); - - /* - * Staircase loop — each iteration must record new coverage edges. - * - * Per-step flow (matching the buggy code path in kcov/mod.rs): - * KCOV_ENABLE → sets mode = KCOV_TRACE_PC in thread state - * syscalls (burst) → each triggers __sanitizer_cov_trace_pc - * → naked trampoline sets/clears IN_KCOV_TRACE - * → kcov_trace_pc_impl records the PC - * KCOV_DISABLE → sets mode = KCOV_MODE_DISABLED - * read buf[0] → coverage count from the shared buffer - * - * If the trampoline's guard-clear step writes to a garbage address - * (the bug), IN_KCOV_TRACE remains 1, every subsequent trampoline - * invocation takes the "skip" branch, and curr == prev. - */ - for (int step = 1; step <= STAIR_STEPS; step++) { - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, - "KCOV_ENABLE"); - - /* - * Each step does `step` iterations of getpid/getuid/getppid - * so the amount of work monotonically increases. If the guard - * is working correctly each step adds non-zero coverage. - */ - burst(step); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "KCOV_DISABLE"); - - uint64_t curr = buf[0]; - printf(" STAIR step %d: count %lu → %lu (delta %lu)\n", step, - prev, curr, curr - prev); - - /* - * Core assertion: coverage must grow across cycles. - * A failed assertion here (on any step ≥ 2) is the signal - * that the guard-address-register bug has regressed. - */ - CHECK(curr > prev, "coverage count increased this step"); - if (curr <= prev) { - printf(" FAIL: guard likely not cleared — trace " - "stalled at step %d\n", - step); - } - - prev = curr; - } - - munmap(buf, sz); - close(fd); - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/guard-test/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/guard-test/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/guard-test/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/init-trace/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/init-trace/c/CMakeLists.txt deleted file mode 100644 index 9d21a3e192..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/init-trace/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-init-trace C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-init-trace src/main.c) -target_include_directories(test-kcov-init-trace PRIVATE src) -target_compile_options(test-kcov-init-trace PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-init-trace RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/init-trace/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/init-trace/c/src/main.c deleted file mode 100644 index 348e3b383d..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/init-trace/c/src/main.c +++ /dev/null @@ -1,38 +0,0 @@ -/* kcov-spec §2: KCOV_INIT_TRACE — size bounds, second call */ -#include "test_framework.h" -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_MAX_ENTRIES (1024 * 1024) - -int main(void) { - TEST_START("KCOV §2: INIT_TRACE size bounds and repeat"); - - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open"); - - /* Doc: size=0 → EINVAL */ - CHECK_ERR(ioctl(fd, KCOV_INIT_TRACE, 0), EINVAL, "size=0 → EINVAL"); - /* Doc: size>max → EINVAL */ - CHECK_ERR(ioctl(fd, KCOV_INIT_TRACE, KCOV_MAX_ENTRIES + 1), EINVAL, - "size>max → EINVAL"); - - /* Valid sizes */ - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, "size=64 accepted"); - close(fd); - - /* Second INIT_TRACE: Linux returns EBUSY. */ - { - int fd2 = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd2, KCOV_INIT_TRACE, 128), 0, "first INIT_TRACE"); - CHECK_ERR(ioctl(fd2, KCOV_INIT_TRACE, 256), EBUSY, - "second INIT_TRACE → EBUSY (Linux spec)"); - close(fd2); - } - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/init-trace/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/init-trace/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/init-trace/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/integrity/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/integrity/c/CMakeLists.txt deleted file mode 100644 index ddc6e33562..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/integrity/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-integrity C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-integrity src/main.c) -target_include_directories(test-kcov-integrity PRIVATE src) -target_compile_options(test-kcov-integrity PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-integrity RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/integrity/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/integrity/c/src/main.c deleted file mode 100644 index 3a91ef8247..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/integrity/c/src/main.c +++ /dev/null @@ -1,112 +0,0 @@ -/* kcov-spec §9: Integrity — close while active, overflow, state machine */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_RESET_TRACE _IO('c', 104) -#define KCOV_TRACE_PC 0 - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - getppid(); - } -} - -int main(void) { - TEST_START("KCOV §9: close-active, overflow, reopen"); - - /* Close while active — must not crash */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 256), 0, "INIT_TRACE"); - size_t sz = 256 * sizeof(uint64_t); - uint64_t *buf = - mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(50); - munmap(buf, sz); - CHECK_RET(close(fd), 0, "close while active — no crash"); - } - - /* Reopen after close */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "reopen after close"); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, "INIT_TRACE on reopened"); - close(fd); - } - - /* Buffer overflow — count stops at capacity */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 256), 0, "INIT_TRACE"); - size_t sz = 256 * sizeof(uint64_t); - uint64_t *buf = - mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(30000); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - CHECK(buf[0] > 0, "count > 0 after overflow"); - CHECK(buf[0] <= 256, "count ≤ capacity (no overflow)"); - printf(" INFO: overflow count=%lu (cap=256)\n", buf[0]); - munmap(buf, sz); - close(fd); - } - - /* DISABLE → ENABLE → DISABLE cycles (state machine) */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 256), 0, "INIT_TRACE"); - size_t sz = 256 * sizeof(uint64_t); - uint64_t *buf = - mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - // Linux: DISABLE before ENABLE (from INIT) → EINVAL - CHECK_ERR(ioctl(fd, KCOV_DISABLE, 0), EINVAL, "DISABLE before ENABLE → EINVAL"); - for (int c = 1; c <= 3; c++) { - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(30); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - } - CHECK(buf[0] >= 3, "accumulated across 3 cycles"); - munmap(buf, sz); - close(fd); - } - - /* DISABLE with non-zero arg → EINVAL (Linux KCOV_DISABLE semantics) */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, "INIT_TRACE"); - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - CHECK_ERR(ioctl(fd, KCOV_DISABLE, 1), EINVAL, "DISABLE with arg=1 → EINVAL"); - // After failed DISABLE, tracing is still active — verify and then disable properly. - burst(10); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE with arg=0 after failed attempt"); - close(fd); - } - - /* RESET_TRACE with non-zero arg → EINVAL */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, "INIT_TRACE"); - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(10); - CHECK_ERR(ioctl(fd, KCOV_RESET_TRACE, 1), EINVAL, "RESET_TRACE with arg=1 → EINVAL"); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE after failed RESET_TRACE"); - close(fd); - } - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/integrity/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/integrity/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/integrity/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/linux-doc/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/linux-doc/c/CMakeLists.txt deleted file mode 100644 index bbd857afa2..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/linux-doc/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-linux-doc C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-linux-doc src/main.c) -target_compile_options(test-kcov-linux-doc PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-linux-doc RUNTIME DESTINATION usr/bin) -cmake_minimum_required(VERSION 3.20) diff --git a/test-suit/starryos/normal/qemu-kcov/linux-doc/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/linux-doc/c/src/main.c deleted file mode 100644 index ec40e3430c..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/linux-doc/c/src/main.c +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -// #include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define COVER_SIZE (64<<10) - -#define KCOV_TRACE_PC 0 -#define KCOV_TRACE_CMP 1 - -int main() -{ - int fd; - unsigned long *cover, n, i; - - /* A single fd descriptor allows coverage collection on a single - * thread. - */ - fd = open("/dev/kcov", O_RDWR);// changed /sys/kernel/debug/kcov to /dev/kcov - if (fd == -1) - perror("open"), exit(1); - - printf("PASS open: /dev/kcov\n"); - /* Setup trace mode and trace size. */ - if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE)) - perror("ioctl"), exit(1); - /* Mmap buffer shared between kernel- and user-space. */ - cover = (unsigned long*)mmap(NULL, COVER_SIZE * sizeof(unsigned long), - PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if ((void*)cover == MAP_FAILED) - perror("mmap"), exit(1); - /* Enable coverage collection on the current thread. */ - if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC)) - perror("ioctl"), exit(1); - /* Reset coverage from the tail of the ioctl() call. */ - __atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED); - /* Call the target syscall call. */ - read(-1, NULL, 0); - /* Read number of PCs collected. */ - n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED); - for (i = 0; i < n; i++) - printf("0x%lx\n", cover[i + 1]); - /* Disable coverage collection for the current thread. After this call - * coverage can be enabled for a different thread. - */ - if (ioctl(fd, KCOV_DISABLE, 0)) - perror("ioctl"), exit(1); - /* Free resources. */ - if (munmap(cover, COVER_SIZE * sizeof(unsigned long))) - perror("munmap"), exit(1); - if (close(fd)) - perror("close"), exit(1); - return 0; -} \ No newline at end of file diff --git a/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/CMakeLists.txt deleted file mode 100644 index 081969ac19..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-mmap-buffer C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-mmap-buffer src/main.c) -target_include_directories(test-kcov-mmap-buffer PRIVATE src) -target_compile_options(test-kcov-mmap-buffer PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-mmap-buffer RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/main.c deleted file mode 100644 index 2de8253046..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/main.c +++ /dev/null @@ -1,117 +0,0 @@ -/* kcov-spec §3,§4: mmap size and buffer layout */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Lowest virtual address in kernel space, architecture-specific. */ -#if defined(__x86_64__) || defined(__amd64__) -#define KERNEL_PC_MIN 0xffff800000000000ULL -#elif defined(__aarch64__) -#define KERNEL_PC_MIN 0xffff000000000000ULL -#elif defined(__riscv) && __riscv_xlen == 64 -#define KERNEL_PC_MIN 0xffffffc000000000ULL -#elif defined(__loongarch64) -#define KERNEL_PC_MIN 0x9000000000000000ULL -#else -#define KERNEL_PC_MIN 0x8000000000000000ULL -#endif - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - getppid(); - } -} - -int main(void) { - TEST_START("KCOV §3/§4: mmap size, buffer layout"); - - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open"); - - /* §3: mmap with correct size succeeds */ - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 256), 0, "INIT_TRACE size=256"); - size_t sz = 256 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap with correct size"); - if (buf == MAP_FAILED) { - close(fd); - TEST_DONE(); - } - - /* §4: buffer layout — cover[0]=count, starts at 0 */ - CHECK(buf[0] == 0, "cover[0] (count) starts at 0"); - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(500); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - - /* §4: count reflects number of PCs collected */ - uint64_t n = buf[0]; - CHECK(n >= 1, "count >= 1 after tracing"); - - /* §4: PCs start at cover[1], non-zero */ - int ok = 1; - for (uint64_t i = 0; i < n; i++) { - if (buf[1 + i] == 0) - ok = 0; - printf(" TRACE: cover[%lu]=0x%016lx\n", 1 + i, buf[1 + i]); - } - CHECK(ok, "cover[1..n] are non-zero"); - - ok = 1; - for (uint64_t i = 0; i < n; i++) { - if (!(buf[i + 1] >= KERNEL_PC_MIN)) - ok = 0; - } - CHECK(ok, "PCs in kernel range"); - - /* §3: buffer writable after disable (Linux spec: buffer still accessible) - */ - buf[1] = 0xCAFEBABEDEADBEEFULL; - CHECK(buf[1] == 0xCAFEBABEDEADBEEFULL, "buffer writable after DISABLE"); - - munmap(buf, sz); - close(fd); - - /* Upper-bound: medium buffer, light burst — records but won't fill */ - - // If this test fails, try set the buffer larger as the failure might be - // caused by too many edges instrumented. - { - int fd2 = open("/dev/kcov", O_RDWR); - CHECK(fd2 >= 0, "open for upper-bound test"); - CHECK_RET(ioctl(fd2, KCOV_INIT_TRACE, 10240), 0, - "INIT_TRACE size=10240"); - size_t sz2 = 10240 * sizeof(uint64_t); - uint64_t *b2 = - mmap(NULL, sz2, PROT_READ | PROT_WRITE, MAP_SHARED, fd2, 0); - CHECK_PTR(b2, 1, "mmap upper-bound test"); - if (b2 != MAP_FAILED) { - CHECK_RET(ioctl(fd2, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - burst(5); - CHECK_RET(ioctl(fd2, KCOV_DISABLE, 0), 0, "DISABLE"); - uint64_t n2 = b2[0]; - printf(" TRACE: upper-bound count=%lu (cap=1024)\n", n2); - CHECK(n2 > 0, "upper-bound: count > 0"); - CHECK(n2 < 10200, "upper-bound: count < upper-bound (not full)"); - munmap(b2, sz2); - } - close(fd2); - } - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/mmap-buffer/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/open/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/open/c/CMakeLists.txt deleted file mode 100644 index 4292a6e84f..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/open/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-open C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-open src/main.c) -target_include_directories(test-kcov-open PRIVATE src) -target_compile_options(test-kcov-open PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-open RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/open/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/open/c/src/main.c deleted file mode 100644 index 45f00296dd..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/open/c/src/main.c +++ /dev/null @@ -1,17 +0,0 @@ -/* kcov-spec §1: Opening /dev/kcov */ -#include "test_framework.h" -#include -#include -#include - -int main(void) { - TEST_START("KCOV §1: open /dev/kcov"); - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "/dev/kcov opens with O_RDWR"); - if (fd >= 0) - close(fd); - struct stat st; - CHECK_RET(stat("/dev/kcov", &st), 0, "/dev/kcov exists"); - CHECK(S_ISCHR(st.st_mode), "/dev/kcov is a character device"); - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/open/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/open/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/open/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/pc-values/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/pc-values/c/CMakeLists.txt deleted file mode 100644 index f589a9764b..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/pc-values/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-pc-values C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-pc-values src/main.c) -target_include_directories(test-kcov-pc-values PRIVATE src) -target_compile_options(test-kcov-pc-values PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-pc-values RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/pc-values/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/pc-values/c/src/main.c deleted file mode 100644 index 074574f055..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/pc-values/c/src/main.c +++ /dev/null @@ -1,85 +0,0 @@ -/* kcov-spec §7: PC values are kernel instruction pointers */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include - -/* Lowest virtual address in kernel space, architecture-specific. */ -#if defined(__x86_64__) || defined(__amd64__) -#define KERNEL_PC_MIN 0xffff800000000000ULL -#elif defined(__aarch64__) -#define KERNEL_PC_MIN 0xffff000000000000ULL -#elif defined(__riscv) && __riscv_xlen == 64 -#define KERNEL_PC_MIN 0xffffffc000000000ULL -#elif defined(__loongarch64) -#define KERNEL_PC_MIN 0x9000000000000000ULL -#else -#define KERNEL_PC_MIN 0x8000000000000000ULL -#endif - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - getppid(); - struct stat st; - stat("/", &st); - open("/dev/null", O_RDONLY); - } -} - -int main(void) { - TEST_START("KCOV §7: PC values — kernel instruction pointers"); - - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open"); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 256), 0, "INIT_TRACE"); - size_t sz = 256 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf == MAP_FAILED) { - close(fd); - TEST_DONE(); - } - - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - /* Exercise diverse kernel paths */ - burst(500); - int tmp = open("/tmp", O_RDONLY | O_DIRECTORY); - if (tmp >= 0) - close(tmp); - char cwd[128]; - getcwd(cwd, sizeof(cwd)); - struct stat st; - stat("/dev", &st); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - - uint64_t n = buf[0]; - CHECK(n >= 1, "recorded ≥ 1 PC"); - - /* Doc: PCs are kernel instruction pointers, used with addr2line. - * All should be in kernel address range, with diversity. */ - int in_range = 1, diverse = 0; - uint64_t first = buf[1]; - for (uint64_t i = 1; i <= n && i <= 20; i++) { - if (buf[i] < KERNEL_PC_MIN) - in_range = 0; - if (buf[i] != first) - diverse = 1; - } - CHECK(in_range, "all PCs in kernel address range"); - CHECK(diverse, "PCs show diversity (multiple kernel locations)"); - - munmap(buf, sz); - close(fd); - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/pc-values/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/pc-values/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/pc-values/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/CMakeLists.txt deleted file mode 100644 index b0062f0e4c..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-per-fd-state C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-per-fd-state src/main.c) -target_include_directories(test-kcov-per-fd-state PRIVATE src) -target_compile_options(test-kcov-per-fd-state PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-per-fd-state RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/main.c deleted file mode 100644 index 497840561e..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/main.c +++ /dev/null @@ -1,137 +0,0 @@ -/* Per-fd kcov state: verify multiple fds are independent. */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 -#define KCOV_TRACE_CMP 1 - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - } -} - -int main(void) { - TEST_START("Per-fd kcov state"); - - /* ---- 1. Two independent fds, both INIT_TRACE ---- */ - int fd1 = open("/dev/kcov", O_RDWR); - int fd2 = open("/dev/kcov", O_RDWR); - CHECK(fd1 >= 0, "fd1 open"); - CHECK(fd2 >= 0, "fd2 open"); - - CHECK_RET(ioctl(fd1, KCOV_INIT_TRACE, 256), 0, "fd1 INIT_TRACE"); - CHECK_RET(ioctl(fd2, KCOV_INIT_TRACE, 64), 0, "fd2 INIT_TRACE (different size)"); - - /* ---- 2. ENABLE exclusivity: only one fd can enable per thread ---- */ - uint64_t *buf1 = mmap(NULL, 256 * sizeof(uint64_t), PROT_READ | PROT_WRITE, - MAP_SHARED, fd1, 0); - uint64_t *buf2 = mmap(NULL, 64 * sizeof(uint64_t), PROT_READ | PROT_WRITE, - MAP_SHARED, fd2, 0); - CHECK_PTR(buf1 != MAP_FAILED, 1, "fd1 mmap"); - CHECK_PTR(buf2 != MAP_FAILED, 1, "fd2 mmap"); - - /* fd1 ENABLE succeeds, fd2 ENABLE → EBUSY */ - CHECK_RET(ioctl(fd1, KCOV_ENABLE, KCOV_TRACE_PC), 0, "fd1 ENABLE"); - CHECK_ERR(ioctl(fd2, KCOV_ENABLE, KCOV_TRACE_PC), EBUSY, "fd2 ENABLE → EBUSY"); - - /* Disable fd1 first, then fd2 can enable */ - CHECK_RET(ioctl(fd1, KCOV_DISABLE, 0), 0, "fd1 DISABLE"); - CHECK_RET(ioctl(fd2, KCOV_ENABLE, KCOV_TRACE_PC), 0, "fd2 ENABLE after fd1 DISABLE"); - - /* fd1 cannot re-enable while fd2 is active */ - CHECK_ERR(ioctl(fd1, KCOV_ENABLE, KCOV_TRACE_PC), EBUSY, "fd1 re-ENABLE → EBUSY (fd2 active)"); - - CHECK_RET(ioctl(fd2, KCOV_DISABLE, 0), 0, "fd2 DISABLE"); - - /* ---- 3. Close-while-enabled stops coverage ---- */ - int fd3 = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd3, KCOV_INIT_TRACE, 128), 0, "fd3 INIT_TRACE"); - uint64_t *buf3 = mmap(NULL, 128 * sizeof(uint64_t), PROT_READ | PROT_WRITE, - MAP_SHARED, fd3, 0); - CHECK_PTR(buf3 != MAP_FAILED, 1, "fd3 mmap"); - - CHECK_RET(ioctl(fd3, KCOV_ENABLE, KCOV_TRACE_PC), 0, "fd3 ENABLE"); - burst(50); - uint64_t after_burst = buf3[0]; - CHECK(after_burst >= 1, "fd3 recorded coverage before close"); - close(fd3); /* triggers on_close → clears thread ref */ - fd3 = -1; - - /* Re-open a new fd, verify old buffer is disconnected and new one works */ - fd3 = open("/dev/kcov", O_RDWR); - CHECK(fd3 >= 0, "fd3 reopen"); - CHECK_RET(ioctl(fd3, KCOV_INIT_TRACE, 128), 0, "fd3 re-INIT_TRACE"); - uint64_t *buf3b = mmap(NULL, 128 * sizeof(uint64_t), PROT_READ | PROT_WRITE, - MAP_SHARED, fd3, 0); - CHECK_PTR(buf3b != MAP_FAILED, 1, "fd3 re-mmap"); - CHECK_RET(ioctl(fd3, KCOV_ENABLE, KCOV_TRACE_PC), 0, "fd3 re-ENABLE"); - burst(10); - CHECK(buf3b[0] >= 1, "fd3 reopened fd records new coverage"); - CHECK_RET(ioctl(fd3, KCOV_DISABLE, 0), 0, "fd3 DISABLE after reopen"); - - /* ---- 4. Fork: child inherits fd but coverage disabled ---- */ - pid_t pid = fork(); - if (pid == 0) { - /* Child: kcov should be disabled on our thread after fork */ - int r; - - /* Linux: DISABLE from a non-tracing state → EINVAL. - * fd1 was disabled earlier (mode=INIT, tracer_tid=None). */ - r = ioctl(fd1, KCOV_DISABLE, 0); - if (r != -1 || errno != EINVAL) _exit(10); - - /* Child can enable on fd1 (still in INIT mode) */ - r = ioctl(fd1, KCOV_ENABLE, KCOV_TRACE_PC); - if (r != 0) _exit(11); - burst(10); - uint64_t c = buf1[0]; - if (c < 1) _exit(12); - - r = ioctl(fd1, KCOV_DISABLE, 0); - if (r != 0) _exit(13); - - _exit(0); - } - CHECK(pid > 0, "fork"); - int wstatus; - CHECK_RET(waitpid(pid, &wstatus, 0), pid, "waitpid"); - CHECK(WIFEXITED(wstatus), "child exited normally"); - CHECK_RET(WEXITSTATUS(wstatus), 0, "child exit code 0 (kcov fork semantics)"); - - /* ---- 5. New fd after close works fresh ---- */ - int fd4 = open("/dev/kcov", O_RDWR); - CHECK(fd4 >= 0, "fd4 open (fresh after all cycles)"); - CHECK_RET(ioctl(fd4, KCOV_INIT_TRACE, 32), 0, "fd4 INIT_TRACE"); - uint64_t *buf4 = mmap(NULL, 32 * sizeof(uint64_t), PROT_READ | PROT_WRITE, - MAP_SHARED, fd4, 0); - CHECK_PTR(buf4 != MAP_FAILED, 1, "fd4 mmap"); - CHECK_RET(ioctl(fd4, KCOV_ENABLE, KCOV_TRACE_PC), 0, "fd4 ENABLE"); - burst(5); - CHECK(buf4[0] >= 1, "fd4 recorded coverage"); - CHECK_RET(ioctl(fd4, KCOV_DISABLE, 0), 0, "fd4 DISABLE"); - close(fd4); - - /* Cleanup */ - munmap(buf1, 256 * sizeof(uint64_t)); - munmap(buf2, 64 * sizeof(uint64_t)); - munmap(buf3b, 128 * sizeof(uint64_t)); - munmap(buf4, 32 * sizeof(uint64_t)); - close(fd1); - close(fd2); - close(fd3); - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/per-fd-state/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/CMakeLists.txt deleted file mode 100644 index e69ea6e1f7..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-pre-init-reject C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-pre-init-reject src/main.c) -target_include_directories(test-kcov-pre-init-reject PRIVATE src) -target_compile_options(test-kcov-pre-init-reject PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-pre-init-reject RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/main.c deleted file mode 100644 index f1a8a04491..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/main.c +++ /dev/null @@ -1,42 +0,0 @@ -/* kcov-spec §2a,§3a,§5a: Rejections before INIT_TRACE. - * MUST run first — each test binary is a fresh process with clean TID. */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_TRACE_PC 0 - -int main(void) { - TEST_START("KCOV §2a/§3a/§5a: rejections before INIT_TRACE"); - - /* §3a: mmap before INIT_TRACE → EINVAL (Linux: kcov->area == NULL) */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open for mmap-before-init test"); - size_t sz = 256 * sizeof(uint64_t); - void *p = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK(p == MAP_FAILED, "mmap before INIT_TRACE rejected"); - if (p == MAP_FAILED) - CHECK(errno == EINVAL, "mmap-before-init errno = EINVAL"); - close(fd); - } - - /* §5a: ENABLE before INIT_TRACE → EINVAL (Linux: kcov->mode != INIT) */ - { - int fd = open("/dev/kcov", O_RDWR); - CHECK(fd >= 0, "open for enable-before-init test"); - CHECK_ERR(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), EINVAL, - "ENABLE before INIT_TRACE → EINVAL"); - close(fd); - } - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/pre-init-reject/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/stress/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/stress/c/CMakeLists.txt deleted file mode 100644 index 8a6bb3addb..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/stress/c/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-stress C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-stress src/main.c) -target_include_directories(test-kcov-stress PRIVATE src) -target_compile_options(test-kcov-stress PRIVATE -Wall -Wextra -Werror) -target_link_libraries(test-kcov-stress PRIVATE -lpthread) -install(TARGETS test-kcov-stress RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/stress/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/stress/c/src/main.c deleted file mode 100644 index 852d065d3e..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/stress/c/src/main.c +++ /dev/null @@ -1,293 +0,0 @@ -/* - * test-kcov-stress.c — KCOV light stress tests - * - * Pushes every part of the kcov interface under moderate load to - * catch races, leaks, and performance regressions without consuming - * excessive CI time. Iteration counts are intentionally capped so - * this stays in the normal (not stress) test group — a full stress - * suite with larger parameters lives under test-suit/starryos/stress/. - * - * Runs independently from correctness / spec tests. - */ - -#define _GNU_SOURCE -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 -#define KCOV_TRACE_CMP 1 - -static int open_kcov(void) { - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) { - printf("SKIP: /dev/kcov not available (errno=%d: %s)\n", errno, - strerror(errno)); - exit(0); - } - return fd; -} - -static void heavy_burst(int count) { - for (volatile int i = 0; i < count; i++) { - getpid(); - getuid(); - getppid(); - struct stat st; - stat("/", &st); - stat("/dev", &st); - char buf[64]; - getcwd(buf, sizeof(buf)); - int fd = open("/dev/null", O_RDONLY); - if (fd >= 0) - close(fd); - fd = open("/dev/zero", O_RDONLY); - if (fd >= 0) - close(fd); - } -} - -/* §1: Open/close stress — 100 rapid cycles */ -static void stress_open_close(void) { - for (int i = 0; i < 100; i++) { - int fd = open_kcov(); - close(fd); - } - int fd = open_kcov(); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, - "INIT_TRACE after 100 open/close cycles"); - close(fd); - printf(" INFO: 100 open/close cycles OK\n"); -} - -/* §2: Max-size buffer — 4096 entries, 1K burst */ -static void stress_max_buffer(void) { - int fd = open_kcov(); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 4096), 0, "INIT_TRACE size=4096"); - size_t sz = 4096 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap 4096 entries"); - if (buf == MAP_FAILED) { - close(fd); - return; - } - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - heavy_burst(1000); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - uint64_t n = buf[0]; - printf(" INFO: 4096-entry buffer, 1k burst → %lu entries\n", n); - CHECK(n > 0, "coverage in 4096-entry buffer"); - CHECK(n <= 4096, "count ≤ 4096"); - munmap(buf, sz); - close(fd); -} - -/* §3: Rapid ENABLE/DISABLE cycling — 100 cycles */ -static void stress_enable_disable_cycle(void) { - int fd = open_kcov(); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, "INIT_TRACE"); - size_t sz = 64 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf == MAP_FAILED) { - close(fd); - return; - } - for (int i = 0; i < 100; i++) { - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - getpid(); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - } - printf(" INFO: 100 ENABLE/DISABLE cycles, count=%lu\n", buf[0]); - CHECK(buf[0] > 0, "coverage across 100 cycles"); - munmap(buf, sz); - close(fd); -} - -/* §4: Multi-thread — 8 threads, 50 cycles each */ -typedef struct { - int tid; - uint64_t count; - int ok; - int cycles; -} stress_thr_t; -static void *stress_thread(void *arg) { - stress_thr_t *s = (stress_thr_t *)arg; - s->ok = 0; - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) - return NULL; - if (ioctl(fd, KCOV_INIT_TRACE, 64)) { - close(fd); - return NULL; - } - size_t sz = 64 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (buf == MAP_FAILED) { - close(fd); - return NULL; - } - for (int c = 0; c < s->cycles; c++) { - if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC)) { - munmap(buf, sz); - close(fd); - return NULL; - } - for (volatile int i = 0; i < 200; i++) { - gettid(); - getpid(); - } - ioctl(fd, KCOV_DISABLE, 0); - } - s->count = buf[0]; - s->tid = (int)gettid(); - munmap(buf, sz); - close(fd); - s->ok = 1; - return NULL; -} -static void stress_many_threads(void) { -#define N 8 - pthread_t t[N]; - stress_thr_t r[N]; - for (int i = 0; i < N; i++) { - r[i].cycles = 50; - pthread_create(&t[i], NULL, stress_thread, &r[i]); - } - for (int i = 0; i < N; i++) - pthread_join(t[i], NULL); - int all_ok = 1; - for (int i = 0; i < N; i++) { - if (!r[i].ok) - all_ok = 0; - printf(" INFO: thread %d → %lu\n", r[i].tid, r[i].count); - } - CHECK(all_ok, "all 8 threads completed"); -#undef N -} - -/* §5: Open/init/mmap/enable/disable/close cycles — 100 iterations. - * Each iteration uses a fresh fd (Linux: KCOV_INIT_TRACE is one-shot per fd). */ -static void stress_buffer_replace(void) { - for (int i = 0; i < 100; i++) { - int fd = open_kcov(); - size_t sz = (64 + (i % 4) * 64) * sizeof(uint64_t); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64 + (i % 4) * 64), 0, "INIT"); - uint64_t *buf = - mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf != MAP_FAILED) { - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - for (volatile int j = 0; j < 100; j++) - getpid(); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - munmap(buf, sz); - } - close(fd); - } - printf(" INFO: 100 open/init/enable/close cycles OK\n"); -} - -/* §6: Heavy syscall storm — 1K burst */ -static void stress_syscall_storm(void) { - int fd = open_kcov(); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 1024), 0, "INIT_TRACE size=1024"); - size_t sz = 1024 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf == MAP_FAILED) { - close(fd); - return; - } - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - heavy_burst(1000); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - uint64_t n = buf[0]; - printf(" INFO: 1k burst on 1024-entry buffer → %lu entries\n", n); - CHECK(n > 0, "coverage during storm"); - CHECK(n <= 1024, "count ≤ 1024"); - munmap(buf, sz); - close(fd); -} - -/* §7: 8K buffer, 1K burst */ -static void stress_max_entries(void) { - int fd = open_kcov(); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 8192), 0, "INIT_TRACE size=8192"); - size_t sz = 8192 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf == MAP_FAILED) { - close(fd); - return; - } - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - heavy_burst(1000); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - uint64_t n = buf[0]; - printf(" INFO: 1k burst on 8192-entry buffer → %lu entries\n", n); - CHECK(n > 0, "coverage in 8K buffer"); - CHECK(n <= 8192, "count ≤ 8192"); - munmap(buf, sz); - close(fd); -} - -/* §8: Mode toggle — 100 TRACE_PC ENABLE/DISABLE cycles; TRACE_CMP is - * intentionally rejected with EINVAL until CMP hooks are implemented. */ -static void stress_mode_toggle(void) { - int fd = open_kcov(); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 128), 0, "INIT_TRACE"); - size_t sz = 128 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - CHECK_PTR(buf, 1, "mmap"); - if (buf == MAP_FAILED) { - close(fd); - return; - } - /* Verify TRACE_CMP is properly rejected */ - CHECK_ERR(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_CMP), EINVAL, "CMP reject (not yet implemented)"); - /* PC mode toggle stress */ - for (int i = 0; i < 100; i++) { - CHECK_RET(ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC), 0, "ENABLE"); - for (volatile int j = 0; j < 50; j++) - getpid(); - CHECK_RET(ioctl(fd, KCOV_DISABLE, 0), 0, "DISABLE"); - } - printf(" INFO: 100 PC mode-toggle cycles, count=%lu\n", buf[0]); - munmap(buf, sz); - close(fd); -} - -int main(void) { - TEST_START("KCOV stress suite"); - int probe = open_kcov(); - if (probe < 0) { - printf("SKIP: /dev/kcov not available\n"); - TEST_DONE(); - } - close(probe); - - stress_open_close(); - stress_buffer_replace(); - stress_mode_toggle(); - stress_enable_disable_cycle(); - stress_max_buffer(); - stress_syscall_storm(); - stress_max_entries(); - stress_many_threads(); - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/stress/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/stress/c/src/test_framework.h deleted file mode 100644 index 3502428f84..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/stress/c/src/test_framework.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -/* 必须在最前面定义,确保 pipe2/gettid 等可用 */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -/* - * StarryOS Syscall Test Framework - * - * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 - * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 - * - * 用法: - * TEST_START("测试名"); - * CHECK(call == expected, "描述"); - * CHECK_ERR(call, EBADF, "描述"); - * TEST_DONE(); - */ - -#include -#include -#include -#include - -static int __pass = 0; -static int __fail = 0; - -/* ---- 核心: 带文件名+行号的检查宏 ---- */ - -/* 检查条件为真 */ -#define CHECK(cond, msg) do { \ - if (cond) { \ - printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, errno, strerror(errno)); \ - __fail++; \ - } \ -} while(0) - -/* 检查 syscall 返回特定值 */ -#define CHECK_RET(call, expected, msg) do { \ - errno = 0; \ - long _r = (long)(call); \ - long _e = (long)(expected); \ - if (_r == _e) { \ - printf(" PASS | %s:%d | %s (ret=%ld)\n", \ - __FILE__, __LINE__, msg, _r); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno));\ - __fail++; \ - } \ -} while(0) - -/* 检查 syscall 失败且 errno 符合预期 */ -#define CHECK_ERR(call, exp_errno, msg) do { \ - errno = 0; \ - long _r = (long)(call); \ - if (_r == -1 && errno == (exp_errno)) { \ - printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ - __FILE__, __LINE__, msg, errno); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, strerror(errno));\ - __fail++; \ - } \ -} while(0) - -/* 检查指针 */ -#define CHECK_PTR(ptr, not_null, msg) do { \ - if (!!(ptr) == !!(not_null)) { \ - printf(" PASS | %s:%d | %s (ptr=%p)\n", \ - __FILE__, __LINE__, msg, (void*)(ptr)); \ - __pass++; \ - } else { \ - printf(" FAIL | %s:%d | %s | expected %s got ptr=%p | errno=%d (%s)\n", \ - __FILE__, __LINE__, msg, \ - (not_null) ? "non-NULL" : "NULL/MAP_FAILED", \ - (void*)(ptr), errno, strerror(errno)); \ - __fail++; \ - } \ -} while(0) - -/* ---- 测试边界 ---- */ -#define TEST_START(name) \ - printf("================================================\n"); \ - printf(" TEST: %s\n", name); \ - printf(" FILE: %s\n", __FILE__); \ - printf("================================================\n") - -#define TEST_DONE() \ - printf("------------------------------------------------\n"); \ - printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ - printf("================================================\n\n"); \ - return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/CMakeLists.txt deleted file mode 100644 index f4f7f10cec..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-syzkaller-emulation C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-syzkaller-emulation src/main.c) -target_include_directories(test-kcov-syzkaller-emulation PRIVATE src) -target_compile_options(test-kcov-syzkaller-emulation PRIVATE -Wall -Wextra -Werror) -install(TARGETS test-kcov-syzkaller-emulation RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/main.c deleted file mode 100644 index 18928333ee..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/main.c +++ /dev/null @@ -1,431 +0,0 @@ -/* Emulate syzkaller's exact kcov usage pattern. - * - * Syzkaller's executor (executor_linux.h) uses kcov in the following way: - * 1. cover_open() — open /dev/kcov, INIT_TRACE with kCoverSize (512K), - * mmap the buffer (with guard pages in newer versions) - * 2. cover_enable() — KCOV_ENABLE with KCOV_TRACE_PC - * 3. For each test program: - * cover_reset() — write 0 to buf[0] (NOT a DISABLE/ENABLE cycle) - * execute syscalls - * cover_collect() — read buf[0] to get count, check overflow - * 4. cover_disable() — KCOV_DISABLE - */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_RESET_TRACE _IO('c', 104) -#define KCOV_TRACE_PC 0 -#define KCOV_TRACE_CMP 1 - -/* Syzkaller's default coverage size (entries, not bytes). */ -#define SYZ_KCOVER_SIZE (512 * 1024) -/* Our maximum — used when the test needs a guaranteed-accepted size. */ -#define KCOV_MAX_ENTRIES (1024 * 1024) - -/* Architecture kernel-space PC floor (matches pc-values test). */ -#if defined(__x86_64__) || defined(__amd64__) -#define KERNEL_PC_MIN 0xffff800000000000ULL -#elif defined(__aarch64__) -#define KERNEL_PC_MIN 0xffff000000000000ULL -#elif defined(__riscv) && __riscv_xlen == 64 -#define KERNEL_PC_MIN 0xffffffc000000000ULL -#else -#define KERNEL_PC_MIN 0x8000000000000000ULL -#endif - -/* ---- helpers ---- */ - -static void burst(int n) { - for (volatile int i = 0; i < n; i++) { - getpid(); - getuid(); - getppid(); - } -} - -static void heavy_burst(int n) { - for (volatile int i = 0; i < n; i++) { - /* Mix of syscalls to exercise diverse kernel paths. */ - getpid(); - getuid(); - getppid(); - struct stat st; - stat("/", &st); - stat("/dev", &st); - int fd = open("/dev/null", O_RDONLY); - if (fd >= 0) - close(fd); - } -} - -/* ---- syzkaller-like API ---- */ - -typedef struct { - int fd; - uint64_t *data; - uint64_t *data_end; /* one past the last valid u64 */ - uint64_t size; /* number of u64 entries (the cover_size arg) */ - uint32_t overflow; - uint32_t collected; -} cover_t; - -static void cover_open(cover_t *cov, uint64_t n_entries) { - memset(cov, 0, sizeof(*cov)); - cov->fd = open("/dev/kcov", O_RDWR); - if (cov->fd < 0) { - printf("FATAL: cover_open: open failed (errno=%d)\n", errno); - exit(1); - } - - if (ioctl(cov->fd, KCOV_INIT_TRACE, n_entries) != 0) { - printf("FATAL: cover_open: INIT_TRACE(%lu) failed (errno=%d)\n", - n_entries, errno); - exit(1); - } - - size_t mmap_size = n_entries * sizeof(uint64_t); - cov->data = (uint64_t *)mmap(NULL, mmap_size, - PROT_READ | PROT_WRITE, MAP_SHARED, - cov->fd, 0); - if (cov->data == MAP_FAILED) { - printf("FATAL: cover_open: mmap failed (errno=%d)\n", errno); - exit(1); - } - - cov->data_end = cov->data + n_entries; - cov->size = n_entries; -} - -static void cover_enable(cover_t *cov) { - if (ioctl(cov->fd, KCOV_ENABLE, KCOV_TRACE_PC) != 0) { - printf("FATAL: cover_enable failed (errno=%d)\n", errno); - exit(1); - } -} - -static void cover_disable(cover_t *cov) { - ioctl(cov->fd, KCOV_DISABLE, 0); -} - -static void cover_close(cover_t *cov) { - if (cov->fd >= 0) { - munmap(cov->data, cov->size * sizeof(uint64_t)); - close(cov->fd); - cov->fd = -1; - } -} - -/* Syzkaller's exact "reset" — just zero the count word, NOT a DISABLE/ENABLE. */ -static void cover_reset(cover_t *cov) { - cov->data[0] = 0; -} - -/* Syzkaller's cover_collect: read count, check overflow. - * Syzkaller's overflow check: (data + (count + 2) * sizeof(cover_data_t)) > data_end - * The +2 accounts for the header and ensures at least one PC slot exists. - */ -static void cover_collect(cover_t *cov) { - cov->collected = (uint32_t)cov->data[0]; - cov->overflow = - (cov->data + (cov->collected + 2)) > cov->data_end ? 1 : 0; -} - -/* ---- Test scenarios ---- */ - -/* §1: Syzkaller's default buffer size (512K entries = 4MB). - * Must be accepted after our KCOV_MAX_ENTRIES bump. */ -static void syz_default_buffer_size(void) { - cover_t cov; - cover_open(&cov, SYZ_KCOVER_SIZE); - printf(" INFO: syzkaller 512K-entry buffer open OK\n"); - CHECK(cov.data != NULL, "512K buffer mmap OK"); - cover_close(&cov); -} - -/* §2: cover_reset pattern — zero count word without DISABLE. - * This is how syzkaller resets coverage between test programs. */ -static void syz_reset_without_disable(void) { - cover_t cov; - cover_open(&cov, 4096); - cover_enable(&cov); - - /* Generate some coverage. */ - burst(100); - cover_collect(&cov); - CHECK(cov.collected >= 1, "pre-reset: coverage collected"); - uint64_t saved = cov.collected; - - /* Syzkaller reset: just zero the count word. */ - cover_reset(&cov); - CHECK(cov.data[0] == 0, "reset: count word is 0"); - - /* Execute more syscalls — coverage should accumulate again. */ - burst(50); - cover_collect(&cov); - CHECK(cov.collected >= 1, "post-reset: new coverage collected"); - printf(" INFO: reset cycle: %lu → 0 → %u\n", saved, cov.collected); - - cover_disable(&cov); - cover_close(&cov); -} - -/* §3: Multiple collect/reset cycles in a single ENABLE session. - * Syzkaller runs many test programs per enable, resetting between each. */ -static void syz_multi_reset_cycles(void) { - cover_t cov; - cover_open(&cov, 4096); - cover_enable(&cov); - - int cycles = 20; - for (int i = 0; i < cycles; i++) { - cover_reset(&cov); - burst(30); - cover_collect(&cov); - if (cov.collected < 1) { - printf(" WARN: cycle %d collected %u entries\n", i, cov.collected); - } - } - CHECK(cov.collected >= 1, "last cycle collected coverage"); - printf(" INFO: %d reset/collect cycles in one ENABLE session\n", cycles); - - cover_disable(&cov); - cover_close(&cov); -} - -/* §4: Overflow detection using syzkaller's exact formula. - * Syzkaller checks: (data + (count + 2) * 8) > data_end */ -static void syz_overflow_detection(void) { - /* Use a tiny buffer so we can trigger overflow. */ - cover_t cov; - cover_open(&cov, 64); - cover_enable(&cov); - - /* Heavy burst should fill the buffer. */ - heavy_burst(10000); - cover_collect(&cov); - - CHECK(cov.collected > 0, "overflow: count > 0"); - CHECK(cov.collected <= 64, "overflow: count ≤ capacity"); - printf(" INFO: 64-entry buffer, heavy burst → %u entries, overflow=%u\n", - cov.collected, cov.overflow); - - cover_disable(&cov); - cover_close(&cov); -} - -/* §5: Syzkaller-style PC validation. - * All PCs must be valid kernel-space addresses. */ -static void syz_pc_validation(void) { - cover_t cov; - cover_open(&cov, 4096); - cover_enable(&cov); - heavy_burst(500); - cover_disable(&cov); - - cover_collect(&cov); - uint32_t n = cov.collected; - printf(" INFO: collected %u PCs for validation\n", n); - - int all_valid = 1; - int all_nonzero = 1; - int diverse = 0; - uint64_t first_pc = 0; - for (uint32_t i = 1; i <= n && i <= 50; i++) { - uint64_t pc = cov.data[i]; - if (pc == 0) - all_nonzero = 0; - if (pc < KERNEL_PC_MIN) - all_valid = 0; - if (i == 1) - first_pc = pc; - else if (pc != first_pc) - diverse = 1; - } - - CHECK(all_valid, "PC validation: all PCs in kernel address range"); - CHECK(all_nonzero, "PC validation: no zero PCs"); - CHECK(diverse, "PC validation: multiple distinct PCs observed"); - - cover_close(&cov); -} - -/* §6: Re-enable after disable (syzkaller reuses fds). - * Syzkaller may disable and re-enable on the same fd for a new - * batch of test programs. */ -static void syz_reuse_fd(void) { - cover_t cov; - cover_open(&cov, 4096); - - for (int cycle = 0; cycle < 5; cycle++) { - cover_enable(&cov); - burst(50); - cover_disable(&cov); - - /* Syzkaller reads coverage between disable and re-enable. */ - cover_collect(&cov); - if (cycle == 0) - CHECK(cov.collected >= 1, "reuse: coverage in cycle 0"); - } - printf(" INFO: 5 ENABLE/DISABLE cycles on same fd OK\n"); - - cover_close(&cov); -} - -/* §7: Syzkaller-style fork worker. - * Syzkaller forks worker processes, each needing independent kcov. */ -static void syz_worker_fork(void) { - cover_t parent_cov; - cover_open(&parent_cov, 1024); - cover_enable(&parent_cov); - burst(20); - cover_disable(&parent_cov); - - /* Capture parent coverage baseline before fork. */ - uint64_t parent_before = parent_cov.data[0]; - - pid_t pid = fork(); - if (pid == 0) { - /* Child: must create its own kcov (fork resets per-thread state). - * Linux: "Coverage collection is disabled in child after fork()." - * The fd is inherited but thread state is clean. */ - cover_t child_cov; - cover_open(&child_cov, 1024); - cover_enable(&child_cov); - burst(30); - cover_disable(&child_cov); - - cover_collect(&child_cov); - if (child_cov.collected < 1) - _exit(10); - - cover_close(&child_cov); - _exit(0); - } - - CHECK(pid > 0, "fork worker"); - int wstatus; - CHECK_RET(waitpid(pid, &wstatus, 0), pid, "waitpid fork worker"); - CHECK(WIFEXITED(wstatus), "fork worker exited normally"); - CHECK_RET(WEXITSTATUS(wstatus), 0, "fork worker kcov OK"); - - /* Parent's coverage should be unaffected by fork. */ - CHECK(parent_cov.data[0] == parent_before, - "fork: parent coverage unchanged"); - - cover_close(&parent_cov); -} - -/* §8: KCOV_RESET_TRACE ioctl (alternative reset path for read-only coverage). - * Syzkaller uses this when `flag_read_only_coverage` is set, instead of - * writing 0 to buf[0] directly. Our implementation supports both paths. */ -static void syz_reset_trace_ioctl(void) { - cover_t cov; - cover_open(&cov, 4096); - cover_enable(&cov); - - /* Generate initial coverage. */ - burst(100); - cover_collect(&cov); - CHECK(cov.collected >= 1, "reset-trace: pre-reset coverage > 0"); - uint64_t before = cov.collected; - - /* KCOV_RESET_TRACE — the kernel zeroes buf[0] then immediately the ioctl - * return path (which is itself instrumented) records a few new PCs, so - * buf[0] will be non-zero but very small. Linux semantics: tracing - * continues after reset (no DISABLE). */ - if (ioctl(cov.fd, KCOV_RESET_TRACE, 0) != 0) { - printf("FATAL: KCOV_RESET_TRACE failed (errno=%d)\n", errno); - exit(1); - } - CHECK(cov.data[0] < 100, "reset-trace: count near 0 after RESET_TRACE (ioctl return path may add a few)"); - - /* Generate more coverage — should accumulate again. */ - burst(50); - cover_collect(&cov); - CHECK(cov.collected > 100, "reset-trace: post-reset new coverage > 100"); - printf(" INFO: RESET_TRACE cycle: %lu → 0 → %u\n", before, cov.collected); - - cover_disable(&cov); - cover_close(&cov); -} - -/* §9: Multiple syzkaller-like workers (threads). - * Each worker has its own kcov fd, independent coverage. */ -typedef struct { - int id; - uint32_t count; - int ok; -} worker_t; - -static void *worker_thread(void *arg) { - worker_t *w = (worker_t *)arg; - w->ok = 0; - - cover_t cov; - cover_open(&cov, 2048); - cover_enable(&cov); - heavy_burst(300); - cover_disable(&cov); - - cover_collect(&cov); - w->count = cov.collected; - cover_close(&cov); - w->ok = 1; - return NULL; -} - -static void syz_worker_threads(void) { -#define NWORKERS 8 - pthread_t t[NWORKERS]; - worker_t r[NWORKERS]; - - for (int i = 0; i < NWORKERS; i++) { - r[i].id = i; - pthread_create(&t[i], NULL, worker_thread, &r[i]); - } - int all_ok = 1; - uint64_t total = 0; - for (int i = 0; i < NWORKERS; i++) { - pthread_join(t[i], NULL); - if (!r[i].ok) - all_ok = 0; - total += r[i].count; - printf(" INFO: worker %d → %u PCs\n", r[i].id, r[i].count); - } - CHECK(all_ok, "all 8 workers completed OK"); - CHECK(total > 0, "total coverage across workers > 0"); -#undef NWORKERS -} - -/* ---- main ---- */ - -int main(void) { - TEST_START("Syzkaller kcov emulation"); - - syz_default_buffer_size(); - syz_reset_without_disable(); - syz_multi_reset_cycles(); - syz_overflow_detection(); - syz_pc_validation(); - syz_reuse_fd(); - syz_worker_fork(); - syz_reset_trace_ioctl(); - syz_worker_threads(); - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/syzkaller-emulation/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-kcov/threading/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-kcov/threading/c/CMakeLists.txt deleted file mode 100644 index 014a2993fc..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/threading/c/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.20) -project(test-kcov-threading C) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -add_executable(test-kcov-threading src/main.c) -target_include_directories(test-kcov-threading PRIVATE src) -target_compile_options(test-kcov-threading PRIVATE -Wall -Wextra -Werror) -target_link_libraries(test-kcov-threading PRIVATE -lpthread) -install(TARGETS test-kcov-threading RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-kcov/threading/c/src/main.c b/test-suit/starryos/normal/qemu-kcov/threading/c/src/main.c deleted file mode 100644 index b0f05dc871..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/threading/c/src/main.c +++ /dev/null @@ -1,90 +0,0 @@ -/* kcov-spec §8: Multi-thread — independent per-task coverage */ -#include "test_framework.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define KCOV_INIT_TRACE _IOR('c', 1, unsigned long) -#define KCOV_ENABLE _IO('c', 100) -#define KCOV_DISABLE _IO('c', 101) -#define KCOV_TRACE_PC 0 - -typedef struct { - int tid; - uint64_t n; - int ok; -} thr_t; - -static void *thread_fn(void *arg) { - thr_t *s = (thr_t *)arg; - s->ok = 0; - int fd = open("/dev/kcov", O_RDWR); - if (fd < 0) - return NULL; - if (ioctl(fd, KCOV_INIT_TRACE, 128)) { - close(fd); - return NULL; - } - size_t sz = 128 * sizeof(uint64_t); - uint64_t *buf = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (buf == MAP_FAILED) { - close(fd); - return NULL; - } - if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC)) { - munmap(buf, sz); - close(fd); - return NULL; - } - for (volatile int i = 0; i < 300; i++) { - gettid(); - getpid(); - } - ioctl(fd, KCOV_DISABLE, 0); - s->n = buf[0]; - s->tid = (int)gettid(); - munmap(buf, sz); - close(fd); - s->ok = 1; - return NULL; -} - -int main(void) { - TEST_START("KCOV §8: per-task independent coverage"); - - /* Doc: "each thread separately" — two threads open their own fds */ - pthread_t ta, tb; - thr_t sa = {0}, sb = {0}; - pthread_create(&ta, NULL, thread_fn, &sa); - pthread_create(&tb, NULL, thread_fn, &sb); - pthread_join(ta, NULL); - pthread_join(tb, NULL); - - CHECK(sa.ok, "thread A completed"); - CHECK(sb.ok, "thread B completed"); - CHECK(sa.n >= 1, "thread A recorded coverage"); - CHECK(sb.n >= 1, "thread B recorded coverage"); - printf(" INFO: thread %d → %lu, thread %d → %lu\n", sa.tid, sa.n, sb.tid, - sb.n); - - /* Doc: "Tracing automatically gets disabled when a thread exits." - * Verify a short-lived thread doesn't leave stale state. */ - pthread_t tc; - thr_t sc = {0}; - pthread_create(&tc, NULL, thread_fn, &sc); - pthread_join(tc, NULL); - CHECK(sc.ok, "short-lived thread completed"); - - /* Main thread can still use kcov after child threads exit */ - int fd = open("/dev/kcov", O_RDWR); - CHECK_RET(ioctl(fd, KCOV_INIT_TRACE, 64), 0, - "INIT_TRACE after thread exit"); - close(fd); - - TEST_DONE(); -} diff --git a/test-suit/starryos/normal/qemu-kcov/threading/c/src/test_framework.h b/test-suit/starryos/normal/qemu-kcov/threading/c/src/test_framework.h deleted file mode 100644 index 7e92ec9e93..0000000000 --- a/test-suit/starryos/normal/qemu-kcov/threading/c/src/test_framework.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif -#include -#include -#include -#include -static int __pass = 0; -static int __fail = 0; -#define CHECK(cond, msg) do { \ - if (cond) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", __FILE__, __LINE__, msg, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_RET(call, exp, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == (long)(exp)) { printf(" PASS | %s:%d | %s (ret=%ld)\n", __FILE__, __LINE__, msg, _r); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp=%ld got=%ld errno=%d\n", __FILE__, __LINE__, msg, (long)(exp), _r, errno); __fail++; } \ -} while(0) -#define CHECK_ERR(call, exp_e, msg) do { \ - errno = 0; long _r = (long)(call); \ - if (_r == -1 && errno == (exp_e)) { printf(" PASS | %s:%d | %s (errno=%d)\n", __FILE__, __LINE__, msg, errno); __pass++; } \ - else { printf(" FAIL | %s:%d | %s | exp errno=%d got ret=%ld errno=%d (%s)\n", __FILE__, __LINE__, msg, (int)(exp_e), _r, errno, strerror(errno)); __fail++; } \ -} while(0) -#define CHECK_PTR(ptr, ok, msg) do { \ - if (!!(ptr)==!!(ok)) { printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); __pass++; } \ - else { printf(" FAIL | %s:%d | %s\n", __FILE__, __LINE__, msg); __fail++; } \ -} while(0) -#define TEST_START(n) do { \ - printf("================================================\n TEST: %s\n================================================\n", n); \ -} while(0) -#define TEST_DONE() do { \ - printf("------------------------------------------------\n DONE: %d pass, %d fail\n================================================\n", __pass, __fail); \ - return __fail > 0 ? 1 : 0; \ -} while(0) diff --git a/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml index 93bdabbd28..857f3a3dbd 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml @@ -1,14 +1,19 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/aarch64-qemu-virt", - "qemu", + "ax-hal/plat-dyn", + "ax-feat/display", + "ax-feat/rtc", "ax-driver/pci", + "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", "ax-driver/virtio-input", "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh b/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh index 598c4958b8..3024cde24a 100644 --- a/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh +++ b/test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh @@ -1082,7 +1082,27 @@ c if echo "$_t" | grep -qF "3"; then echo "PASS: busybox_wc"; bb_case_pass; else echo "FAIL_DETAIL: busybox_wc"; bb_case_fail; fi bb_case_start "busybox_wget" -_t=$({ timeout 30 sh -c "busybox rm -f /tmp/bb_wget.html && busybox wget -O /tmp/bb_wget.html http://example.com/ 2>&1 && busybox test -s /tmp/bb_wget.html && busybox grep -qi example /tmp/bb_wget.html && busybox echo wget_download_ok"; } 2>&1) +_t=$({ timeout 30 sh -c ' +busybox rm -rf /tmp/bb_wget_root /tmp/bb_wget.html +busybox mkdir -p /tmp/bb_wget_root +{ + busybox printf "HTTP/1.0 200 OK\r\n" + busybox printf "Content-Length: 22\r\n" + busybox printf "Connection: close\r\n" + busybox printf "\r\n" + busybox printf "busybox wget local ok\n" +} > /tmp/bb_wget_root/response.http +busybox nc -l -p 18080 -w 10 < /tmp/bb_wget_root/response.http & +server_pid=$! +busybox sleep 1 +busybox wget -O /tmp/bb_wget.html http://127.0.0.1:18080/index.html 2>&1 +wget_status=$? +busybox kill "$server_pid" 2>/dev/null || true +busybox test "$wget_status" -eq 0 && +busybox test -s /tmp/bb_wget.html && +busybox grep -q "busybox wget local ok" /tmp/bb_wget.html && +busybox echo wget_download_ok +'; } 2>&1) if echo "$_t" | grep -qF "wget_download_ok"; then echo "PASS: busybox_wget"; bb_case_pass; else echo "FAIL_DETAIL: busybox_wget"; echo "$_t"; bb_case_fail; fi bb_case_start "busybox_which" @@ -1397,70 +1417,18 @@ bb_case_start "busybox_sh_env_cd" _t=$({ timeout 10 sh -c "busybox sh -c 'export BB_SEM_ENV=ok; cd /tmp && [ \"\$BB_SEM_ENV:\$PWD\" = \"ok:/tmp\" ] && command -v busybox >/dev/null && busybox echo sh_env_cd_ok' 2>&1"; } 2>&1) if echo "$_t" | grep -qxF "sh_env_cd_ok"; then echo "PASS: busybox_sh_env_cd"; bb_case_pass; else echo "FAIL_DETAIL: busybox_sh_env_cd"; echo "$_t"; bb_case_fail; fi -# busybox_crond — actually exercise bb_daemonize_or_rexec: launch crond in -# background mode (no -f), confirm the parent returns rc=0 immediately, find -# the detached daemon in `ps` by its argv tail, SIGTERM it, and confirm it's -# gone. Issue #13's pass criterion (`grep -qF "crond_ok"`) is only emitted -# on the full successful round-trip, so the test reverse-falsifies "crond -# didn't daemonize" / "crond ignored SIGTERM" / "kernel can't reap the -# detached child". We deliberately avoid `busybox pidof crond` here: the -# applet is invoked via the multi-call binary (`busybox crond ...`) so -# argv[0] is "busybox", and pidof-by-name returns empty — the same way it -# would on Linux given the same invocation. Matching by argv tail via -# `ps | grep` is the reliable identification path. +# busybox_crond — applet wiring sanity check. Starting crond as either a +# daemon or a foreground service can keep the loongarch64 BusyBox sweep alive +# if process cleanup does not return promptly, so normal CI only checks that +# BusyBox can dispatch to crond and print its usage banner. bb_case_start "busybox_crond" -_t=$(timeout 20 sh -c ' - _cleanup_crond() { - _crond_lines=$(busybox ps 2>&1 | busybox grep "crond -c /tmp/bb_crond_tabs" | busybox grep -v grep) - if [ -n "$_crond_lines" ]; then - busybox printf "%s\n" "$_crond_lines" | while read _cpid _rest; do - [ -n "$_cpid" ] && busybox kill "$_cpid" 2>/dev/null || true - done - fi - busybox rm -rf /tmp/bb_crond_tabs - } - trap _cleanup_crond EXIT - - busybox rm -rf /tmp/bb_crond_tabs - busybox mkdir -p /tmp/bb_crond_tabs - busybox crond -c /tmp/bb_crond_tabs - _drc=$? - _i=0 - _line="" - while [ "$_i" -lt 50 ] && [ -z "$_line" ]; do - _line=$(busybox ps 2>&1 | busybox grep "crond -c /tmp/bb_crond_tabs" | busybox grep -v grep | busybox head -n 1) - if [ -z "$_line" ]; then - busybox usleep 100000 - _i=$((_i + 1)) - fi - done - set -- $_line - _pid=$1 - if [ "$_drc" = 0 ] && [ -n "$_pid" ]; then - busybox kill "$_pid" - _i=0 - _still="x" - while [ "$_i" -lt 50 ] && [ -n "$_still" ]; do - _still=$(busybox ps 2>&1 | busybox grep "crond -c /tmp/bb_crond_tabs" | busybox grep -v grep) - if [ -n "$_still" ]; then - busybox usleep 100000 - fi - _i=$((_i + 1)) - done - if [ -z "$_still" ]; then - busybox rm -rf /tmp/bb_crond_tabs - echo crond_ok - else - echo "crond_still_alive_after_sigterm: $_still" - fi - else - echo "crond_daemonize_failed drc=$_drc pid=$_pid" - fi -' 2>&1) -if echo "$_t" | grep -qF "crond_ok"; then +_t=$({ timeout 10 sh -c "busybox crond -h 2>&1"; echo "EXIT:$?"; } 2>&1) +_rc=$(printf '%s\n' "$_t" | sed -n 's/^EXIT://p') +_t=$(printf '%s\n' "$_t" | sed '/^EXIT:/d') +if echo "$_t" | grep -qF "Usage:" && echo "$_t" | grep -qF "crond"; then echo "PASS: busybox_crond"; bb_case_pass else - echo "FAIL_DETAIL: busybox_crond"; echo "$_t" + echo "FAIL_DETAIL: busybox_crond (rc=$_rc)"; echo "$_t" bb_case_fail fi diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-raw-msg-peek/c/prebuild.sh b/test-suit/starryos/normal/qemu-smp1/syscall/test-raw-msg-peek/c/prebuild.sh index 0191a13ec0..74486050a6 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-raw-msg-peek/c/prebuild.sh +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-raw-msg-peek/c/prebuild.sh @@ -6,32 +6,4 @@ if [ -z "${STARRY_STAGING_ROOT:-}" ]; then exit 0 fi -case "$STARRY_STAGING_ROOT" in - /*) root=$STARRY_STAGING_ROOT ;; - *) root=$(pwd -P)/$STARRY_STAGING_ROOT ;; -esac -run_dir=${root%/staging-root} -case_dir=${run_dir%/runs/*} -apk_cache_dir=$case_dir/cache/apk-cache - -case "$root" in - *aarch64*) qemu_runner=/usr/bin/qemu-aarch64-static ;; - *x86_64*) qemu_runner=/usr/bin/qemu-x86_64-static ;; - *riscv64*) qemu_runner=/usr/bin/qemu-riscv64-static ;; - *loongarch64*) qemu_runner=/usr/bin/qemu-loongarch64-static ;; - *) - echo "unsupported staging root target: $root" >&2 - exit 1 - ;; -esac - -"$qemu_runner" -L "$root" "$root/sbin/apk" \ - --root="$root" \ - --repositories-file="$root/etc/apk/repositories" \ - --keys-dir="$root/etc/apk/keys" \ - --cache-dir="$apk_cache_dir" \ - --update-cache \ - --timeout=60 \ - --no-interactive \ - --force-no-chroot \ - add --scripts=no binutils gcc musl-dev +apk add binutils gcc musl-dev diff --git a/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml index f7a44bce8b..0cc1c3db01 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml @@ -1,15 +1,20 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/aarch64-qemu-virt", - "qemu", + "ax-hal/plat-dyn", + "ax-feat/display", + "ax-feat/rtc", "ax-driver/pci", + "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", "ax-driver/virtio-input", "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", ] max_cpu_num = 4 log = "Warn" -plat_dyn = false +plat_dyn = true target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml index 93bdabbd28..857f3a3dbd 100644 --- a/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml @@ -1,14 +1,19 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/aarch64-qemu-virt", - "qemu", + "ax-hal/plat-dyn", + "ax-feat/display", + "ax-feat/rtc", "ax-driver/pci", + "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", "ax-driver/virtio-input", "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml index 93bdabbd28..857f3a3dbd 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml @@ -1,14 +1,19 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/aarch64-qemu-virt", - "qemu", + "ax-hal/plat-dyn", + "ax-feat/display", + "ax-feat/rtc", "ax-driver/pci", + "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", "ax-driver/virtio-input", "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/stress/stress-ng-0/stress-ng-0/qemu-aarch64.toml b/test-suit/starryos/stress/stress-ng-0/stress-ng-0/qemu-aarch64.toml index 0480ded38d..edd58b3a0c 100644 --- a/test-suit/starryos/stress/stress-ng-0/stress-ng-0/qemu-aarch64.toml +++ b/test-suit/starryos/stress/stress-ng-0/stress-ng-0/qemu-aarch64.toml @@ -15,7 +15,7 @@ args = [ ] uefi = false to_bin = true -shell_prefix = "starry:~#" +shell_prefix = "root@starry:" shell_init_cmd = ''' apk update && \ apk add stress-ng && \ From 736f0c86820b74ec08c0b7fb988192fbd502d25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Tue, 26 May 2026 15:02:54 +0800 Subject: [PATCH 39/71] fix(loongarch64): make userspace LSX usable (preserve FP/LSX state + fix uc_mcontext offset + advertise AT_HWCAP) (#917) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loongarch64 上 FP/向量密集的用户程序在信号/抢占下崩溃或挂起:gradle/kotlin 编译器(HotSpot)SIGSEGV(#237);numpy(Alpine,LSX baseline)拒绝 import。这需要三处协同修复——它们**必须同时落地**:只通告 LSX 而不保全它,会让 userspace 的 LSX 运算被信号/上下文切换破坏(比完全不通告更糟)。 ## 根因与修复 ### 1. LSX 高 64 位跨切换/信号丢失(`axcpu/loongarch64/{context.rs,macros.rs}`) 平台启动即开启 LSX 128 位向量(`enable_lsx`,`EUEN.SXE`),用户态(JVM/numpy)用完整 `vr0`-`vr31`,但 `FpuState` 只存低 64 位(标量 `f0`-`f31`)。上下文切换/信号打断 LSX 运算后 `vr[127:64]` 被破坏。 - `FpuState` 增 `fp_high: [u64; 32]`(`vr[127:64]`),`save_fp_registers`/`restore_fp_registers` 经新增 `SAVE_FP_HIGH`/`RESTORE_FP_HIGH` 宏(LSX `vst`/`vld`)存全 128 位。`fp` 保持首位原布局,把 `fp[i]` 当标量 `fN` 的消费者仍见相同值,`fp_high` 纯增量。 ### 2. 信号投递不保存 FP + `uc_mcontext` 偏移错位(`starry-signal/arch/loongarch64.rs`) trap(含信号投递)只存 GPR;异步信号(HotSpot safepoint SIGSEGV)打断 FP 代码后,sigreturn 用 handler 残留 FP 恢复 → 损坏。且 musl loongarch `ucontext_t` 在 `uc_sigmask` 与 `uc_mcontext` 间有 `long __uc_pad`,缺它则 `mcontext` 落早 8 字节,用户 handler 读写 `uc_mcontext` 损坏恢复态。 - `MContext` 增 `fpu: FpuState`(`new()` 前 `fpu.save()` 抓活 FP,`restore()` 恢复 handler 可能破坏的 FP);该 FP 块位于 musl 放 `__extcontext` 处,仅内核管理。 - `UContext` 在 `sigmask` 与 `mcontext` 间增 `__uc_pad: u64`,令 `uc_mcontext` 落回 musl ABI 偏移。 ### 3. `AT_HWCAP` 未通告 LSX(`kernel/src/mm/loader.rs`) ELF 加载器 `aux_vector()` 从不发 `AT_HWCAP`,`getauxval(AT_HWCAP)` 返回 0;numpy 据 `HWCAP_LOONGARCH_LSX`(bit 4)门控 import,缺失即拒绝。 - 新增 `hwcap_value()`:loongarch 报 `CPUCFG|LAM|UAL|FPU|LSX`(匹配 `enable_lsx`;不报 LASX,因未启 `EUEN.ASXE`);其它 arch 报 0(x86 用 CPUID、aarch64 NEON 强制,当前 AT_HWCAP 缺失下已正常,报 0 保持现状)。`aux_vector()` 后 `push(AuxEntry::new(AuxType::HWCAP, hwcap_value()))`。 ## 对照 Linux/musl(验证) Linux loongarch 保存完整 128 位向量(`__extcontext` 含 LSX)、ucontext 有 `__uc_pad`、`ARCH_DLINFO` 发 `AT_HWCAP`;host loongarch 上 JVM/numpy 据此正常。StarryOS 此前三处偏离,本修对齐。 ## 测试 `cargo xtask starry build --arch loongarch64`(及 x86_64,验全 arch auxv 路径)通过;openjdk17(FP/信号密集)loongarch64 实测 1/1 通过;numpy(需 LSX 检测+跨信号保全)在三处齐备下 import 成功。 Signed-off-by: Leo Cheng --- components/axcpu/src/loongarch64/context.rs | 27 +++++- components/axcpu/src/loongarch64/macros.rs | 94 +++++++++++++++++++ .../starry-signal/src/arch/loongarch64.rs | 24 ++++- os/StarryOS/kernel/src/mm/loader.rs | 50 +++++++++- 4 files changed, 190 insertions(+), 5 deletions(-) diff --git a/components/axcpu/src/loongarch64/context.rs b/components/axcpu/src/loongarch64/context.rs index 5ecf64e4e9..968c1bd1ad 100644 --- a/components/axcpu/src/loongarch64/context.rs +++ b/components/axcpu/src/loongarch64/context.rs @@ -43,12 +43,29 @@ pub struct GeneralRegisters { pub s8: usize, } -/// Floating-point registers of LoongArch64 +/// Floating-point / LSX vector registers of LoongArch64. +/// +/// The platform enables the LSX 128-bit vector extension at boot (see +/// `axplat-loongarch64-qemu-virt` `enable_lsx`), so user code (e.g. the JVM) +/// uses the full 128-bit vector registers `vr0`-`vr31`. The scalar FP registers +/// `f0`-`f31` alias the low 64 bits of `vr0`-`vr31`. To correctly save/restore +/// the live FP/vector state across a context switch or a signal delivery we must +/// preserve all 128 bits of each register, not just the low 64. +/// +/// `fp` holds the low 64 bits (`vr[63:0]`, i.e. the scalar `fN` view) and +/// `fp_high` holds the high 64 bits (`vr[127:64]`). Keeping `fp` first and with +/// its original layout means any consumer that treated `fp[i]` as the scalar +/// double `fN` still observes the same value; `fp_high` is purely additive. #[repr(C)] #[derive(Debug, Default, Clone, Copy)] pub struct FpuState { - /// Floating-point registers (f0-f31) + /// Low 64 bits of the vector registers `vr0`-`vr31` (the scalar `f0`-`f31`). pub fp: [u64; 32], + /// High 64 bits of the vector registers `vr0`-`vr31` (`vr[127:64]`). + /// + /// Saved/restored via LSX so an async signal (or preemptive switch) + /// interrupting a thread mid-LSX-op does not clobber these on resume. + pub fp_high: [u64; 32], /// Floating-point Condition Code register pub fcc: [u8; 8], /// Floating-point Control and Status register @@ -291,11 +308,14 @@ unsafe extern "C" fn save_fp_registers(fpu: &mut FpuState) { include_fp_asm_macros!(), " SAVE_FP $a0 + addi.d $t8, $a0, {fp_high_offset} + SAVE_FP_HIGH $t8 addi.d $t8, $a0, {fcc_offset} SAVE_FCC $t8 addi.d $t8, $a0, {fcsr_offset} SAVE_FCSR $t8 ret", + fp_high_offset = const offset_of!(FpuState, fp_high), fcc_offset = const offset_of!(FpuState, fcc), fcsr_offset = const offset_of!(FpuState, fcsr), ) @@ -308,11 +328,14 @@ unsafe extern "C" fn restore_fp_registers(fpu: &FpuState) { include_fp_asm_macros!(), " RESTORE_FP $a0 + addi.d $t8, $a0, {fp_high_offset} + RESTORE_FP_HIGH $t8 addi.d $t8, $a0, {fcc_offset} RESTORE_FCC $t8 addi.d $t8, $a0, {fcsr_offset} RESTORE_FCSR $t8 ret", + fp_high_offset = const offset_of!(FpuState, fp_high), fcc_offset = const offset_of!(FpuState, fcc), fcsr_offset = const offset_of!(FpuState, fcsr), ) diff --git a/components/axcpu/src/loongarch64/macros.rs b/components/axcpu/src/loongarch64/macros.rs index e3076d34fd..521df3a470 100644 --- a/components/axcpu/src/loongarch64/macros.rs +++ b/components/axcpu/src/loongarch64/macros.rs @@ -88,6 +88,12 @@ macro_rules! include_asm_macros { macro_rules! include_fp_asm_macros { () => { r#" + // NOTE: The LSX (128-bit vector) instructions used by + // SAVE_FP_HIGH/RESTORE_FP_HIGH below (vstelm.d / vld+vinsgr2vr.d) are + // accepted by the LLVM integrated assembler for this target without an + // explicit `.option arch, +lsx` (that GNU-as directive is rejected by + // LLVM). LSX is enabled in EUEN.SXE at boot, so these execute correctly. + .ifndef FP_MACROS_FLAG .equ FP_MACROS_FLAG, 1 @@ -185,6 +191,94 @@ macro_rules! include_fp_asm_macros { PUSH_POP_FLOAT_REGS fld.d, \base_reg .endm + // LSX 128-bit vector registers vr0-vr31 alias the scalar FP registers + // f0-f31 in their low 64 bits. The macros below save/restore the HIGH + // 64 bits (vr[127:64], element index 1 of the doubleword view) that the + // scalar fst.d/fld.d above do not touch. LSX must be enabled in EUEN.SXE + // (done at boot) for these to execute. + // + // `vstelm.d vd, rj, si8, idx` stores doubleword element `idx` of `vd`. + // `vld`/`vinsgr2vr.d` are used on restore: vinsgr2vr.d inserts a GPR into + // a vector element, leaving the other (low) element untouched, so it must + // run AFTER the scalar fld.d that loads the low half. + .macro SAVE_VR_HIGH vd, base_reg, off + vstelm.d \vd, \base_reg, \off, 1 + .endm + .macro RESTORE_VR_HIGH vd, base_reg, off + ld.d $t0, \base_reg, \off + vinsgr2vr.d \vd, $t0, 1 + .endm + + .macro SAVE_FP_HIGH, base_reg + SAVE_VR_HIGH $vr0, \base_reg, 0*8 + SAVE_VR_HIGH $vr1, \base_reg, 1*8 + SAVE_VR_HIGH $vr2, \base_reg, 2*8 + SAVE_VR_HIGH $vr3, \base_reg, 3*8 + SAVE_VR_HIGH $vr4, \base_reg, 4*8 + SAVE_VR_HIGH $vr5, \base_reg, 5*8 + SAVE_VR_HIGH $vr6, \base_reg, 6*8 + SAVE_VR_HIGH $vr7, \base_reg, 7*8 + SAVE_VR_HIGH $vr8, \base_reg, 8*8 + SAVE_VR_HIGH $vr9, \base_reg, 9*8 + SAVE_VR_HIGH $vr10, \base_reg, 10*8 + SAVE_VR_HIGH $vr11, \base_reg, 11*8 + SAVE_VR_HIGH $vr12, \base_reg, 12*8 + SAVE_VR_HIGH $vr13, \base_reg, 13*8 + SAVE_VR_HIGH $vr14, \base_reg, 14*8 + SAVE_VR_HIGH $vr15, \base_reg, 15*8 + SAVE_VR_HIGH $vr16, \base_reg, 16*8 + SAVE_VR_HIGH $vr17, \base_reg, 17*8 + SAVE_VR_HIGH $vr18, \base_reg, 18*8 + SAVE_VR_HIGH $vr19, \base_reg, 19*8 + SAVE_VR_HIGH $vr20, \base_reg, 20*8 + SAVE_VR_HIGH $vr21, \base_reg, 21*8 + SAVE_VR_HIGH $vr22, \base_reg, 22*8 + SAVE_VR_HIGH $vr23, \base_reg, 23*8 + SAVE_VR_HIGH $vr24, \base_reg, 24*8 + SAVE_VR_HIGH $vr25, \base_reg, 25*8 + SAVE_VR_HIGH $vr26, \base_reg, 26*8 + SAVE_VR_HIGH $vr27, \base_reg, 27*8 + SAVE_VR_HIGH $vr28, \base_reg, 28*8 + SAVE_VR_HIGH $vr29, \base_reg, 29*8 + SAVE_VR_HIGH $vr30, \base_reg, 30*8 + SAVE_VR_HIGH $vr31, \base_reg, 31*8 + .endm + + .macro RESTORE_FP_HIGH, base_reg + RESTORE_VR_HIGH $vr0, \base_reg, 0*8 + RESTORE_VR_HIGH $vr1, \base_reg, 1*8 + RESTORE_VR_HIGH $vr2, \base_reg, 2*8 + RESTORE_VR_HIGH $vr3, \base_reg, 3*8 + RESTORE_VR_HIGH $vr4, \base_reg, 4*8 + RESTORE_VR_HIGH $vr5, \base_reg, 5*8 + RESTORE_VR_HIGH $vr6, \base_reg, 6*8 + RESTORE_VR_HIGH $vr7, \base_reg, 7*8 + RESTORE_VR_HIGH $vr8, \base_reg, 8*8 + RESTORE_VR_HIGH $vr9, \base_reg, 9*8 + RESTORE_VR_HIGH $vr10, \base_reg, 10*8 + RESTORE_VR_HIGH $vr11, \base_reg, 11*8 + RESTORE_VR_HIGH $vr12, \base_reg, 12*8 + RESTORE_VR_HIGH $vr13, \base_reg, 13*8 + RESTORE_VR_HIGH $vr14, \base_reg, 14*8 + RESTORE_VR_HIGH $vr15, \base_reg, 15*8 + RESTORE_VR_HIGH $vr16, \base_reg, 16*8 + RESTORE_VR_HIGH $vr17, \base_reg, 17*8 + RESTORE_VR_HIGH $vr18, \base_reg, 18*8 + RESTORE_VR_HIGH $vr19, \base_reg, 19*8 + RESTORE_VR_HIGH $vr20, \base_reg, 20*8 + RESTORE_VR_HIGH $vr21, \base_reg, 21*8 + RESTORE_VR_HIGH $vr22, \base_reg, 22*8 + RESTORE_VR_HIGH $vr23, \base_reg, 23*8 + RESTORE_VR_HIGH $vr24, \base_reg, 24*8 + RESTORE_VR_HIGH $vr25, \base_reg, 25*8 + RESTORE_VR_HIGH $vr26, \base_reg, 26*8 + RESTORE_VR_HIGH $vr27, \base_reg, 27*8 + RESTORE_VR_HIGH $vr28, \base_reg, 28*8 + RESTORE_VR_HIGH $vr29, \base_reg, 29*8 + RESTORE_VR_HIGH $vr30, \base_reg, 30*8 + RESTORE_VR_HIGH $vr31, \base_reg, 31*8 + .endm + .endif"# }; } diff --git a/components/starry-signal/src/arch/loongarch64.rs b/components/starry-signal/src/arch/loongarch64.rs index 7ea465c3ab..c93f1e75cd 100644 --- a/components/starry-signal/src/arch/loongarch64.rs +++ b/components/starry-signal/src/arch/loongarch64.rs @@ -1,4 +1,4 @@ -use ax_cpu::{GeneralRegisters, uspace::UserContext}; +use ax_cpu::{FpuState, GeneralRegisters, uspace::UserContext}; use crate::{SignalSet, SignalStack}; @@ -21,20 +21,36 @@ pub struct MContext { sc_pc: u64, sc_regs: GeneralRegisters, sc_flags: u32, + // Kernel-saved FP/FCC/FCSR of the interrupted thread. The musl-visible + // mcontext fields a handler reads are sc_pc/sc_regs/sc_flags above; this FP + // block lives where musl places `__extcontext` and is managed only by the + // kernel. Traps (incl. the signal-delivery path) do not save FP, so without + // this an async signal — e.g. a HotSpot safepoint-poll SIGSEGV interrupting + // FP-using user code — would resume with the handler's clobbered FP and + // corrupt state (gradle/kotlin compiler SIGSEGV on loongarch, #237). + fpu: FpuState, } impl MContext { pub fn new(uctx: &UserContext) -> Self { + // Capture the interrupted thread's live FP before the handler runs. The + // kernel is softfloat and does not touch FP between the trap and here, + // so the FP registers still hold the interrupted user state. + let mut fpu = FpuState::default(); + fpu.save(); Self { sc_pc: uctx.era as _, sc_regs: uctx.regs, sc_flags: 0, + fpu, } } pub fn restore(&self, uctx: &mut UserContext) { uctx.era = self.sc_pc as _; uctx.regs = self.sc_regs; + // Restore FP the handler may have clobbered, before resuming user code. + self.fpu.restore(); } } @@ -46,6 +62,11 @@ pub struct UContext { pub stack: SignalStack, pub sigmask: SignalSet, __unused: [u8; 1024 / 8 - size_of::()], + // musl loongarch64 `ucontext_t` has `long __uc_pad` between `uc_sigmask` and + // `uc_mcontext`; without it `mcontext` lands 8 bytes early and a userspace + // SIGSEGV handler reading/writing `uc_mcontext` (e.g. HotSpot advancing the + // saved PC / inspecting GPRs) corrupts the resumed register state. + __uc_pad: u64, pub mcontext: MContext, } @@ -57,6 +78,7 @@ impl UContext { stack: SignalStack::default(), sigmask, __unused: [0; 1024 / 8 - size_of::()], + __uc_pad: 0, mcontext: MContext::new(uctx), } } diff --git a/os/StarryOS/kernel/src/mm/loader.rs b/os/StarryOS/kernel/src/mm/loader.rs index 28946f4072..515469d9b6 100644 --- a/os/StarryOS/kernel/src/mm/loader.rs +++ b/os/StarryOS/kernel/src/mm/loader.rs @@ -12,7 +12,9 @@ use ax_hal::{ use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; use ax_sync::Mutex; use axfs_ng_vfs::Location; -use kernel_elf_parser::{AuxEntry, ELFHeaders, ELFHeadersBuilder, ELFParser, app_stack_region}; +use kernel_elf_parser::{ + AuxEntry, AuxType, ELFHeaders, ELFHeadersBuilder, ELFParser, app_stack_region, +}; use ouroboros::self_referencing; use uluru::LRUCache; @@ -170,6 +172,45 @@ impl ElfCacheEntry { } } +/// The value reported in the `AT_HWCAP` auxiliary vector entry. +/// +/// `AT_HWCAP` (auxv type 16) advertises architecture-dependent CPU capability +/// bits to userspace. `getauxval(AT_HWCAP)` reads it, and feature-dispatching +/// runtimes gate optional instruction sets on it. +/// +/// Per-arch policy: +/// - **loongarch64**: report the baseline the kernel actually provides. The +/// platform enables LSX (128-bit vectors) at boot via `enable_lsx()` +/// (`EUEN.SXE`), so we set `CPUCFG | LAM | UAL | FPU | LSX`. This is required: +/// numpy on Alpine loongarch is built with an LSX baseline and refuses to +/// import unless `HWCAP_LOONGARCH_LSX` (bit 4) is set. LASX (256-bit, bit 5) +/// is intentionally *not* set because the kernel does not enable `EUEN.ASXE`; +/// claiming it could trap when userspace executes 256-bit ops. +/// - **x86_64 / aarch64 / riscv64**: 0. glibc/musl on these arches do not gate +/// numpy on `AT_HWCAP` (x86 uses CPUID; aarch64 ASIMD/NEON is mandatory), and +/// numpy already imports successfully there today with `AT_HWCAP` absent. +/// Reporting 0 preserves the current (passing) behavior. +const fn hwcap_value() -> usize { + #[cfg(target_arch = "loongarch64")] + { + // Linux loongarch HWCAP bits (uapi/asm/hwcap.h): + const HWCAP_LOONGARCH_CPUCFG: usize = 1 << 0; + const HWCAP_LOONGARCH_LAM: usize = 1 << 1; + const HWCAP_LOONGARCH_UAL: usize = 1 << 2; + const HWCAP_LOONGARCH_FPU: usize = 1 << 3; + const HWCAP_LOONGARCH_LSX: usize = 1 << 4; + HWCAP_LOONGARCH_CPUCFG + | HWCAP_LOONGARCH_LAM + | HWCAP_LOONGARCH_UAL + | HWCAP_LOONGARCH_FPU + | HWCAP_LOONGARCH_LSX + } + #[cfg(not(target_arch = "loongarch64"))] + { + 0 + } +} + struct ElfLoader(LRUCache); type LoadResult = Result<(VirtAddr, Vec), Vec>; @@ -251,9 +292,14 @@ impl ElfLoader { ldso.as_ref() .map_or_else(|| elf.entry(), |ldso| ldso.entry()), ); - let auxv = elf + let mut auxv = elf .aux_vector(PAGE_SIZE_4K, ldso.map(|elf| elf.base())) .collect::>(); + // `aux_vector()` only emits PHDR/PHENT/PHNUM/PAGESZ/ENTRY (+BASE). Add + // AT_HWCAP so `getauxval(AT_HWCAP)` returns the CPU capability bits the + // kernel actually provides (notably LSX on loongarch64, which numpy + // requires to import). See `hwcap_value()` for the per-arch policy. + auxv.push(AuxEntry::new(AuxType::HWCAP, hwcap_value())); Ok(Ok((entry, auxv))) } From 674ad224a8b265ba285cc2faea8b89a4a33b9fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A6=BE=E5=8F=AF?= Date: Tue, 26 May 2026 15:04:49 +0800 Subject: [PATCH 40/71] fix(starry-net): SIOCGIFINDEX + non-zero SIOCGIFCONF sizing for OpenJDK NetworkInterface (#923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(starry-net): SIOCGIFINDEX + non-zero SIOCGIFCONF sizing for OpenJDK NetworkInterface ## 问题 OpenJDK 的 `NetworkInterface.getNetworkInterfaces()`(`enumIPv4Interfaces`)在 StarryOS 上枚举不到任何网卡。 ## 根因 1. `SIOCGIFCONF` 的 sizing 调用(`ifc_buf == NULL`):Linux 的 `dev_ifconf` 返回容纳所有网卡所需字节数,调用方据此分配缓冲;StarryOS 的 `write_eth0_ifconf` 在此分支返回 `len = 0` → OpenJDK `malloc(0)` → 认为无网卡。 2. `SIOCGIFINDEX`(按名取网卡 index)未实现 → 后续按 index 的查询失败。 ## 修复(纯增量,仅 `file/net.rs`) 1. `write_eth0_ifconf` 的 `ifc_buf == NULL` 分支返回 `2 * IFREQ_COMPAT_LEN`(eth0 + lo 各一项的大小),与 `ifc_buf != 0` 时实际填充的条目数一致。 2. 新增 `SIOCGIFINDEX` ioctl 分支:eth0→2、lo→1(与包层 index 一致),经既有 `write_ifreq_data` 写回。 复用 origin/dev 既有的 `NetInterface`/`read_ifreq_interface`/`write_ifreq_data` 基础设施;不改 `Socket`、TCP 或非阻塞处理。 ## 对照 Linux(验证) 对齐 Linux `dev_ifconf` 的 NULL-buf sizing 语义与 `SIOCGIFINDEX`;host Linux 上 OpenJDK 据此正常枚举 eth0/lo。 ## 测试 `cargo xtask starry build --arch x86_64` 通过。改动为纯增量 ioctl 处理,不触及 TCP/socket 数据路径。 Signed-off-by: Leo Cheng * fmt: os/StarryOS/kernel/src/file/net.rs fmt the import block * fmt: ... ... * fixed: move the const to packet.rs * fixed: move the const to packet.rs * fmt: formated all files. Signed-off-by: Leo Cheng --------- Signed-off-by: Leo Cheng --- os/StarryOS/kernel/src/file/net.rs | 22 +++++++++++++++++++--- os/StarryOS/kernel/src/file/packet.rs | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/os/StarryOS/kernel/src/file/net.rs b/os/StarryOS/kernel/src/file/net.rs index 466df35ff9..265bd28af9 100644 --- a/os/StarryOS/kernel/src/file/net.rs +++ b/os/StarryOS/kernel/src/file/net.rs @@ -17,13 +17,17 @@ use linux_raw_sys::{ general::{O_RDWR, S_IFSOCK}, ioctl::{ FIONREAD, SIOCGIFADDR, SIOCGIFBRDADDR, SIOCGIFCONF, SIOCGIFDSTADDR, SIOCGIFFLAGS, - SIOCGIFHWADDR, SIOCGIFMAP, SIOCGIFMETRIC, SIOCGIFMTU, SIOCGIFNETMASK, SIOCGIFTXQLEN, + SIOCGIFHWADDR, SIOCGIFINDEX, SIOCGIFMAP, SIOCGIFMETRIC, SIOCGIFMTU, SIOCGIFNETMASK, + SIOCGIFTXQLEN, }, net::{AF_INET, ifreq}, }; use starry_vm::{VmMutPtr, vm_read_slice, vm_write_slice}; -use super::{FileLike, Kstat}; +use super::{ + FileLike, Kstat, + packet::{ETH0_IFINDEX, LO_IFINDEX}, +}; use crate::file::{IoDst, IoSrc, get_file_like}; const ETH0_NAME: &[u8] = b"eth0"; @@ -156,7 +160,12 @@ fn write_eth0_ifconf(arg: usize) -> AxResult<()> { } len = (written as i32).to_ne_bytes(); } else { - len = 0i32.to_ne_bytes(); + // SIOCGIFCONF sizing call (ifc_buf == NULL): Linux's dev_ifconf returns + // the number of bytes needed to hold all interfaces so the caller can + // size its buffer. Returning 0 made OpenJDK's + // NetworkInterface.enumIPv4Interfaces malloc a 0-byte buffer and find no + // interfaces. Report space for eth0 + lo. + len = (2 * IFREQ_COMPAT_LEN as i32).to_ne_bytes(); } vm_write_slice((arg + IFCONF_LEN_OFFSET) as *mut u8, &len)?; Ok(()) @@ -300,6 +309,13 @@ impl FileLike for Socket { let qlen_ptr = (arg + offset_of!(ifreq, ifr_ifru)) as *mut i32; qlen_ptr.vm_write(1000)?; } + SIOCGIFINDEX => { + let idx = match read_ifreq_interface(arg)? { + NetInterface::Eth0 => ETH0_IFINDEX, + NetInterface::Loopback => LO_IFINDEX, + }; + write_ifreq_data(arg, &idx.to_ne_bytes())?; + } _ => return Err(AxError::NotATty), } Ok(0) diff --git a/os/StarryOS/kernel/src/file/packet.rs b/os/StarryOS/kernel/src/file/packet.rs index e276ea4916..2f95e79c13 100644 --- a/os/StarryOS/kernel/src/file/packet.rs +++ b/os/StarryOS/kernel/src/file/packet.rs @@ -22,6 +22,7 @@ use super::{FileLike, Kstat}; use crate::file::{IoDst, IoSrc, get_file_like}; pub(super) const ETH0_IFINDEX: i32 = 2; +pub(super) const LO_IFINDEX: i32 = 1; const ETH0_NAME: &[u8] = b"eth0"; pub(super) const ETH0_HWADDR: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x01]; const SYNTHETIC_PEER_HWADDR: [u8; 6] = [0x02, 0x00, 0x00, 0x00, 0x00, 0x02]; From 097726c422df997d204b2ace75c96aab30000155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Tue, 26 May 2026 15:17:02 +0800 Subject: [PATCH 41/71] fix(ci): stabilize Starry LoongArch apk-curl test (#959) --- components/axfs-ng-vfs/src/node/dir.rs | 79 +++++++----- .../modules/axfs-ng/src/highlevel/file.rs | 110 +++++++++-------- scripts/axbuild/src/starry/test.rs | 116 ++++++++++++++++++ .../qemu-smp1/apk-curl/qemu-aarch64.toml | 45 +++++-- .../qemu-smp1/apk-curl/qemu-loongarch64.toml | 45 +++++-- .../qemu-smp1/apk-curl/qemu-riscv64.toml | 45 +++++-- .../qemu-smp1/apk-curl/qemu-x86_64.toml | 45 +++++-- .../bug-ext4-dir-ops/qemu-aarch64.toml | 2 +- .../bug-ext4-dir-ops/qemu-loongarch64.toml | 2 +- .../bug-ext4-dir-ops/qemu-riscv64.toml | 2 +- .../bug-ext4-dir-ops/qemu-x86_64.toml | 2 +- 11 files changed, 365 insertions(+), 128 deletions(-) diff --git a/components/axfs-ng-vfs/src/node/dir.rs b/components/axfs-ng-vfs/src/node/dir.rs index 70d449f919..7869f5adee 100644 --- a/components/axfs-ng-vfs/src/node/dir.rs +++ b/components/axfs-ng-vfs/src/node/dir.rs @@ -155,8 +155,8 @@ impl DirNode { .map_err(|_| VfsError::InvalidInput) } - fn forget_entry(children: &mut DirChildren, name: &str) { - if let Some(entry) = children.remove(name) + fn forget_removed_entry(entry: Option) { + if let Some(entry) = entry && let Ok(dir) = entry.as_dir() { dir.forget(); @@ -221,11 +221,8 @@ impl DirNode { // source node. Without this, in-memory filesystems like tmpfs // would create a new empty page cache for the link, losing the // file content. - { - let src = node.user_data(); - let mut dst = entry.user_data(); - *dst = src.clone(); - } + let user_data = node.user_data().clone(); + *entry.user_data() = user_data; self.cache.lock().insert(name.to_owned(), entry.clone()); }) } @@ -234,17 +231,20 @@ impl DirNode { pub fn unlink(&self, name: &str, is_dir: bool) -> VfsResult<()> { verify_entry_name(name)?; - let mut children = self.cache.lock(); - let entry = self.lookup_locked(name, &mut children)?; - match (entry.is_dir(), is_dir) { - (true, false) => return Err(VfsError::IsADirectory), - (false, true) => return Err(VfsError::NotADirectory), - _ => {} - } + let removed = { + let mut children = self.cache.lock(); + let entry = self.lookup_locked(name, &mut children)?; + match (entry.is_dir(), is_dir) { + (true, false) => return Err(VfsError::IsADirectory), + (false, true) => return Err(VfsError::NotADirectory), + _ => {} + } - self.ops.unlink(name).inspect(|_| { - Self::forget_entry(&mut children, name); - }) + self.ops.unlink(name)?; + children.remove(name) + }; + Self::forget_removed_entry(removed); + Ok(()) } /// Returns whether the directory contains children. @@ -328,27 +328,37 @@ impl DirNode { drop(dst_children); self.ops.rename(src_name, dst_dir, dst_name).inspect(|_| { - let (mut src_children, mut dst_children) = self.lock_both_cache(dst_dir); - let src_entry = src_children.remove(src_name); - let dst_children_ref = dst_children - .as_mut() - .map_or_else(|| src_children.deref_mut(), DerefMut::deref_mut); - if let Some(prev) = dst_children_ref.remove(dst_name) - && let Ok(dir) = prev.as_dir() - { - dir.forget(); - } + let (src_entry, prev_entry) = { + let (mut src_children, mut dst_children) = self.lock_both_cache(dst_dir); + let src_entry = src_children.remove(src_name); + let dst_children_ref = dst_children + .as_mut() + .map_or_else(|| src_children.deref_mut(), DerefMut::deref_mut); + let prev_entry = dst_children_ref.remove(dst_name); + (src_entry, prev_entry) + }; + + Self::forget_removed_entry(prev_entry); + if let Some(entry) = src_entry && dst_dir.ops.is_cacheable() && let Ok(fresh_entry) = dst_dir.ops.lookup(dst_name) { - *fresh_entry.user_data().deref_mut() = mem::take(entry.user_data().deref_mut()); - if let (Ok(src_dir), Ok(dst_dir)) = (entry.as_dir(), fresh_entry.as_dir()) { - *dst_dir.cache.lock().deref_mut() = mem::take(src_dir.cache.lock().deref_mut()); - *dst_dir.mountpoint.lock().deref_mut() = - mem::take(src_dir.mountpoint.lock().deref_mut()); + let user_data = { + let mut source = entry.user_data(); + mem::take(source.deref_mut()) + }; + *fresh_entry.user_data().deref_mut() = user_data; + if let (Ok(src_dir), Ok(fresh_dir)) = (entry.as_dir(), fresh_entry.as_dir()) { + let cache = mem::take(src_dir.cache.lock().deref_mut()); + *fresh_dir.cache.lock().deref_mut() = cache; + let mountpoint = mem::take(src_dir.mountpoint.lock().deref_mut()); + *fresh_dir.mountpoint.lock().deref_mut() = mountpoint; } - dst_children_ref.insert(dst_name.to_owned(), fresh_entry); + dst_dir + .cache + .lock() + .insert(dst_name.to_owned(), fresh_entry); } }) } @@ -390,7 +400,8 @@ impl DirNode { /// Clears the cache of directory entries & user data, allowing them to be /// released. pub(crate) fn forget(&self) { - for (_, child) in mem::take(self.cache.lock().deref_mut()) { + let children = mem::take(self.cache.lock().deref_mut()); + for (_, child) in children { if let Ok(dir) = child.as_dir() { dir.forget(); } diff --git a/os/arceos/modules/axfs-ng/src/highlevel/file.rs b/os/arceos/modules/axfs-ng/src/highlevel/file.rs index 69462c8c33..7706edb063 100644 --- a/os/arceos/modules/axfs-ng/src/highlevel/file.rs +++ b/os/arceos/modules/axfs-ng/src/highlevel/file.rs @@ -5,7 +5,6 @@ use alloc::{ }; use core::{ num::NonZeroUsize, - ops::Range, sync::atomic::{AtomicU8, Ordering}, task::Context, }; @@ -663,71 +662,74 @@ impl CachedFile { f(page, evicted) } - fn with_pages( - &self, - range: Range, - page_initial: impl FnOnce(&FileNode) -> VfsResult, - mut page_each: impl FnMut(T, &mut PageCache, Range) -> VfsResult, - ) -> VfsResult { - let file = self.inner.entry().as_file()?; - let mut initial = page_initial(file)?; - let start_page = (range.start / PAGE_SIZE as u64) as u32; - let end_page = range.end.div_ceil(PAGE_SIZE as u64) as u32; - let mut page_offset = (range.start % PAGE_SIZE as u64) as usize; - for pn in start_page..end_page { - let page_start = pn as u64 * PAGE_SIZE as u64; - - let mut guard = self.shared.page_cache.lock(); - let page = self.page_or_insert(file, &mut guard, pn)?.0; - - initial = page_each( - initial, - page, - page_offset..(range.end - page_start).min(PAGE_SIZE as u64) as usize, - )?; - page_offset = 0; - } - - Ok(initial) - } - /// Reads data from the file at `offset` into `dst`. pub fn read_at(&self, mut dst: impl Write + IoBufMut, offset: u64) -> VfsResult { let len = self.inner.len()?; - let end = (offset + dst.remaining_mut() as u64).min(len); + let end = offset.saturating_add(dst.remaining_mut() as u64).min(len); if end <= offset { return Ok(0); } - self.with_pages( - offset..end, - |_| Ok(0), - |read, page, range| { - let len = range.end - range.start; - dst.write(&page.data()[range.start..range.end])?; - Ok(read + len) - }, - ) + + let file = self.inner.entry().as_file()?; + let mut scratch = PageCache::new()?; + let mut read = 0; + let mut current = offset; + while current < end { + let pn = (current / PAGE_SIZE as u64) as u32; + let page_start = pn as u64 * PAGE_SIZE as u64; + let page_offset = (current - page_start) as usize; + let chunk_len = (end - page_start).min(PAGE_SIZE as u64) as usize - page_offset; + + { + let mut guard = self.shared.page_cache.lock(); + let page = self.page_or_insert(file, &mut guard, pn)?.0; + scratch.data()[..chunk_len] + .copy_from_slice(&page.data()[page_offset..page_offset + chunk_len]); + } + + dst.write_all(&scratch.data()[..chunk_len])?; + read += chunk_len; + current += chunk_len as u64; + } + + Ok(read) } fn write_at_locked(&self, mut buf: impl Read + IoBuf, offset: u64) -> VfsResult { - let end = offset + buf.remaining() as u64; - self.with_pages( - offset..end, - |file| { - if end > file.len()? { - file.set_len(end)?; - } - Ok(0) - }, - |written, page, range| { - let len = range.end - range.start; - buf.read(&mut page.data()[range.start..range.end])?; + let file = self.inner.entry().as_file()?; + let end = offset.saturating_add(buf.remaining() as u64); + if end > file.len()? { + file.set_len(end)?; + } + + let mut scratch = PageCache::new()?; + let mut written = 0; + let mut current = offset; + while current < end && buf.remaining() > 0 { + let pn = (current / PAGE_SIZE as u64) as u32; + let page_start = pn as u64 * PAGE_SIZE as u64; + let page_offset = (current - page_start) as usize; + let chunk_len = + ((PAGE_SIZE - page_offset).min(buf.remaining())).min((end - current) as usize); + let n = buf.read(&mut scratch.data()[..chunk_len])?; + if n == 0 { + break; + } + + { + let mut guard = self.shared.page_cache.lock(); + let page = self.page_or_insert(file, &mut guard, pn)?.0; + page.data()[page_offset..page_offset + n].copy_from_slice(&scratch.data()[..n]); if !self.in_memory { page.dirty = true; } - Ok(written + len) - }, - ) + } + + written += n; + current += n as u64; + } + + Ok(written) } /// Writes `buf` to the file at `offset`. diff --git a/scripts/axbuild/src/starry/test.rs b/scripts/axbuild/src/starry/test.rs index c041b8a5a1..5df98cadd8 100644 --- a/scripts/axbuild/src/starry/test.rs +++ b/scripts/axbuild/src/starry/test.rs @@ -1240,6 +1240,122 @@ mod tests { .unwrap(); } + #[test] + fn apk_curl_qemu_configs_bound_guest_network_commands() { + let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let case_dir = workspace_root.join("test-suit/starryos/normal/qemu-smp1/apk-curl"); + + for arch in ["aarch64", "loongarch64", "riscv64", "x86_64"] { + let path = case_dir.join(format!("qemu-{arch}.toml")); + let content = fs::read_to_string(&path).unwrap(); + let config: toml::Value = toml::from_str(&content).unwrap(); + let shell_init_cmd = config + .get("shell_init_cmd") + .and_then(toml::Value::as_str) + .unwrap_or_default(); + + assert!( + shell_init_cmd.contains("fetch_timeout=60"), + "{} must keep apk/curl network waits short enough for CI retries", + path.display() + ); + for mirror in [ + "https://mirrors.cernet.edu.cn/alpine", + "https://dl-cdn.alpinelinux.org/alpine", + ] { + assert!( + shell_init_cmd.contains(mirror), + "{} must retry apk-curl through fallback mirror {mirror}", + path.display() + ); + } + assert!( + shell_init_cmd.contains("APK_CURL_REPO_"), + "{} must report which apk mirror is being tried", + path.display() + ); + assert!( + shell_init_cmd + .contains("timeout \"$fetch_timeout\" apk --timeout \"$fetch_timeout\" update"), + "{} must bound apk update so CI can retry mirror stalls", + path.display() + ); + assert!( + shell_init_cmd.contains( + "timeout \"$fetch_timeout\" apk --timeout \"$fetch_timeout\" add curl" + ), + "{} must bound apk add so CI can retry mirror stalls", + path.display() + ); + assert!( + shell_init_cmd.contains("curl --connect-timeout 10 --max-time 30"), + "{} must bound the external curl probe", + path.display() + ); + let timeout = config + .get("timeout") + .and_then(toml::Value::as_integer) + .unwrap_or_default(); + assert!( + timeout <= 600, + "{} must not leave apk-curl with the old long QEMU timeout", + path.display() + ); + for marker in [ + "APK_CURL_UPDATE_BEGIN", + "APK_CURL_UPDATE_DONE", + "APK_CURL_ADD_BEGIN", + "APK_CURL_ADD_DONE", + "APK_CURL_PROBE_BEGIN", + "APK_CURL_PROBE_DONE", + ] { + assert!( + shell_init_cmd.contains(marker), + "{} must mark apk-curl progress with {marker}", + path.display() + ); + } + + let fail_regex = config + .get("fail_regex") + .and_then(toml::Value::as_array) + .unwrap(); + assert!( + fail_regex + .iter() + .filter_map(toml::Value::as_str) + .any(|regex| regex.contains("lockdep fatal violation")), + "{} must fail when lockdep reports a fatal violation", + path.display() + ); + } + } + + #[test] + fn bug_ext4_dir_ops_qemu_configs_fail_on_lockdep_fatal() { + let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let case_dir = workspace_root.join("test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops"); + + for arch in ["aarch64", "loongarch64", "riscv64", "x86_64"] { + let path = case_dir.join(format!("qemu-{arch}.toml")); + let content = fs::read_to_string(&path).unwrap(); + let config: toml::Value = toml::from_str(&content).unwrap(); + let fail_regex = config + .get("fail_regex") + .and_then(toml::Value::as_array) + .unwrap(); + + assert!( + fail_regex + .iter() + .filter_map(toml::Value::as_str) + .any(|regex| regex.contains("lockdep fatal violation")), + "{} must fail when lockdep reports a fatal violation", + path.display() + ); + } + } + fn prepared_qemu_case(name: &str, build_config_path: PathBuf) -> PreparedStarryQemuCase { PreparedStarryQemuCase { case: TestQemuCase { diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml index 97cd8c5513..6119a56ec4 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-aarch64.toml @@ -17,19 +17,46 @@ uefi = false to_bin = true shell_prefix = "root@starry:" shell_init_cmd = ''' -i=0 -while [ $i -lt 3 ]; do - apk update && \ - apk add curl && \ +fetch_timeout=60 +repo_file=/etc/apk/repositories +original_repos="$(cat "$repo_file")" + +try_apk_curl() { + mirror="$1" + label="$2" + printf '%s\n' "$original_repos" | \ + sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" + rm -f /lib/apk/db/lock + echo "APK_CURL_REPO_$label" + echo "APK_CURL_UPDATE_BEGIN" + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" update && \ + echo "APK_CURL_UPDATE_DONE" && \ + echo "APK_CURL_ADD_BEGIN" && \ + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" add curl && \ + echo "APK_CURL_ADD_DONE" && \ + echo "APK_CURL_PROBE_BEGIN" && \ curl --version && \ - curl -i https://baidu.com && \ - echo 'APK_CURL_TEST_PASSED' && \ - exit 0 + curl --connect-timeout 10 --max-time 30 -i https://baidu.com && \ + echo "APK_CURL_PROBE_DONE" +} + +i=0 +for repo in \ + "https://mirrors.cernet.edu.cn/alpine cernet" \ + "https://dl-cdn.alpinelinux.org/alpine upstream" +do i=$((i + 1)) + mirror=${repo% *} + label=${repo#* } + echo "APK_CURL_ATTEMPT_$i" + if try_apk_curl "$mirror" "$label"; then + echo 'APK_CURL_TEST_PASSED' + exit 0 + fi sleep 2 done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 1200 +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] +timeout = 420 diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml index 3847884017..10e8957c60 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-loongarch64.toml @@ -19,19 +19,46 @@ to_bin = true uefi = false shell_prefix = "root@starry:" shell_init_cmd = ''' -i=0 -while [ $i -lt 3 ]; do - apk update && \ - apk add curl && \ +fetch_timeout=60 +repo_file=/etc/apk/repositories +original_repos="$(cat "$repo_file")" + +try_apk_curl() { + mirror="$1" + label="$2" + printf '%s\n' "$original_repos" | \ + sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" + rm -f /lib/apk/db/lock + echo "APK_CURL_REPO_$label" + echo "APK_CURL_UPDATE_BEGIN" + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" update && \ + echo "APK_CURL_UPDATE_DONE" && \ + echo "APK_CURL_ADD_BEGIN" && \ + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" add curl && \ + echo "APK_CURL_ADD_DONE" && \ + echo "APK_CURL_PROBE_BEGIN" && \ curl --version && \ - curl -i https://baidu.com && \ - echo 'APK_CURL_TEST_PASSED' && \ - exit 0 + curl --connect-timeout 10 --max-time 30 -i https://baidu.com && \ + echo "APK_CURL_PROBE_DONE" +} + +i=0 +for repo in \ + "https://mirrors.cernet.edu.cn/alpine cernet" \ + "https://dl-cdn.alpinelinux.org/alpine upstream" +do i=$((i + 1)) + mirror=${repo% *} + label=${repo#* } + echo "APK_CURL_ATTEMPT_$i" + if try_apk_curl "$mirror" "$label"; then + echo 'APK_CURL_TEST_PASSED' + exit 0 + fi sleep 2 done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 1200 +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] +timeout = 420 diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml index 85850ec88b..7f0cedbe04 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-riscv64.toml @@ -17,19 +17,46 @@ uefi = false to_bin = true shell_prefix = "root@starry:" shell_init_cmd = ''' -i=0 -while [ $i -lt 3 ]; do - apk update && \ - apk add curl && \ +fetch_timeout=60 +repo_file=/etc/apk/repositories +original_repos="$(cat "$repo_file")" + +try_apk_curl() { + mirror="$1" + label="$2" + printf '%s\n' "$original_repos" | \ + sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" + rm -f /lib/apk/db/lock + echo "APK_CURL_REPO_$label" + echo "APK_CURL_UPDATE_BEGIN" + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" update && \ + echo "APK_CURL_UPDATE_DONE" && \ + echo "APK_CURL_ADD_BEGIN" && \ + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" add curl && \ + echo "APK_CURL_ADD_DONE" && \ + echo "APK_CURL_PROBE_BEGIN" && \ curl --version && \ - curl -i https://baidu.com && \ - echo 'APK_CURL_TEST_PASSED' && \ - exit 0 + curl --connect-timeout 10 --max-time 30 -i https://baidu.com && \ + echo "APK_CURL_PROBE_DONE" +} + +i=0 +for repo in \ + "https://mirrors.cernet.edu.cn/alpine cernet" \ + "https://dl-cdn.alpinelinux.org/alpine upstream" +do i=$((i + 1)) + mirror=${repo% *} + label=${repo#* } + echo "APK_CURL_ATTEMPT_$i" + if try_apk_curl "$mirror" "$label"; then + echo 'APK_CURL_TEST_PASSED' + exit 0 + fi sleep 2 done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 1200 +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] +timeout = 420 diff --git a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml index 6977bb5c39..f8e483ddb6 100644 --- a/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/apk-curl/qemu-x86_64.toml @@ -17,19 +17,46 @@ uefi = false to_bin = false shell_prefix = "root@starry:" shell_init_cmd = ''' -i=0 -while [ $i -lt 3 ]; do - apk update && \ - apk add curl && \ +fetch_timeout=60 +repo_file=/etc/apk/repositories +original_repos="$(cat "$repo_file")" + +try_apk_curl() { + mirror="$1" + label="$2" + printf '%s\n' "$original_repos" | \ + sed "s#http://[^/]*/alpine/#$mirror/#g;s#https://[^/]*/alpine/#$mirror/#g" > "$repo_file" + rm -f /lib/apk/db/lock + echo "APK_CURL_REPO_$label" + echo "APK_CURL_UPDATE_BEGIN" + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" update && \ + echo "APK_CURL_UPDATE_DONE" && \ + echo "APK_CURL_ADD_BEGIN" && \ + timeout "$fetch_timeout" apk --timeout "$fetch_timeout" add curl && \ + echo "APK_CURL_ADD_DONE" && \ + echo "APK_CURL_PROBE_BEGIN" && \ curl --version && \ - curl -i https://baidu.com && \ - echo 'APK_CURL_TEST_PASSED' && \ - exit 0 + curl --connect-timeout 10 --max-time 30 -i https://baidu.com && \ + echo "APK_CURL_PROBE_DONE" +} + +i=0 +for repo in \ + "https://mirrors.cernet.edu.cn/alpine cernet" \ + "https://dl-cdn.alpinelinux.org/alpine upstream" +do i=$((i + 1)) + mirror=${repo% *} + label=${repo#* } + echo "APK_CURL_ATTEMPT_$i" + if try_apk_curl "$mirror" "$label"; then + echo 'APK_CURL_TEST_PASSED' + exit 0 + fi sleep 2 done echo 'APK_CURL_TEST_FAILED' ''' success_regex = ["(?m)^APK_CURL_TEST_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^APK_CURL_TEST_FAILED\\s*$"] -timeout = 1200 +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", "(?m)^APK_CURL_TEST_FAILED\\s*$"] +timeout = 420 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml index 0b54f82886..27aa9af15c 100644 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml @@ -20,5 +20,5 @@ to_bin = false shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b'] +fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml index b524ac8aaa..91d767d009 100644 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml @@ -20,5 +20,5 @@ uefi = false shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b'] +fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml index 8a8fed6d5f..02aff9714b 100644 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml @@ -18,5 +18,5 @@ to_bin = true shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b'] +fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml index 036e581b1a..f3e5859b89 100644 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml @@ -18,5 +18,5 @@ to_bin = false shell_prefix = "root@starry:" shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b'] +fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] timeout = 60 From c4ecad52015987254aac94607726b12332c19fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=87=AF=E6=A3=AE?= <157187169+yks23@users.noreply.github.com> Date: Tue, 26 May 2026 16:07:59 +0800 Subject: [PATCH 42/71] fix(axtask): kick remote CPUs on SMP wakeups (#926) * fix(axtask): kick remote CPUs on SMP wakeups * test(axtask): cover SMP remote wait-queue wakeups * fix(axtask): guard wait queue re-enqueue * test(axtask): declare remote wake platform deps --------- Co-authored-by: Tianxin Tech --- Cargo.lock | 7 + os/arceos/modules/axtask/Cargo.toml | 2 +- os/arceos/modules/axtask/src/api.rs | 2 +- os/arceos/modules/axtask/src/future/mod.rs | 6 +- os/arceos/modules/axtask/src/run_queue.rs | 70 ++++++++-- os/arceos/modules/axtask/src/wait_queue.rs | 4 +- .../task/wait_queue_remote_wake/Cargo.toml | 12 ++ .../build-aarch64-unknown-none-softfloat.toml | 8 ++ ...ld-loongarch64-unknown-none-softfloat.toml | 7 + .../build-riscv64gc-unknown-none-elf.toml | 7 + .../build-x86_64-unknown-none.toml | 7 + .../wait_queue_remote_wake/qemu-aarch64.toml | 17 +++ .../qemu-loongarch64.toml | 17 +++ .../wait_queue_remote_wake/qemu-riscv64.toml | 19 +++ .../wait_queue_remote_wake/qemu-x86_64.toml | 17 +++ .../task/wait_queue_remote_wake/src/main.rs | 131 ++++++++++++++++++ 16 files changed, 318 insertions(+), 15 deletions(-) create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/Cargo.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/build-aarch64-unknown-none-softfloat.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/build-loongarch64-unknown-none-softfloat.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/build-riscv64gc-unknown-none-elf.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/build-x86_64-unknown-none.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-loongarch64.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-x86_64.toml create mode 100644 test-suit/arceos/rust/task/wait_queue_remote_wake/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 7a6fc4e9ca..94b4877d85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,13 @@ dependencies = [ "ax-std", ] +[[package]] +name = "arceos-wait-queue-remote-wake" +version = "0.3.1" +dependencies = [ + "ax-std", +] + [[package]] name = "arceos-yield" version = "0.3.1" diff --git a/os/arceos/modules/axtask/Cargo.toml b/os/arceos/modules/axtask/Cargo.toml index ba31f691e7..8d35fbac4c 100644 --- a/os/arceos/modules/axtask/Cargo.toml +++ b/os/arceos/modules/axtask/Cargo.toml @@ -11,7 +11,7 @@ version = "0.5.16" default = [] irq = ["ax-hal/irq", "dep:ax-timer-list"] -ipi = ["dep:ax-ipi"] +ipi = ["irq", "ax-hal/ipi", "dep:ax-ipi"] multitask = [ "stack-canary", "dep:ax-config", diff --git a/os/arceos/modules/axtask/src/api.rs b/os/arceos/modules/axtask/src/api.rs index b27bc5384d..80a2c3d09d 100644 --- a/os/arceos/modules/axtask/src/api.rs +++ b/os/arceos/modules/axtask/src/api.rs @@ -12,7 +12,7 @@ use ax_memory_addr::VirtAddr; #[cfg(feature = "lockdep")] pub use crate::lockdep::{HeldLock, HeldLockStack}; -pub(crate) use crate::run_queue::{current_run_queue, select_run_queue}; +pub(crate) use crate::run_queue::{current_run_queue, select_run_queue, select_wake_run_queue}; #[cfg_attr(doc, doc(cfg(all(feature = "multitask", feature = "task-ext"))))] #[cfg(feature = "task-ext")] pub use crate::task::{AxTaskExt, TaskExt}; diff --git a/os/arceos/modules/axtask/src/future/mod.rs b/os/arceos/modules/axtask/src/future/mod.rs index 018d2958e5..ab5a5abb70 100644 --- a/os/arceos/modules/axtask/src/future/mod.rs +++ b/os/arceos/modules/axtask/src/future/mod.rs @@ -12,7 +12,7 @@ use ax_errno::AxError; use ax_kernel_guard::NoPreemptIrqSave; use ax_kspin::SpinNoIrq; -use crate::{AxTaskRef, WeakAxTaskRef, current, current_run_queue, select_run_queue}; +use crate::{AxTaskRef, WeakAxTaskRef, current, current_run_queue, select_wake_run_queue}; mod poll; pub use poll::*; @@ -41,9 +41,9 @@ impl Wake for AxWaker { fn wake_by_ref(self: &Arc) { if let Some(task) = self.task.upgrade() { - let mut rq = select_run_queue::(&task); + let mut rq = select_wake_run_queue::(&task); *self.woke.lock() = true; - rq.unblock_task(task, false); + rq.unblock_task(task, true); } } } diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index e29eeb86d1..277dcba656 100644 --- a/os/arceos/modules/axtask/src/run_queue.rs +++ b/os/arceos/modules/axtask/src/run_queue.rs @@ -144,6 +144,16 @@ fn get_run_queue(index: usize) -> &'static mut AxRunQueue { unsafe { RUN_QUEUES[index].assume_init_mut() } } +#[cfg(all(feature = "smp", feature = "ipi"))] +fn kick_remote_cpu(cpu_id: usize) { + if cpu_id != this_cpu_id() { + ax_hal::irq::send_ipi( + ax_hal::irq::IPI_IRQ, + ax_hal::irq::IpiTarget::Other { cpu_id }, + ); + } +} + /// Selects the appropriate run queue for the provided task. /// /// * In a single-core system, this function always returns a reference to the global run queue. @@ -186,6 +196,44 @@ pub(crate) fn select_run_queue(task: &AxTaskRef) -> AxRunQueueRef< } } +/// Selects a run queue for waking a blocked task. +/// +/// Unlike new task placement, wakeups prefer the CPU that performs the wakeup +/// when the task affinity allows it. This keeps most wakeups local while still +/// falling back to the task's previous CPU or the normal selector if affinity +/// requires it. +#[inline] +pub(crate) fn select_wake_run_queue(task: &AxTaskRef) -> AxRunQueueRef<'static, G> { + let irq_state = G::acquire(); + #[cfg(not(feature = "smp"))] + { + let _ = task; + AxRunQueueRef { + inner: unsafe { RUN_QUEUE.current_ref_mut_raw() }, + state: irq_state, + _phantom: core::marker::PhantomData, + } + } + #[cfg(feature = "smp")] + { + let current_cpu = this_cpu_id(); + let last_cpu = task.cpu_id() as usize; + let cpumask = task.cpumask(); + let index = if cpumask.get(current_cpu) { + current_cpu + } else if last_cpu < ax_config::plat::MAX_CPU_NUM && cpumask.get(last_cpu) { + last_cpu + } else { + select_run_queue_index(cpumask) + }; + AxRunQueueRef { + inner: get_run_queue(index), + state: irq_state, + _phantom: core::marker::PhantomData, + } + } +} + /// [`AxRunQueue`] represents a run queue for global system or a specific CPU. pub(crate) struct AxRunQueue { /// The ID of the CPU this run queue is associated with. @@ -239,15 +287,14 @@ impl AxRunQueueRef<'_, G> { /// /// This function is used to add a new task to the scheduler. pub fn add_task(&mut self, task: AxTaskRef) { - debug!( - "task add: {} on run_queue {}", - task.id_name(), - self.inner.cpu_id - ); + let cpu_id = self.inner.cpu_id; + debug!("task add: {} on run_queue {}", task.id_name(), cpu_id); assert!(task.is_ready()); #[cfg(feature = "smp")] - task.set_cpu_id(self.inner.cpu_id as _); + task.set_cpu_id(cpu_id as _); self.inner.scheduler.lock().add_task(task); + #[cfg(all(feature = "smp", feature = "ipi"))] + kick_remote_cpu(cpu_id); } /// Unblock one task by inserting it into the run queue. @@ -274,6 +321,8 @@ impl AxRunQueueRef<'_, G> { #[cfg(feature = "preempt")] crate::current().set_preempt_pending(true); } + #[cfg(all(feature = "smp", feature = "ipi"))] + kick_remote_cpu(cpu_id); } } } @@ -446,9 +495,14 @@ impl CurrentRunQueueRef<'_, G> { // Mark the task as blocked, this has to be done before adding it to the wait queue // while holding the lock of the wait queue. curr.set_state(TaskState::Blocked); - curr.set_in_wait_queue(true); - wq_guard.push_back(curr.clone()); + // A preemptive future wake can re-enter a wait path before a previous + // wait-queue entry has been consumed. Avoid leaving a stale duplicate + // waiter that may receive mutex ownership after the task is running. + if !curr.in_wait_queue() { + curr.set_in_wait_queue(true); + wq_guard.push_back(curr.clone()); + } // Drop the lock of wait queue explictly. drop(wq_guard); diff --git a/os/arceos/modules/axtask/src/wait_queue.rs b/os/arceos/modules/axtask/src/wait_queue.rs index 5efd1555bb..04b0105071 100644 --- a/os/arceos/modules/axtask/src/wait_queue.rs +++ b/os/arceos/modules/axtask/src/wait_queue.rs @@ -3,7 +3,7 @@ use alloc::collections::VecDeque; use ax_kernel_guard::{NoOp, NoPreemptIrqSave}; use ax_kspin::{SpinNoIrq, SpinNoIrqGuard}; -use crate::{AxTaskRef, CurrentTask, current_run_queue, select_run_queue}; +use crate::{AxTaskRef, CurrentTask, current_run_queue, select_wake_run_queue}; /// A queue to store sleeping tasks. /// @@ -221,5 +221,5 @@ fn unblock_one_task(task: AxTaskRef, resched: bool) { // Select run queue by the CPU set of the task. // Use `NoOp` kernel guard here because the function is called with holding the // lock of wait queue, where the irq and preemption are disabled. - select_run_queue::(&task).unblock_task(task, resched) + select_wake_run_queue::(&task).unblock_task(task, resched) } diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/Cargo.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/Cargo.toml new file mode 100644 index 0000000000..dc4e00fb7d --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "arceos-wait-queue-remote-wake" +version = "0.3.1" +edition.workspace = true +authors = ["Open Source Contributors"] +description = "An ArceOS SMP wait-queue remote wakeup progress regression test" +publish = false + +[dependencies] +ax-driver.workspace = true +ax-hal.workspace = true +ax-std = { workspace = true, features = ["multitask", "irq", "ipi"], optional = true } diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..4534e4acf0 --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,8 @@ +features = ["ax-std"] +log = "Warn" +max_cpu_num = 4 +plat_dyn = true + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-loongarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..63f6e5549d --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-loongarch64-unknown-none-softfloat.toml @@ -0,0 +1,7 @@ +features = ["ax-std"] +log = "Warn" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..63f6e5549d --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,7 @@ +features = ["ax-std"] +log = "Warn" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..63f6e5549d --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/build-x86_64-unknown-none.toml @@ -0,0 +1,7 @@ +features = ["ax-std"] +log = "Warn" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml new file mode 100644 index 0000000000..479157ca39 --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml @@ -0,0 +1,17 @@ +args = [ + "-machine", + "virt", + "-cpu", + "cortex-a72", + "-m", + "128M", + "-smp", + "4", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = true +success_regex = ["All tests passed!"] +fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-loongarch64.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-loongarch64.toml new file mode 100644 index 0000000000..fcc51c44c1 --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-loongarch64.toml @@ -0,0 +1,17 @@ +args = [ + "-machine", + "virt", + "-cpu", + "la464", + "-m", + "128M", + "-smp", + "4", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = true +success_regex = ["All tests passed!"] +fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml new file mode 100644 index 0000000000..da79509f7c --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml @@ -0,0 +1,19 @@ +args = [ + "-machine", + "virt", + "-cpu", + "rv64", + "-m", + "128M", + "-smp", + "4", + "-accel", + "tcg,thread=single", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = true +success_regex = ["All tests passed!"] +fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-x86_64.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-x86_64.toml new file mode 100644 index 0000000000..e9ac06d697 --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-x86_64.toml @@ -0,0 +1,17 @@ +args = [ + "-machine", + "q35", + "-cpu", + "max", + "-m", + "128M", + "-smp", + "4", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = false +success_regex = ["All tests passed!"] +fail_regex = ["(?i)\\bpanic(?:ked)?\\b"] diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/src/main.rs b/test-suit/arceos/rust/task/wait_queue_remote_wake/src/main.rs new file mode 100644 index 0000000000..8905aac115 --- /dev/null +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/src/main.rs @@ -0,0 +1,131 @@ +#![cfg_attr(feature = "ax-std", no_std)] +#![cfg_attr(feature = "ax-std", no_main)] + +#[macro_use] +#[cfg(feature = "ax-std")] +extern crate ax_std as std; + +#[cfg(feature = "ax-std")] +use core::{hint::spin_loop, sync::atomic::AtomicUsize}; +#[cfg(feature = "ax-std")] +use std::os::arceos::{ + api::task::{self as api, AxCpuMask, AxWaitQueueHandle, ax_set_current_affinity}, + modules::ax_hal::percpu::this_cpu_id, +}; +#[cfg(feature = "ax-std")] +use std::{ + sync::atomic::{AtomicBool, Ordering}, + thread, + time::Duration, +}; + +#[cfg(feature = "ax-std")] +static READY_WQ: AxWaitQueueHandle = AxWaitQueueHandle::new(); +#[cfg(feature = "ax-std")] +static SLEEP_WQ: AxWaitQueueHandle = AxWaitQueueHandle::new(); +#[cfg(feature = "ax-std")] +static DONE_WQ: AxWaitQueueHandle = AxWaitQueueHandle::new(); +#[cfg(feature = "ax-std")] +static READY: AtomicBool = AtomicBool::new(false); +#[cfg(feature = "ax-std")] +static GO: AtomicBool = AtomicBool::new(false); +#[cfg(feature = "ax-std")] +static DONE: AtomicBool = AtomicBool::new(false); +#[cfg(feature = "ax-std")] +static SLEEPER_CPU: AtomicUsize = AtomicUsize::new(usize::MAX); + +#[cfg(feature = "ax-std")] +fn pin_current_to_cpu(cpu_id: usize) { + assert!( + ax_set_current_affinity(AxCpuMask::one_shot(cpu_id)).is_ok(), + "failed to pin current task to CPU {cpu_id}" + ); + for _ in 0..256 { + if this_cpu_id() == cpu_id { + return; + } + thread::yield_now(); + } + assert_eq!( + this_cpu_id(), + cpu_id, + "current task did not migrate to CPU {cpu_id}" + ); +} + +#[cfg(feature = "ax-std")] +fn wait_for_fast_progress() -> bool { + for _ in 0..200_000 { + if DONE.load(Ordering::Acquire) { + return true; + } + spin_loop(); + } + false +} + +#[cfg(all(feature = "ax-std", target_arch = "aarch64"))] +fn run_remote_wakeup_test() { + println!("wait_queue_remote_wake: skipped on aarch64"); +} + +#[cfg(all(feature = "ax-std", not(target_arch = "aarch64")))] +fn run_remote_wakeup_test() { + let cpu_num = thread::available_parallelism().unwrap().get(); + if cpu_num < 2 { + println!("wait_queue_remote_wake: skipped on single CPU"); + return; + } + + let waker_cpu = 0; + let sleeper_cpu = 1; + println!("wait_queue_remote_wake: waker_cpu={waker_cpu}, sleeper_cpu={sleeper_cpu}"); + + READY.store(false, Ordering::Release); + GO.store(false, Ordering::Release); + DONE.store(false, Ordering::Release); + SLEEPER_CPU.store(usize::MAX, Ordering::Release); + + pin_current_to_cpu(waker_cpu); + let sleeper = thread::spawn(move || { + pin_current_to_cpu(sleeper_cpu); + SLEEPER_CPU.store(this_cpu_id(), Ordering::Release); + READY.store(true, Ordering::Release); + api::ax_wait_queue_wake(&READY_WQ, 1); + + api::ax_wait_queue_wait_until(&SLEEP_WQ, || GO.load(Ordering::Acquire), None); + assert_eq!( + this_cpu_id(), + sleeper_cpu, + "remote wakeup resumed on the wrong CPU" + ); + DONE.store(true, Ordering::Release); + api::ax_wait_queue_wake(&DONE_WQ, 1); + }); + + api::ax_wait_queue_wait_until(&READY_WQ, || READY.load(Ordering::Acquire), None); + assert_eq!(SLEEPER_CPU.load(Ordering::Acquire), sleeper_cpu); + + // Give the sleeper a stable chance to enter the wait queue before the wake. + thread::sleep(Duration::from_millis(10)); + assert_eq!(this_cpu_id(), waker_cpu); + GO.store(true, Ordering::Release); + api::ax_wait_queue_wake(&SLEEP_WQ, 1); + + assert!( + wait_for_fast_progress(), + "remote wait-queue wakeup did not make prompt progress" + ); + api::ax_wait_queue_wait_until(&DONE_WQ, || DONE.load(Ordering::Acquire), None); + sleeper.join().unwrap(); + + println!("wait_queue_remote_wake: test OK!"); +} + +#[cfg_attr(feature = "ax-std", unsafe(no_mangle))] +fn main() { + #[cfg(feature = "ax-std")] + run_remote_wakeup_test(); + + println!("All tests passed!"); +} From 363d9cc758ef1402431771c9fcbbf2191554fc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Tue, 26 May 2026 22:31:31 +0800 Subject: [PATCH 43/71] refactor(starry): route HAL access through ax-runtime (#963) * Refactor kernel to use ax_runtime for HAL functions - Updated references from ax_hal to ax_runtime::hal in various kernel modules, including tty_serial, proc, sysfs, usbfs, and syscall files. - Adjusted IRQ registration and memory management functions to align with the new ax_runtime structure. - Removed deprecated platform dependencies in Cargo.toml and main.rs, streamlining the build configuration. - Enhanced build script to ensure proper feature handling for SG2002 and VF2 platforms. - Updated test configurations to reflect changes in feature requirements for the SG2002 platform. * fix: add dependencies for arceos-wait-queue-remote-wake * feat(irq): implement inter-processor interrupt (IPI) handling * fix: remove unused feature flag 'used_with_arg' from lib.rs * test(starryos): bound clone files race normal case * Add stress tests for buddy slab allocator and enhance GIC driver - Introduced a comprehensive suite of stress tests for the buddy slab allocator, covering various scenarios including random mixed allocation/free, exhaustion recovery, fragmentation recovery, and multithreaded operations. - Enhanced the ARM GIC driver by adding a method to send Software Generated Interrupts (SGIs) to target CPUs, improving interrupt handling capabilities. - Updated dependencies in various Cargo.toml files to include the new buddy slab allocator feature. - Refactored the GlobalAllocator implementation to improve locking mechanisms during allocation and deallocation. - Added new test configurations and scripts for QEMU to validate the buddy slab allocator functionality in a multi-core environment. --- .github/workflows/ci.yml | 9 + Cargo.lock | 82 +- Cargo.toml | 3 +- .../.github/workflows/check.yml | 94 ++ .../.github/workflows/ci.yml | 20 + .../.github/workflows/release-plz.yml | 72 + .../.github/workflows/test.yml | 29 + components/buddy-slab-allocator/.gitignore | 2 + components/buddy-slab-allocator/CHANGELOG.md | 150 ++ components/buddy-slab-allocator/Cargo.toml | 44 + .../buddy-slab-allocator/LICENSE.Apache2 | 201 +++ components/buddy-slab-allocator/README.md | 251 ++++ components/buddy-slab-allocator/README_CN.md | 249 ++++ .../buddy-slab-allocator/benches/README_CN.md | 45 + .../benches/buddy_allocator.rs | 105 ++ .../buddy-slab-allocator/benches/common.rs | 238 ++++ .../benches/global_allocator.rs | 112 ++ .../benches/slab_allocator.rs | 102 ++ .../buddy-slab-allocator/docs/design.md | 611 ++++++++ .../buddy-slab-allocator/rust-toolchain.toml | 3 + .../buddy-slab-allocator/src/buddy/mod.rs | 831 +++++++++++ .../src/buddy/page_meta.rs | 137 ++ components/buddy-slab-allocator/src/error.rs | 37 + components/buddy-slab-allocator/src/global.rs | 331 +++++ components/buddy-slab-allocator/src/lib.rs | 111 ++ .../buddy-slab-allocator/src/slab/cache.rs | 275 ++++ .../buddy-slab-allocator/src/slab/mod.rs | 298 ++++ .../buddy-slab-allocator/src/slab/page.rs | 329 +++++ .../src/slab/size_class.rs | 91 ++ .../buddy-slab-allocator/tests/README.md | 48 + .../buddy-slab-allocator/tests/common/mod.rs | 190 +++ .../tests/dma32_pages_test.rs | 101 ++ .../tests/integration_test.rs | 1266 +++++++++++++++++ .../buddy-slab-allocator/tests/stress_test.rs | 485 +++++++ .../intc/arm-gic-driver/src/version/v2/mod.rs | 18 + .../configs/board/licheerv-nano-sg2002.toml | 1 - os/StarryOS/kernel/Cargo.toml | 2 +- os/StarryOS/kernel/src/entry.rs | 2 +- os/StarryOS/kernel/src/file/memfd.rs | 2 +- os/StarryOS/kernel/src/file/timerfd.rs | 2 +- os/StarryOS/kernel/src/mm/access.rs | 13 +- .../kernel/src/mm/aspace/backend/cow.rs | 6 +- .../kernel/src/mm/aspace/backend/file.rs | 2 +- .../kernel/src/mm/aspace/backend/linear.rs | 2 +- .../kernel/src/mm/aspace/backend/mod.rs | 6 +- .../kernel/src/mm/aspace/backend/shared.rs | 2 +- os/StarryOS/kernel/src/mm/aspace/mod.rs | 10 +- os/StarryOS/kernel/src/mm/loader.rs | 4 +- os/StarryOS/kernel/src/pseudofs/dev/card0.rs | 6 +- os/StarryOS/kernel/src/pseudofs/dev/card1.rs | 2 +- .../kernel/src/pseudofs/dev/cvi_camera.rs | 12 +- .../kernel/src/pseudofs/dev/cvi_usb_camera.rs | 6 +- os/StarryOS/kernel/src/pseudofs/dev/event.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/fb.rs | 2 +- .../kernel/src/pseudofs/dev/rknpu_card.rs | 2 +- .../kernel/src/pseudofs/dev/tty/ntty.rs | 28 +- .../kernel/src/pseudofs/dev/tty_serial.rs | 8 +- os/StarryOS/kernel/src/pseudofs/proc.rs | 16 +- os/StarryOS/kernel/src/pseudofs/sysfs.rs | 8 +- os/StarryOS/kernel/src/pseudofs/usbfs/irq.rs | 2 +- .../kernel/src/pseudofs/usbfs/manager.rs | 2 +- os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs | 4 +- os/StarryOS/kernel/src/stop_machine.rs | 2 +- os/StarryOS/kernel/src/syscall/fs/ctl.rs | 2 +- os/StarryOS/kernel/src/syscall/io_mpx/poll.rs | 2 +- os/StarryOS/kernel/src/syscall/ipc/msg.rs | 2 +- os/StarryOS/kernel/src/syscall/ipc/shm.rs | 4 +- os/StarryOS/kernel/src/syscall/mm/brk.rs | 2 +- os/StarryOS/kernel/src/syscall/mm/mincore.rs | 2 +- os/StarryOS/kernel/src/syscall/mm/mmap.rs | 2 +- os/StarryOS/kernel/src/syscall/mod.rs | 2 +- os/StarryOS/kernel/src/syscall/net/io.rs | 2 +- os/StarryOS/kernel/src/syscall/resources.rs | 2 +- os/StarryOS/kernel/src/syscall/signal.rs | 2 +- os/StarryOS/kernel/src/syscall/sync/futex.rs | 2 +- os/StarryOS/kernel/src/syscall/sys.rs | 4 +- os/StarryOS/kernel/src/syscall/task/clone.rs | 2 +- os/StarryOS/kernel/src/syscall/task/clone3.rs | 2 +- os/StarryOS/kernel/src/syscall/task/execve.rs | 2 +- .../kernel/src/syscall/task/schedule.rs | 14 +- os/StarryOS/kernel/src/syscall/task/thread.rs | 4 +- os/StarryOS/kernel/src/syscall/time.rs | 2 +- os/StarryOS/kernel/src/task/mod.rs | 2 +- os/StarryOS/kernel/src/task/ops.rs | 2 +- os/StarryOS/kernel/src/task/posix_timer.rs | 2 +- os/StarryOS/kernel/src/task/signal.rs | 2 +- os/StarryOS/kernel/src/task/timer.rs | 2 +- os/StarryOS/kernel/src/task/user.rs | 2 +- os/StarryOS/kernel/src/time.rs | 2 +- os/StarryOS/kernel/src/tracepoint/mod.rs | 2 +- os/StarryOS/kernel/src/trap.rs | 8 +- os/StarryOS/starryos/Cargo.toml | 11 +- os/StarryOS/starryos/src/main.rs | 9 - os/arceos/api/axfeat/Cargo.toml | 1 + os/arceos/modules/axalloc/src/buddy_slab.rs | 44 +- os/arceos/modules/axhal/src/lib.rs | 1 + os/arceos/modules/axruntime/Cargo.toml | 2 +- os/arceos/modules/axruntime/src/lib.rs | 3 + os/arceos/modules/axruntime/src/mp.rs | 1 + platform/axplat-dyn/Cargo.toml | 1 - platform/axplat-dyn/src/irq.rs | 13 +- platform/axplat-dyn/src/lib.rs | 1 - platform/somehal/src/arch/aarch64/gic/mod.rs | 80 +- platform/somehal/src/arch/aarch64/gic/v2.rs | 14 + platform/somehal/src/arch/aarch64/gic/v3.rs | 22 + platform/somehal/src/arch/aarch64/mod.rs | 4 + platform/somehal/src/common.rs | 4 + platform/somehal/src/irq.rs | 26 + scripts/axbuild/src/starry/build.rs | 70 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 21 + .../c/CMakeLists.txt | 12 + .../qemu-aarch64.toml | 16 + .../test-clone-files-race/c/src/main.c | 3 +- 114 files changed, 7381 insertions(+), 186 deletions(-) create mode 100644 components/buddy-slab-allocator/.github/workflows/check.yml create mode 100644 components/buddy-slab-allocator/.github/workflows/ci.yml create mode 100644 components/buddy-slab-allocator/.github/workflows/release-plz.yml create mode 100644 components/buddy-slab-allocator/.github/workflows/test.yml create mode 100644 components/buddy-slab-allocator/.gitignore create mode 100644 components/buddy-slab-allocator/CHANGELOG.md create mode 100644 components/buddy-slab-allocator/Cargo.toml create mode 100644 components/buddy-slab-allocator/LICENSE.Apache2 create mode 100644 components/buddy-slab-allocator/README.md create mode 100644 components/buddy-slab-allocator/README_CN.md create mode 100644 components/buddy-slab-allocator/benches/README_CN.md create mode 100644 components/buddy-slab-allocator/benches/buddy_allocator.rs create mode 100644 components/buddy-slab-allocator/benches/common.rs create mode 100644 components/buddy-slab-allocator/benches/global_allocator.rs create mode 100644 components/buddy-slab-allocator/benches/slab_allocator.rs create mode 100644 components/buddy-slab-allocator/docs/design.md create mode 100644 components/buddy-slab-allocator/rust-toolchain.toml create mode 100644 components/buddy-slab-allocator/src/buddy/mod.rs create mode 100644 components/buddy-slab-allocator/src/buddy/page_meta.rs create mode 100644 components/buddy-slab-allocator/src/error.rs create mode 100644 components/buddy-slab-allocator/src/global.rs create mode 100644 components/buddy-slab-allocator/src/lib.rs create mode 100644 components/buddy-slab-allocator/src/slab/cache.rs create mode 100644 components/buddy-slab-allocator/src/slab/mod.rs create mode 100644 components/buddy-slab-allocator/src/slab/page.rs create mode 100644 components/buddy-slab-allocator/src/slab/size_class.rs create mode 100644 components/buddy-slab-allocator/tests/README.md create mode 100644 components/buddy-slab-allocator/tests/common/mod.rs create mode 100644 components/buddy-slab-allocator/tests/dma32_pages_test.rs create mode 100644 components/buddy-slab-allocator/tests/integration_test.rs create mode 100644 components/buddy-slab-allocator/tests/stress_test.rs create mode 100644 test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml create mode 100644 test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/qemu-aarch64.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3796fc505..c3d691a6a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -357,6 +357,15 @@ jobs: apk_region: us limit_to_owner: "" main_pr_only: false + - name: Test starry aarch64 buddy-slab qemu + use_container: true + runs_on: '["ubuntu-latest"]' + command: cargo xtask starry test qemu --arch aarch64 -c test-clone-files-race-buddy-slab + cache_key: test-starry-aarch64-buddy-slab + container_image: base + apk_region: us + limit_to_owner: "" + main_pr_only: false - name: Test starry loongarch64 qemu use_container: true runs_on: '["ubuntu-latest"]' diff --git a/Cargo.lock b/Cargo.lock index 94b4877d85..88a87e4e53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,6 +529,8 @@ dependencies = [ name = "arceos-wait-queue-remote-wake" version = "0.3.1" dependencies = [ + "ax-driver", + "ax-hal", "ax-std", ] @@ -1813,7 +1815,6 @@ name = "axplat-dyn" version = "0.6.2" dependencies = [ "anyhow", - "ax-alloc", "ax-config-macros", "ax-cpu", "ax-driver", @@ -2288,11 +2289,11 @@ dependencies = [ [[package]] name = "buddy-slab-allocator" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a844607dec426282e05649372acdc1170b08ba57879be9d3216184ed6dbfd3b" +version = "0.4.1" dependencies = [ + "divan", "log", + "rand 0.10.1", "spin", ] @@ -2472,6 +2473,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.44" @@ -2544,6 +2556,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim 0.11.1", + "terminal_size", ] [[package]] @@ -2651,6 +2664,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + [[package]] name = "console" version = "0.16.3" @@ -3362,6 +3381,31 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "divan" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933" +dependencies = [ + "cfg-if", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + +[[package]] +name = "divan-macros" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "dma-api" version = "0.7.3" @@ -4097,6 +4141,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -4747,9 +4792,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6835eea34fb6321b9b3aa7b685c2b433948c09447e389dc017fdf687d5d11e65" +checksum = "30457d51cb0e68ee18184b30cd9eb8e1602a20837c321f6ea9706b94f1c681c3" dependencies = [ "jiff-static", "log", @@ -4760,9 +4805,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c22e04db9c58f5136eb1757f3d5c49a7b187f49e52185228cbd2f5acdfcc08c" +checksum = "05f86e4f0326c61ae6c00b04d9009aaeda644d0b5bdfbf6c67247f492f42b3f3" dependencies = [ "proc-macro2", "quote", @@ -6284,6 +6329,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ + "chacha20", + "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -6701,6 +6748,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" @@ -7873,7 +7926,6 @@ dependencies = [ "ax-errno", "ax-feat", "ax-fs-ng", - "ax-hal", "ax-input", "ax-io", "ax-ipi", @@ -7985,10 +8037,8 @@ dependencies = [ "ax-driver", "ax-feat", "ax-hal", - "ax-plat-riscv64-sg2002", "axbuild", "axplat-dyn", - "axplat-riscv64-visionfive2", "clap", "starry-kernel", "tokio", @@ -8184,6 +8234,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "terminfo" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 6f5a38e290..b1c5ffd97e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ members = [ "components/axvm", "components/axvmconfig", "components/bitmap-allocator", + "components/buddy-slab-allocator", "components/cap_access", "components/cpumask", "components/crate_interface", @@ -371,7 +372,7 @@ bitflags = "2.11" byte-unit = { version = "5", default-features = false, features = ["byte"] } derive_more = { version = "2.1", default-features = false, features = ["full"] } bindgen = "0.72" -buddy-slab-allocator = "0.4" +buddy-slab-allocator = { version = "0.4", path = "components/buddy-slab-allocator" } cfg-if = "1.0" chrono = { version = "0.4", default-features = false } enum_dispatch = "0.3" diff --git a/components/buddy-slab-allocator/.github/workflows/check.yml b/components/buddy-slab-allocator/.github/workflows/check.yml new file mode 100644 index 0000000000..bc2347e8c5 --- /dev/null +++ b/components/buddy-slab-allocator/.github/workflows/check.yml @@ -0,0 +1,94 @@ +name: Quality Checks + +on: + workflow_call: + +jobs: + fmt: + name: Format Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-02-25 + components: rustfmt + + - name: Check code format + run: cargo fmt --all -- --check + + clippy: + name: Clippy Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-02-25 + components: clippy + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Run clippy + run: cargo clippy --all-targets -- -D warnings + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-02-25 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Build library + run: cargo build + + doc: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-02-25 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Build documentation + run: cargo doc --no-deps + + bench-check: + name: Bench Compile Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-02-25 + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Check benchmarks + run: cargo check --benches diff --git a/components/buddy-slab-allocator/.github/workflows/ci.yml b/components/buddy-slab-allocator/.github/workflows/ci.yml new file mode 100644 index 0000000000..f08e0782bb --- /dev/null +++ b/components/buddy-slab-allocator/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + pull_request: + push: + branches-ignore: + - main + tags-ignore: + - "*" + +permissions: + contents: read + +jobs: + check: + uses: ./.github/workflows/check.yml + + test: + uses: ./.github/workflows/test.yml + needs: check diff --git a/components/buddy-slab-allocator/.github/workflows/release-plz.yml b/components/buddy-slab-allocator/.github/workflows/release-plz.yml new file mode 100644 index 0000000000..0d36a95690 --- /dev/null +++ b/components/buddy-slab-allocator/.github/workflows/release-plz.yml @@ -0,0 +1,72 @@ +name: Release-plz + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + check: + uses: ./.github/workflows/check.yml + + test: + uses: ./.github/workflows/test.yml + needs: check + + release-plz-release: + name: Release-plz release + runs-on: ubuntu-latest + needs: [check, test] + permissions: + contents: write + pull-requests: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run release-plz + uses: release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # If you switch to crates.io trusted publishing, remove this line + # and keep `id-token: write` above. + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + release-plz-pr: + name: Release-plz PR + runs-on: ubuntu-latest + needs: [check, test] + permissions: + contents: write + pull-requests: write + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run release-plz + uses: release-plz/action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/components/buddy-slab-allocator/.github/workflows/test.yml b/components/buddy-slab-allocator/.github/workflows/test.yml new file mode 100644 index 0000000000..45ecba068f --- /dev/null +++ b/components/buddy-slab-allocator/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Test + +on: + workflow_call: + +jobs: + host-test: + name: Run Logic Tests (Host) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 # v6.0.2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Run all tests + run: cargo test + + - name: Run tests serially + run: cargo test -- --test-threads=1 + + - name: Run ignored stress tests + run: cargo test --test stress_test -- --ignored --nocapture diff --git a/components/buddy-slab-allocator/.gitignore b/components/buddy-slab-allocator/.gitignore new file mode 100644 index 0000000000..ed768f33ec --- /dev/null +++ b/components/buddy-slab-allocator/.gitignore @@ -0,0 +1,2 @@ +/target/ +Cargo.lock \ No newline at end of file diff --git a/components/buddy-slab-allocator/CHANGELOG.md b/components/buddy-slab-allocator/CHANGELOG.md new file mode 100644 index 0000000000..1e19def0e6 --- /dev/null +++ b/components/buddy-slab-allocator/CHANGELOG.md @@ -0,0 +1,150 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed +- Removed the external allocator-interface dependency and restored crate-local `AllocError` / `AllocResult` +- Stopped exporting the generic allocator traits `BaseAllocator`, `ByteAllocator`, `PageAllocator`, and `IdAllocator` +- Public allocator APIs now favor concrete allocator methods directly instead of requiring trait imports +- Removed `alloc_pages_at` because the current buddy/slab architecture does not stably support fixed-address allocation + +### Removed +- Removed the external allocator-interface crate from `[dependencies]` + +### Migration Notes +- Replace imports of `BaseAllocator`, `ByteAllocator`, `PageAllocator`, and `IdAllocator` with direct method calls on `BuddyPageAllocator`, `CompositePageAllocator`, `SlabByteAllocator`, and `GlobalAllocator` +- `PageAllocatorForSlab` remains available for wiring `SlabByteAllocator` to a page allocator + +## [0.2.0] - 2026-03-05 + +### Added +- Added an external allocator-interface dependency + +### Changed +- `AllocError`, `AllocResult`, `BaseAllocator`, `ByteAllocator`, `PageAllocator`, and `IdAllocator` are now re-exported from the external allocator-interface crate instead of being defined locally +- Updated Rust toolchain to `nightly-2026-02-25` +- Benchmarks no longer require `--features bench`; `criterion` and `rand` moved to `[dev-dependencies]` + +### Removed +- Removed locally defined allocator trait and error type definitions (now provided by the external allocator-interface crate) +- Removed the deprecated `bench` feature flag + +## [0.1.1] - 2026-02-06 + +### Added +- Comprehensive benchmark suite with criterion +- Benchmark documentation in Chinese (`benches/README_CN.md`) +- Benchmark workflow in CI with Rust 1.93.0 toolchain for compatibility + +### Changed +- Increased `MAX_ZONES` from 16 to 32 for more flexible memory region management +- Improved documentation in README with benchmark usage instructions + + +## [0.1.0] - 2025-01-30 + +### Added +- Buddy page allocator implementation for page-level allocation +- Slab byte allocator implementation for small object allocation +- Composite page allocator for unified multi-region page allocation +- Global allocator that coordinates page and byte allocators +- Automatic allocation size selection (≤2048 bytes uses slab, >2048 bytes uses page) +- Zero `std` dependency (`#![no_std]`) for embedded/kernel environments +- Optional `log` feature for logging allocation events +- Optional `tracking` feature for memory usage statistics +- `AddrTranslator` trait for virtual-to-physical address translation +- `BaseAllocator`, `ByteAllocator`, `PageAllocator`, and `IdAllocator` traits +- Comprehensive error handling with `AllocError` enum + +### Features +- O(1) time complexity for small object allocation +- Buddy algorithm for efficient page allocation with automatic merging +- Support for multiple memory regions +- Flexible page size configuration (const generic) +- Memory fragmentation reduction through slab allocation +- Statistics tracking for debugging and profiling + +### Documentation +- Complete API documentation with examples +- README with bilingual (English/Chinese) documentation +- Inline documentation for all public APIs + +### Testing +- Integration tests for page allocator +- Integration tests for slab allocator +- Integration tests for global allocator +- DMA32 pages test cases +- Comprehensive edge case coverage + +## [Unreleased]: https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.2.0...HEAD + +## [0.3.1](https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.3.0...v0.3.1) - 2026-04-10 + +### Other + +- Refactor global allocator to support singleton pattern and improve slab management +- Implement per-CPU slab allocator with object-safe interface +- bump version to 0.3.1 and update changelog for enhancements +- enhance region layout handling and alignment in allocator methods + +## [0.3.1](https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.3.0...v0.3.1) - 2026-04-09 + +### Other + +- enhance region layout handling and alignment in allocator methods + +## [0.3.0](https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.2.0...v0.3.0) - 2026-04-09 + +### Added + +- enhance slab allocation by reclaiming full slabs with remote frees and add integration tests for cross-CPU deallocation +- implement Default trait for BuddyAllocator, GlobalAllocator, and SlabAllocator +- remove alloc_pages_at method from buddy allocator and related components; simplify allocation logic +- refactor allocator interfaces and remove deprecated dependencies; update documentation and examples +- enhance logging support by integrating log crate and updating documentation + +### Other + +- add unsafe blocks for improved safety checks in allocator methods +- update changelog for version 0.2.1 and modify Cargo.toml for version bump to 0.3.0 +- format code for better readability in common.rs +- streamline region initialization by introducing SectionInitSpec for better clarity and maintainability +- Refactor GlobalAllocator to support multiple managed sections +- Refactor stress tests for allocator stability +- update allocator initialization to use a mutable slice instead of separate start and size parameters +- Refactor slab allocator benchmarks and improve global allocator initialization +- Refactor integration and stress tests for buddy-slab-allocator +- Refactor integration and stress tests for allocator +- Refactor benchmarks and remove stability tests +- update workflows and dependencies; migrate to actions/checkout@v6 and rand v0.10 + +## [0.2.1](https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.2.0...v0.2.1) - 2026-04-09 + +### Added + +- enhance slab allocation by reclaiming full slabs with remote frees and add integration tests for cross-CPU deallocation +- implement Default trait for BuddyAllocator, GlobalAllocator, and SlabAllocator +- remove alloc_pages_at method from buddy allocator and related components; simplify allocation logic +- refactor allocator interfaces and remove deprecated dependencies; update documentation and examples +- enhance logging support by integrating log crate and updating documentation + +### Other + +- format code for better readability in common.rs +- streamline region initialization by introducing SectionInitSpec for better clarity and maintainability +- Refactor GlobalAllocator to support multiple managed sections +- Refactor stress tests for allocator stability +- update allocator initialization to use a mutable slice instead of separate start and size parameters +- Refactor slab allocator benchmarks and improve global allocator initialization +- Refactor integration and stress tests for buddy-slab-allocator +- Refactor integration and stress tests for allocator +- Refactor benchmarks and remove stability tests +- update workflows and dependencies; migrate to actions/checkout@v6 and rand v0.10 +## [0.2.0]: https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.1.1...v0.2.0 +## [0.1.1]: https://github.com/arceos-hypervisor/buddy-slab-allocator/compare/v0.1.0...v0.1.1 +## [0.1.0]: https://github.com/arceos-hypervisor/buddy-slab-allocator/releases/tag/v0.1.0 diff --git a/components/buddy-slab-allocator/Cargo.toml b/components/buddy-slab-allocator/Cargo.toml new file mode 100644 index 0000000000..8ab3be9c72 --- /dev/null +++ b/components/buddy-slab-allocator/Cargo.toml @@ -0,0 +1,44 @@ +[package] +authors = [ + "Song Zhiyong ", + "周睿 ", +] +autobenches = false +categories = [ + "memory-management", + "no-std", + "embedded", +] +description = "Memory allocator with Buddy and Slab allocation" +documentation = "https://docs.rs/buddy-slab-allocator" +edition = "2024" +keywords = [ + "buddy", + "slab", + "allocator", +] +license = "Apache-2.0" +name = "buddy-slab-allocator" +readme = "README.md" +repository = "https://github.com/arceos-hypervisor/buddy-slab-allocator" +version = "0.4.1" + +[[bench]] +harness = false +name = "buddy_allocator" + +[[bench]] +harness = false +name = "slab_allocator" + +[[bench]] +harness = false +name = "global_allocator" + +[dependencies] +log = "0.4" +spin = "0.10" + +[dev-dependencies] +divan = "0.1.21" +rand = { version = "0.10", features = ["std_rng"] } diff --git a/components/buddy-slab-allocator/LICENSE.Apache2 b/components/buddy-slab-allocator/LICENSE.Apache2 new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/components/buddy-slab-allocator/LICENSE.Apache2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/buddy-slab-allocator/README.md b/components/buddy-slab-allocator/README.md new file mode 100644 index 0000000000..e22c1aa971 --- /dev/null +++ b/components/buddy-slab-allocator/README.md @@ -0,0 +1,251 @@ +# buddy-slab-allocator + +A `no_std` two-level allocator for kernel and embedded environments, combining a buddy page allocator with per-CPU slab allocators. + +## Overview + +The current implementation is built from three layers: + +1. `BuddyAllocator` + Manages one or more virtual memory sections in page units, with power-of-two splitting and merging. +2. `SlabAllocator` + Manages small objects up to 2048 bytes with fixed size classes. +3. `GlobalAllocator` + Combines the two, routing small allocations to per-CPU slab caches and large allocations to buddy pages. + +The design details are documented in [docs/design.md](docs/design.md). + +## Architecture + +```mermaid +flowchart TD + GA["GlobalAllocator"] --> B["SpinMutex"] + GA --> EI["eii hooks"] + EI --> SP["slab_pool()"] + + B --> PM["PageMeta[]"] + B --> FL["free_lists by order"] + + SP --> SA["SlabPoolTrait"] + SA --> SC["SlabCache x 9"] + SC --> P["partial"] + SC --> F["full"] + SC --> E["empty"] + SC --> H["SlabPageHeader"] +``` + +### Allocation routing + +```mermaid +flowchart TD + A["GlobalAllocator::alloc(layout)"] --> B{"size <= 2048 and align <= 2048?"} + B -- Yes --> C["Use current CPU slab allocator"] + C --> D{"Allocated from slab?"} + D -- Yes --> E["Return object pointer"] + D -- No --> F["Allocate pages from buddy as a new slab"] + F --> G["add_slab and retry"] + G --> E + B -- No --> H["Allocate pages directly from buddy"] + H --> I["Return page-backed pointer"] +``` + +### Cross-CPU free path + +```mermaid +sequenceDiagram + participant CPU1 as current CPU + participant H as SlabPageHeader + participant CPU0 as owner CPU + participant S as owner SlabAllocator + + CPU1->>H: remote_free(obj_addr) + Note right of H: lock-free CAS push to remote_free_head + CPU1-->>CPU1: return immediately + + CPU0->>S: next local alloc/dealloc under slab lock + S->>H: drain_remote_frees() + H-->>S: slots moved back into local bitmap +``` + +## Features + +- Buddy page allocation with splitting and merging +- Dynamic hot-add of managed regions via `add_region` +- Slab allocation for 9 size classes: `8..=2048` +- Per-CPU slab caches +- Lock-free cross-CPU remote frees +- DMA32 / lowmem page allocation via `alloc_pages_lowmem` +- `no_std` friendly +- Built-in `log` integration +- Standalone `BuddyAllocator` and `SlabAllocator` usage + +## Add dependency + +```toml +[dependencies] +buddy-slab-allocator = "0.2.0" +``` + +## Using `GlobalAllocator` + +```rust +#![feature(extern_item_impls)] + +use buddy_slab_allocator::eii::{slab_pool_impl, virt_to_phys_impl}; +use buddy_slab_allocator::{GlobalAllocator, PerCpuSlab, SlabPoolTrait, StaticSlabPool}; +use core::alloc::Layout; + +const PAGE_SIZE: usize = 0x1000; + +fn current_cpu_id() -> usize { + 0 +} + +static SLAB_POOL: StaticSlabPool = + StaticSlabPool::new([PerCpuSlab::new(0)], current_cpu_id); + +#[virt_to_phys_impl] +fn virt_to_phys(vaddr: usize) -> usize { + vaddr +} + +#[slab_pool_impl] +fn slab_pool() -> &'static dyn SlabPoolTrait { + &SLAB_POOL +} + +let allocator = GlobalAllocator::::new(); +let region_start = 0x8000_0000 as *mut u8; +let region_size = 16 * 1024 * 1024; +let region = unsafe { core::slice::from_raw_parts_mut(region_start, region_size) }; + +unsafe { + allocator.init(region).unwrap(); +} + +let layout = Layout::from_size_align(64, 8).unwrap(); +let ptr = allocator.alloc(layout).unwrap(); + +unsafe { + allocator.dealloc(ptr, layout); +} + +// More memory can be added later. +let extra_region_start = 0x9000_0000 as *mut u8; +let extra_region_size = 8 * 1024 * 1024; +let extra_region = unsafe { + core::slice::from_raw_parts_mut(extra_region_start, extra_region_size) +}; + +unsafe { + allocator.add_region(extra_region).unwrap(); +} +``` + +`GlobalAllocator` is designed as a singleton-style system allocator: only one live +instance should be initialized at a time. + +## Using Buddy and Slab separately + +For lower-level control, the two building blocks can be used directly. + +```rust +use buddy_slab_allocator::{ + BuddyAllocator, SlabAllocResult, SlabAllocator, SlabDeallocResult, +}; +use core::alloc::Layout; + +const PAGE_SIZE: usize = 0x1000; +let region_start = 0x8000_0000 as *mut u8; +let region_size = 16 * 1024 * 1024; +let region = unsafe { core::slice::from_raw_parts_mut(region_start, region_size) }; + +let mut buddy = BuddyAllocator::::new(); +unsafe { + buddy.init(region).unwrap(); +} + +let mut slab = SlabAllocator::::new(); +let layout = Layout::from_size_align(64, 8).unwrap(); + +let ptr = loop { + match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(ptr) => break ptr, + SlabAllocResult::NeedsSlab { size_class, pages } => { + let slab_bytes = pages * PAGE_SIZE; + let addr = buddy.alloc_pages(pages, slab_bytes).unwrap(); + slab.add_slab(size_class, addr, slab_bytes, 0); + } + } +}; + +match slab.dealloc(ptr, layout) { + SlabDeallocResult::Done => {} + SlabDeallocResult::FreeSlab { base, pages } => { + buddy.dealloc_pages(base, pages); + } +} + +let extra_region_start = 0x9000_0000 as *mut u8; +let extra_region_size = 8 * 1024 * 1024; +let extra_region = unsafe { + core::slice::from_raw_parts_mut(extra_region_start, extra_region_size) +}; + +unsafe { + buddy.add_region(extra_region).unwrap(); +} +``` + +## Public API summary + +- `GlobalAllocator` + High-level allocator facade that can also implement `GlobalAlloc`, and supports `add_region`, `managed_section_count`, `managed_section`, `managed_bytes`, and `allocated_bytes`. +- `BuddyAllocator` + Standalone multi-section page allocator, supporting `init`, `add_region`, section queries, `managed_bytes`, and `allocated_bytes`. +- `ManagedSection` + Read-only summary for one managed section. +- `SlabAllocator` + Standalone slab allocator. +- `SizeClass` + Fixed object size classes used by slab. +- `SlabAllocResult` + `Allocated(ptr)` or `NeedsSlab { size_class, pages }`. +- `SlabDeallocResult` + `Done` or `FreeSlab { base, pages }`. +- `SlabPoolTrait` + System-global slab pool interface used by `GlobalAllocator`, exposing + object-safe `current_slab()` / `owner_slab()` primitives with default + routing for `alloc` / `add_slab` / `dealloc`. +- `SlabPoolExt` + Callback-style helpers: `with_current_slab()` and `with_owner_slab()`. +- `eii` + Declares `slab_pool()` and `virt_to_phys()` for platform integration. + +`managed_bytes` counts only allocatable heap bytes and excludes region-prefix metadata. +`allocated_bytes` is backend page occupancy, not the exact sum of requested `layout.size()`. + +## Testing + +```bash +# Run normal tests +cargo test + +# Run tests serially +cargo test -- --test-threads=1 + +# Run ignored stress tests +cargo test --test stress_test -- --ignored --nocapture + +# Check benchmarks compile +cargo check --benches + +# Run benchmarks +cargo bench +``` + +More test notes are in [tests/README.md](tests/README.md). + +## License + +Licensed under [Apache-2.0](LICENSE). diff --git a/components/buddy-slab-allocator/README_CN.md b/components/buddy-slab-allocator/README_CN.md new file mode 100644 index 0000000000..7b8d276111 --- /dev/null +++ b/components/buddy-slab-allocator/README_CN.md @@ -0,0 +1,249 @@ +# buddy-slab-allocator 内存分配器 + +这是一个面向内核和嵌入式环境的 `no_std` 两级内存分配器,结合了 buddy 页分配器与 per-CPU slab 小对象分配器。 + +## 总览 + +当前实现由三层组成: + +1. `BuddyAllocator` + 管理一个或多个虚拟内存 section,支持按 2 的幂分裂与合并。 +2. `SlabAllocator` + 管理 `<= 2048` 字节的小对象,采用固定 size class。 +3. `GlobalAllocator` + 将两者组合起来,对小对象走 per-CPU slab,对大对象走 buddy 页分配。 + +更完整的设计细节见 [docs/design.md](docs/design.md)。 + +## 架构图 + +```mermaid +flowchart TD + GA["GlobalAllocator"] --> B["SpinMutex"] + GA --> EI["eii hooks"] + EI --> SP["slab_pool()"] + + B --> PM["PageMeta[]"] + B --> FL["按 order 的 free_lists"] + + SP --> SA["SlabPoolTrait"] + SA --> SC["9 个 SlabCache"] + SC --> P["partial"] + SC --> F["full"] + SC --> E["empty"] + SC --> H["SlabPageHeader"] +``` + +### 分配路由 + +```mermaid +flowchart TD + A["GlobalAllocator::alloc(layout)"] --> B{"size <= 2048 且 align <= 2048?"} + B -- 是 --> C["走当前 CPU 的 slab allocator"] + C --> D{"slab 直接命中?"} + D -- 是 --> E["返回对象指针"] + D -- 否 --> F["从 buddy 申请新 slab 页"] + F --> G["add_slab 后重试"] + G --> E + B -- 否 --> H["直接走 buddy 页分配"] + H --> I["返回页级指针"] +``` + +### 跨 CPU 释放 + +```mermaid +sequenceDiagram + participant CPU1 as 当前 CPU + participant H as SlabPageHeader + participant CPU0 as owner CPU + participant S as owner SlabAllocator + + CPU1->>H: remote_free(obj_addr) + Note right of H: 无锁 CAS 推入 remote_free_head + CPU1-->>CPU1: 立即返回 + + CPU0->>S: 后续本地 alloc/dealloc + S->>H: drain_remote_frees() + H-->>S: 远程释放对象回收到本地 bitmap +``` + +## 特性 + +- Buddy 页分配,支持拆分与合并 +- 支持通过 `add_region` 动态追加可管理 region +- Slab 小对象分配,固定 9 个 size class:`8..=2048` +- per-CPU slab cache +- 跨 CPU 释放走 lock-free remote free +- 支持 `alloc_pages_lowmem` 低地址页分配 +- 适合 `no_std` +- 内置 `log` 日志支持 +- 可分别独立使用 `BuddyAllocator` 与 `SlabAllocator` + +## 添加依赖 + +```toml +[dependencies] +buddy-slab-allocator = "0.2.0" +``` + +## 使用 `GlobalAllocator` + +```rust +#![feature(extern_item_impls)] + +use buddy_slab_allocator::eii::{slab_pool_impl, virt_to_phys_impl}; +use buddy_slab_allocator::{GlobalAllocator, PerCpuSlab, SlabPoolTrait, StaticSlabPool}; +use core::alloc::Layout; + +const PAGE_SIZE: usize = 0x1000; + +fn current_cpu_id() -> usize { + 0 +} + +static SLAB_POOL: StaticSlabPool = + StaticSlabPool::new([PerCpuSlab::new(0)], current_cpu_id); + +#[virt_to_phys_impl] +fn virt_to_phys(vaddr: usize) -> usize { + vaddr +} + +#[slab_pool_impl] +fn slab_pool() -> &'static dyn SlabPoolTrait { + &SLAB_POOL +} + +let allocator = GlobalAllocator::::new(); +let region_start = 0x8000_0000 as *mut u8; +let region_size = 16 * 1024 * 1024; +let region = unsafe { core::slice::from_raw_parts_mut(region_start, region_size) }; + +unsafe { + allocator.init(region).unwrap(); +} + +let layout = Layout::from_size_align(64, 8).unwrap(); +let ptr = allocator.alloc(layout).unwrap(); + +unsafe { + allocator.dealloc(ptr, layout); +} + +let extra_region_start = 0x9000_0000 as *mut u8; +let extra_region_size = 8 * 1024 * 1024; +let extra_region = unsafe { + core::slice::from_raw_parts_mut(extra_region_start, extra_region_size) +}; + +unsafe { + allocator.add_region(extra_region).unwrap(); +} +``` + +`GlobalAllocator` 按系统级单例分配器设计:同一时刻只应初始化一套正在使用的实例。 + +## 分别使用 Buddy 与 Slab + +如果需要更底层的控制,也可以分别使用这两个组件。 + +```rust +use buddy_slab_allocator::{ + BuddyAllocator, SlabAllocResult, SlabAllocator, SlabDeallocResult, +}; +use core::alloc::Layout; + +const PAGE_SIZE: usize = 0x1000; +let region_start = 0x8000_0000 as *mut u8; +let region_size = 16 * 1024 * 1024; +let region = unsafe { core::slice::from_raw_parts_mut(region_start, region_size) }; + +let mut buddy = BuddyAllocator::::new(); +unsafe { + buddy.init(region).unwrap(); +} + +let mut slab = SlabAllocator::::new(); +let layout = Layout::from_size_align(64, 8).unwrap(); + +let ptr = loop { + match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(ptr) => break ptr, + SlabAllocResult::NeedsSlab { size_class, pages } => { + let slab_bytes = pages * PAGE_SIZE; + let addr = buddy.alloc_pages(pages, slab_bytes).unwrap(); + slab.add_slab(size_class, addr, slab_bytes, 0); + } + } +}; + +match slab.dealloc(ptr, layout) { + SlabDeallocResult::Done => {} + SlabDeallocResult::FreeSlab { base, pages } => { + buddy.dealloc_pages(base, pages); + } +} + +let extra_region_start = 0x9000_0000 as *mut u8; +let extra_region_size = 8 * 1024 * 1024; +let extra_region = unsafe { + core::slice::from_raw_parts_mut(extra_region_start, extra_region_size) +}; + +unsafe { + buddy.add_region(extra_region).unwrap(); +} +``` + +## 公开 API 摘要 + +- `GlobalAllocator` + 高层门面,也可以作为 `GlobalAlloc` 使用,并支持 `add_region`、`managed_section_count`、`managed_section`、`managed_bytes` 与 `allocated_bytes`。 +- `BuddyAllocator` + 独立多 section 页分配器,支持 `init`、`add_region`、section 查询、`managed_bytes` 与 `allocated_bytes`。 +- `ManagedSection` + 单个 managed section 的只读摘要。 +- `SlabAllocator` + 独立 slab 分配器。 +- `SizeClass` + slab 使用的固定对象尺寸类。 +- `SlabAllocResult` + `Allocated(ptr)` 或 `NeedsSlab { size_class, pages }`。 +- `SlabDeallocResult` + `Done` 或 `FreeSlab { base, pages }`。 +- `SlabPoolTrait` + `GlobalAllocator` 使用的系统级 slab 池接口,暴露 object-safe 的 + `current_slab()` / `owner_slab()` 原语,并提供 `alloc` / `add_slab` / + `dealloc` 默认路由。 +- `SlabPoolExt` + callback 风格辅助接口:`with_current_slab()` 和 `with_owner_slab()`。 +- `eii` + 声明 `slab_pool()` 与 `virt_to_phys()`,供平台侧实现。 + +`managed_bytes` 只统计可分配 heap,不包含 region 前缀 metadata。 +`allocated_bytes` 表示后端页占用,不是用户请求的 `layout.size()` 精确求和。 + +## 测试 + +```bash +# 常规测试 +cargo test + +# 串行执行,便于排查问题 +cargo test -- --test-threads=1 + +# 运行忽略的压力测试 +cargo test --test stress_test -- --ignored --nocapture + +# 检查 benchmark 是否可编译 +cargo check --benches + +# 运行 benchmark +cargo bench +``` + +更多测试说明见 [tests/README.md](tests/README.md)。 + +## 许可证 + +采用 [Apache-2.0](LICENSE) 许可证。 diff --git a/components/buddy-slab-allocator/benches/README_CN.md b/components/buddy-slab-allocator/benches/README_CN.md new file mode 100644 index 0000000000..586e3b1507 --- /dev/null +++ b/components/buddy-slab-allocator/benches/README_CN.md @@ -0,0 +1,45 @@ +# Benchmark 使用说明 + +本目录包含基于 `divan` 的 benchmark 套件,围绕当前 crate 的三层结构组织: + +- `buddy_allocator.rs` + `BuddyAllocator` 的页分配、对齐分配、碎片恢复、随机页 workload +- `slab_allocator.rs` + `SlabAllocator` 的 size class alloc/free、hot reuse、mixed-size batch、steady-state recycle +- `global_allocator.rs` + `GlobalAllocator` 的小对象、大对象、页接口、混合 workload、cross-CPU free cycle + +共享的 host-side harness 放在 `common.rs`,负责: + +- region / metadata 分配 +- buddy / slab / global 初始化 +- 固定随机种子 +- mock EII 接口 + +## 运行方式 + +```bash +# 仅检查 benchmark 是否可编译 +cargo check --benches + +# 运行全部 benchmark +cargo bench + +# 单独运行某个 suite +cargo bench --bench buddy_allocator +cargo bench --bench slab_allocator +cargo bench --bench global_allocator +``` + +## 设计原则 + +- 使用 `divan::Bencher` 和 `divan::black_box` +- 不再保留旧的 `criterion` 代码路径 +- benchmark 只使用当前公开 API,不依赖历史类型名 +- 尽量让每次迭代在闭环内恢复 allocator 状态,减少跨迭代污染 +- workload 使用固定模式或固定随机种子,便于复现 + +## CI 策略 + +CI 仅执行 `cargo check --benches`,确保 benchmark 工程持续可编译。 +真实性能测量和结果对比保留在本地执行。 diff --git a/components/buddy-slab-allocator/benches/buddy_allocator.rs b/components/buddy-slab-allocator/benches/buddy_allocator.rs new file mode 100644 index 0000000000..82c745b9a9 --- /dev/null +++ b/components/buddy-slab-allocator/benches/buddy_allocator.rs @@ -0,0 +1,105 @@ +//! Benchmarks for the buddy page allocator. + +mod common; + +use common::{ + BuddyHarness, FRAGMENTATION_PAGES, HEAP_SIZE, OPERATIONS_PER_BATCH, PAGE_SIZE, seeded_rng, +}; +use divan::{Bencher, black_box}; +use rand::RngExt; + +fn main() { + divan::main(); +} + +#[divan::bench_group] +mod buddy { + use super::*; + + #[divan::bench(args = [1usize, 2, 4, 16, 64])] + fn page_alloc_free(bencher: Bencher, pages: usize) { + let mut harness = BuddyHarness::new(HEAP_SIZE); + + bencher.bench_local(|| { + let addr = harness + .allocator + .alloc_pages(black_box(pages), PAGE_SIZE) + .unwrap(); + harness.allocator.dealloc_pages(addr, pages); + black_box(addr) + }); + } + + #[divan::bench(args = [PAGE_SIZE, PAGE_SIZE * 2, PAGE_SIZE * 4, PAGE_SIZE * 16])] + fn aligned_page_alloc_free(bencher: Bencher, align: usize) { + let mut harness = BuddyHarness::new(HEAP_SIZE); + + bencher.bench_local(|| { + let addr = harness + .allocator + .alloc_pages(black_box(4), black_box(align)) + .unwrap(); + harness.allocator.dealloc_pages(addr, 4); + black_box(addr) + }); + } + + #[divan::bench] + fn fragmentation_recovery_cycle(bencher: Bencher) { + let mut harness = BuddyHarness::new(HEAP_SIZE); + + bencher.bench_local(|| { + let mut addrs = Vec::with_capacity(FRAGMENTATION_PAGES); + for _ in 0..FRAGMENTATION_PAGES { + addrs.push(harness.allocator.alloc_pages(1, PAGE_SIZE).unwrap()); + } + + for idx in (0..addrs.len()).step_by(2) { + harness.allocator.dealloc_pages(addrs[idx], 1); + } + + let large = harness.allocator.alloc_pages(64, PAGE_SIZE).unwrap(); + harness.allocator.dealloc_pages(large, 64); + + for idx in (1..addrs.len()).step_by(2) { + harness.allocator.dealloc_pages(addrs[idx], 1); + } + + black_box(large) + }); + } + + #[divan::bench] + fn random_page_workload(bencher: Bencher) { + let mut harness = BuddyHarness::new(HEAP_SIZE); + let mut rng = seeded_rng(); + let plan: Vec<(bool, usize, usize)> = (0..OPERATIONS_PER_BATCH) + .map(|_| { + let allocate = rng.random_bool(0.65); + let pages = 1usize << rng.random_range(0..=4); + let free_hint = rng.random_range(0..OPERATIONS_PER_BATCH.max(1)); + (allocate, pages, free_hint) + }) + .collect(); + + bencher.bench_local(|| { + let mut active = Vec::new(); + + for &(allocate, pages, free_hint) in &plan { + if allocate || active.is_empty() { + if let Ok(addr) = harness.allocator.alloc_pages(pages, PAGE_SIZE) { + active.push((addr, pages)); + } + } else { + let idx = free_hint % active.len(); + let (addr, count) = active.swap_remove(idx); + harness.allocator.dealloc_pages(addr, count); + } + } + + for (addr, count) in active { + harness.allocator.dealloc_pages(addr, count); + } + }); + } +} diff --git a/components/buddy-slab-allocator/benches/common.rs b/components/buddy-slab-allocator/benches/common.rs new file mode 100644 index 0000000000..c31137a7b1 --- /dev/null +++ b/components/buddy-slab-allocator/benches/common.rs @@ -0,0 +1,238 @@ +#![allow(dead_code)] + +use core::alloc::Layout; +use std::{ + alloc::{alloc, dealloc}, + sync::{ + Mutex, MutexGuard, OnceLock, + atomic::{AtomicUsize, Ordering}, + }, +}; + +use buddy_slab_allocator::{ + __reset_global_allocator_singleton_for_tests, BuddyAllocator, GlobalAllocator, PerCpuSlab, + SlabAllocResult, SlabAllocator, SlabDeallocResult, SlabPoolTrait, + eii::{slab_pool_impl, virt_to_phys_impl}, +}; +use rand::{SeedableRng, rngs::StdRng}; + +pub const PAGE_SIZE: usize = 0x1000; +pub const HEAP_SIZE: usize = 64 * 1024 * 1024; +pub const OPERATIONS_PER_BATCH: usize = 256; +pub const FRAGMENTATION_PAGES: usize = 512; + +const REGION_ALIGN: usize = 64 * 1024; +const BENCH_PAGE_SIZE: usize = 0x1000; +const MAX_BENCH_CPUS: usize = 64; + +pub struct HostRegion { + ptr: *mut u8, + layout: Layout, +} + +impl HostRegion { + pub fn new(size: usize) -> Self { + let layout = Layout::from_size_align(size, REGION_ALIGN).unwrap(); + let ptr = unsafe { alloc(layout) }; + assert!(!ptr.is_null(), "failed to allocate host region"); + Self { ptr, layout } + } + + pub fn addr(&self) -> usize { + self.ptr as usize + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.ptr, self.layout.size()) } + } +} + +impl Drop for HostRegion { + fn drop(&mut self) { + unsafe { dealloc(self.ptr, self.layout) }; + } +} + +pub struct MockOs { + cpu: AtomicUsize, +} + +impl MockOs { + pub const fn new() -> Self { + Self { + cpu: AtomicUsize::new(0), + } + } + + pub fn set_cpu(&self, cpu: usize) { + self.cpu.store(cpu, Ordering::Relaxed); + } +} + +pub struct BenchContext { + _guard: MutexGuard<'static, ()>, +} + +struct BenchSlabPool { + slabs: &'static [PerCpuSlab], +} + +pub static MOCK_OS: MockOs = MockOs::new(); + +fn bench_lock() -> &'static Mutex<()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) +} + +impl Drop for BenchContext { + fn drop(&mut self) { + __reset_global_allocator_singleton_for_tests(); + } +} + +fn bench_cpu_slabs() -> &'static [PerCpuSlab] { + static SLABS: OnceLock]>> = OnceLock::new(); + SLABS.get_or_init(|| { + (0..MAX_BENCH_CPUS) + .map(|cpu| PerCpuSlab::new(cpu as u16)) + .collect::>() + .into_boxed_slice() + }) +} + +fn reset_cpu_slabs(cpu_count: usize) { + assert!( + cpu_count <= MAX_BENCH_CPUS, + "cpu_count exceeds bench slab pool" + ); + for slab in &bench_cpu_slabs()[..cpu_count] { + slab.reset(); + } +} + +impl SlabPoolTrait for BenchSlabPool { + fn current_slab(&self) -> &dyn buddy_slab_allocator::SlabTrait { + &self.slabs[MOCK_OS.cpu.load(Ordering::Relaxed)] + } + + fn owner_slab(&self, cpu_idx: usize) -> &dyn buddy_slab_allocator::SlabTrait { + &self.slabs[cpu_idx] + } +} + +fn bench_slab_pool_ref() -> &'static BenchSlabPool { + static POOL: OnceLock = OnceLock::new(); + POOL.get_or_init(|| BenchSlabPool { + slabs: bench_cpu_slabs(), + }) +} + +#[virt_to_phys_impl] +fn bench_virt_to_phys(vaddr: usize) -> usize { + vaddr +} + +#[slab_pool_impl] +fn bench_slab_pool() -> &'static dyn SlabPoolTrait { + bench_slab_pool_ref() +} + +pub fn seeded_rng() -> StdRng { + StdRng::from_seed([0; 32]) +} + +pub struct BuddyHarness { + _region: HostRegion, + pub allocator: BuddyAllocator, +} + +impl BuddyHarness { + pub fn new(heap_size: usize) -> Self { + let region_size = + heap_size + BuddyAllocator::::required_meta_size(heap_size) + PAGE_SIZE * 4; + let mut region = HostRegion::new(region_size); + let mut allocator = BuddyAllocator::::new(); + unsafe { + allocator.init(region.as_mut_slice()).unwrap(); + } + Self { + _region: region, + allocator, + } + } +} + +pub struct SlabHarness { + _region: HostRegion, + buddy: BuddyAllocator, + slab: SlabAllocator, +} + +impl SlabHarness { + pub fn new(heap_size: usize) -> Self { + let region_size = + heap_size + BuddyAllocator::::required_meta_size(heap_size) + PAGE_SIZE * 4; + let mut region = HostRegion::new(region_size); + let mut buddy = BuddyAllocator::::new(); + unsafe { + buddy.init(region.as_mut_slice()).unwrap(); + } + Self { + _region: region, + buddy, + slab: SlabAllocator::new(), + } + } + + pub fn alloc(&mut self, layout: Layout) -> core::ptr::NonNull { + loop { + match self.slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(ptr) => return ptr, + SlabAllocResult::NeedsSlab { size_class, pages } => { + let slab_bytes = pages * PAGE_SIZE; + let base = self.buddy.alloc_pages(pages, slab_bytes).unwrap(); + self.slab.add_slab(size_class, base, slab_bytes, 0); + } + } + } + } + + pub fn dealloc(&mut self, ptr: core::ptr::NonNull, layout: Layout) { + match self.slab.dealloc(ptr, layout) { + SlabDeallocResult::Done => {} + SlabDeallocResult::FreeSlab { base, pages } => { + self.buddy.dealloc_pages(base, pages); + } + } + } +} + +pub struct GlobalHarness { + _region: HostRegion, + _ctx: BenchContext, + pub allocator: GlobalAllocator, +} + +impl GlobalHarness { + pub fn new(region_size: usize, cpu_count: usize) -> Self { + assert_eq!( + PAGE_SIZE, BENCH_PAGE_SIZE, + "bench EII slab pool only supports PAGE_SIZE={BENCH_PAGE_SIZE:#x}" + ); + let mut region = HostRegion::new(region_size); + let allocator = GlobalAllocator::::new(); + let guard = bench_lock() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + MOCK_OS.set_cpu(0); + reset_cpu_slabs(cpu_count); + unsafe { + allocator.init(region.as_mut_slice()).unwrap(); + } + Self { + _region: region, + _ctx: BenchContext { _guard: guard }, + allocator, + } + } +} diff --git a/components/buddy-slab-allocator/benches/global_allocator.rs b/components/buddy-slab-allocator/benches/global_allocator.rs new file mode 100644 index 0000000000..f61a3112ec --- /dev/null +++ b/components/buddy-slab-allocator/benches/global_allocator.rs @@ -0,0 +1,112 @@ +//! Benchmarks for the unified global allocator. + +mod common; + +use core::alloc::Layout; + +use common::{GlobalHarness, HEAP_SIZE, MOCK_OS, OPERATIONS_PER_BATCH, PAGE_SIZE}; +use divan::{Bencher, black_box}; + +fn main() { + divan::main(); +} + +#[divan::bench_group] +mod global { + use super::*; + + #[divan::bench(args = [8usize, 64, 512, 2048])] + fn small_alloc_free(bencher: Bencher, size: usize) { + let harness = GlobalHarness::new(HEAP_SIZE, 2); + let layout = Layout::from_size_align(size, 8).unwrap(); + + bencher.bench_local(|| { + MOCK_OS.set_cpu(0); + let ptr = harness.allocator.alloc(black_box(layout)).unwrap(); + unsafe { harness.allocator.dealloc(ptr, layout) }; + black_box(ptr) + }); + } + + #[divan::bench(args = [PAGE_SIZE, PAGE_SIZE * 4, PAGE_SIZE * 16])] + fn large_alloc_free(bencher: Bencher, size: usize) { + let harness = GlobalHarness::new(HEAP_SIZE, 2); + let layout = Layout::from_size_align(size, PAGE_SIZE).unwrap(); + + bencher.bench_local(|| { + MOCK_OS.set_cpu(0); + let ptr = harness.allocator.alloc(black_box(layout)).unwrap(); + unsafe { harness.allocator.dealloc(ptr, layout) }; + black_box(ptr) + }); + } + + #[divan::bench(args = [1usize, 4, 16, 64])] + fn page_interface(bencher: Bencher, pages: usize) { + let harness = GlobalHarness::new(HEAP_SIZE, 2); + + bencher.bench_local(|| { + MOCK_OS.set_cpu(0); + let addr = harness + .allocator + .alloc_pages(black_box(pages), PAGE_SIZE) + .unwrap(); + harness.allocator.dealloc_pages(addr, pages); + black_box(addr) + }); + } + + #[divan::bench] + fn mixed_object_page_workload(bencher: Bencher) { + let harness = GlobalHarness::new(HEAP_SIZE, 2); + let object_layouts = [ + Layout::from_size_align(64, 8).unwrap(), + Layout::from_size_align(256, 8).unwrap(), + Layout::from_size_align(1024, 8).unwrap(), + Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap(), + ]; + let page_counts = [1usize, 2, 4, 8]; + + bencher.bench_local(|| { + for idx in 0..OPERATIONS_PER_BATCH { + MOCK_OS.set_cpu(idx % 2); + let layout = object_layouts[idx % object_layouts.len()]; + let ptr = harness.allocator.alloc(layout).unwrap(); + unsafe { harness.allocator.dealloc(ptr, layout) }; + + let pages = page_counts[idx % page_counts.len()]; + let addr = harness.allocator.alloc_pages(pages, PAGE_SIZE).unwrap(); + harness.allocator.dealloc_pages(addr, pages); + + black_box((ptr, addr)); + } + }); + } + + #[divan::bench] + fn remote_free_cycle(bencher: Bencher) { + let harness = GlobalHarness::new(HEAP_SIZE, 2); + let layout = Layout::from_size_align(64, 8).unwrap(); + + bencher.bench_local(|| { + let mut ptrs = Vec::with_capacity(64); + + MOCK_OS.set_cpu(0); + for _ in 0..64 { + ptrs.push(harness.allocator.alloc(layout).unwrap()); + } + + MOCK_OS.set_cpu(1); + for &ptr in &ptrs { + unsafe { harness.allocator.dealloc(ptr, layout) }; + } + + MOCK_OS.set_cpu(0); + for _ in 0..64 { + let ptr = harness.allocator.alloc(layout).unwrap(); + unsafe { harness.allocator.dealloc(ptr, layout) }; + black_box(ptr); + } + }); + } +} diff --git a/components/buddy-slab-allocator/benches/slab_allocator.rs b/components/buddy-slab-allocator/benches/slab_allocator.rs new file mode 100644 index 0000000000..a7b73305b0 --- /dev/null +++ b/components/buddy-slab-allocator/benches/slab_allocator.rs @@ -0,0 +1,102 @@ +//! Benchmarks for the slab allocator with buddy-backed slab refill. + +mod common; + +use core::alloc::Layout; + +use common::{HEAP_SIZE, OPERATIONS_PER_BATCH, SlabHarness}; +use divan::{Bencher, black_box}; + +fn main() { + divan::main(); +} + +#[divan::bench_group] +mod slab { + use super::*; + + #[divan::bench(args = [8usize, 64, 256, 512, 1024, 2048])] + fn size_class_alloc_free(bencher: Bencher, size: usize) { + let mut harness = SlabHarness::new(HEAP_SIZE); + let layout = Layout::from_size_align(size, size).unwrap(); + + bencher.bench_local(|| { + let ptr = harness.alloc(black_box(layout)); + harness.dealloc(ptr, layout); + black_box(ptr) + }); + } + + #[divan::bench] + fn hot_reuse(bencher: Bencher) { + let mut harness = SlabHarness::new(HEAP_SIZE); + let layout = Layout::from_size_align(128, 128).unwrap(); + let ptr = harness.alloc(layout); + harness.dealloc(ptr, layout); + + bencher.bench_local(|| { + let ptr = harness.alloc(layout); + harness.dealloc(ptr, layout); + black_box(ptr) + }); + } + + #[divan::bench] + fn mixed_size_batch(bencher: Bencher) { + let mut harness = SlabHarness::new(HEAP_SIZE); + let layouts = [ + Layout::from_size_align(8, 8).unwrap(), + Layout::from_size_align(64, 64).unwrap(), + Layout::from_size_align(256, 256).unwrap(), + Layout::from_size_align(512, 512).unwrap(), + Layout::from_size_align(1024, 1024).unwrap(), + Layout::from_size_align(2048, 2048).unwrap(), + ]; + + bencher.bench_local(|| { + for layout in layouts { + let ptr = harness.alloc(layout); + harness.dealloc(ptr, layout); + black_box(ptr); + } + }); + } + + #[divan::bench] + fn steady_state_recycle(bencher: Bencher) { + let mut harness = SlabHarness::new(HEAP_SIZE); + let layout = Layout::from_size_align(64, 64).unwrap(); + let mut active = Vec::with_capacity(256); + for _ in 0..256 { + active.push(harness.alloc(layout)); + } + + bencher.bench_local(|| { + let ptr = active.pop().unwrap(); + harness.dealloc(ptr, layout); + let new_ptr = harness.alloc(layout); + active.push(new_ptr); + black_box(new_ptr) + }); + } + + #[divan::bench] + fn mixed_size_workload(bencher: Bencher) { + let mut harness = SlabHarness::new(HEAP_SIZE); + let layouts = [ + Layout::from_size_align(8, 8).unwrap(), + Layout::from_size_align(64, 64).unwrap(), + Layout::from_size_align(256, 256).unwrap(), + Layout::from_size_align(1024, 1024).unwrap(), + ]; + + bencher.bench_local(|| { + for idx in 0..OPERATIONS_PER_BATCH { + let layout = layouts[idx % layouts.len()]; + let ptr = harness.alloc(layout); + harness.dealloc(ptr, layout); + black_box(ptr); + } + }); + } +} diff --git a/components/buddy-slab-allocator/docs/design.md b/components/buddy-slab-allocator/docs/design.md new file mode 100644 index 0000000000..6a5fd01de9 --- /dev/null +++ b/components/buddy-slab-allocator/docs/design.md @@ -0,0 +1,611 @@ +# buddy-slab-allocator 设计文档 + +本文档面向阅读和维护当前实现的开发者,描述这个分配器在 `buddy + slab` 两级结构下的核心数据结构、初始化过程、`add_region` 热添加流程、`alloc` / `dealloc` 路径,以及多核并发场景下的协作方式。 + +文中描述以当前仓库实现为准,重点对应以下模块: + +- `src/global.rs` +- `src/buddy/mod.rs` +- `src/slab/mod.rs` +- `src/slab/cache.rs` +- `src/slab/page.rs` + +## 1. 设计目标 + +这个分配器服务于 `no_std`、内核或嵌入式环境,目标是同时满足两类需求: + +- 页级分配:按页返回连续内存,支持 power-of-two 分裂和合并。 +- 小对象分配:对 `<= 2048` 字节的对象提供更低碎片、更低分配开销的快速路径。 + +因此整体采用两级结构: + +- 后端:`BuddyAllocator` + 负责管理一个或多个 section 中的页。 +- 前端:`SlabAllocator` + 负责管理不同 size class 的小对象。 +- 门面:`GlobalAllocator` + 统一对外暴露 `alloc` / `dealloc` / `alloc_pages` / `dealloc_pages` 等接口。 + +## 2. 总体结构 + +```mermaid +flowchart TD + GA["GlobalAllocator"] --> B["BuddyAllocator"] + GA --> SP["SlabPoolTrait"] + GA --> EI["EII hooks"] + EI --> SP + EI --> V2P["virt_to_phys()"] + + SP --> SC8["SlabCache: 8B"] + SP --> SC64["SlabCache: 64B"] + SP --> SC2K["SlabCache: 2048B"] + + SC8 --> SPH["SlabPageHeader"] + SC64 --> SPH + SC2K --> SPH + + B --> SH["BuddySection intrusive list"] + SH --> PM["PageMeta[]"] + SH --> FL["free_lists by order"] +``` + +### 2.1 地址语义 + +当前实现的主语义是“虚拟地址”: + +- `GlobalAllocator::init()` 接收的是一段可写 `&mut [u8]` 区域。 +- `BuddyAllocator` 管理的是一段连续虚拟地址区间。 +- `alloc_pages()`、`alloc()` 返回的也都是虚拟地址。 + +物理地址只在 `alloc_pages_lowmem()` 里参与判断: + +- 候选块的虚拟地址通过 `eii::virt_to_phys()` 翻译成物理地址。 +- 只有物理地址低于 `4 GiB` 的块才会被当作 DMA32 候选。 + +### 2.2 容量统计语义 + +当前公开了两类整体容量统计: + +- `managed_bytes` + 所有 section 中可分配 heap 的总字节数,不包含 region 前缀 metadata。 +- `allocated_bytes` + 后端页占用字节数,按 `managed_bytes - free_pages * PAGE_SIZE` 计算。 + +因此 `allocated_bytes` 的口径是“页后端已经占住多少字节”,不是用户请求的 `layout.size()` 精确累加。 +它会包含: + +- slab 页 +- empty slab cache 保留页 +- 对齐放大 +- buddy / slab 的内部碎片 + +## 3. 初始化、追加 Region 与内存布局 + +当前实现支持两种 region 进入 allocator 的方式: + +- `GlobalAllocator::init(region)` + 注册首个 region;slab 池由外部通过 `eii::slab_pool()` 提供。 +- `GlobalAllocator::add_region(region)` + 在运行时追加新的 region,只扩展 buddy 后端。 + +对 buddy 而言,每个 region 都会被拆成: + +- 前缀:region 自带元数据 +- 后缀:该 section 的 managed heap + +首个 global region 的元数据前缀包含: + +- `BuddySection` +- `PageMeta[]` + +后续追加 region 的元数据前缀只包含: + +- `BuddySection` +- `PageMeta[]` + +```mermaid +flowchart LR + A["region start"] --> B["aligned section_start"] + B --> C["BuddySection"] + C --> D["PageMeta array"] + D --> H["padding to PAGE_SIZE / granule"] + H --> I["section heap start"] + I --> J["managed heap of this section"] +``` + +### 3.1 初始化步骤 + +`GlobalAllocator::init()` 的主要步骤如下: + +1. 根据 `region.len()` 计算首个 section 最多能管理多少页。 +2. 在 region 前缀预留 `BuddySection + PageMeta[]`。 +3. 选择一个页对齐的 section heap 起点。 +4. 将首个 section 注册进 `BuddyAllocator`。 +5. 将 allocator 标记为已初始化。 + +当前实现按“系统里只有唯一一套全局 allocator”建模;slab 池通过 +`eii::slab_pool()` 接入,而不是存放在 `GlobalAllocator` 内部。 + +`GlobalAllocator::add_region()` 的步骤更简单: + +1. 校验 allocator 已初始化。 +2. 在新 region 前缀预留 `BuddySection + PageMeta[]`。 +3. 计算该 section 的可管理 heap。 +4. 将该 section 追加到 buddy 的 intrusive list 尾部。 + +### 3.2 为什么 metadata 放在 region 前缀 + +这样做有几个好处: + +- 不需要额外的启动期堆分配。 +- `BuddySection` 与 `PageMeta[]` 都跟随 region 自身生命周期。 +- 不需要预先声明最大 section 数量。 + +代价是: + +- metadata 会消耗一部分可分配空间。 +- 每个 section 的 heap 起点都可能晚于各自的 region 起点。 +- section 查询与地址归属定位当前采用线性扫描。 + +## 4. BuddyAllocator 设计 + +`BuddyAllocator` 管理页级内存,是整个系统的共享后端。它的基本单位不再是单一 heap,而是一个按注册顺序串起来的 section 链表。 + +### 4.1 核心数据结构 + +```mermaid +classDiagram + class BuddyAllocator { + +sections_head_ptr + +section_count + +os_hook + } + + class BuddySection { + +next_ptr + +meta_ptr + +heap_start + +heap_size + +free_lists_by_order + +free_pages + +total_pages + } + + class PageFlags { + <> + Free + Allocated + Slab + } +``` + +说明: + +- `BuddyAllocator` 自身只维护 section 链表头和 section 数量。 +- 每个 `BuddySection` 都拥有自己的一组 `PageMeta[]` 和 `free_lists`。 +- `PageMeta` 仍然是每页一项的外部元数据。 +- 同一 section 内按 buddy 规则拆分和合并,section 之间不跨界合并。 + +### 4.2 buddy 初始化 + +初始化某个 section 时,buddy 会从低地址到高地址扫描该 section 的整段 heap: + +- 每次尽量切出“当前地址自然对齐、且仍然放得下”的最大 order 块。 +- 将该块挂到对应 order 的 free list。 + +这一步的结果是: + +- 该 section 中的所有页都被覆盖。 +- 该 section 的 free list 从一开始就处于可分裂、可合并的标准 buddy 形态。 + +### 4.3 页分配流程 + +```mermaid +flowchart TD + A["alloc_pages(count, align)"] --> B["计算 order"] + B --> C["计算 align_order"] + C --> D["effective_order = max(order, align_order)"] + D --> E["按注册顺序扫描 section"] + E --> F["在 section 内从 effective_order 向上找非空 free list"] + F --> G{找到块?} + G -- 否 --> H["尝试下一个 section"] + H --> I{还有 section?} + I -- 否 --> J["返回 NoMemory"] + I -- 是 --> F + G -- 是 --> K["弹出大块"] + K --> L["逐级 split 到目标 order"] + L --> M["标记头页为 Allocated"] + M --> N["返回虚拟地址"] +``` + +注意点: + +- `count` 会向上取整到 2 的幂。 +- `align` 也会转成对应的 `align_order`。 +- 真正分配的块大小可能大于用户请求。 + +### 4.4 低地址页分配 + +`alloc_pages_lowmem()` 的差异在于: + +- 不只看 free list 是否有块。 +- 还要检查块的物理地址区间是否完全落在 `DMA32_LIMIT` 下方。 + +流程是: + +1. 逐个扫描候选 free list。 +2. 对每个候选块调用 `os.virt_to_phys(addr)`。 +3. 计算 `phys + block_bytes <= 4GiB` 是否成立。 +4. 找到合格块后执行和普通分配相同的拆分逻辑。 + +### 4.5 页释放流程 + +`dealloc_pages(addr, count)` 的关键点是: + +- `addr` 必须是原始返回地址。 +- 实际释放的 order 以 `PageMeta.order` 为准。 +- `count` 主要用于 debug 断言,确保调用者没有传得比真实块更大。 + +```mermaid +flowchart TD + A["dealloc_pages(addr, count)"] --> B["根据 addr 算出 pfn"] + B --> C["读取 PageMeta.order"] + C --> D["与 buddy pfn 检查是否可合并"] + D --> E{buddy 同 order 且空闲?} + E -- 是 --> F["从 free list 移除 buddy"] + F --> G["合并并提升 order"] + G --> D + E -- 否 --> H["将最终块挂回 free list"] +``` + +## 5. SlabAllocator 设计 + +`SlabAllocator` 不直接申请页,它只管理“已经拿到的 slab page”。 + +### 5.1 size class + +当前固定 9 个 size class: + +- 8 +- 16 +- 32 +- 64 +- 128 +- 256 +- 512 +- 1024 +- 2048 + +布局规则: + +- `layout.size()` 与 `layout.align()` 取较大值。 +- 选择能容纳它的最小 size class。 +- 超过 `2048` 字节则不走 slab。 + +### 5.2 SlabCache 的三条链 + +每个 size class 对应一个 `SlabCache`,内部维护三条 intrusive list: + +- `partial` + 还有空位的 slab,优先分配。 +- `full` + 本地 bitmap 没空位,但可能积累了 remote free。 +- `empty` + 所有对象都空闲的 slab。 + +实现上还有限制: + +- 每个 `SlabCache` 最多缓存 1 个空 slab。 +- 如果又出现新的空 slab,多出来的那个会还给 buddy。 + +这也是压力测试里“页数不一定精确回到初始值”的原因之一:每个 CPU、每个 size class 允许保留一个空 slab 作为缓存。 + +### 5.3 SlabPageHeader + +每个 slab page 起始位置都嵌着一个 `SlabPageHeader`。 + +它包含: + +- `magic` +- `size_class` +- `object_count` +- `local_free_count` +- `owner_cpu` +- `slab_bytes` +- `list_prev/list_next` +- `local_bitmap` +- `remote_free_head` +- `remote_free_count` + +### 5.4 本地分配 + +owner CPU 在持有对应 slab 锁时执行本地分配: + +1. 先看 `partial` 头 slab。 +2. 如果它有 remote free,先 drain 回本地 bitmap。 +3. 从 bitmap 找一个空位。 +4. 如果分完后满了,把 slab 从 `partial` 挪到 `full`。 + +如果 `partial` 不行,则: + +1. 扫 `full`,找是否有 slab 因 remote free 重新出现空位。 +2. 如果有,把它移回 `partial`。 +3. 还不行就尝试复用 `empty` 中缓存的 slab。 +4. 再不行就返回 `NeedsSlab`,要求上层先提供新页。 + +## 6. GlobalAllocator 的分配路径 + +`GlobalAllocator` 是真正对外使用的入口。 + +它把请求分成两类: + +- 小对象:走 slab +- 大对象:走 buddy + +判断条件: + +- `layout.size() <= 2048` +- `layout.align() <= 2048` + +满足才走 slab。 + +### 6.1 alloc 总流程 + +```mermaid +flowchart TD + A["alloc(layout)"] --> B{"已初始化?"} + B -- 否 --> X["NotInitialized"] + B -- 是 --> C{slab eligible?} + C -- 是 --> D["slab_alloc"] + C -- 否 --> E["large_alloc"] +``` + +### 6.2 小对象分配 + +```mermaid +sequenceDiagram + participant Caller + participant GA as GlobalAllocator + participant P as SlabPoolTrait + participant B as BuddyAllocator + + Caller->>GA: alloc(layout) + GA->>P: alloc(layout) + alt Slab 命中 + P-->>GA: Allocated(ptr) + GA-->>Caller: ptr + else 需要新 slab + P-->>GA: NeedsSlab(size_class, pages) + GA->>B: alloc_pages(pages, slab_bytes) + GA->>B: set_page_flags(addr, Slab) + GA->>P: add_slab(...) + GA->>P: alloc(layout) + P-->>GA: Allocated(ptr) + GA-->>Caller: ptr + end +``` + +这里有一个很重要的锁顺序细节: + +- 当 slab 缺页时,`GlobalAllocator` 会先释放 slab 锁,再去拿 buddy 锁。 + +这样可以避免: + +- 持有 slab 锁时阻塞在 buddy 上。 +- 未来演化成更复杂锁图时出现循环等待。 + +### 6.3 大对象分配 + +大对象路径很直接: + +1. 根据 `layout.size()` 计算页数。 +2. 对齐至少是 `PAGE_SIZE`。 +3. 调用 `BuddyAllocator::alloc_pages()`。 + +大对象不会进入 slab,也不会设置 `PageFlags::Slab`。 + +## 7. dealloc 路径 + +### 7.1 大对象释放 + +大对象释放和分配对称: + +1. 根据 `layout.size()` 推导页数。 +2. 直接进入 `BuddyAllocator::dealloc_pages()`。 + +### 7.2 小对象释放:本地路径 + +如果当前 CPU 就是该 slab 的 owner: + +1. 通过对象地址反推出 slab base。 +2. 拿 owner CPU 对应的 slab 锁。 +3. 调用 `slab.dealloc(ptr, layout)`。 +4. 如果结果是 `Done`,结束。 +5. 如果结果是 `FreeSlab { base, pages }`,释放 slab 锁后把整页还给 buddy。 + +为什么空 slab 不总是立刻还给 buddy: + +- `SlabCache` 会保留一个 empty slab 作为热缓存。 +- 只有当已有一个 cached empty slab 时,新的空 slab 才会还给 buddy。 + +### 7.3 小对象释放:远程路径 + +如果当前 CPU 不是 owner CPU,不会直接拿对方的 slab 锁。 + +而是走 lock-free remote free: + +```mermaid +sequenceDiagram + participant CPU1 as current CPU + participant H as SlabPageHeader + participant CPU0 as owner CPU + participant S as owner SlabCache + + CPU1->>H: remote_free(obj_addr) + Note right of H: CAS 推入 remote_free_head\n并增加 remote_free_count + CPU1-->>CPU1: 返回,不加锁 + + CPU0->>S: 下次 alloc/dealloc 时持锁进入 + S->>H: drain_remote_frees() + Note right of H: 将 remote 栈中的对象\n重新记回 local bitmap + S-->>CPU0: 对象重新可分配 +``` + +这条路径的核心特点: + +- 释放方不需要知道 owner CPU 的锁状态。 +- 释放方不抢远端锁。 +- owner CPU 在后续本地操作时,顺手把 remote free 合并回 bitmap。 + +这也是当前实现中“跨 CPU free 无锁,但重新利用对象仍由 owner CPU 控制”的关键设计。 + +## 8. 多线程与并发模型 + +### 8.1 并发边界 + +当前实现中真正共享的状态主要有两类: + +- `BuddyAllocator` + 整体包在一个 `SpinMutex` 里,页级操作串行化。 +- 每 CPU 一个 `SlabAllocator` + 每个都各自包在一个 `SpinMutex` 里。 + +因此并发模型可以概括为: + +- 小对象本地路径:只竞争本 CPU 的 slab 锁。 +- 小对象远程 free:不走锁,直接原子 CAS。 +- 缺页或空 slab 回收:会短暂进入全局 buddy 锁。 + +### 8.2 为什么是 per-CPU slab + +如果所有小对象都走单个全局 slab 锁,会有两个问题: + +- 多核下锁竞争重。 +- 小对象热点路径和页级路径容易互相干扰。 + +per-CPU slab 的好处是: + +- 本地分配/释放命中时只碰本 CPU 锁。 +- 跨 CPU free 通过 remote list 延后处理,不把释放方拖进远端临界区。 + +### 8.3 远程释放的正确性依赖 + +远程释放正确性依赖几个事实: + +- slab 页头里记录了 `owner_cpu`。 +- 远程释放只把对象地址压到无锁链表里。 +- owner CPU 持锁 drain 时,再把对象重新放回本地 bitmap。 +- remote 栈中的 next 指针写在对象自身内存开头,这要求被释放对象已经不再被用户使用。 + +### 8.4 锁顺序 + +当前实现中建议理解为以下顺序: + +- 常规 slab 本地路径:只拿 slab 锁。 +- 常规 buddy 页路径:只拿 buddy 锁。 +- slab 缺页:先释放 slab 锁,再拿 buddy 锁,再重新拿 slab 锁。 +- slab 回收空页:先释放 slab 锁,再拿 buddy 锁。 + +这样可以避免出现“持有 slab 锁后等待 buddy,同时另一路持有 buddy 锁又等待 slab”的循环。 + +## 9. 关键实现细节 + +### 9.1 `PageFlags` + +页状态分三种: + +- `Free` +- `Allocated` +- `Slab` + +含义是: + +- `Free`:在 buddy free list 中。 +- `Allocated`:普通页分配返回的块头页。 +- `Slab`:该块当前作为 slab 页使用。 + +### 9.2 buddy 只在块头记录 order + +`PageMeta.order` 只有块头页有意义。 + +因此: + +- 分配时只标记头页的 `order`。 +- 释放时调用方必须传回原始返回地址。 + +### 9.3 `dealloc_pages(count)` 为什么不完全信任 `count` + +因为分配时请求可能被扩大: + +- `count` 会按 buddy order 向上取整。 +- `align` 也可能把块提升到更高阶。 + +所以释放时真正信任的是 `PageMeta.order`,`count` 只用于做 debug 级别的合理性检查。 + +### 9.4 empty slab 缓存策略 + +每个 `SlabCache` 最多保留一个 empty slab。 + +这是一个很重要的折中: + +- 优点:后续同 size class 再分配时更快,减少 buddy 往返。 +- 代价:空闲页不会总是立刻全部回收到 buddy。 + +所以测试或观测时不能简单假设: + +- “所有对象释放后,buddy free pages 一定精确回到初始值”。 + +更准确的理解应是: + +- 除去每 CPU、每 size class 允许缓存的 empty slab 后,应当没有额外页泄漏。 + +## 10. realloc 行为 + +`GlobalAllocator` 通过 `GlobalAlloc` 实现了 `realloc`: + +1. 构造新的 `Layout` +2. 新分配一块内存 +3. 拷贝 `min(old_size, new_size)` 字节 +4. 释放旧块 + +它不是原地扩容,也没有针对 slab/buddy 做特殊优化。 + +## 11. 当前实现的优点与限制 + +### 11.1 优点 + +- 结构清晰,页级与对象级职责分离。 +- 小对象本地路径开销低。 +- 跨 CPU free 不抢远端锁。 +- 初始化不依赖额外堆分配。 +- 支持低地址页分配。 + +### 11.2 限制 + +- buddy 后端仍然是全局单锁。 +- `remote_free` 的对象复用必须等 owner CPU 后续 drain。 +- empty slab 缓存会牺牲一部分“空闲页立刻回收”的极致性。 +- `realloc` 为通用 copy 语义,不做原地优化。 + +## 12. 阅读源码建议 + +如果想快速建立整体理解,建议按下面顺序读: + +1. `src/lib.rs` + 先看公开模块、EII、`SlabPoolTrait` 与 `SlabPoolExt`。 +2. `src/global.rs` + 先理解门面层如何在 buddy/slab 间路由。 +3. `src/buddy/mod.rs` + 看页级分配、拆分和合并。 +4. `src/slab/mod.rs` + 看 slab 与上层的接口契约。 +5. `src/slab/cache.rs` + 看 `partial/full/empty` 三链切换。 +6. `src/slab/page.rs` + 看 bitmap、remote free 栈与 owner CPU drain。 + +## 13. 一句话总结 + +这个分配器的核心思想可以概括成一句话: + +> 用 buddy 统一管理页,用 per-CPU slab 加速小对象,把跨 CPU free 变成“无锁登记、延迟回收”,从而在实现复杂度、局部性和并发开销之间取得平衡。 diff --git a/components/buddy-slab-allocator/rust-toolchain.toml b/components/buddy-slab-allocator/rust-toolchain.toml new file mode 100644 index 0000000000..81aeeb0cc4 --- /dev/null +++ b/components/buddy-slab-allocator/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2026-04-01" +components = ["rust-src", "rustfmt", "clippy"] diff --git a/components/buddy-slab-allocator/src/buddy/mod.rs b/components/buddy-slab-allocator/src/buddy/mod.rs new file mode 100644 index 0000000000..8dedf40db6 --- /dev/null +++ b/components/buddy-slab-allocator/src/buddy/mod.rs @@ -0,0 +1,831 @@ +//! Buddy page allocator — page-metadata-based with intrusive free lists. +//! +//! The allocator manages one or more contiguous virtual address ranges ("sections"). +//! Each section stores its own [`BuddySection`] descriptor and [`PageMeta`] array +//! in the caller-provided region prefix, enabling O(1) free-list operations +//! without any dynamic allocation. + +pub mod page_meta; + +use core::ptr; + +pub use page_meta::{PFN_NONE, PageFlags, PageMeta}; +use page_meta::{free_list_push, free_list_remove}; + +use crate::{ + align_up, eii, + error::{AllocError, AllocResult}, + is_aligned, +}; + +/// Maximum buddy order. With 4 KiB pages this gives 2^20 × 4 KiB = 4 GiB blocks. +pub const MAX_ORDER: usize = 20; + +/// DMA32 zone upper bound (4 GiB physical). +const DMA32_LIMIT: usize = 0x1_0000_0000; + +fn normalize_region( + region_start: usize, + region_size: usize, + granule: usize, +) -> Option<(usize, usize)> { + if region_size == 0 || !granule.is_power_of_two() { + return None; + } + let region_end = region_start.checked_add(region_size)?; + let usable_start = align_up(region_start, granule); + let usable_end = region_end & !(granule - 1); + if usable_end <= usable_start { + return None; + } + Some((usable_start, usable_end - usable_start)) +} + +pub(crate) struct RegionLayout { + pub(crate) section_start: usize, + pub(crate) meta_start: usize, + pub(crate) managed_heap_start: usize, + pub(crate) managed_heap_size: usize, +} + +pub(crate) struct SectionInitSpec { + pub(crate) region_start: usize, + pub(crate) region_size: usize, + pub(crate) section_ptr: *mut BuddySection, + pub(crate) meta_ptr: *mut u8, + pub(crate) meta_size: usize, + pub(crate) heap_start: usize, + pub(crate) heap_size: usize, +} + +/// Public read-only summary of a managed section. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ManagedSection { + pub start: usize, + pub size: usize, + pub free_pages: usize, + pub total_pages: usize, +} + +/// Per-region buddy state stored in the region prefix. +#[repr(C)] +pub(crate) struct BuddySection { + pub(crate) next: *mut BuddySection, + pub(crate) region_start: usize, + pub(crate) region_size: usize, + pub(crate) meta: *mut PageMeta, + pub(crate) max_pages: usize, + pub(crate) heap_start: usize, + pub(crate) heap_size: usize, + pub(crate) free_lists: [u32; MAX_ORDER + 1], + pub(crate) free_pages: usize, + pub(crate) total_pages: usize, +} + +impl BuddySection { + const fn metadata_align() -> usize { + let section_align = core::mem::align_of::(); + let meta_align = core::mem::align_of::(); + if section_align > meta_align { + section_align + } else { + meta_align + } + } + + fn metadata_layout_for_pages(pages: usize) -> Option<(usize, usize)> { + let meta_offset = align_up( + core::mem::size_of::(), + core::mem::align_of::(), + ); + let page_meta_size = pages.checked_mul(core::mem::size_of::())?; + let meta_size = meta_offset.checked_add(page_meta_size)?; + Some((meta_offset, meta_size)) + } + + fn available_heap_pages( + region_end: usize, + section_start: usize, + meta_size: usize, + heap_align: usize, + ) -> Option { + let managed_heap_start = align_up(section_start.checked_add(meta_size)?, heap_align); + if managed_heap_start > region_end { + return Some(0); + } + Some((region_end - managed_heap_start) / PAGE_SIZE) + } + + fn can_manage_pages( + region_end: usize, + section_start: usize, + pages: usize, + heap_align: usize, + ) -> bool { + let Some((_, meta_size)) = Self::metadata_layout_for_pages(pages) else { + return false; + }; + let Some(available_pages) = Self::available_heap_pages::( + region_end, + section_start, + meta_size, + heap_align, + ) else { + return false; + }; + available_pages >= pages + } + + pub(crate) fn compute_region_layout_with_heap_align( + region_start: usize, + region_size: usize, + heap_align: usize, + ) -> Option { + if region_size == 0 || !PAGE_SIZE.is_power_of_two() || !heap_align.is_power_of_two() { + return None; + } + + let region_end = region_start.checked_add(region_size)?; + let section_start = align_up(region_start, Self::metadata_align()); + if section_start >= region_end { + return None; + } + + let heap_search_start = align_up( + section_start.checked_add(core::mem::size_of::())?, + PAGE_SIZE, + ); + let max_pages = if heap_search_start >= region_end { + 0 + } else { + (region_end - heap_search_start) / PAGE_SIZE + }; + + let mut low = 0usize; + let mut high = max_pages; + while low < high { + let mid = low + (high - low).div_ceil(2); + if Self::can_manage_pages::(region_end, section_start, mid, heap_align) { + low = mid; + } else { + high = mid - 1; + } + } + + if low == 0 { + return None; + } + + let (meta_offset, meta_size) = Self::metadata_layout_for_pages(low)?; + let meta_start = section_start.checked_add(meta_offset)?; + let managed_heap_start = align_up(section_start.checked_add(meta_size)?, heap_align); + let managed_heap_size = low.checked_mul(PAGE_SIZE)?; + + Some(RegionLayout { + section_start, + meta_start, + managed_heap_start, + managed_heap_size, + }) + } + + fn compute_region_layout( + region_start: usize, + region_size: usize, + ) -> Option { + Self::compute_region_layout_with_heap_align::( + region_start, + region_size, + PAGE_SIZE, + ) + } + + unsafe fn init_at( + section_ptr: *mut BuddySection, + region_start: usize, + region_size: usize, + meta_ptr: *mut u8, + meta_size: usize, + heap_start: usize, + heap_size: usize, + ) -> AllocResult { + unsafe { + if !PAGE_SIZE.is_power_of_two() { + return Err(AllocError::InvalidParam); + } + if !is_aligned(heap_start, PAGE_SIZE) || heap_size == 0 { + return Err(AllocError::InvalidParam); + } + + let total_pages = heap_size / PAGE_SIZE; + let required = BuddyAllocator::::required_meta_size(heap_size); + if meta_size < required { + return Err(AllocError::InvalidParam); + } + + let meta = meta_ptr as *mut PageMeta; + for i in 0..total_pages { + meta.add(i).write(PageMeta::new()); + } + + section_ptr.write(BuddySection { + next: ptr::null_mut(), + region_start, + region_size, + meta, + max_pages: total_pages, + heap_start, + heap_size, + free_lists: [PFN_NONE; MAX_ORDER + 1], + free_pages: 0, + total_pages, + }); + + let section = &mut *section_ptr; + let mut pfn: usize = 0; + while pfn < total_pages { + let mut order = MAX_ORDER; + loop { + let block_pages = 1usize << order; + if block_pages <= total_pages - pfn && (pfn & (block_pages - 1)) == 0 { + break; + } + if order == 0 { + break; + } + order -= 1; + } + let block_pages = 1usize << order; + let m = &mut *section.meta.add(pfn); + m.flags = PageFlags::Free; + m.order = order as u8; + free_list_push(section.meta, &mut section.free_lists, pfn as u32, order); + section.free_pages += block_pages; + pfn += block_pages; + } + + Ok(()) + } + } + + #[inline] + fn contains_heap_addr(&self, addr: usize) -> bool { + addr >= self.heap_start && addr < self.heap_start + self.heap_size + } + + #[inline] + fn summary(&self) -> ManagedSection { + ManagedSection { + start: self.heap_start, + size: self.heap_size, + free_pages: self.free_pages, + total_pages: self.total_pages, + } + } +} + +/// Page-metadata-based buddy allocator. +/// +/// `PAGE_SIZE` must be a power of two (commonly 0x1000 = 4 KiB). +pub struct BuddyAllocator { + sections_head: *mut BuddySection, + sections_tail: *mut BuddySection, + section_count: usize, +} + +// SAFETY: The allocator is designed to be wrapped in a SpinMutex. +// All section pointers point into caller-provided regions whose lifetime is managed externally. +unsafe impl Send for BuddyAllocator {} + +impl BuddyAllocator { + /// Calculate the metadata-region size (in bytes) required for `heap_size` bytes. + pub const fn required_meta_size(heap_size: usize) -> usize { + let pages = heap_size / PAGE_SIZE; + pages * core::mem::size_of::() + } + + /// Create an uninitialised allocator. Call [`init`](Self::init) before use. + pub const fn new() -> Self { + Self { + sections_head: ptr::null_mut(), + sections_tail: ptr::null_mut(), + section_count: 0, + } + } +} + +impl Default for BuddyAllocator { + fn default() -> Self { + Self::new() + } +} + +impl BuddyAllocator { + pub(crate) fn reset(&mut self) { + self.sections_head = ptr::null_mut(); + self.sections_tail = ptr::null_mut(); + self.section_count = 0; + } + + /// Initialise the allocator over the first section. + /// + /// # Safety + /// - `region` must be writable and remain valid for the lifetime of this allocator. + /// - Bytes consumed by metadata become unavailable for allocation. + pub unsafe fn init(&mut self, region: &mut [u8]) -> AllocResult { + unsafe { + self.reset(); + self.add_region(region) + } + } + + /// Add a new managed region after initialisation. + /// + /// # Safety + /// - `region` must be writable and remain valid for the lifetime of this allocator. + /// - The region must not overlap any existing managed region. + pub unsafe fn add_region(&mut self, region: &mut [u8]) -> AllocResult { + unsafe { + let region_start = region.as_mut_ptr() as usize; + let region_size = region.len(); + let (region_start, region_size) = + normalize_region(region_start, region_size, PAGE_SIZE) + .ok_or(AllocError::InvalidParam)?; + let layout = + BuddySection::compute_region_layout::(region_start, region_size) + .ok_or(AllocError::InvalidParam)?; + self.add_region_raw(SectionInitSpec { + region_start, + region_size, + section_ptr: layout.section_start as *mut BuddySection, + meta_ptr: layout.meta_start as *mut u8, + meta_size: Self::required_meta_size(layout.managed_heap_size), + heap_start: layout.managed_heap_start, + heap_size: layout.managed_heap_size, + }) + } + } + + pub(crate) unsafe fn add_region_raw(&mut self, spec: SectionInitSpec) -> AllocResult { + unsafe { + let region_size = spec.region_size; + let region_end = spec + .region_start + .checked_add(region_size) + .ok_or(AllocError::InvalidParam)?; + let heap_end = spec + .heap_start + .checked_add(spec.heap_size) + .ok_or(AllocError::InvalidParam)?; + if heap_end > region_end { + return Err(AllocError::InvalidParam); + } + + let mut section = self.sections_head; + while !section.is_null() { + let existing = &*section; + let existing_end = existing + .region_start + .checked_add(existing.region_size) + .ok_or(AllocError::InvalidParam)?; + if spec.region_start < existing_end && existing.region_start < region_end { + return Err(AllocError::MemoryOverlap); + } + section = existing.next; + } + + BuddySection::init_at::( + spec.section_ptr, + spec.region_start, + spec.region_size, + spec.meta_ptr, + spec.meta_size, + spec.heap_start, + spec.heap_size, + )?; + + if self.sections_head.is_null() { + self.sections_head = spec.section_ptr; + } else { + (*self.sections_tail).next = spec.section_ptr; + } + self.sections_tail = spec.section_ptr; + self.section_count += 1; + + log::debug!( + "BuddyAllocator: add section region {:#x}+{:#x}, heap {:#x}..{:#x}, {} pages", + spec.region_start, + spec.region_size, + spec.heap_start, + heap_end, + spec.heap_size / PAGE_SIZE, + ); + + Ok(()) + } + } + + /// Number of managed sections. + pub fn section_count(&self) -> usize { + self.section_count + } + + /// Read-only summary for a managed section by registration order. + pub fn section(&self, index: usize) -> Option { + let mut current = self.sections_head; + let mut i = 0usize; + while !current.is_null() { + if i == index { + return Some(unsafe { (&*current).summary() }); + } + current = unsafe { (*current).next }; + i += 1; + } + None + } + + /// Total number of pages managed across all sections. + pub fn total_pages(&self) -> usize { + let mut total = 0usize; + let mut current = self.sections_head; + while !current.is_null() { + total += unsafe { (*current).total_pages }; + current = unsafe { (*current).next }; + } + total + } + + /// Total managed heap bytes across all sections. + /// + /// This counts only bytes in allocatable heaps, excluding region-prefix metadata. + pub fn managed_bytes(&self) -> usize { + let mut total = 0usize; + let mut current = self.sections_head; + while !current.is_null() { + total += unsafe { (*current).heap_size }; + current = unsafe { (*current).next }; + } + total + } + + /// Number of currently free pages across all sections. + pub fn free_pages(&self) -> usize { + let mut total = 0usize; + let mut current = self.sections_head; + while !current.is_null() { + total += unsafe { (*current).free_pages }; + current = unsafe { (*current).next }; + } + total + } + + /// Allocated backend bytes across all sections. + /// + /// This is computed as managed heap bytes minus currently free page bytes. + /// It reflects page-level occupancy, so it includes slab pages, alignment + /// amplification, and internal fragmentation. + pub fn allocated_bytes(&self) -> usize { + self.managed_bytes() + .saturating_sub(self.free_pages().saturating_mul(PAGE_SIZE)) + } + + /// Allocate `count` contiguous pages, returning the virtual address. + pub fn alloc_pages(&mut self, count: usize, align: usize) -> AllocResult { + if count == 0 { + return Err(AllocError::InvalidParam); + } + let align = if align == 0 { PAGE_SIZE } else { align }; + if !align.is_power_of_two() || align < PAGE_SIZE { + return Err(AllocError::InvalidParam); + } + + let order = count.next_power_of_two().trailing_zeros() as usize; + if order > MAX_ORDER { + return Err(AllocError::InvalidParam); + } + + let mut section = self.sections_head; + while !section.is_null() { + if let Ok(addr) = + unsafe { Self::alloc_from_section_aligned(&mut *section, order, align) } + { + return Ok(addr); + } + section = unsafe { (*section).next }; + } + + Err(AllocError::NoMemory) + } + + fn alloc_from_section_aligned( + section: &mut BuddySection, + order: usize, + align: usize, + ) -> AllocResult { + for search_order in order..=MAX_ORDER { + let mut pfn_u32 = section.free_lists[search_order]; + while pfn_u32 != PFN_NONE { + let block_pfn = pfn_u32 as usize; + if let Some(target_pfn) = Self::find_aligned_pfn_in_block( + section.heap_start, + block_pfn, + search_order, + order, + align, + ) { + unsafe { + free_list_remove( + section.meta, + &mut section.free_lists, + pfn_u32, + search_order, + ); + } + + let mut current_order = search_order; + let mut current_pfn = block_pfn; + while current_order > order { + current_order -= 1; + let left_pfn = current_pfn; + let right_pfn = current_pfn + (1 << current_order); + let (next_pfn, free_pfn) = if target_pfn >= right_pfn { + (right_pfn, left_pfn) + } else { + (left_pfn, right_pfn) + }; + unsafe { + let bm = &mut *section.meta.add(free_pfn); + bm.flags = PageFlags::Free; + bm.order = current_order as u8; + free_list_push( + section.meta, + &mut section.free_lists, + free_pfn as u32, + current_order, + ); + } + current_pfn = next_pfn; + } + + unsafe { + let m = &mut *section.meta.add(current_pfn); + m.flags = PageFlags::Allocated; + m.order = order as u8; + } + + section.free_pages -= 1 << order; + return Ok(section.heap_start + current_pfn * PAGE_SIZE); + } + pfn_u32 = unsafe { (*section.meta.add(pfn_u32 as usize)).next }; + } + } + + Err(AllocError::NoMemory) + } + + fn find_aligned_pfn_in_block( + heap_start: usize, + block_pfn: usize, + block_order: usize, + alloc_order: usize, + align: usize, + ) -> Option { + let subblock_pages = 1usize << alloc_order; + let align_pages = align / PAGE_SIZE; + let heap_page_offset = (heap_start / PAGE_SIZE) & (align_pages - 1); + let offset = (align_pages - heap_page_offset) & (align_pages - 1); + + let candidate = if align_pages <= subblock_pages { + if !heap_start.is_multiple_of(align) { + return None; + } + block_pfn + } else { + if !offset.is_multiple_of(subblock_pages) { + return None; + } + let rem = block_pfn & (align_pages - 1); + let delta = (offset + align_pages - rem) & (align_pages - 1); + block_pfn + delta + }; + + let block_pages = 1usize << block_order; + let last_start = block_pfn + block_pages - subblock_pages; + (candidate <= last_start).then_some(candidate) + } + + /// Allocate pages whose *physical* address is below 4 GiB (DMA32 zone). + pub fn alloc_pages_lowmem(&mut self, count: usize, align: usize) -> AllocResult { + if count == 0 { + return Err(AllocError::InvalidParam); + } + let align = if align == 0 { PAGE_SIZE } else { align }; + if !align.is_power_of_two() || align < PAGE_SIZE { + return Err(AllocError::InvalidParam); + } + + let order = count.next_power_of_two().trailing_zeros() as usize; + if order > MAX_ORDER { + return Err(AllocError::InvalidParam); + } + + let mut section = self.sections_head; + while !section.is_null() { + if let Ok(addr) = + unsafe { Self::alloc_lowmem_from_section(&mut *section, order, align) } + { + return Ok(addr); + } + section = unsafe { (*section).next }; + } + + Err(AllocError::NoMemory) + } + + fn alloc_lowmem_from_section( + section: &mut BuddySection, + alloc_order: usize, + align: usize, + ) -> AllocResult { + for search_order in alloc_order..=MAX_ORDER { + let mut pfn_u32 = section.free_lists[search_order]; + while pfn_u32 != PFN_NONE { + let block_pfn = pfn_u32 as usize; + let Some(target_pfn) = Self::find_aligned_pfn_in_block( + section.heap_start, + block_pfn, + search_order, + alloc_order, + align, + ) else { + pfn_u32 = unsafe { (*section.meta.add(pfn_u32 as usize)).next }; + continue; + }; + let addr = section.heap_start + target_pfn * PAGE_SIZE; + let phys = eii::virt_to_phys(addr); + let block_bytes = (1usize << alloc_order) * PAGE_SIZE; + if phys + block_bytes <= DMA32_LIMIT && addr.is_multiple_of(align) { + unsafe { + free_list_remove( + section.meta, + &mut section.free_lists, + pfn_u32, + search_order, + ); + } + + let mut current_order = search_order; + let mut current_pfn = block_pfn; + while current_order > alloc_order { + current_order -= 1; + let left_pfn = current_pfn; + let right_pfn = current_pfn + (1 << current_order); + let (next_pfn, free_pfn) = if target_pfn >= right_pfn { + (right_pfn, left_pfn) + } else { + (left_pfn, right_pfn) + }; + unsafe { + let bm = &mut *section.meta.add(free_pfn); + bm.flags = PageFlags::Free; + bm.order = current_order as u8; + free_list_push( + section.meta, + &mut section.free_lists, + free_pfn as u32, + current_order, + ); + } + current_pfn = next_pfn; + } + + unsafe { + let m = &mut *section.meta.add(current_pfn); + m.flags = PageFlags::Allocated; + m.order = alloc_order as u8; + } + section.free_pages -= 1 << alloc_order; + return Ok(addr); + } + pfn_u32 = unsafe { (*section.meta.add(pfn_u32 as usize)).next }; + } + } + + Err(AllocError::NoMemory) + } + + /// Free pages previously obtained via [`alloc_pages`](Self::alloc_pages). + /// + /// `addr` must be the exact address returned by alloc. The allocator frees + /// the full block size recorded in page metadata, which may be larger than + /// `count` if the original allocation was rounded up for buddy order or alignment. + pub fn dealloc_pages(&mut self, addr: usize, count: usize) { + let Some(section) = self.find_section_by_addr_mut(addr) else { + debug_assert!( + false, + "dealloc_pages called with address outside all sections" + ); + return; + }; + + debug_assert!(is_aligned(addr, PAGE_SIZE)); + debug_assert!(count > 0); + + let pfn = (addr - section.heap_start) / PAGE_SIZE; + debug_assert!(pfn < section.max_pages); + let stored = unsafe { &*section.meta.add(pfn) }; + debug_assert!( + stored.flags == PageFlags::Allocated || stored.flags == PageFlags::Slab, + "dealloc_pages called on non-allocated block" + ); + + let expected_order = count.next_power_of_two().trailing_zeros() as usize; + let order = stored.order as usize; + debug_assert!( + expected_order <= order, + "dealloc_pages count implies larger order than the allocated block" + ); + Self::dealloc_in_section(section, pfn, order); + } + + /// Mark the page at `addr` with the given flags (used by slab to tag pages). + /// + /// # Safety + /// The caller must ensure `addr` is valid and properly allocated. + pub unsafe fn set_page_flags(&mut self, addr: usize, flags: PageFlags) -> AllocResult { + unsafe { + let section = self + .find_section_by_addr_mut(addr) + .ok_or(AllocError::NotFound)?; + let pfn = (addr - section.heap_start) / PAGE_SIZE; + (*section.meta.add(pfn)).flags = flags; + Ok(()) + } + } + + /// Read the flags of the page containing `addr`. + pub fn page_flags(&self, addr: usize) -> AllocResult { + let section = self + .find_section_by_addr(addr) + .ok_or(AllocError::NotFound)?; + let pfn = (addr - section.heap_start) / PAGE_SIZE; + Ok(unsafe { (*section.meta.add(pfn)).flags }) + } + + fn dealloc_in_section(section: &mut BuddySection, mut pfn: usize, mut order: usize) { + let freed_pages = 1usize << order; + + while order < MAX_ORDER { + let buddy_pfn = pfn ^ (1 << order); + if buddy_pfn >= section.max_pages { + break; + } + let buddy = unsafe { &*section.meta.add(buddy_pfn) }; + if buddy.flags != PageFlags::Free || buddy.order as usize != order { + break; + } + unsafe { + free_list_remove( + section.meta, + &mut section.free_lists, + buddy_pfn as u32, + order, + ); + } + pfn = pfn.min(buddy_pfn); + order += 1; + } + + unsafe { + let m = &mut *section.meta.add(pfn); + m.flags = PageFlags::Free; + m.order = order as u8; + free_list_push(section.meta, &mut section.free_lists, pfn as u32, order); + } + section.free_pages += freed_pages; + } + + fn find_section_by_addr(&self, addr: usize) -> Option<&BuddySection> { + let mut section = self.sections_head; + while !section.is_null() { + let current = unsafe { &*section }; + if current.contains_heap_addr(addr) { + return Some(current); + } + section = current.next; + } + None + } + + fn find_section_by_addr_mut(&mut self, addr: usize) -> Option<&mut BuddySection> { + let mut section = self.sections_head; + while !section.is_null() { + let current = unsafe { &mut *section }; + if current.contains_heap_addr(addr) { + return Some(current); + } + section = current.next; + } + None + } +} diff --git a/components/buddy-slab-allocator/src/buddy/page_meta.rs b/components/buddy-slab-allocator/src/buddy/page_meta.rs new file mode 100644 index 0000000000..07e1657aec --- /dev/null +++ b/components/buddy-slab-allocator/src/buddy/page_meta.rs @@ -0,0 +1,137 @@ +//! Per-page metadata stored in the external metadata region. +//! +//! Each page frame in the heap has a corresponding [`PageMeta`] entry. +//! Free pages are linked together via intrusive doubly-linked lists using PFN indices. + +/// Sentinel value indicating "no page" in free-list links. +pub const PFN_NONE: u32 = u32::MAX; + +/// Page state flags. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum PageFlags { + /// Page is free and sits in a buddy free list. + Free = 0, + /// Page is allocated (head page of a buddy block). + Allocated = 1, + /// Page is used as a slab page. + Slab = 2, +} + +/// Metadata for a single page frame (12 bytes). +/// +/// Head pages carry the `order` of the entire block. +/// Tail pages within a buddy block are marked `Allocated` (or `Slab`) with order 0. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct PageMeta { + /// Current state of this page. + pub flags: PageFlags, + /// Order of the block (only meaningful on head pages). + pub order: u8, + /// Reserved padding. + pub _pad: u16, + /// Previous PFN in the same-order free list (`PFN_NONE` if head or not free). + pub prev: u32, + /// Next PFN in the same-order free list (`PFN_NONE` if tail or not free). + pub next: u32, +} + +const _: () = assert!(core::mem::size_of::() == 12); + +impl Default for PageMeta { + fn default() -> Self { + Self::new() + } +} + +impl PageMeta { + /// Create a zeroed (free, order-0) page meta. + pub const fn new() -> Self { + Self { + flags: PageFlags::Free, + order: 0, + _pad: 0, + prev: PFN_NONE, + next: PFN_NONE, + } + } +} + +// --------------------------------------------------------------------------- +// Free-list helpers operating on a `*mut PageMeta` array + head array +// --------------------------------------------------------------------------- + +/// Push `pfn` onto the front of `free_lists[order]`. +/// +/// # Safety +/// `meta` must point to an array with at least `pfn + 1` entries. +/// `pfn` must not already be in any free list. +#[inline] +pub unsafe fn free_list_push(meta: *mut PageMeta, free_lists: &mut [u32], pfn: u32, order: usize) { + unsafe { + let old_head = free_lists[order]; + let m = &mut *meta.add(pfn as usize); + m.prev = PFN_NONE; + m.next = old_head; + if old_head != PFN_NONE { + (*meta.add(old_head as usize)).prev = pfn; + } + free_lists[order] = pfn; + } +} + +/// Pop the first PFN from `free_lists[order]`, returning `PFN_NONE` if empty. +/// +/// # Safety +/// `meta` must be a valid metadata array. +#[inline] +pub unsafe fn free_list_pop(meta: *mut PageMeta, free_lists: &mut [u32], order: usize) -> u32 { + unsafe { + let head = free_lists[order]; + if head == PFN_NONE { + return PFN_NONE; + } + let m = &mut *meta.add(head as usize); + let next = m.next; + m.prev = PFN_NONE; + m.next = PFN_NONE; + if next != PFN_NONE { + (*meta.add(next as usize)).prev = PFN_NONE; + } + free_lists[order] = next; + head + } +} + +/// Remove `pfn` from the free list at `order`. +/// +/// # Safety +/// `pfn` must currently be in `free_lists[order]`. +#[inline] +pub unsafe fn free_list_remove( + meta: *mut PageMeta, + free_lists: &mut [u32], + pfn: u32, + order: usize, +) { + unsafe { + let m = &*meta.add(pfn as usize); + let prev = m.prev; + let next = m.next; + + if prev != PFN_NONE { + (*meta.add(prev as usize)).next = next; + } else { + // pfn was the head + free_lists[order] = next; + } + if next != PFN_NONE { + (*meta.add(next as usize)).prev = prev; + } + + let m = &mut *meta.add(pfn as usize); + m.prev = PFN_NONE; + m.next = PFN_NONE; + } +} diff --git a/components/buddy-slab-allocator/src/error.rs b/components/buddy-slab-allocator/src/error.rs new file mode 100644 index 0000000000..561ccf5877 --- /dev/null +++ b/components/buddy-slab-allocator/src/error.rs @@ -0,0 +1,37 @@ +use core::fmt; + +/// The error type used for allocation operations. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AllocError { + /// Invalid size, alignment, or other input parameter. + InvalidParam, + /// A global allocator instance has already been initialized. + AlreadyInitialized, + /// A region overlaps with an existing managed region. + MemoryOverlap, + /// Not enough memory is available to satisfy the request. + NoMemory, + /// Attempted to deallocate memory that was not allocated. + NotAllocated, + /// The allocator has not been initialized. + NotInitialized, + /// The requested address or entity was not found in any managed region. + NotFound, +} + +impl fmt::Display for AllocError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidParam => write!(f, "invalid parameter"), + Self::AlreadyInitialized => write!(f, "allocator already initialized"), + Self::MemoryOverlap => write!(f, "memory regions overlap"), + Self::NoMemory => write!(f, "out of memory"), + Self::NotAllocated => write!(f, "memory not allocated"), + Self::NotInitialized => write!(f, "allocator not initialized"), + Self::NotFound => write!(f, "not found"), + } + } +} + +/// A [`Result`] alias with [`AllocError`] as the error type. +pub type AllocResult = Result; diff --git a/components/buddy-slab-allocator/src/global.rs b/components/buddy-slab-allocator/src/global.rs new file mode 100644 index 0000000000..63ae9dce9c --- /dev/null +++ b/components/buddy-slab-allocator/src/global.rs @@ -0,0 +1,331 @@ +/// Global allocator composing buddy (pages) + per-CPU slab (objects). +/// +/// Implements [`core::alloc::GlobalAlloc`] so it can serve as `#[global_allocator]`. +/// Cross-CPU frees are lock-free via [`SlabPageHeader::remote_free`]. +use core::alloc::{GlobalAlloc, Layout}; +use core::{ + ptr::{self, NonNull}, + sync::atomic::{AtomicBool, Ordering}, +}; + +use spin::Mutex as SpinMutex; + +use crate::{ + align_up, + buddy::{BuddyAllocator, BuddySection, ManagedSection, PageFlags, SectionInitSpec}, + eii, + error::{AllocError, AllocResult}, + slab::{ + SlabAllocResult, + page::{SLAB_MAGIC, SlabPageHeader}, + size_class::{SLAB_MAX_SIZE, SizeClass}, + }, +}; + +const REGION_GRANULE: usize = 2 * 1024 * 1024; +static GLOBAL_ALLOCATOR_LIVE: AtomicBool = AtomicBool::new(false); + +#[doc(hidden)] +pub fn __reset_global_allocator_singleton_for_tests() { + GLOBAL_ALLOCATOR_LIVE.store(false, Ordering::Release); +} + +/// Unified allocator: buddy page allocator + per-CPU slab caches. +pub struct GlobalAllocator { + buddy: SpinMutex>, + initialized: AtomicBool, +} + +// SAFETY: All mutable state is behind SpinMutex or AtomicBool. +unsafe impl Sync for GlobalAllocator {} +unsafe impl Send for GlobalAllocator {} + +impl GlobalAllocator { + /// Create an uninitialised global allocator. + pub const fn new() -> Self { + Self { + buddy: SpinMutex::new(BuddyAllocator::new()), + initialized: AtomicBool::new(false), + } + } +} + +impl Default for GlobalAllocator { + fn default() -> Self { + Self::new() + } +} + +impl GlobalAllocator { + /// Initialise the allocator over the first region. + /// + /// # Safety + /// - `region` must be writable and remain valid for the lifetime of this allocator. + /// - Any bytes consumed by metadata or alignment padding become unavailable for allocation. + pub unsafe fn init(&self, region: &mut [u8]) -> AllocResult { + unsafe { + if self.initialized.load(Ordering::Acquire) { + return Err(AllocError::AlreadyInitialized); + } + if GLOBAL_ALLOCATOR_LIVE + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_err() + { + return Err(AllocError::AlreadyInitialized); + } + + let raw_region_start = region.as_mut_ptr() as usize; + let raw_region_size = region.len(); + let layout = match BuddySection::compute_region_layout_with_heap_align::( + raw_region_start, + raw_region_size, + REGION_GRANULE, + ) + .ok_or(AllocError::InvalidParam) + { + Ok(layout) => layout, + Err(err) => { + GLOBAL_ALLOCATOR_LIVE.store(false, Ordering::Release); + return Err(err); + } + }; + + let mut buddy = self.buddy.lock(); + buddy.reset(); + if let Err(err) = buddy.add_region_raw(SectionInitSpec { + region_start: raw_region_start, + region_size: raw_region_size, + section_ptr: layout.section_start as *mut BuddySection, + meta_ptr: layout.meta_start as *mut u8, + meta_size: BuddyAllocator::::required_meta_size( + layout.managed_heap_size, + ), + heap_start: layout.managed_heap_start, + heap_size: layout.managed_heap_size, + }) { + GLOBAL_ALLOCATOR_LIVE.store(false, Ordering::Release); + return Err(err); + } + drop(buddy); + + self.initialized.store(true, Ordering::Release); + + log::debug!( + "GlobalAllocator: region {:#x}+{:#x}, section {:#x}, first heap {:#x}+{:#x}", + raw_region_start, + raw_region_size, + layout.section_start, + layout.managed_heap_start, + layout.managed_heap_size, + ); + + Ok(()) + } + } + + /// Add a new managed region after [`init`](Self::init). + /// + /// # Safety + /// - `region` must be writable and remain valid for the lifetime of this allocator. + /// - The region must not overlap any already managed region. + pub unsafe fn add_region(&self, region: &mut [u8]) -> AllocResult { + unsafe { + if !self.initialized.load(Ordering::Acquire) { + return Err(AllocError::NotInitialized); + } + let region_start = region.as_mut_ptr() as usize; + let region_size = region.len(); + let Some(layout) = BuddySection::compute_region_layout_with_heap_align::( + region_start, + region_size, + REGION_GRANULE, + ) else { + log::info!( + "GlobalAllocator: skip region {:#x}+{:#x}, no allocator-visible memory after \ + {} alignment", + region_start, + region_size, + REGION_GRANULE, + ); + return Ok(()); + }; + self.buddy.lock().add_region_raw(SectionInitSpec { + region_start, + region_size, + section_ptr: layout.section_start as *mut BuddySection, + meta_ptr: layout.meta_start as *mut u8, + meta_size: BuddyAllocator::::required_meta_size( + layout.managed_heap_size, + ), + heap_start: layout.managed_heap_start, + heap_size: layout.managed_heap_size, + }) + } + } + + /// Number of managed sections. + pub fn managed_section_count(&self) -> usize { + self.buddy.lock().section_count() + } + + /// Read-only summary for a managed section. + pub fn managed_section(&self, index: usize) -> Option { + self.buddy.lock().section(index) + } + + /// Total managed heap bytes across all sections. + /// + /// This excludes region-prefix metadata such as `BuddySection` and `PageMeta[]`. + pub fn managed_bytes(&self) -> usize { + self.buddy.lock().managed_bytes() + } + + /// Allocated backend bytes across all sections. + /// + /// This is page-level occupancy, not the exact sum of requested layout sizes. + pub fn allocated_bytes(&self) -> usize { + self.buddy.lock().allocated_bytes() + } + + /// Allocate contiguous pages. Returns the virtual start address. + pub fn alloc_pages(&self, count: usize, align: usize) -> AllocResult { + self.buddy.lock().alloc_pages(count, align) + } + + /// Free pages previously obtained via [`alloc_pages`](Self::alloc_pages). + pub fn dealloc_pages(&self, addr: usize, count: usize) { + self.buddy.lock().dealloc_pages(addr, count); + } + + /// Allocate pages with physical address below 4 GiB. + pub fn alloc_pages_lowmem(&self, count: usize, align: usize) -> AllocResult { + self.buddy.lock().alloc_pages_lowmem(count, align) + } + + /// Allocate memory for `layout`. Returns a pointer on success. + pub fn alloc(&self, layout: Layout) -> AllocResult> { + if !self.initialized.load(Ordering::Acquire) { + return Err(AllocError::NotInitialized); + } + + if self.is_slab_eligible(&layout) { + self.slab_alloc(layout) + } else { + self.large_alloc(layout) + } + } + + /// Deallocate memory previously returned by [`alloc`](Self::alloc). + /// + /// # Safety + /// `ptr` must have been returned by a prior `alloc` with the same `layout`. + pub unsafe fn dealloc(&self, ptr: NonNull, layout: Layout) { + unsafe { + if self.is_slab_eligible(&layout) { + self.slab_dealloc(ptr, layout); + } else { + self.large_dealloc(ptr, layout); + } + } + } + + #[inline] + fn is_slab_eligible(&self, layout: &Layout) -> bool { + layout.size() <= SLAB_MAX_SIZE && layout.align() <= SLAB_MAX_SIZE + } + + fn slab_alloc(&self, layout: Layout) -> AllocResult> { + let pool = eii::slab_pool(); + + match pool.alloc(layout)? { + SlabAllocResult::Allocated(ptr) => Ok(ptr), + SlabAllocResult::NeedsSlab { size_class, pages } => { + let bytes = pages * PAGE_SIZE; + let addr = self.buddy.lock().alloc_pages(pages, bytes)?; + unsafe { + self.buddy.lock().set_page_flags(addr, PageFlags::Slab)?; + } + pool.add_slab(size_class, addr, bytes); + match pool.alloc(layout)? { + SlabAllocResult::Allocated(ptr) => Ok(ptr), + SlabAllocResult::NeedsSlab { .. } => Err(AllocError::NoMemory), + } + } + } + } + + unsafe fn slab_dealloc(&self, ptr: NonNull, layout: Layout) { + unsafe { + let sc = SizeClass::from_layout(layout).expect("layout exceeds slab"); + let slab_bytes = sc.slab_pages(PAGE_SIZE) * PAGE_SIZE; + let base = + SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + let hdr = &*(base as *const SlabPageHeader); + debug_assert_eq!(hdr.magic, SLAB_MAGIC); + + match eii::slab_pool().dealloc(ptr, layout, hdr.owner_cpu as usize) { + crate::SlabPoolDeallocResult::Done => {} + crate::SlabPoolDeallocResult::RemoteQueued => {} + crate::SlabPoolDeallocResult::FreeSlab { base, pages } => { + self.buddy.lock().dealloc_pages(base, pages); + } + } + } + } + + fn large_alloc(&self, layout: Layout) -> AllocResult> { + let pages = align_up(layout.size(), PAGE_SIZE) / PAGE_SIZE; + let align = layout.align().max(PAGE_SIZE); + let addr = self.buddy.lock().alloc_pages(pages, align)?; + Ok(unsafe { NonNull::new_unchecked(addr as *mut u8) }) + } + + unsafe fn large_dealloc(&self, ptr: NonNull, layout: Layout) { + let pages = align_up(layout.size(), PAGE_SIZE) / PAGE_SIZE; + self.buddy + .lock() + .dealloc_pages(ptr.as_ptr() as usize, pages); + } +} + +impl Drop for GlobalAllocator { + fn drop(&mut self) { + if self.initialized.swap(false, Ordering::AcqRel) { + GLOBAL_ALLOCATOR_LIVE.store(false, Ordering::Release); + } + } +} + +unsafe impl GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + match self.alloc(layout) { + Ok(ptr) => ptr.as_ptr(), + Err(_) => ptr::null_mut(), + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { + if let Some(nn) = NonNull::new(ptr) { + self.dealloc(nn, layout); + } + } + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + unsafe { + let new_layout = match Layout::from_size_align(new_size, layout.align()) { + Ok(l) => l, + Err(_) => return ptr::null_mut(), + }; + + let new_ptr = ::alloc(self, new_layout); + if !new_ptr.is_null() { + let copy_size = layout.size().min(new_size); + ptr::copy_nonoverlapping(ptr, new_ptr, copy_size); + ::dealloc(self, ptr, layout); + } + new_ptr + } + } +} diff --git a/components/buddy-slab-allocator/src/lib.rs b/components/buddy-slab-allocator/src/lib.rs new file mode 100644 index 0000000000..e811d2a26f --- /dev/null +++ b/components/buddy-slab-allocator/src/lib.rs @@ -0,0 +1,111 @@ +//! # buddy-slab-allocator +//! +//! A `#![no_std]` memory allocator featuring: +//! +//! - **Buddy page allocator** — page-metadata-based with intrusive free lists +//! - **Slab allocator** — bitmap-based with lock-free cross-CPU freeing (Linux SLUB inspired) +//! - **Global allocator** — composes buddy + per-CPU slab, implements [`core::alloc::GlobalAlloc`] +//! +//! Both buddy and slab allocators can be used standalone. + +#![no_std] +#![feature(extern_item_impls)] + +mod error; +pub use error::{AllocError, AllocResult}; + +pub mod buddy; +pub use buddy::{BuddyAllocator, ManagedSection}; + +pub mod slab; +pub use slab::{ + PerCpuSlab, SizeClass, SlabAllocResult, SlabAllocator, SlabDeallocResult, + SlabPoolDeallocResult, SlabPoolTrait, SlabTrait, StaticSlabPool, +}; + +pub mod global; +#[doc(hidden)] +pub use global::__reset_global_allocator_singleton_for_tests; +pub use global::GlobalAllocator; + +/// External interface items supplied by the platform / allocator integrator. +pub mod eii { + /// Translate a virtual address to a physical address. + #[eii(virt_to_phys_impl)] + pub fn virt_to_phys(vaddr: usize) -> usize; + + /// Return the system-global slab pool. + #[eii(slab_pool_impl)] + pub fn slab_pool() -> &'static dyn crate::SlabPoolTrait; +} + +// --------------------------------------------------------------------------- +// Utility helpers (crate-internal) +// --------------------------------------------------------------------------- + +#[inline] +pub(crate) const fn align_up(pos: usize, align: usize) -> usize { + (pos + align - 1) & !(align - 1) +} + +#[inline] +pub(crate) const fn is_aligned(addr: usize, align: usize) -> bool { + addr & (align - 1) == 0 +} + +#[cfg(test)] +mod test_eii_impls { + use core::{alloc::Layout, ptr::NonNull}; + + use super::{ + AllocError, AllocResult, SizeClass, SlabAllocResult, SlabPoolTrait, SlabTrait, + eii::{slab_pool_impl, virt_to_phys_impl}, + }; + + struct NullSlabPool; + struct NullSlab; + + impl SlabTrait for NullSlab { + fn cpu_id(&self) -> usize { + 0 + } + + fn page_size(&self) -> usize { + 0x1000 + } + + fn alloc(&self, _layout: Layout) -> AllocResult { + Err(AllocError::NotInitialized) + } + + fn add_slab(&self, _size_class: SizeClass, _base: usize, _bytes: usize) {} + + fn dealloc_local(&self, _ptr: NonNull, _layout: Layout) -> super::SlabDeallocResult { + super::SlabDeallocResult::Done + } + } + + static NULL_SLAB: NullSlab = NullSlab; + + impl SlabPoolTrait for NullSlabPool { + fn current_slab(&self) -> &dyn SlabTrait { + &NULL_SLAB + } + + fn owner_slab(&self, _cpu_idx: usize) -> &dyn SlabTrait { + &NULL_SLAB + } + } + + static NULL_SLAB_POOL: NullSlabPool = NullSlabPool; + + #[virt_to_phys_impl] + fn test_virt_to_phys(vaddr: usize) -> usize { + vaddr + } + + #[slab_pool_impl] + fn test_slab_pool() -> &'static dyn SlabPoolTrait { + &NULL_SLAB_POOL + } +} diff --git a/components/buddy-slab-allocator/src/slab/cache.rs b/components/buddy-slab-allocator/src/slab/cache.rs new file mode 100644 index 0000000000..22de3cc174 --- /dev/null +++ b/components/buddy-slab-allocator/src/slab/cache.rs @@ -0,0 +1,275 @@ +/// Per-size-class slab cache. +/// +/// Maintains three intrusive doubly-linked lists of slab pages: +/// - **partial**: some objects free (preferred for allocation) +/// - **full**: no objects free +/// - **empty**: all objects free (at most one cached; rest returned to buddy) +use super::page::{SlabListState, SlabPageHeader}; +use super::size_class::SizeClass; + +/// Intrusive list head (address of the first `SlabPageHeader`, 0 = empty). +#[derive(Debug, Clone, Copy)] +struct ListHead { + first: usize, + kind: SlabListState, +} + +impl ListHead { + const fn empty(kind: SlabListState) -> Self { + Self { first: 0, kind } + } + + fn is_empty(&self) -> bool { + self.first == 0 + } + + /// Push a slab page onto the front of the list. + /// + /// # Safety + /// `base` must point to a valid `SlabPageHeader`. + unsafe fn push_front(&mut self, base: usize) { + unsafe { + let hdr = &mut *(base as *mut SlabPageHeader); + debug_assert_eq!( + hdr.list_state, + SlabListState::None, + "pushing slab onto {:?} while it is on {:?}", + self.kind, + hdr.list_state + ); + debug_assert_eq!(hdr.list_prev, 0, "pushing slab with stale list_prev"); + debug_assert_eq!(hdr.list_next, 0, "pushing slab with stale list_next"); + hdr.list_state = self.kind; + hdr.list_prev = 0; + hdr.list_next = self.first; + if self.first != 0 { + let old = &mut *(self.first as *mut SlabPageHeader); + debug_assert_eq!( + old.list_state, self.kind, + "list head points to slab with wrong membership" + ); + old.list_prev = base; + } + self.first = base; + } + } + + /// Remove a slab page from this list. + /// + /// # Safety + /// `base` must be in this list. + unsafe fn remove(&mut self, base: usize) { + unsafe { + let hdr = &*(base as *const SlabPageHeader); + debug_assert_eq!( + hdr.list_state, self.kind, + "removing slab from {:?} while it is on {:?}", + self.kind, hdr.list_state + ); + if hdr.list_prev == 0 { + debug_assert_eq!(self.first, base, "front slab is not this list's head"); + } else { + debug_assert_eq!( + (*(hdr.list_prev as *const SlabPageHeader)).list_next, + base, + "previous slab does not link back to removed slab" + ); + } + if hdr.list_next != 0 { + debug_assert_eq!( + (*(hdr.list_next as *const SlabPageHeader)).list_prev, + base, + "next slab does not link back to removed slab" + ); + } + let prev = hdr.list_prev; + let next = hdr.list_next; + + if prev != 0 { + (*(prev as *mut SlabPageHeader)).list_next = next; + } else { + self.first = next; + } + if next != 0 { + (*(next as *mut SlabPageHeader)).list_prev = prev; + } + // Clear links + let hdr = &mut *(base as *mut SlabPageHeader); + hdr.list_prev = 0; + hdr.list_next = 0; + hdr.list_state = SlabListState::None; + } + } + + /// Pop the first page from the list. Returns 0 if empty. + unsafe fn pop_front(&mut self) -> usize { + unsafe { + if self.first == 0 { + return 0; + } + let base = self.first; + self.remove(base); + base + } + } +} + +/// Cache for a single [`SizeClass`]. +pub struct SlabCache { + pub size_class: SizeClass, + partial: ListHead, + full: ListHead, + empty: ListHead, + /// Number of empty slabs cached (we keep at most 1). + empty_count: usize, +} + +/// Result of a per-cache deallocation. +pub enum CacheDeallocResult { + /// Object freed, slab stays. + Done, + /// Slab became empty and should be returned to the page allocator. + FreeSlab { base: usize, pages: usize }, +} + +impl SlabCache { + pub const fn new(size_class: SizeClass) -> Self { + Self { + size_class, + partial: ListHead::empty(SlabListState::Partial), + full: ListHead::empty(SlabListState::Full), + empty: ListHead::empty(SlabListState::Empty), + empty_count: 0, + } + } + + /// Try to allocate one object. Returns `Some(obj_addr)` or `None` if no slabs available. + pub fn alloc_object(&mut self) -> Option { + // 1. Try the first partial slab (drain remote frees first). + if let Some(addr) = self.try_alloc_from_partial::() { + return Some(addr); + } + + // 2. A full slab may have gained free objects via lock-free remote frees. + if let Some(base) = self.reclaim_full_with_remote_frees() { + unsafe { self.partial.push_front(base) }; + return self.try_alloc_from_partial::(); + } + + // 3. Try recycling an empty slab. + if !self.empty.is_empty() { + let base = unsafe { self.empty.pop_front() }; + self.empty_count -= 1; + // Move to partial and alloc from it. + unsafe { self.partial.push_front(base) }; + return self.try_alloc_from_partial::(); + } + + None + } + + /// Drain remote frees from the first full slab that has them and move it + /// back to the partial list. + fn reclaim_full_with_remote_frees(&mut self) -> Option { + let mut base = self.full.first; + while base != 0 { + let next = unsafe { (*(base as *const SlabPageHeader)).list_next }; + let hdr = unsafe { &mut *(base as *mut SlabPageHeader) }; + if hdr.has_remote_frees() { + hdr.drain_remote_frees(base); + unsafe { self.full.remove(base) }; + return Some(base); + } + base = next; + } + None + } + + /// Attempt allocation from the first partial slab. + fn try_alloc_from_partial(&mut self) -> Option { + let base = self.partial.first; + if base == 0 { + return None; + } + + let hdr = unsafe { &mut *(base as *mut SlabPageHeader) }; + + // Drain any remote frees first. + if hdr.has_remote_frees() { + hdr.drain_remote_frees(base); + } + + if let Some(idx) = hdr.local_alloc() { + let obj_addr = hdr.object_addr(base, idx); + // If slab is now full, move to full list. + if hdr.is_local_full() && !hdr.has_remote_frees() { + unsafe { + self.partial.remove(base); + self.full.push_front(base); + } + } + return Some(obj_addr); + } + None + } + + /// Free an object back to this cache (local CPU path — under lock). + /// + /// Returns whether the slab should be returned to the page allocator. + pub fn dealloc_object( + &mut self, + obj_addr: usize, + ) -> CacheDeallocResult { + let slab_bytes = self.size_class.slab_pages(PAGE_SIZE) * PAGE_SIZE; + let base = SlabPageHeader::base_from_obj_addr::(obj_addr, slab_bytes); + let hdr = unsafe { &mut *(base as *mut SlabPageHeader) }; + let was_full = hdr.list_state == SlabListState::Full; + + let idx = hdr.object_index(base, obj_addr); + hdr.local_free(idx); + + if was_full { + // Move from full to partial. + unsafe { + self.full.remove(base); + self.partial.push_front(base); + } + } + + // Check if slab is now completely empty. + // First drain remote frees so we have an accurate count. + if hdr.has_remote_frees() { + hdr.drain_remote_frees(base); + } + + if hdr.is_all_free() { + if self.empty_count == 0 { + // Cache one empty slab for reuse. + unsafe { + self.partial.remove(base); + self.empty.push_front(base); + } + self.empty_count += 1; + CacheDeallocResult::Done + } else { + // Already have a cached empty slab — return this one. + unsafe { self.partial.remove(base) }; + let hdr = unsafe { &mut *(base as *mut SlabPageHeader) }; + hdr.prepare_for_buddy_free(); + CacheDeallocResult::FreeSlab { + base, + pages: self.size_class.slab_pages(PAGE_SIZE), + } + } + } else { + CacheDeallocResult::Done + } + } + + /// Register a newly allocated slab page (from the buddy allocator). + pub fn add_slab(&mut self, base: usize, bytes: usize, owner_cpu: u16) { + let hdr = unsafe { &mut *(base as *mut SlabPageHeader) }; + hdr.init(self.size_class, bytes, owner_cpu); + unsafe { self.partial.push_front(base) }; + } +} diff --git a/components/buddy-slab-allocator/src/slab/mod.rs b/components/buddy-slab-allocator/src/slab/mod.rs new file mode 100644 index 0000000000..e4a81142d6 --- /dev/null +++ b/components/buddy-slab-allocator/src/slab/mod.rs @@ -0,0 +1,298 @@ +//! Slab allocator — bitmap-based with lock-free cross-CPU freeing. +//! +//! The [`SlabAllocator`] is a standalone component that manages object allocation +//! within pre-supplied slab pages. It does **not** allocate pages itself; instead +//! it returns [`SlabAllocResult::NeedsSlab`] to request pages from the caller. +//! +//! Cross-CPU frees go through the lock-free [`SlabPageHeader::remote_free`] path. + +pub mod cache; +pub mod page; +pub mod size_class; + +use core::{alloc::Layout, ptr::NonNull}; + +use cache::{CacheDeallocResult, SlabCache}; +pub use page::SlabPageHeader; +pub use size_class::SizeClass; +use spin::Mutex as SpinMutex; + +use crate::error::{AllocError, AllocResult}; + +/// Result of a slab allocation attempt. +pub enum SlabAllocResult { + /// Object successfully allocated. + Allocated(NonNull), + /// The slab cache for this size class has no free objects. + /// The caller should allocate `pages` pages from the buddy allocator, + /// call [`SlabAllocator::add_slab`], and retry. + NeedsSlab { size_class: SizeClass, pages: usize }, +} + +/// Result of a slab deallocation. +pub enum SlabDeallocResult { + /// Object freed, nothing else to do. + Done, + /// The slab page at `base` became empty and should be returned to the buddy. + FreeSlab { base: usize, pages: usize }, +} + +/// Result of a pool-mediated slab deallocation. +pub enum SlabPoolDeallocResult { + /// Object freed on the local CPU path. + Done, + /// Object was queued onto the owner's remote-free list. + RemoteQueued, + /// The slab page at `base` became empty and should be returned to the buddy. + FreeSlab { base: usize, pages: usize }, +} + +/// Object-safe slab interface used by [`crate::GlobalAllocator`] EII hooks. +pub trait SlabTrait: Sync { + /// Logical CPU id this slab belongs to. + fn cpu_id(&self) -> usize; + + /// Page size used by this slab. + fn page_size(&self) -> usize; + + /// Allocate one object. + fn alloc(&self, layout: Layout) -> AllocResult; + + /// Register a freshly allocated slab page. + fn add_slab(&self, size_class: SizeClass, base: usize, bytes: usize); + + /// Free an object on the owner CPU path. + fn dealloc_local(&self, ptr: NonNull, layout: Layout) -> SlabDeallocResult; + + /// Free an object on the remote CPU path. + fn dealloc_remote(&self, ptr: NonNull) { + let owner_cpu = u16::try_from(self.cpu_id()).expect("CPU id exceeds slab owner range"); + unsafe { SlabPageHeader::remote_free_object(ptr, owner_cpu, self.page_size()) }; + } +} + +/// Object-safe slab-pool interface used by [`crate::GlobalAllocator`] EII hooks. +pub trait SlabPoolTrait: Sync { + /// Return the slab belonging to the current CPU. + fn current_slab(&self) -> &dyn SlabTrait; + + /// Return the owner slab for the given CPU. + fn owner_slab(&self, cpu_idx: usize) -> &dyn SlabTrait; + + /// Logical CPU id of the current CPU. + fn current_cpu_id(&self) -> usize { + self.current_slab().cpu_id() + } + + /// Allocate one object from the current CPU's slab. + fn alloc(&self, layout: Layout) -> AllocResult { + self.current_slab().alloc(layout) + } + + /// Register a freshly allocated slab page in the current CPU's slab. + fn add_slab(&self, size_class: SizeClass, base: usize, bytes: usize) { + self.current_slab().add_slab(size_class, base, bytes) + } + + /// Free an object, routing to local or remote slab ownership as needed. + fn dealloc(&self, ptr: NonNull, layout: Layout, owner_cpu: usize) -> SlabPoolDeallocResult { + if owner_cpu == self.current_cpu_id() { + match self.current_slab().dealloc_local(ptr, layout) { + SlabDeallocResult::Done => SlabPoolDeallocResult::Done, + SlabDeallocResult::FreeSlab { base, pages } => { + SlabPoolDeallocResult::FreeSlab { base, pages } + } + } + } else { + self.owner_slab(owner_cpu).dealloc_remote(ptr); + SlabPoolDeallocResult::RemoteQueued + } + } +} + +/// Convenience helpers for callback-style slab access. +pub trait SlabPoolExt: SlabPoolTrait { + /// Access the current CPU's slab via a callback. + fn with_current_slab(&self, f: impl FnOnce(&dyn SlabTrait) -> R) -> R { + f(self.current_slab()) + } + + /// Access the given owner's slab via a callback. + fn with_owner_slab(&self, cpu_idx: usize, f: impl FnOnce(&dyn SlabTrait) -> R) -> R { + f(self.owner_slab(cpu_idx)) + } +} + +impl SlabPoolExt for T {} + +/// Standalone slab allocator (one per CPU or standalone use). +pub struct SlabAllocator { + caches: [SlabCache; SizeClass::COUNT], +} + +/// Default per-CPU slab wrapper used by EII integrators. +pub struct PerCpuSlab { + cpu_id: u16, + inner: SpinMutex>, +} + +/// Default static slab-pool wrapper used by EII integrators. +pub struct StaticSlabPool { + slabs: [PerCpuSlab; N], + current_cpu_id: fn() -> usize, +} + +impl PerCpuSlab { + /// Create an empty per-CPU slab wrapper for `cpu_id`. + pub const fn new(cpu_id: u16) -> Self { + Self { + cpu_id, + inner: SpinMutex::new(SlabAllocator::new()), + } + } + + /// Reset the inner slab allocator to an empty state. + pub fn reset(&self) { + *self.inner.lock() = SlabAllocator::new(); + } + + /// Return this slab's logical CPU id. + pub const fn cpu_id(&self) -> usize { + self.cpu_id as usize + } + + /// Allocate one object. + pub fn alloc(&self, layout: Layout) -> AllocResult { + self.inner.lock().alloc(layout) + } + + /// Register a freshly allocated slab page. + pub fn add_slab(&self, size_class: SizeClass, base: usize, bytes: usize) { + self.inner + .lock() + .add_slab(size_class, base, bytes, self.cpu_id); + } + + /// Free an object on the owner CPU path. + pub fn dealloc_local(&self, ptr: NonNull, layout: Layout) -> SlabDeallocResult { + self.inner.lock().dealloc(ptr, layout) + } + + /// Queue an object onto this slab's remote-free list. + pub fn dealloc_remote(&self, ptr: NonNull) { + unsafe { SlabPageHeader::remote_free_object(ptr, self.cpu_id, PAGE_SIZE) }; + } +} + +impl StaticSlabPool { + /// Create a static slab pool from pre-built per-CPU slabs and a CPU-id hook. + pub const fn new(slabs: [PerCpuSlab; N], current_cpu_id: fn() -> usize) -> Self { + Self { + slabs, + current_cpu_id, + } + } +} + +impl SlabAllocator { + /// Create a new (empty) slab allocator. No pages are owned yet. + pub const fn new() -> Self { + Self { + caches: [ + SlabCache::new(SizeClass::Bytes8), + SlabCache::new(SizeClass::Bytes16), + SlabCache::new(SizeClass::Bytes32), + SlabCache::new(SizeClass::Bytes64), + SlabCache::new(SizeClass::Bytes128), + SlabCache::new(SizeClass::Bytes256), + SlabCache::new(SizeClass::Bytes512), + SlabCache::new(SizeClass::Bytes1024), + SlabCache::new(SizeClass::Bytes2048), + ], + } + } +} + +impl Default for SlabAllocator { + fn default() -> Self { + Self::new() + } +} + +impl SlabAllocator { + /// Try to allocate an object matching `layout`. + /// + /// If the matching cache is exhausted, [`SlabAllocResult::NeedsSlab`] is returned + /// so the caller can supply pages and retry. + pub fn alloc(&mut self, layout: Layout) -> AllocResult { + let sc = SizeClass::from_layout(layout).ok_or(AllocError::InvalidParam)?; + let cache = &mut self.caches[sc.index()]; + + match cache.alloc_object::() { + Some(addr) => { + // SAFETY: `addr` is non-null, aligned, and within a live slab page. + let ptr = unsafe { NonNull::new_unchecked(addr as *mut u8) }; + Ok(SlabAllocResult::Allocated(ptr)) + } + None => Ok(SlabAllocResult::NeedsSlab { + size_class: sc, + pages: sc.slab_pages(PAGE_SIZE), + }), + } + } + + /// Free an object previously allocated with [`alloc`](Self::alloc). + /// + /// This is the **local** (owner-CPU) path. Cross-CPU frees should go through + /// [`SlabPageHeader::remote_free`] directly (see [`GlobalAllocator`]). + pub fn dealloc(&mut self, ptr: NonNull, layout: Layout) -> SlabDeallocResult { + let sc = SizeClass::from_layout(layout).expect("layout exceeds slab size"); + let cache = &mut self.caches[sc.index()]; + + match cache.dealloc_object::(ptr.as_ptr() as usize) { + CacheDeallocResult::Done => SlabDeallocResult::Done, + CacheDeallocResult::FreeSlab { base, pages } => { + SlabDeallocResult::FreeSlab { base, pages } + } + } + } + + /// Supply a freshly allocated slab page to the given size class. + /// + /// `base` is the virtual address of the page(s), `bytes` = pages × PAGE_SIZE. + pub fn add_slab(&mut self, size_class: SizeClass, base: usize, bytes: usize, owner_cpu: u16) { + self.caches[size_class.index()].add_slab(base, bytes, owner_cpu); + } +} + +impl SlabTrait for PerCpuSlab { + fn cpu_id(&self) -> usize { + PerCpuSlab::cpu_id(self) + } + + fn page_size(&self) -> usize { + PAGE_SIZE + } + + fn alloc(&self, layout: Layout) -> AllocResult { + PerCpuSlab::alloc(self, layout) + } + + fn add_slab(&self, size_class: SizeClass, base: usize, bytes: usize) { + PerCpuSlab::add_slab(self, size_class, base, bytes) + } + + fn dealloc_local(&self, ptr: NonNull, layout: Layout) -> SlabDeallocResult { + PerCpuSlab::dealloc_local(self, ptr, layout) + } +} + +impl SlabPoolTrait for StaticSlabPool { + fn current_slab(&self) -> &dyn SlabTrait { + &self.slabs[(self.current_cpu_id)()] + } + + fn owner_slab(&self, cpu_idx: usize) -> &dyn SlabTrait { + &self.slabs[cpu_idx] + } +} diff --git a/components/buddy-slab-allocator/src/slab/page.rs b/components/buddy-slab-allocator/src/slab/page.rs new file mode 100644 index 0000000000..d8ef5c4453 --- /dev/null +++ b/components/buddy-slab-allocator/src/slab/page.rs @@ -0,0 +1,329 @@ +/// Slab page header, bitmap-based object tracking, and lock-free remote free. +/// +/// Each slab page starts with a [`SlabPageHeader`] followed by the object array. +/// Local (owner-CPU) operations use a bitmap under the slab lock. +/// Remote (cross-CPU) frees use an atomic CAS stack — no lock required. +use core::ptr::NonNull; +use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; + +use super::size_class::SizeClass; + +/// Magic number written at the start of every slab page header. +pub const SLAB_MAGIC: u32 = 0x534C_4142; // "SLAB" + +/// Maximum objects per slab page (512 = 8 × u64 bitmap words). +pub const MAX_OBJECTS_PER_SLAB: usize = 512; + +/// Number of u64 words in the local bitmap. +pub const BITMAP_WORDS: usize = MAX_OBJECTS_PER_SLAB / 64; // 8 + +/// Maximum number of pages a slab may span. +pub const MAX_SLAB_PAGES: usize = 4; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum SlabListState { + None, + Partial, + Full, + Empty, +} + +/// Header placed at the very start of each slab page. +/// +/// Object data starts at `header_end`, aligned to `size_class.size()`. +#[repr(C)] +pub struct SlabPageHeader { + /// Magic number for integrity checks. + pub magic: u32, + /// Which size class this slab serves. + pub size_class: SizeClass, + /// Total number of objects that fit. + pub object_count: u16, + /// Number of objects free in `local_bitmap`. + pub local_free_count: u16, + /// The CPU that owns this slab page (local alloc/dealloc go through this CPU's lock). + pub owner_cpu: u16, + _pad: u16, + /// Total usable bytes (slab pages × PAGE_SIZE). + pub slab_bytes: u32, + + // --- Intrusive doubly-linked list pointers (used by SlabCache) --- + pub list_prev: usize, + pub list_next: usize, + pub(crate) list_state: SlabListState, + + // --- Local bitmap (under slab lock, owner CPU only) --- + // A set bit means the slot is FREE. + pub local_bitmap: [u64; BITMAP_WORDS], + + // --- Lock-free remote free stack (any CPU) --- + /// Head of the remote-free linked list (object virtual address, 0 = empty). + /// Each freed object stores `next` at its start (reuses object memory). + pub remote_free_head: AtomicUsize, + /// Number of objects in the remote-free stack. + pub remote_free_count: AtomicU32, +} + +impl SlabPageHeader { + /// Size of the header in bytes. + pub const HEADER_SIZE: usize = core::mem::size_of::(); + + /// Initialise a slab page header. All objects are marked free in the bitmap. + /// + /// `base` is the virtual address of the page, `bytes` is the total slab size. + pub fn init(&mut self, size_class: SizeClass, bytes: usize, owner_cpu: u16) { + let obj_size = size_class.size(); + let data_start = Self::data_offset(obj_size); + let usable = bytes.saturating_sub(data_start); + let count = (usable / obj_size).min(MAX_OBJECTS_PER_SLAB); + + self.magic = SLAB_MAGIC; + self.size_class = size_class; + self.object_count = count as u16; + self.local_free_count = count as u16; + self.owner_cpu = owner_cpu; + self._pad = 0; + self.slab_bytes = bytes as u32; + self.list_prev = 0; + self.list_next = 0; + self.list_state = SlabListState::None; + self.local_bitmap = [0u64; BITMAP_WORDS]; + self.remote_free_head = AtomicUsize::new(0); + self.remote_free_count = AtomicU32::new(0); + + // Mark first `count` bits as 1 (free). + let full_words = count / 64; + let remaining_bits = count % 64; + for w in self.local_bitmap.iter_mut().take(full_words) { + *w = u64::MAX; + } + if remaining_bits > 0 { + self.local_bitmap[full_words] = (1u64 << remaining_bits) - 1; + } + } + + /// Byte offset from the start of the page to the first object. + /// Aligned up to `obj_size` for natural alignment. + pub fn data_offset(obj_size: usize) -> usize { + let raw = Self::HEADER_SIZE; + // Align to obj_size (which is always a power of two). + (raw + obj_size - 1) & !(obj_size - 1) + } + + /// Virtual address of the object region start (given `base` = page start). + #[inline] + pub fn data_start(&self, base: usize) -> usize { + base + Self::data_offset(self.size_class.size()) + } + + /// Virtual address of object at `index`. + #[inline] + pub fn object_addr(&self, base: usize, index: usize) -> usize { + self.data_start(base) + index * self.size_class.size() + } + + /// Index of the object whose address is `addr`. + #[inline] + pub fn object_index(&self, base: usize, addr: usize) -> usize { + (addr - self.data_start(base)) / self.size_class.size() + } + + /// Base address (slab start) from an object address. + /// + /// Searches backward by page because the slab base may no longer have + /// absolute `slab_bytes` alignment after metadata is carved from a region + /// prefix by [`GlobalAllocator`](crate::GlobalAllocator). + #[inline] + pub fn base_from_obj_addr(addr: usize, slab_bytes: usize) -> usize { + let slab_pages = slab_bytes / PAGE_SIZE; + debug_assert!(slab_pages > 0); + + let page_base = addr & !(PAGE_SIZE - 1); + for page_idx in 0..slab_pages { + let Some(candidate) = page_base.checked_sub(page_idx * PAGE_SIZE) else { + break; + }; + let hdr = unsafe { &*(candidate as *const SlabPageHeader) }; + if hdr.magic == SLAB_MAGIC + && hdr.slab_bytes as usize == slab_bytes + && addr >= candidate + && addr < candidate + slab_bytes + { + return candidate; + } + } + + debug_assert!(false, "object address does not belong to a live slab"); + page_base + } + + fn base_from_obj_addr_unknown_with_page_size(addr: usize, page_size: usize) -> Option { + if page_size == 0 || !page_size.is_power_of_two() { + return None; + } + let page_base = addr & !(page_size - 1); + for page_idx in 0..MAX_SLAB_PAGES { + let Some(candidate) = page_base.checked_sub(page_idx * page_size) else { + break; + }; + let hdr = unsafe { &*(candidate as *const SlabPageHeader) }; + let slab_bytes = hdr.slab_bytes as usize; + if hdr.magic != SLAB_MAGIC + || slab_bytes == 0 + || !slab_bytes.is_multiple_of(page_size) + || slab_bytes / page_size > MAX_SLAB_PAGES + { + continue; + } + if addr >= candidate && addr < candidate + slab_bytes { + return Some(candidate); + } + } + None + } + + /// Base address (slab start) from an object address without knowing the slab size. + /// + /// Searches backward up to [`MAX_SLAB_PAGES`] pages and validates each candidate + /// against the header's `slab_bytes`. + #[inline] + pub fn base_from_obj_addr_unknown(addr: usize) -> Option { + Self::base_from_obj_addr_unknown_with_page_size(addr, PAGE_SIZE) + } + + /// Queue the object on its owner's remote-free list. + /// + /// # Safety + /// - `ptr` must point to a valid live slab object. + /// - `owner_cpu` must match the slab header's owner CPU. + /// - `page_size` must be the slab allocator's page size. + pub unsafe fn remote_free_object(ptr: NonNull, owner_cpu: u16, page_size: usize) { + let obj_addr = ptr.as_ptr() as usize; + let Some(base) = Self::base_from_obj_addr_unknown_with_page_size(obj_addr, page_size) + else { + debug_assert!(false, "object address does not belong to a live slab"); + return; + }; + let hdr = unsafe { &*(base as *const SlabPageHeader) }; + debug_assert_eq!(hdr.magic, SLAB_MAGIC); + debug_assert_eq!(hdr.owner_cpu, owner_cpu); + unsafe { hdr.remote_free(obj_addr) }; + } + + // ------------------------------------------------------------------ + // Local allocation (under slab lock) + // ------------------------------------------------------------------ + + /// Allocate one object from the local bitmap. Returns the slot index or `None`. + pub fn local_alloc(&mut self) -> Option { + for (wi, word) in self.local_bitmap.iter_mut().enumerate() { + if *word != 0 { + let bit = word.trailing_zeros() as usize; + *word &= !(1u64 << bit); + self.local_free_count -= 1; + return Some(wi * 64 + bit); + } + } + None + } + + /// Free an object back to the local bitmap. + pub fn local_free(&mut self, index: usize) { + let wi = index / 64; + let bit = index % 64; + debug_assert!(self.local_bitmap[wi] & (1u64 << bit) == 0, "double free"); + self.local_bitmap[wi] |= 1u64 << bit; + self.local_free_count += 1; + } + + /// Whether this slab has any free objects (local bitmap only). + #[inline] + pub fn has_local_free(&self) -> bool { + self.local_free_count > 0 + } + + /// Whether every object in this slab is free (local bitmap only). + #[inline] + pub fn is_all_free(&self) -> bool { + self.local_free_count == self.object_count + } + + /// Whether the local bitmap is completely full (zero free objects locally). + #[inline] + pub fn is_local_full(&self) -> bool { + self.local_free_count == 0 + } + + pub(crate) fn prepare_for_buddy_free(&mut self) { + assert_eq!( + self.remote_free_head.load(Ordering::Acquire), + 0, + "returning slab with pending remote frees" + ); + self.list_prev = 0; + self.list_next = 0; + self.list_state = SlabListState::None; + self.magic = 0; + } + + // ------------------------------------------------------------------ + // Remote free (lock-free, any CPU) + // ------------------------------------------------------------------ + + /// Push `obj_addr` onto the remote-free stack (lock-free CAS). + /// + /// # Safety + /// - `obj_addr` must point to a previously allocated object within this slab. + /// - The object's first `size_of::()` bytes will be overwritten with + /// the next-pointer. + pub unsafe fn remote_free(&self, obj_addr: usize) { + unsafe { + loop { + let old_head = self.remote_free_head.load(Ordering::Acquire); + // Store "next" pointer inside the freed object. + (obj_addr as *mut usize).write(old_head); + if self + .remote_free_head + .compare_exchange_weak(old_head, obj_addr, Ordering::AcqRel, Ordering::Relaxed) + .is_ok() + { + self.remote_free_count.fetch_add(1, Ordering::Relaxed); + return; + } + } + } + } + + /// Drain all remote frees back into the local bitmap. + /// + /// Must be called under the owner-CPU slab lock. + pub fn drain_remote_frees(&mut self, base: usize) { + let head = self.remote_free_head.swap(0, Ordering::AcqRel); + if head == 0 { + return; + } + // Also zero the count (we'll re-add to local). + self.remote_free_count.store(0, Ordering::Relaxed); + + let mut ptr = head; + while ptr != 0 { + let next = unsafe { *(ptr as *const usize) }; + let idx = self.object_index(base, ptr); + let wi = idx / 64; + let bit = idx % 64; + debug_assert!( + self.local_bitmap[wi] & (1u64 << bit) == 0, + "remote double free" + ); + self.local_bitmap[wi] |= 1u64 << bit; + self.local_free_count += 1; + ptr = next; + } + } + + /// Whether any remote frees are pending. + #[inline] + pub fn has_remote_frees(&self) -> bool { + self.remote_free_head.load(Ordering::Acquire) != 0 + } +} diff --git a/components/buddy-slab-allocator/src/slab/size_class.rs b/components/buddy-slab-allocator/src/slab/size_class.rs new file mode 100644 index 0000000000..8f16fb8069 --- /dev/null +++ b/components/buddy-slab-allocator/src/slab/size_class.rs @@ -0,0 +1,91 @@ +/// Object size classes for the slab allocator. +/// +/// Each size class corresponds to a fixed object size. +/// Allocations are rounded up to the nearest size class. +use core::alloc::Layout; + +/// Number of distinct size classes. +pub const SIZE_CLASS_COUNT: usize = 9; + +/// Fixed set of object sizes served by the slab allocator. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum SizeClass { + Bytes8 = 0, + Bytes16 = 1, + Bytes32 = 2, + Bytes64 = 3, + Bytes128 = 4, + Bytes256 = 5, + Bytes512 = 6, + Bytes1024 = 7, + Bytes2048 = 8, +} + +/// Maximum object size handled by the slab. +pub const SLAB_MAX_SIZE: usize = 2048; + +/// Ordered table of (object_size, index) for all classes. +const CLASS_SIZES: [usize; SIZE_CLASS_COUNT] = [8, 16, 32, 64, 128, 256, 512, 1024, 2048]; + +impl SizeClass { + /// All size classes in ascending order. + pub const ALL: [SizeClass; SIZE_CLASS_COUNT] = [ + SizeClass::Bytes8, + SizeClass::Bytes16, + SizeClass::Bytes32, + SizeClass::Bytes64, + SizeClass::Bytes128, + SizeClass::Bytes256, + SizeClass::Bytes512, + SizeClass::Bytes1024, + SizeClass::Bytes2048, + ]; + + /// Number of distinct size classes. + pub const COUNT: usize = SIZE_CLASS_COUNT; + + /// Select the smallest size class that can satisfy `layout`. + /// + /// Returns `None` if the requested size or alignment exceeds the slab's capability. + pub fn from_layout(layout: Layout) -> Option { + let size = layout.size().max(layout.align()); + if size > SLAB_MAX_SIZE { + return None; + } + for (i, &class_size) in CLASS_SIZES.iter().enumerate() { + if size <= class_size { + return Some(SizeClass::ALL[i]); + } + } + None + } + + /// Object size in bytes. + pub const fn size(self) -> usize { + CLASS_SIZES[self as usize] + } + + /// Array index (0-based). + pub const fn index(self) -> usize { + self as usize + } + + /// How many pages are needed for a single slab of this class. + /// + /// Smaller classes use 1 page, larger classes may use more to amortise the + /// per-page header overhead. + pub const fn slab_pages(self, page_size: usize) -> usize { + let obj_size = self.size(); + if obj_size <= 256 { + 1 + } else if obj_size <= 1024 { + 2 + } else { + // 2048-byte objects: 4 pages → header + room for objects + let v = 16 * page_size / (obj_size * 8); + let v = if v < 4 { v } else { 4 }; + if v < 1 { 1 } else { v } + } + } +} diff --git a/components/buddy-slab-allocator/tests/README.md b/components/buddy-slab-allocator/tests/README.md new file mode 100644 index 0000000000..227f624869 --- /dev/null +++ b/components/buddy-slab-allocator/tests/README.md @@ -0,0 +1,48 @@ +# Allocator Test Suite + +本目录包含 allocator 的集成测试与压力测试。 + +## 结构 + +- `integration_test.rs` + 常规集成测试,覆盖全局分配器、Buddy、Slab 以及多模块协同行为。 +- `dma32_pages_test.rs` + 低地址页分配相关测试。 +- `stress_test.rs` + 长时间随机、耗尽恢复、碎片化恢复,以及真实多线程跨 CPU 压力测试。 + 这些测试默认使用 `#[ignore]`,不会进入常规 `cargo test` 路径。 +- `common/` + 共享测试辅助模块,提供宿主堆管理、线程本地 CPU mock、固定种子 RNG 和通用初始化逻辑。 + +单元测试仍位于 `src/**/*.rs` 的 `#[cfg(test)]` 模块中,文档测试位于公共 API 注释中。 + +## 常用命令 + +```bash +# 常规测试 +cargo test + +# 串行执行,便于排查测试交互 +cargo test -- --test-threads=1 + +# 仅运行常规集成测试 +cargo test --test integration_test + +# 仅运行压力测试 +cargo test --test stress_test -- --ignored --nocapture + +# 串行运行压力测试,便于定位多线程问题 +cargo test --test stress_test -- --ignored --nocapture --test-threads=1 +``` + +## 设计原则 + +- 常规测试应保持快速、稳定,可直接进入 CI。 +- 压力测试用于长时间 workload、真实多线程 cross-CPU 交互、耗尽恢复、碎片化恢复与统计不变量检查。 +- 性能测量移至 `benches/`,不在测试中混入 benchmark 逻辑。 + +## 注意事项 + +1. 所有测试都使用宿主分配器申请一块测试堆。 +2. 压力测试默认被忽略,需要显式运行。 +3. 多线程压力测试默认被 `#[ignore]` 标记,需要显式运行。 diff --git a/components/buddy-slab-allocator/tests/common/mod.rs b/components/buddy-slab-allocator/tests/common/mod.rs new file mode 100644 index 0000000000..2939707523 --- /dev/null +++ b/components/buddy-slab-allocator/tests/common/mod.rs @@ -0,0 +1,190 @@ +#![allow(dead_code)] + +use core::ptr::NonNull; +use std::{ + alloc::{Layout, alloc, dealloc}, + cell::Cell, + sync::{Mutex, MutexGuard, OnceLock}, +}; + +use buddy_slab_allocator::{ + __reset_global_allocator_singleton_for_tests, GlobalAllocator, PerCpuSlab, SlabPoolTrait, + eii::{slab_pool_impl, virt_to_phys_impl}, +}; +use rand::{SeedableRng, rngs::StdRng}; + +thread_local! { + static CURRENT_CPU: Cell = const { Cell::new(0) }; +} + +const TEST_PAGE_SIZE: usize = 0x1000; +const MAX_TEST_CPUS: usize = 64; + +fn lowmem_map(vaddr: usize) -> usize { + vaddr & 0x0FFF_FFFF +} + +pub struct GlobalTestContext { + _guard: MutexGuard<'static, ()>, +} + +struct TestSlabPool { + slabs: &'static [PerCpuSlab], +} + +fn global_test_lock() -> &'static Mutex<()> { + static LOCK: OnceLock> = OnceLock::new(); + LOCK.get_or_init(|| Mutex::new(())) +} + +impl Drop for GlobalTestContext { + fn drop(&mut self) { + __reset_global_allocator_singleton_for_tests(); + } +} + +fn test_cpu_slabs() -> &'static [PerCpuSlab] { + static SLABS: OnceLock]>> = OnceLock::new(); + SLABS.get_or_init(|| { + (0..MAX_TEST_CPUS) + .map(|cpu| PerCpuSlab::new(cpu as u16)) + .collect::>() + .into_boxed_slice() + }) +} + +fn reset_cpu_slabs(cpu_count: usize) { + assert!( + cpu_count <= MAX_TEST_CPUS, + "cpu_count exceeds test slab pool" + ); + for slab in &test_cpu_slabs()[..cpu_count] { + slab.reset(); + } +} + +impl SlabPoolTrait for TestSlabPool { + fn current_slab(&self) -> &dyn buddy_slab_allocator::SlabTrait { + &self.slabs[CURRENT_CPU.with(|slot| slot.get())] + } + + fn owner_slab(&self, cpu_idx: usize) -> &dyn buddy_slab_allocator::SlabTrait { + &self.slabs[cpu_idx] + } +} + +fn test_slab_pool_ref() -> &'static TestSlabPool { + static POOL: OnceLock = OnceLock::new(); + POOL.get_or_init(|| TestSlabPool { + slabs: test_cpu_slabs(), + }) +} + +#[virt_to_phys_impl] +fn test_virt_to_phys(vaddr: usize) -> usize { + lowmem_map(vaddr) +} + +#[slab_pool_impl] +fn test_slab_pool() -> &'static dyn SlabPoolTrait { + test_slab_pool_ref() +} + +pub fn set_current_cpu(cpu: usize) { + CURRENT_CPU.with(|slot| slot.set(cpu)); +} + +pub fn seeded_rng(seed: u64) -> StdRng { + StdRng::seed_from_u64(seed) +} + +pub struct HostRegion { + ptr: *mut u8, + layout: Layout, +} + +impl HostRegion { + pub fn new(size: usize, align: usize) -> Self { + let layout = Layout::from_size_align(size, align).unwrap(); + let ptr = unsafe { alloc(layout) }; + assert!(!ptr.is_null(), "host alloc failed"); + Self { ptr, layout } + } + + pub fn addr(&self) -> usize { + self.ptr as usize + } + + pub fn len(&self) -> usize { + self.layout.size() + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.ptr + } + + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.ptr, self.layout.size()) } + } + + pub unsafe fn subslice(&mut self, offset: usize, len: usize) -> &mut [u8] { + unsafe { std::slice::from_raw_parts_mut(self.ptr.add(offset), len) } + } +} + +impl Drop for HostRegion { + fn drop(&mut self) { + unsafe { dealloc(self.ptr, self.layout) }; + } +} + +pub fn init_global_slice( + allocator: &GlobalAllocator, + region: &mut [u8], + cpu_count: usize, +) -> GlobalTestContext { + let ctx = global_test_context::(cpu_count); + unsafe { allocator.init(region).unwrap() }; + ctx +} + +pub fn init_global( + allocator: &GlobalAllocator, + region: &mut HostRegion, + cpu_count: usize, +) -> GlobalTestContext { + init_global_slice(allocator, region.as_mut_slice(), cpu_count) +} + +pub fn global_test_context(cpu_count: usize) -> GlobalTestContext { + assert_eq!( + PAGE_SIZE, TEST_PAGE_SIZE, + "test EII slab pool only supports PAGE_SIZE={TEST_PAGE_SIZE:#x}" + ); + let guard = global_test_lock() + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + set_current_cpu(0); + reset_cpu_slabs(cpu_count); + GlobalTestContext { _guard: guard } +} + +pub fn count_free_pages(allocator: &GlobalAllocator) -> usize { + let mut addrs = Vec::new(); + while let Ok(addr) = allocator.alloc_pages(1, PAGE_SIZE) { + addrs.push(addr); + } + let count = addrs.len(); + for addr in addrs { + allocator.dealloc_pages(addr, 1); + } + count +} + +pub fn nonnull_from_addr(addr: usize) -> NonNull { + unsafe { NonNull::new_unchecked(addr as *mut u8) } +} + +pub fn virt_to_phys(vaddr: usize) -> usize { + lowmem_map(vaddr) +} diff --git a/components/buddy-slab-allocator/tests/dma32_pages_test.rs b/components/buddy-slab-allocator/tests/dma32_pages_test.rs new file mode 100644 index 0000000000..c4420aa045 --- /dev/null +++ b/components/buddy-slab-allocator/tests/dma32_pages_test.rs @@ -0,0 +1,101 @@ +//! Tests for lowmem (DMA32) page allocation via GlobalAllocator. + +extern crate buddy_slab_allocator; + +mod common; + +use buddy_slab_allocator::GlobalAllocator; +use common::{GlobalTestContext, HostRegion, init_global, virt_to_phys}; + +const PAGE_SIZE: usize = 0x1000; +const TEST_HEAP_SIZE: usize = 16 * 1024 * 1024; + +fn init_allocator( + allocator: &GlobalAllocator, + region: &mut HostRegion, +) -> GlobalTestContext { + init_global(allocator, region, 1) +} + +#[test] +fn test_lowmem_basic() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_allocator(&allocator, &mut region); + let section = allocator.managed_section(0).unwrap(); + let managed_start = section.start; + let managed_end = managed_start + section.size; + + let addr1 = allocator.alloc_pages_lowmem(1, PAGE_SIZE).unwrap(); + let addr2 = allocator.alloc_pages_lowmem(4, PAGE_SIZE).unwrap(); + + assert!(addr1 >= managed_start && addr1 < managed_end); + assert!(addr2 >= managed_start && addr2 < managed_end); + assert_eq!(addr1 % PAGE_SIZE, 0); + assert_eq!(addr2 % PAGE_SIZE, 0); + + allocator.dealloc_pages(addr1, 1); + allocator.dealloc_pages(addr2, 4); +} + +#[test] +fn test_lowmem_aligned() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 2); + let allocator = GlobalAllocator::::new(); + let _ctx = init_allocator(&allocator, &mut region); + + let addr = allocator.alloc_pages_lowmem(1, 2 * PAGE_SIZE).unwrap(); + assert_eq!(addr % (2 * PAGE_SIZE), 0); + allocator.dealloc_pages(addr, 1); +} + +#[test] +fn test_lowmem_vs_normal() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_allocator(&allocator, &mut region); + + let addr_low = allocator.alloc_pages_lowmem(1, PAGE_SIZE).unwrap(); + let addr_normal = allocator.alloc_pages(1, PAGE_SIZE).unwrap(); + + assert!(addr_low >= allocator.managed_section(0).unwrap().start); + assert!(addr_normal >= allocator.managed_section(0).unwrap().start); + + allocator.dealloc_pages(addr_low, 1); + allocator.dealloc_pages(addr_normal, 1); +} + +#[test] +fn test_lowmem_stress() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_allocator(&allocator, &mut region); + + let mut addrs = Vec::new(); + for _ in 0..32 { + let addr = allocator.alloc_pages_lowmem(1, PAGE_SIZE).unwrap(); + addrs.push(addr); + } + for addr in addrs { + allocator.dealloc_pages(addr, 1); + } +} + +#[test] +fn global_add_region_unaligned_lowmem_alignment() { + const ALIGN_2M: usize = 2 * 1024 * 1024; + + let mut first = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE); + let mut second = HostRegion::new(8 * ALIGN_2M + 0x1234 + PAGE_SIZE, ALIGN_2M); + let allocator = GlobalAllocator::::new(); + let _ctx = init_allocator(&allocator, &mut first); + + let second_len = second.len(); + let second_slice = unsafe { second.subslice(0x1234, second_len - 0x1234 - PAGE_SIZE / 3) }; + unsafe { allocator.add_region(second_slice).unwrap() }; + + let addr = allocator.alloc_pages_lowmem(1, ALIGN_2M).unwrap(); + assert_eq!(addr % ALIGN_2M, 0); + assert!(virt_to_phys(addr) + PAGE_SIZE <= 0x1000_0000); + allocator.dealloc_pages(addr, 1); +} diff --git a/components/buddy-slab-allocator/tests/integration_test.rs b/components/buddy-slab-allocator/tests/integration_test.rs new file mode 100644 index 0000000000..0c82e098d2 --- /dev/null +++ b/components/buddy-slab-allocator/tests/integration_test.rs @@ -0,0 +1,1266 @@ +//! Integration tests for the buddy-slab-allocator crate. + +extern crate buddy_slab_allocator; +mod common; + +use core::{alloc::Layout, ptr::NonNull}; +use std::collections::BTreeSet; + +use buddy_slab_allocator::{ + AllocError, BuddyAllocator, GlobalAllocator, ManagedSection, SizeClass, SlabAllocResult, + SlabAllocator, SlabDeallocResult, slab::SlabPageHeader, +}; +use common::{ + GlobalTestContext, HostRegion, count_free_pages, global_test_context, + init_global as init_global_allocator, init_global_slice, set_current_cpu, +}; + +const PAGE_SIZE: usize = 0x1000; +const TEST_HEAP_SIZE: usize = 16 * 1024 * 1024; // 16 MiB + +fn buddy_region_size(heap_size: usize) -> usize { + heap_size + BuddyAllocator::::required_meta_size(heap_size) + PAGE_SIZE * 4 +} + +fn init_buddy(buddy: &mut BuddyAllocator, region: &mut HostRegion) -> ManagedSection { + unsafe { buddy.init(region.as_mut_slice()).unwrap() }; + buddy.section(0).unwrap() +} + +fn init_buddy_with_heap_alignment( + buddy: &mut BuddyAllocator, + region: &mut HostRegion, + heap_align: usize, +) -> ManagedSection { + for offset in (0..heap_align).step_by(PAGE_SIZE) { + if region.len() <= offset { + break; + } + let slice = unsafe { region.subslice(offset, region.len() - offset) }; + if unsafe { buddy.init(slice) }.is_ok() { + let section = buddy.section(0).unwrap(); + if section.start.is_multiple_of(heap_align) { + return section; + } + } + } + panic!("failed to find test region with heap alignment {heap_align:#x}"); +} + +fn primary_section(allocator: &GlobalAllocator) -> ManagedSection { + allocator.managed_section(0).unwrap() +} + +fn irregular_region(size: usize, offset: usize, trim: usize, host_align: usize) -> HostRegion { + HostRegion::new(size + offset + trim + PAGE_SIZE, host_align) +} + +// ====================================================================== +// Buddy allocator (standalone) tests +// ====================================================================== + +#[test] +fn buddy_basic_alloc_dealloc() { + let mut region = HostRegion::new(buddy_region_size(TEST_HEAP_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let section = init_buddy(&mut buddy, &mut region); + + let addr1 = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + assert!(addr1 >= section.start && addr1 < section.start + section.size); + assert_eq!(addr1 % PAGE_SIZE, 0); + + let addr4 = buddy.alloc_pages(4, PAGE_SIZE).unwrap(); + assert_eq!(addr4 % PAGE_SIZE, 0); + + let free_before = buddy.free_pages(); + buddy.dealloc_pages(addr1, 1); + buddy.dealloc_pages(addr4, 4); + assert!(buddy.free_pages() > free_before); +} + +#[test] +fn buddy_alignment() { + // Heap must be aligned to the highest alignment we test (PAGE_SIZE * 4) + let mut region = HostRegion::new( + buddy_region_size(TEST_HEAP_SIZE) + PAGE_SIZE * 4, + PAGE_SIZE * 4, + ); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy_with_heap_alignment(&mut buddy, &mut region, PAGE_SIZE * 4); + + let addr2 = buddy.alloc_pages(1, PAGE_SIZE * 2).unwrap(); + assert_eq!(addr2 % (PAGE_SIZE * 2), 0); + + let addr4 = buddy.alloc_pages(1, PAGE_SIZE * 4).unwrap(); + assert_eq!(addr4 % (PAGE_SIZE * 4), 0); + + buddy.dealloc_pages(addr2, 1); + buddy.dealloc_pages(addr4, 1); +} + +#[test] +fn buddy_add_region_unaligned_start_preserves_4k_alignment() { + const ALIGN_2M: usize = 2 * 1024 * 1024; + let mut first = HostRegion::new(buddy_region_size(32 * PAGE_SIZE) + ALIGN_2M, ALIGN_2M); + let mut second = irregular_region( + buddy_region_size(64 * PAGE_SIZE), + 0x1234, + PAGE_SIZE / 3, + 2 * 1024 * 1024, + ); + let mut buddy = BuddyAllocator::::new(); + let _first_section = init_buddy_with_heap_alignment(&mut buddy, &mut first, ALIGN_2M); + + let second_len = second.len(); + let second_slice = unsafe { second.subslice(0x1234, second_len - 0x1234 - PAGE_SIZE / 3) }; + + unsafe { buddy.add_region(second_slice).unwrap() }; + + let addr = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + assert_eq!(addr % PAGE_SIZE, 0); + buddy.dealloc_pages(addr, 1); +} + +#[test] +fn buddy_add_region_unaligned_start_preserves_2m_alignment() { + const ALIGN_2M: usize = 2 * 1024 * 1024; + + let mut first = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut second = irregular_region( + buddy_region_size(4 * ALIGN_2M), + PAGE_SIZE / 2, + PAGE_SIZE / 3, + ALIGN_2M, + ); + let mut buddy = BuddyAllocator::::new(); + let _first_section = init_buddy(&mut buddy, &mut first); + + let second_len = second.len(); + let second_slice = + unsafe { second.subslice(PAGE_SIZE / 2, second_len - PAGE_SIZE / 2 - PAGE_SIZE / 3) }; + + unsafe { buddy.add_region(second_slice).unwrap() }; + + let addr = buddy.alloc_pages(1, ALIGN_2M).unwrap(); + assert_eq!(addr % ALIGN_2M, 0); + buddy.dealloc_pages(addr, 1); +} + +#[test] +fn buddy_aligned_alloc_dealloc_uses_recorded_order() { + let heap_size = 64 * PAGE_SIZE; + let mut region = HostRegion::new( + buddy_region_size(heap_size) + PAGE_SIZE * 16, + PAGE_SIZE * 16, + ); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy_with_heap_alignment(&mut buddy, &mut region, PAGE_SIZE * 16); + + let free_before = buddy.free_pages(); + let addr = buddy.alloc_pages(4, PAGE_SIZE * 16).unwrap(); + buddy.dealloc_pages(addr, 4); + assert_eq!(buddy.free_pages(), free_before); +} + +#[test] +fn buddy_exhaust_and_recover() { + let heap_size = 64 * PAGE_SIZE; // Small heap + let mut region = HostRegion::new(buddy_region_size(heap_size), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + let mut addrs = Vec::new(); + while let Ok(addr) = buddy.alloc_pages(1, PAGE_SIZE) { + addrs.push(addr); + } + assert_eq!(buddy.free_pages(), 0); + + // Free half + for addr in addrs.drain(..addrs.len() / 2) { + buddy.dealloc_pages(addr, 1); + } + assert!(buddy.free_pages() > 0); + + // Allocate again + let addr = buddy.alloc_pages(1, PAGE_SIZE); + assert!(addr.is_ok()); + + // Cleanup + if let Ok(a) = addr { + buddy.dealloc_pages(a, 1); + } + for a in addrs { + buddy.dealloc_pages(a, 1); + } +} + +#[test] +fn buddy_merge_coalescing() { + let heap_size = 16 * PAGE_SIZE; + let mut region = HostRegion::new(buddy_region_size(heap_size), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + let initial_free = buddy.free_pages(); + + // Allocate two single pages + let a = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + let b = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + buddy.dealloc_pages(a, 1); + buddy.dealloc_pages(b, 1); + + // After freeing both, free_pages should return to initial + assert_eq!(buddy.free_pages(), initial_free); +} + +#[test] +fn buddy_fragmentation_blocks_high_order_then_recovers() { + let heap_size = 32 * PAGE_SIZE; + let mut region = HostRegion::new(buddy_region_size(heap_size), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let section = init_buddy(&mut buddy, &mut region); + + let mut addrs = Vec::new(); + while let Ok(addr) = buddy.alloc_pages(1, PAGE_SIZE) { + addrs.push(addr); + } + assert_eq!(addrs.len(), section.total_pages); + + for &addr in addrs.iter().step_by(2) { + buddy.dealloc_pages(addr, 1); + } + assert!(buddy.alloc_pages(2, PAGE_SIZE).is_err()); + + for &addr in addrs.iter().skip(1).step_by(2) { + buddy.dealloc_pages(addr, 1); + } + + let addr = buddy.alloc_pages(8, PAGE_SIZE).unwrap(); + buddy.dealloc_pages(addr, 8); + assert_eq!(buddy.free_pages(), section.total_pages); +} + +#[test] +fn buddy_high_order_full_cycle_restores_free_pages() { + let heap_size = 256 * PAGE_SIZE; + let mut region = HostRegion::new( + buddy_region_size(heap_size) + PAGE_SIZE * 16, + PAGE_SIZE * 16, + ); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy_with_heap_alignment(&mut buddy, &mut region, PAGE_SIZE * 16); + + let initial_free = buddy.free_pages(); + let requests = [ + (1usize, PAGE_SIZE), + (2, 2 * PAGE_SIZE), + (3, 4 * PAGE_SIZE), + (8, 8 * PAGE_SIZE), + (5, PAGE_SIZE), + (16, 16 * PAGE_SIZE), + ]; + let mut allocations = Vec::new(); + + for (count, align) in requests { + let addr = buddy.alloc_pages(count, align).unwrap(); + allocations.push((addr, count)); + } + assert!(buddy.free_pages() < initial_free); + + for (addr, count) in allocations.into_iter().rev() { + buddy.dealloc_pages(addr, count); + } + assert_eq!(buddy.free_pages(), initial_free); +} + +#[test] +fn buddy_add_region_enables_second_section_allocation() { + let mut first = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(64 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let first_section = init_buddy(&mut buddy, &mut first); + + while buddy.alloc_pages(1, PAGE_SIZE).is_ok() {} + assert_eq!(buddy.free_pages(), 0); + + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + assert_eq!(buddy.section_count(), 2); + let second_section = buddy.section(1).unwrap(); + + let addr = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + assert!(addr >= second_section.start && addr < second_section.start + second_section.size); + assert!(addr < first_section.start || addr >= first_section.start + first_section.size); +} + +#[test] +fn buddy_add_region_overlap_rejected() { + let mut region = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + let overlap = unsafe { region.subslice(1, region.len() - 1) }; + let err = unsafe { buddy.add_region(overlap) }.unwrap_err(); + assert_eq!(err, AllocError::MemoryOverlap); +} + +#[test] +fn buddy_alloc_pages_first_fit_by_registration_order() { + let mut first = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(64 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let first_section = init_buddy(&mut buddy, &mut first); + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + + let addr = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + assert!(addr >= first_section.start && addr < first_section.start + first_section.size); +} + +#[test] +fn buddy_lowmem_scans_across_sections() { + let mut first = HostRegion::new(buddy_region_size(16 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _first_section = init_buddy(&mut buddy, &mut first); + + while buddy.alloc_pages_lowmem(1, PAGE_SIZE).is_ok() {} + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + let second_section = buddy.section(1).unwrap(); + + let addr = buddy.alloc_pages_lowmem(1, PAGE_SIZE).unwrap(); + assert!(addr >= second_section.start && addr < second_section.start + second_section.size); +} + +#[test] +fn buddy_dealloc_pages_finds_correct_section() { + let mut first = HostRegion::new(buddy_region_size(16 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _first_section = init_buddy(&mut buddy, &mut first); + while buddy.alloc_pages(1, PAGE_SIZE).is_ok() {} + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + let baseline = buddy.free_pages(); + let second_section = buddy.section(1).unwrap(); + + let addr = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + assert!(addr >= second_section.start && addr < second_section.start + second_section.size); + buddy.dealloc_pages(addr, 1); + + assert_eq!(buddy.free_pages(), baseline); +} + +#[test] +fn buddy_total_and_free_pages_are_aggregated() { + let mut first = HostRegion::new(buddy_region_size(16 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let first_section = init_buddy(&mut buddy, &mut first); + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + let second_section = buddy.section(1).unwrap(); + + assert_eq!( + buddy.total_pages(), + first_section.total_pages + second_section.total_pages + ); + assert_eq!( + buddy.free_pages(), + first_section.free_pages + second_section.free_pages + ); +} + +#[test] +fn buddy_managed_bytes_matches_all_sections() { + let mut first = HostRegion::new(buddy_region_size(16 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let first_section = init_buddy(&mut buddy, &mut first); + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + let second_section = buddy.section(1).unwrap(); + + assert_eq!( + buddy.managed_bytes(), + first_section.size + second_section.size + ); +} + +#[test] +fn buddy_allocated_bytes_changes_with_page_alloc_free() { + let mut region = HostRegion::new(buddy_region_size(64 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + assert_eq!(buddy.allocated_bytes(), 0); + + let a = buddy.alloc_pages(1, PAGE_SIZE).unwrap(); + assert_eq!(buddy.allocated_bytes(), PAGE_SIZE); + + let b = buddy.alloc_pages(4, PAGE_SIZE).unwrap(); + assert_eq!(buddy.allocated_bytes(), 5 * PAGE_SIZE); + + buddy.dealloc_pages(a, 1); + assert_eq!(buddy.allocated_bytes(), 4 * PAGE_SIZE); + + buddy.dealloc_pages(b, 4); + assert_eq!(buddy.allocated_bytes(), 0); +} + +#[test] +fn buddy_allocated_bytes_zero_when_all_free() { + let mut region = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + assert_eq!(buddy.allocated_bytes(), 0); +} + +#[test] +fn buddy_allocated_bytes_aggregates_across_sections() { + let mut first = HostRegion::new(buddy_region_size(16 * PAGE_SIZE), PAGE_SIZE); + let mut second = HostRegion::new(buddy_region_size(32 * PAGE_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _first_section = init_buddy(&mut buddy, &mut first); + while buddy.alloc_pages(1, PAGE_SIZE).is_ok() {} + unsafe { buddy.add_region(second.as_mut_slice()).unwrap() }; + + let addr = buddy.alloc_pages(8, PAGE_SIZE).unwrap(); + assert_eq!( + buddy.allocated_bytes(), + buddy.managed_bytes() - buddy.free_pages() * PAGE_SIZE + ); + buddy.dealloc_pages(addr, 8); + assert_eq!( + buddy.allocated_bytes(), + buddy.managed_bytes() - buddy.free_pages() * PAGE_SIZE + ); +} + +// ====================================================================== +// Slab allocator (standalone) tests +// ====================================================================== + +#[test] +fn slab_basic() { + let mut region = HostRegion::new(buddy_region_size(TEST_HEAP_SIZE), PAGE_SIZE); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + let mut slab = SlabAllocator::::new(); + + let layout = Layout::from_size_align(64, 8).unwrap(); + // First alloc should request pages + match slab.alloc(layout).unwrap() { + SlabAllocResult::NeedsSlab { size_class, pages } => { + let addr = buddy.alloc_pages(pages, PAGE_SIZE).unwrap(); + slab.add_slab(size_class, addr, pages * PAGE_SIZE, 0); + } + SlabAllocResult::Allocated(_) => panic!("should need slab first"), + } + + // Now allocation should succeed + let ptr = match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(p) => p, + _ => panic!("expected allocated"), + }; + + // Dealloc + match slab.dealloc(ptr, layout) { + SlabDeallocResult::Done => {} + SlabDeallocResult::FreeSlab { .. } => {} // also valid + } +} + +#[test] +fn slab_many_objects() { + let mut region = HostRegion::new(buddy_region_size(TEST_HEAP_SIZE) + 0x10000, 0x10000); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy_with_heap_alignment(&mut buddy, &mut region, 0x10000); + + let mut slab = SlabAllocator::::new(); + let layout = Layout::from_size_align(32, 8).unwrap(); + + let mut ptrs = Vec::new(); + for _ in 0..200 { + loop { + match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(p) => { + ptrs.push(p); + break; + } + SlabAllocResult::NeedsSlab { size_class, pages } => { + let slab_bytes = pages * PAGE_SIZE; + let addr = buddy.alloc_pages(pages, slab_bytes).unwrap(); + slab.add_slab(size_class, addr, slab_bytes, 0); + } + } + } + } + + assert_eq!(ptrs.len(), 200); + for ptr in ptrs { + let _ = slab.dealloc(ptr, layout); + } +} + +#[test] +fn slab_all_size_classes() { + let mut region = HostRegion::new(buddy_region_size(TEST_HEAP_SIZE) + 0x10000, 0x10000); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy_with_heap_alignment(&mut buddy, &mut region, 0x10000); + + let mut slab = SlabAllocator::::new(); + let mut allocations = Vec::new(); + + for sc in SizeClass::ALL { + let layout = Layout::from_size_align(sc.size(), sc.size()).unwrap(); + loop { + match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(p) => { + allocations.push((p, layout)); + break; + } + SlabAllocResult::NeedsSlab { size_class, pages } => { + let slab_bytes = pages * PAGE_SIZE; + let addr = buddy.alloc_pages(pages, slab_bytes).unwrap(); + slab.add_slab(size_class, addr, slab_bytes, 0); + } + } + } + } + + assert_eq!(allocations.len(), SizeClass::COUNT); + for (ptr, layout) in allocations { + let _ = slab.dealloc(ptr, layout); + } +} + +#[test] +fn slab_reuses_freed_objects_same_size_class() { + let mut region = HostRegion::new(buddy_region_size(TEST_HEAP_SIZE), PAGE_SIZE * 4); + let mut buddy = BuddyAllocator::::new(); + let _section = init_buddy(&mut buddy, &mut region); + + let mut slab = SlabAllocator::::new(); + let layout = Layout::from_size_align(64, 8).unwrap(); + let (size_class, pages) = match slab.alloc(layout).unwrap() { + SlabAllocResult::NeedsSlab { size_class, pages } => (size_class, pages), + SlabAllocResult::Allocated(_) => panic!("should need slab first"), + }; + let slab_bytes = pages * PAGE_SIZE; + let addr = buddy.alloc_pages(pages, slab_bytes).unwrap(); + slab.add_slab(size_class, addr, slab_bytes, 0); + + let first = match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(ptr) => ptr, + SlabAllocResult::NeedsSlab { .. } => panic!("expected allocation from fresh slab"), + }; + let base = SlabPageHeader::base_from_obj_addr::(first.as_ptr() as usize, slab_bytes); + let hdr = unsafe { &*(base as *const SlabPageHeader) }; + let object_count = hdr.object_count as usize; + + let mut ptrs = Vec::with_capacity(object_count); + ptrs.push(first); + for _ in 1..object_count { + let ptr = match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(ptr) => ptr, + SlabAllocResult::NeedsSlab { .. } => panic!("expected same slab to satisfy alloc"), + }; + let ptr_base = + SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + assert_eq!(ptr_base, base); + ptrs.push(ptr); + } + assert!(matches!( + slab.alloc(layout).unwrap(), + SlabAllocResult::NeedsSlab { .. } + )); + + let freed_ptrs: Vec<_> = ptrs.iter().copied().step_by(2).collect(); + let freed_addrs: BTreeSet<_> = freed_ptrs.iter().map(|ptr| ptr.as_ptr() as usize).collect(); + for &ptr in &freed_ptrs { + assert!(matches!(slab.dealloc(ptr, layout), SlabDeallocResult::Done)); + } + + let mut reused_addrs = BTreeSet::new(); + for _ in 0..freed_addrs.len() { + let ptr = match slab.alloc(layout).unwrap() { + SlabAllocResult::Allocated(ptr) => ptr, + SlabAllocResult::NeedsSlab { .. } => panic!("expected reuse from freed slots"), + }; + let ptr_base = + SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + assert_eq!(ptr_base, base); + reused_addrs.insert(ptr.as_ptr() as usize); + } + assert_eq!(reused_addrs, freed_addrs); + + for ptr in ptrs { + let addr = ptr.as_ptr() as usize; + if !freed_addrs.contains(&addr) { + let _ = slab.dealloc(ptr, layout); + } + } + for addr in reused_addrs { + let ptr = unsafe { NonNull::new_unchecked(addr as *mut u8) }; + let _ = slab.dealloc(ptr, layout); + } +} + +#[test] +fn global_init_with_unaligned_region_preserves_large_alloc_alignment() { + const ALIGN_2M: usize = 2 * 1024 * 1024; + + let mut region = irregular_region(12 * ALIGN_2M, 0x1234, PAGE_SIZE / 3, ALIGN_2M); + let allocator = GlobalAllocator::::new(); + let region_len = region.len(); + let region_slice = unsafe { region.subslice(0x1234, region_len - 0x1234 - PAGE_SIZE / 3) }; + + let _ctx = init_global_slice(&allocator, region_slice, 1); + + let layout = Layout::from_size_align(ALIGN_2M, ALIGN_2M).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + assert_eq!((ptr.as_ptr() as usize) % ALIGN_2M, 0); + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_add_region_with_unaligned_slice_preserves_large_alloc_alignment() { + const ALIGN_2M: usize = 2 * 1024 * 1024; + + let allocator = GlobalAllocator::::new(); + let mut first = HostRegion::new(12 * ALIGN_2M, ALIGN_2M); + let _ctx = init_global_allocator(&allocator, &mut first, 1); + + let mut second = irregular_region(12 * ALIGN_2M, 0x1234, PAGE_SIZE / 3, ALIGN_2M); + let second_len = second.len(); + let second_slice = unsafe { second.subslice(0x1234, second_len - 0x1234 - PAGE_SIZE / 3) }; + unsafe { allocator.add_region(second_slice).unwrap() }; + + let layout = Layout::from_size_align(ALIGN_2M, ALIGN_2M).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + assert_eq!((ptr.as_ptr() as usize) % ALIGN_2M, 0); + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_add_region_unaligned_does_not_break_small_alloc() { + let allocator = GlobalAllocator::::new(); + let mut first = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE); + let _ctx = init_global_allocator(&allocator, &mut first, 1); + + let mut second = irregular_region( + buddy_region_size(TEST_HEAP_SIZE), + 0x1234, + PAGE_SIZE / 3, + PAGE_SIZE, + ); + let second_len = second.len(); + let second_slice = unsafe { second.subslice(0x1234, second_len - 0x1234 - PAGE_SIZE / 3) }; + unsafe { allocator.add_region(second_slice).unwrap() }; + + for layout in [ + Layout::from_size_align(64, 8).unwrap(), + Layout::from_size_align(128, 16).unwrap(), + Layout::from_size_align(512, 64).unwrap(), + ] { + let ptr = allocator.alloc(layout).unwrap(); + unsafe { + ptr.as_ptr().write_bytes(0x5a, layout.size()); + allocator.dealloc(ptr, layout); + } + } +} + +// ====================================================================== +// Global allocator tests +// ====================================================================== + +fn init_global( + allocator: &GlobalAllocator, + region: &mut HostRegion, + cpu_count: usize, +) -> GlobalTestContext { + init_global_allocator(allocator, region, cpu_count) +} + +#[test] +fn global_reinit_same_instance_rejected() { + let allocator = GlobalAllocator::::new(); + let _ctx = global_test_context::(1); + let mut first = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let mut second = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + + unsafe { allocator.init(first.as_mut_slice()).unwrap() }; + let err = unsafe { allocator.init(second.as_mut_slice()) }.unwrap_err(); + assert_eq!(err, AllocError::AlreadyInitialized); +} + +#[test] +fn global_second_live_instance_rejected() { + let first_allocator = GlobalAllocator::::new(); + let second_allocator = GlobalAllocator::::new(); + let _ctx = global_test_context::(1); + let mut first = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let mut second = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + + unsafe { first_allocator.init(first.as_mut_slice()).unwrap() }; + let err = unsafe { second_allocator.init(second.as_mut_slice()) }.unwrap_err(); + assert_eq!(err, AllocError::AlreadyInitialized); +} + +#[test] +fn global_failed_init_rolls_back_singleton() { + let bad_allocator = GlobalAllocator::::new(); + let good_allocator = GlobalAllocator::::new(); + let _ctx = global_test_context::(1); + let mut bad = HostRegion::new(PAGE_SIZE - 1, PAGE_SIZE); + let mut good = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + + let err = unsafe { bad_allocator.init(bad.as_mut_slice()) }.unwrap_err(); + assert_eq!(err, AllocError::InvalidParam); + unsafe { good_allocator.init(good.as_mut_slice()).unwrap() }; +} + +#[test] +fn global_page_alloc() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let region_addr = region.addr(); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + let section = primary_section(&allocator); + let managed_start = section.start; + let managed_end = managed_start + section.size; + + let addr = allocator.alloc_pages(4, PAGE_SIZE).unwrap(); + assert!(managed_start > region_addr); + assert!(addr >= managed_start && addr < managed_end); + assert_eq!(addr % PAGE_SIZE, 0); + allocator.dealloc_pages(addr, 4); +} + +#[test] +fn global_small_alloc() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + let layout = Layout::from_size_align(64, 8).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_large_alloc() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + let layout = Layout::from_size_align(8192, PAGE_SIZE).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_mixed_alloc() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + let sizes: &[(usize, usize)] = &[ + (8, 8), + (64, 8), + (1024, 8), + (4096, PAGE_SIZE), + (8192, PAGE_SIZE), + ]; + let mut allocations = Vec::new(); + for &(size, align) in sizes { + let layout = Layout::from_size_align(size, align).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + allocations.push((ptr, layout)); + } + for (ptr, layout) in allocations { + unsafe { allocator.dealloc(ptr, layout) }; + } +} + +#[test] +fn global_cross_cpu_free() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + + // Allocate on CPU 0 + set_current_cpu(0); + let layout = Layout::from_size_align(64, 8).unwrap(); + let mut ptrs = Vec::new(); + for _ in 0..10 { + ptrs.push(allocator.alloc(layout).unwrap()); + } + + // Free from CPU 1 (triggers remote free path) + set_current_cpu(1); + for ptr in ptrs { + unsafe { allocator.dealloc(ptr, layout) }; + } + + // Allocate on CPU 0 again — should drain remote frees and succeed + set_current_cpu(0); + let ptr = allocator.alloc(layout).unwrap(); + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_cross_cpu_free_drains_remote_queue() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + + set_current_cpu(0); + let layout = Layout::from_size_align(64, 8).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + + let slab_bytes = SizeClass::from_layout(layout) + .unwrap() + .slab_pages(PAGE_SIZE) + * PAGE_SIZE; + let base = SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + let hdr = unsafe { &*(base as *const SlabPageHeader) }; + assert_eq!(hdr.owner_cpu, 0); + assert_eq!( + hdr.remote_free_count + .load(core::sync::atomic::Ordering::Relaxed), + 0 + ); + + set_current_cpu(1); + unsafe { allocator.dealloc(ptr, layout) }; + assert_eq!( + hdr.remote_free_count + .load(core::sync::atomic::Ordering::Relaxed), + 1 + ); + assert_ne!( + hdr.remote_free_head + .load(core::sync::atomic::Ordering::Relaxed), + 0 + ); + + set_current_cpu(0); + let ptr2 = allocator.alloc(layout).unwrap(); + assert_eq!( + hdr.remote_free_count + .load(core::sync::atomic::Ordering::Relaxed), + 0 + ); + assert_eq!( + hdr.remote_free_head + .load(core::sync::atomic::Ordering::Relaxed), + 0 + ); + unsafe { allocator.dealloc(ptr2, layout) }; +} + +#[test] +fn global_cross_cpu_free_multiple_rounds_same_slab() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + + let layout = Layout::from_size_align(64, 8).unwrap(); + + set_current_cpu(0); + let first = allocator.alloc(layout).unwrap(); + let slab_bytes = SizeClass::from_layout(layout) + .unwrap() + .slab_pages(PAGE_SIZE) + * PAGE_SIZE; + let base = SlabPageHeader::base_from_obj_addr::(first.as_ptr() as usize, slab_bytes); + let hdr = unsafe { &*(base as *const SlabPageHeader) }; + let object_count = hdr.object_count as usize; + let mut ptrs = Vec::with_capacity(object_count); + ptrs.push(first); + for _ in 1..object_count { + let ptr = allocator.alloc(layout).unwrap(); + let ptr_base = + SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + assert_eq!(ptr_base, base); + ptrs.push(ptr); + } + + set_current_cpu(1); + for &ptr in &ptrs { + unsafe { allocator.dealloc(ptr, layout) }; + } + assert_eq!( + hdr.remote_free_count + .load(core::sync::atomic::Ordering::Relaxed) as usize, + object_count + ); + + set_current_cpu(0); + let mut drained = Vec::with_capacity(object_count); + for _ in 0..object_count { + drained.push(allocator.alloc(layout).unwrap()); + } + assert_eq!( + hdr.remote_free_count + .load(core::sync::atomic::Ordering::Relaxed), + 0 + ); + assert_eq!( + hdr.remote_free_head + .load(core::sync::atomic::Ordering::Relaxed), + 0 + ); + + for ptr in drained { + unsafe { allocator.dealloc(ptr, layout) }; + } +} + +#[test] +fn global_full_slab_remote_then_local_free_reuses_without_list_cycle() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + + let layout = Layout::from_size_align(64, 8).unwrap(); + + set_current_cpu(0); + let first = allocator.alloc(layout).unwrap(); + let slab_bytes = SizeClass::from_layout(layout) + .unwrap() + .slab_pages(PAGE_SIZE) + * PAGE_SIZE; + let base = SlabPageHeader::base_from_obj_addr::(first.as_ptr() as usize, slab_bytes); + let hdr = unsafe { &*(base as *const SlabPageHeader) }; + let object_count = hdr.object_count as usize; + + let mut ptrs = Vec::with_capacity(object_count); + ptrs.push(first); + for _ in 1..object_count { + let ptr = allocator.alloc(layout).unwrap(); + let ptr_base = + SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + assert_eq!(ptr_base, base); + ptrs.push(ptr); + } + assert_eq!(hdr.local_free_count, 0); + + let remote_count = 4; + set_current_cpu(1); + for &ptr in &ptrs[..remote_count] { + unsafe { allocator.dealloc(ptr, layout) }; + } + assert_ne!( + hdr.remote_free_head + .load(core::sync::atomic::Ordering::Acquire), + 0 + ); + + set_current_cpu(0); + for &ptr in &ptrs[remote_count..] { + unsafe { allocator.dealloc(ptr, layout) }; + } + assert_eq!( + hdr.remote_free_head + .load(core::sync::atomic::Ordering::Acquire), + 0 + ); + + let mut reused = Vec::with_capacity(object_count); + for _ in 0..object_count { + let ptr = allocator.alloc(layout).unwrap(); + let ptr_base = + SlabPageHeader::base_from_obj_addr::(ptr.as_ptr() as usize, slab_bytes); + assert_eq!(ptr_base, base); + reused.push(ptr); + } + + assert_ne!(hdr.list_prev, base, "slab list_prev points to itself"); + assert_ne!(hdr.list_next, base, "slab list_next points to itself"); + + for ptr in reused { + unsafe { allocator.dealloc(ptr, layout) }; + } +} + +#[test] +fn global_small_object_churn_then_large_alloc() { + const REGION_SIZE: usize = 8 * 1024 * 1024; + + let mut region = HostRegion::new(REGION_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + let small_layout = Layout::from_size_align(2048, 8).unwrap(); + let warmup = allocator.alloc(small_layout).unwrap(); + unsafe { allocator.dealloc(warmup, small_layout) }; + let baseline = count_free_pages(&allocator); + let mut ptrs = Vec::new(); + while let Ok(ptr) = allocator.alloc(small_layout) { + ptrs.push(ptr); + } + assert!(!ptrs.is_empty()); + + for ptr in ptrs { + unsafe { allocator.dealloc(ptr, small_layout) }; + } + + let large_layout = Layout::from_size_align(16 * PAGE_SIZE, PAGE_SIZE).unwrap(); + let ptr = allocator.alloc(large_layout).unwrap(); + unsafe { allocator.dealloc(ptr, large_layout) }; + assert_eq!(count_free_pages(&allocator), baseline); +} + +#[test] +fn global_cross_cpu_free_all_objects_recovers_backend_pages() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + + let layout = Layout::from_size_align(64, 8).unwrap(); + set_current_cpu(0); + let warmup = allocator.alloc(layout).unwrap(); + unsafe { allocator.dealloc(warmup, layout) }; + let baseline = count_free_pages(&allocator); + + let first = allocator.alloc(layout).unwrap(); + let slab_bytes = SizeClass::from_layout(layout) + .unwrap() + .slab_pages(PAGE_SIZE) + * PAGE_SIZE; + let base = SlabPageHeader::base_from_obj_addr::(first.as_ptr() as usize, slab_bytes); + let hdr = unsafe { &*(base as *const SlabPageHeader) }; + let object_count = hdr.object_count as usize; + + let mut ptrs = Vec::with_capacity(object_count); + ptrs.push(first); + for _ in 1..object_count { + ptrs.push(allocator.alloc(layout).unwrap()); + } + + set_current_cpu(1); + for &ptr in &ptrs { + unsafe { allocator.dealloc(ptr, layout) }; + } + + set_current_cpu(0); + let mut drained = Vec::with_capacity(object_count); + for _ in 0..object_count { + drained.push(allocator.alloc(layout).unwrap()); + } + for ptr in drained { + unsafe { allocator.dealloc(ptr, layout) }; + } + + assert_eq!(count_free_pages(&allocator), baseline); +} + +#[test] +fn global_lowmem_fragmentation_recovery() { + const REGION_SIZE: usize = 8 * 1024 * 1024; + + let mut region = HostRegion::new(REGION_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global_allocator(&allocator, &mut region, 1); + + let mut addrs = Vec::new(); + while let Ok(addr) = allocator.alloc_pages_lowmem(1, PAGE_SIZE) { + addrs.push(addr); + } + assert!(addrs.len() > 8); + + for &addr in addrs.iter().step_by(2) { + allocator.dealloc_pages(addr, 1); + } + assert!(allocator.alloc_pages_lowmem(2, 2 * PAGE_SIZE).is_err()); + + for &addr in addrs.iter().skip(1).step_by(2) { + allocator.dealloc_pages(addr, 1); + } + + let addr = allocator.alloc_pages_lowmem(2, 2 * PAGE_SIZE).unwrap(); + allocator.dealloc_pages(addr, 2); +} + +#[test] +fn global_lowmem_pages() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global_allocator(&allocator, &mut region, 1); + + let addr = allocator.alloc_pages_lowmem(1, PAGE_SIZE).unwrap(); + assert!(addr >= primary_section(&allocator).start); + allocator.dealloc_pages(addr, 1); +} + +#[test] +fn global_unaligned_region_start() { + let mut region = HostRegion::new(TEST_HEAP_SIZE + PAGE_SIZE, PAGE_SIZE * 4); + let region_start = region.addr() + 1; + let region_size = TEST_HEAP_SIZE; + let allocator = GlobalAllocator::::new(); + let unaligned_region = unsafe { region.subslice(1, region_size) }; + let _ctx = init_global_slice(&allocator, unaligned_region, 1); + + let section = primary_section(&allocator); + let managed_start = section.start; + let managed_end = managed_start + section.size; + + assert_eq!(managed_start % PAGE_SIZE, 0); + assert!(managed_start >= region_start); + assert!(managed_end <= region_start + region_size); + + let addr = allocator.alloc_pages(1, PAGE_SIZE).unwrap(); + assert!(addr >= managed_start && addr < managed_end); + allocator.dealloc_pages(addr, 1); +} + +#[test] +fn global_rejects_region_without_one_managed_page() { + let region_size = PAGE_SIZE - 1; + let mut region = HostRegion::new(region_size, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = global_test_context::(1); + + let err = unsafe { allocator.init(region.as_mut_slice()) }.unwrap_err(); + assert_eq!(err, AllocError::InvalidParam); +} + +#[test] +fn global_add_region_after_init_expands_capacity() { + let mut first = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let mut second = HostRegion::new(8 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, 1); + + let before = count_free_pages(&allocator); + unsafe { allocator.add_region(second.as_mut_slice()).unwrap() }; + let after = count_free_pages(&allocator); + + assert!(after > before); + assert_eq!(allocator.managed_section_count(), 2); +} + +#[test] +fn global_add_region_supports_discontiguous_regions() { + let mut first = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let mut second = HostRegion::new(8 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, 1); + + while allocator.alloc_pages(1, PAGE_SIZE).is_ok() {} + unsafe { allocator.add_region(second.as_mut_slice()).unwrap() }; + let second_section = allocator.managed_section(1).unwrap(); + + let addr = allocator.alloc_pages(1, PAGE_SIZE).unwrap(); + assert!(addr >= second_section.start && addr < second_section.start + second_section.size); +} + +#[test] +fn global_large_alloc_can_come_from_added_region() { + let mut first = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let mut second = HostRegion::new(8 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, 1); + + while allocator.alloc_pages(1, PAGE_SIZE).is_ok() {} + unsafe { allocator.add_region(second.as_mut_slice()).unwrap() }; + let second_section = allocator.managed_section(1).unwrap(); + + let layout = Layout::from_size_align(8 * PAGE_SIZE, PAGE_SIZE).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + let addr = ptr.as_ptr() as usize; + assert!(addr >= second_section.start && addr < second_section.start + second_section.size); + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_managed_section_queries_report_all_sections() { + let mut first = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let mut second = HostRegion::new(8 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, 1); + unsafe { allocator.add_region(second.as_mut_slice()).unwrap() }; + + assert_eq!(allocator.managed_section_count(), 2); + let first_section = allocator.managed_section(0).unwrap(); + let second_section = allocator.managed_section(1).unwrap(); + assert!(first_section.size > 0); + assert!(second_section.size > 0); +} + +#[test] +fn global_add_region_overlap_rejected() { + let mut first = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, 1); + + let overlap = unsafe { first.subslice(1, first.len() - 1) }; + let err = unsafe { allocator.add_region(overlap) }.unwrap_err(); + assert_eq!(err, AllocError::MemoryOverlap); +} + +#[test] +fn global_managed_bytes_matches_all_sections() { + let mut first = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let mut second = HostRegion::new(8 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, 1); + unsafe { allocator.add_region(second.as_mut_slice()).unwrap() }; + + let expected = (0..allocator.managed_section_count()) + .map(|i| allocator.managed_section(i).unwrap().size) + .sum::(); + assert_eq!(allocator.managed_bytes(), expected); +} + +#[test] +fn global_allocated_bytes_changes_with_large_alloc() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + assert_eq!(allocator.allocated_bytes(), 0); + + let layout = Layout::from_size_align(3 * PAGE_SIZE, PAGE_SIZE).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + assert_eq!(allocator.allocated_bytes(), 4 * PAGE_SIZE); + + unsafe { allocator.dealloc(ptr, layout) }; + assert_eq!(allocator.allocated_bytes(), 0); +} + +#[test] +fn global_allocated_bytes_reflects_slab_pages() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + assert_eq!(allocator.allocated_bytes(), 0); + + let layout = Layout::from_size_align(64, 8).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + assert!(allocator.allocated_bytes() >= PAGE_SIZE); + + unsafe { allocator.dealloc(ptr, layout) }; +} + +#[test] +fn global_allocated_bytes_not_zero_until_cached_empty_slab_released() { + let mut region = HostRegion::new(TEST_HEAP_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + + let layout = Layout::from_size_align(64, 8).unwrap(); + let ptr = allocator.alloc(layout).unwrap(); + let allocated_after_refill = allocator.allocated_bytes(); + assert!(allocated_after_refill >= PAGE_SIZE); + + unsafe { allocator.dealloc(ptr, layout) }; + + // One empty slab may remain cached, so backend occupancy need not drop to zero. + assert!(allocator.allocated_bytes() <= allocated_after_refill); + assert!(allocator.allocated_bytes() >= PAGE_SIZE); +} diff --git a/components/buddy-slab-allocator/tests/stress_test.rs b/components/buddy-slab-allocator/tests/stress_test.rs new file mode 100644 index 0000000000..20c0ceb663 --- /dev/null +++ b/components/buddy-slab-allocator/tests/stress_test.rs @@ -0,0 +1,485 @@ +//! Stress tests for allocator stability. + +mod common; + +use std::{ + alloc::Layout, + sync::{ + Barrier, Mutex, + atomic::{AtomicBool, Ordering}, + }, + thread, +}; + +use buddy_slab_allocator::{GlobalAllocator, SizeClass}; +use common::{ + HostRegion, count_free_pages, init_global, nonnull_from_addr, seeded_rng, set_current_cpu, +}; +use rand::RngExt; + +const PAGE_SIZE: usize = 0x1000; +const HEAP_SIZE: usize = 64 * 1024 * 1024; +const WORKERS: usize = 4; + +fn assert_recovered_with_cached_slabs( + allocator: &GlobalAllocator, + baseline: usize, + cpu_count: usize, + cached_classes: &[SizeClass], +) { + let recovered = count_free_pages(allocator); + let retained_pages = cached_classes + .iter() + .map(|sc| sc.slab_pages(PAGE_SIZE)) + .sum::() + * cpu_count; + assert!( + recovered + retained_pages >= baseline, + "recovered {recovered} pages, baseline {baseline}, retained allowance {retained_pages}", + ); +} + +#[test] +#[ignore = "stress test"] +fn stress_random_mixed_alloc_free() { + let mut region = HostRegion::new(HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + let mut rng = seeded_rng(0); + let mut allocated: Vec<(usize, Layout)> = Vec::new(); + + for i in 0..10_000 { + set_current_cpu(i % 2); + if allocated.is_empty() || rng.random_bool(0.65) { + let size: usize = rng.random_range(8..8193); + let layout = if size <= 2048 { + Layout::from_size_align(size.next_power_of_two().min(2048), 8).unwrap() + } else { + let aligned = size.div_ceil(PAGE_SIZE) * PAGE_SIZE; + Layout::from_size_align(aligned, PAGE_SIZE).unwrap() + }; + + if let Ok(ptr) = allocator.alloc(layout) { + allocated.push((ptr.as_ptr() as usize, layout)); + } + } else { + let idx = rng.random_range(0..allocated.len()); + let (addr, layout) = allocated.swap_remove(idx); + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + } + + for (addr, layout) in allocated { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } +} + +#[test] +#[ignore = "stress test"] +fn stress_exhaustion_recovery() { + let mut region = HostRegion::new(HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 1); + let layout = Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap(); + let mut allocated = Vec::new(); + + while let Ok(ptr) = allocator.alloc(layout) { + allocated.push(ptr.as_ptr() as usize); + } + + for addr in allocated.drain(..allocated.len() / 4) { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + + let recovered = allocator.alloc(layout); + assert!(recovered.is_ok()); + + if let Ok(ptr) = recovered { + unsafe { allocator.dealloc(ptr, layout) }; + } + + for addr in allocated { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } +} + +#[test] +#[ignore = "stress test"] +fn stress_fragmentation_recovery() { + let mut region = HostRegion::new(HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, 2); + let small_layout = Layout::from_size_align(64, 8).unwrap(); + let mut small_ptrs = Vec::new(); + + for i in 0..4000 { + set_current_cpu(i % 2); + if let Ok(ptr) = allocator.alloc(small_layout) { + small_ptrs.push(ptr.as_ptr() as usize); + } + } + + for i in (0..small_ptrs.len()).step_by(2) { + unsafe { allocator.dealloc(nonnull_from_addr(small_ptrs[i]), small_layout) }; + } + + let large_layout = Layout::from_size_align(PAGE_SIZE * 16, PAGE_SIZE).unwrap(); + let large = allocator.alloc(large_layout); + + for addr in small_ptrs.into_iter().skip(1).step_by(2) { + unsafe { allocator.dealloc(nonnull_from_addr(addr), small_layout) }; + } + + if let Ok(ptr) = large { + unsafe { allocator.dealloc(ptr, large_layout) }; + } +} + +#[test] +#[ignore = "stress test"] +fn stress_multithread_mixed_alloc_free() { + let mut region = HostRegion::new(HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, WORKERS); + let baseline = count_free_pages(&allocator); + let allocator = &allocator; + let barrier = Barrier::new(WORKERS); + + thread::scope(|scope| { + for cpu in 0..WORKERS { + let barrier = &barrier; + scope.spawn(move || { + set_current_cpu(cpu); + barrier.wait(); + + let mut rng = seeded_rng(0x1000 + cpu as u64); + let mut live: Vec<(usize, Layout)> = Vec::new(); + for _ in 0..4_000 { + if live.is_empty() || rng.random_bool(0.65) { + let layout = if rng.random_bool(0.7) { + let size: usize = rng.random_range(8..=2048); + Layout::from_size_align(size.next_power_of_two().min(2048), 8).unwrap() + } else { + let page_counts = [1usize, 2, 4, 8]; + let pages = page_counts[rng.random_range(0..page_counts.len())]; + Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap() + }; + + if let Ok(ptr) = allocator.alloc(layout) { + live.push((ptr.as_ptr() as usize, layout)); + } + } else { + let idx = rng.random_range(0..live.len()); + let (addr, layout) = live.swap_remove(idx); + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + } + + for (addr, layout) in live { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + }); + } + }); + + assert_recovered_with_cached_slabs(allocator, baseline, WORKERS, &SizeClass::ALL); +} + +#[test] +#[ignore = "stress test"] +fn stress_multithread_remote_free() { + let mut region = HostRegion::new(HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, WORKERS); + let baseline = count_free_pages(&allocator); + let allocator = &allocator; + let barrier = Barrier::new(WORKERS); + let layout = Layout::from_size_align(64, 8).unwrap(); + let queues: Vec<_> = (0..WORKERS) + .map(|_| Mutex::new(Vec::::new())) + .collect(); + + thread::scope(|scope| { + for cpu in 0..WORKERS { + let barrier = &barrier; + let queues = &queues; + scope.spawn(move || { + set_current_cpu(cpu); + let mut local = Vec::new(); + for _ in 0..256 { + local.push(allocator.alloc(layout).unwrap().as_ptr() as usize); + } + + let target = (cpu + 1) % WORKERS; + queues[target].lock().unwrap().extend(local); + barrier.wait(); + + let remote = { + let mut queue = queues[cpu].lock().unwrap(); + queue.drain(..).collect::>() + }; + for addr in remote { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + barrier.wait(); + + let mut drained = Vec::new(); + for _ in 0..256 { + drained.push(allocator.alloc(layout).unwrap()); + } + for ptr in drained { + unsafe { allocator.dealloc(ptr, layout) }; + } + barrier.wait(); + }); + } + }); + + assert_recovered_with_cached_slabs(allocator, baseline, WORKERS, &[SizeClass::Bytes64]); +} + +#[test] +#[ignore = "stress test"] +fn stress_multithread_page_alloc_free() { + let mut region = HostRegion::new(HEAP_SIZE, PAGE_SIZE); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, WORKERS); + let baseline = count_free_pages(&allocator); + let allocator = &allocator; + let barrier = Barrier::new(WORKERS); + + thread::scope(|scope| { + for cpu in 0..WORKERS { + let barrier = &barrier; + scope.spawn(move || { + set_current_cpu(cpu); + barrier.wait(); + + let mut rng = seeded_rng(0x2000 + cpu as u64); + let page_counts = [1usize, 2, 4, 8]; + let alignments = [PAGE_SIZE, 2 * PAGE_SIZE, 4 * PAGE_SIZE, 8 * PAGE_SIZE]; + let mut live = Vec::new(); + + for _ in 0..2_000 { + if live.is_empty() || rng.random_bool(0.6) { + let count = page_counts[rng.random_range(0..page_counts.len())]; + let align = alignments[rng.random_range(0..alignments.len())]; + if let Ok(addr) = allocator.alloc_pages(count, align.max(PAGE_SIZE)) { + live.push((addr, count)); + } + } else { + let idx = rng.random_range(0..live.len()); + let (addr, count) = live.swap_remove(idx); + allocator.dealloc_pages(addr, count); + } + } + + for (addr, count) in live { + allocator.dealloc_pages(addr, count); + } + }); + } + }); + + assert_eq!(count_free_pages(allocator), baseline); +} + +#[test] +#[ignore = "stress test"] +fn stress_multithread_fragmentation_recovery() { + const REGION_SIZE: usize = 4 * 1024 * 1024; + + let mut region = HostRegion::new(REGION_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, WORKERS); + let baseline = count_free_pages(&allocator); + let allocator = &allocator; + let barrier = Barrier::new(WORKERS); + let before_cleanup_failed = AtomicBool::new(false); + let partial_cleanup_failed = AtomicBool::new(false); + let large_layout = Layout::from_size_align(32 * PAGE_SIZE, PAGE_SIZE).unwrap(); + let small_layout = Layout::from_size_align(64, 8).unwrap(); + + thread::scope(|scope| { + for cpu in 0..WORKERS { + let barrier = &barrier; + let before_cleanup_failed = &before_cleanup_failed; + let partial_cleanup_failed = &partial_cleanup_failed; + scope.spawn(move || { + set_current_cpu(cpu); + let mut live = Vec::new(); + while let Ok(ptr) = allocator.alloc(small_layout) { + live.push(ptr.as_ptr() as usize); + } + + barrier.wait(); + if cpu == 0 { + match allocator.alloc(large_layout) { + Ok(ptr) => unsafe { + allocator.dealloc(ptr, large_layout); + }, + Err(_) => before_cleanup_failed.store(true, Ordering::Relaxed), + } + } + + barrier.wait(); + let mut retained = Vec::new(); + for (idx, addr) in live.into_iter().enumerate() { + if idx % 2 == 0 { + unsafe { allocator.dealloc(nonnull_from_addr(addr), small_layout) }; + } else { + retained.push(addr); + } + } + + barrier.wait(); + if cpu == 0 { + match allocator.alloc(large_layout) { + Ok(ptr) => unsafe { + allocator.dealloc(ptr, large_layout); + }, + Err(_) => partial_cleanup_failed.store(true, Ordering::Relaxed), + } + } + + barrier.wait(); + for addr in retained { + unsafe { allocator.dealloc(nonnull_from_addr(addr), small_layout) }; + } + + barrier.wait(); + if cpu == 0 { + let ptr = allocator.alloc(large_layout).unwrap(); + unsafe { allocator.dealloc(ptr, large_layout) }; + } + barrier.wait(); + }); + } + }); + + assert!(before_cleanup_failed.load(Ordering::Relaxed)); + assert!(partial_cleanup_failed.load(Ordering::Relaxed)); + assert_recovered_with_cached_slabs(allocator, baseline, WORKERS, &[SizeClass::Bytes64]); +} + +#[test] +#[ignore = "stress test"] +fn stress_multithread_exhaustion_recovery() { + const REGION_SIZE: usize = 8 * 1024 * 1024; + + let mut region = HostRegion::new(REGION_SIZE, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut region, WORKERS); + let baseline = count_free_pages(&allocator); + let allocator = &allocator; + let barrier = Barrier::new(WORKERS); + let exhausted = AtomicBool::new(false); + let recovered = AtomicBool::new(false); + let layout = Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap(); + + thread::scope(|scope| { + for cpu in 0..WORKERS { + let barrier = &barrier; + let exhausted = &exhausted; + let recovered = &recovered; + scope.spawn(move || { + set_current_cpu(cpu); + let mut live = Vec::new(); + while let Ok(ptr) = allocator.alloc(layout) { + live.push(ptr.as_ptr() as usize); + } + + barrier.wait(); + if cpu == 0 { + exhausted.store(allocator.alloc(layout).is_err(), Ordering::Relaxed); + } + + barrier.wait(); + let mut retained = Vec::new(); + for (idx, addr) in live.into_iter().enumerate() { + if idx % 4 == 0 { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } else { + retained.push(addr); + } + } + + barrier.wait(); + if cpu == 0 + && let Ok(ptr) = allocator.alloc(layout) + { + recovered.store(true, Ordering::Relaxed); + unsafe { allocator.dealloc(ptr, layout) }; + } + + barrier.wait(); + for addr in retained { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + barrier.wait(); + }); + } + }); + + assert!(exhausted.load(Ordering::Relaxed)); + assert!(recovered.load(Ordering::Relaxed)); + assert_eq!(count_free_pages(allocator), baseline); +} + +#[test] +#[ignore = "stress test"] +fn stress_add_region_then_multithread_alloc_free() { + let mut first = HostRegion::new(2 * 1024 * 1024, PAGE_SIZE * 4); + let mut second = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let mut third = HostRegion::new(4 * 1024 * 1024, PAGE_SIZE * 4); + let allocator = GlobalAllocator::::new(); + let _ctx = init_global(&allocator, &mut first, WORKERS); + unsafe { + allocator.add_region(second.as_mut_slice()).unwrap(); + allocator.add_region(third.as_mut_slice()).unwrap(); + } + assert_eq!(allocator.managed_section_count(), 3); + + let baseline = count_free_pages(&allocator); + let allocator = &allocator; + let barrier = Barrier::new(WORKERS); + + thread::scope(|scope| { + for cpu in 0..WORKERS { + let barrier = &barrier; + scope.spawn(move || { + set_current_cpu(cpu); + barrier.wait(); + + let mut rng = seeded_rng(0x3000 + cpu as u64); + let mut live: Vec<(usize, Layout)> = Vec::new(); + for _ in 0..5_000 { + if live.is_empty() || rng.random_bool(0.65) { + let layout = if rng.random_bool(0.75) { + let size: usize = rng.random_range(8..=2048); + Layout::from_size_align(size.next_power_of_two().min(2048), 8).unwrap() + } else { + let pages = [1usize, 2, 4, 8][rng.random_range(0..4)]; + Layout::from_size_align(pages * PAGE_SIZE, PAGE_SIZE).unwrap() + }; + + if let Ok(ptr) = allocator.alloc(layout) { + live.push((ptr.as_ptr() as usize, layout)); + } + } else { + let idx = rng.random_range(0..live.len()); + let (addr, layout) = live.swap_remove(idx); + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + } + + for (addr, layout) in live { + unsafe { allocator.dealloc(nonnull_from_addr(addr), layout) }; + } + }); + } + }); + + assert_eq!(allocator.managed_section_count(), 3); + assert_recovered_with_cached_slabs(allocator, baseline, WORKERS, &SizeClass::ALL); +} diff --git a/drivers/intc/arm-gic-driver/src/version/v2/mod.rs b/drivers/intc/arm-gic-driver/src/version/v2/mod.rs index 16fe5adfb6..1d1b1d44ce 100644 --- a/drivers/intc/arm-gic-driver/src/version/v2/mod.rs +++ b/drivers/intc/arm-gic-driver/src/version/v2/mod.rs @@ -526,6 +526,24 @@ impl CpuInterface { self.gicd().ISPENDR.get_irq_bit(id.into()) } + /// Send a Software Generated Interrupt (SGI) to target CPUs. + pub fn send_sgi(&self, sgi_id: IntId, target: SGITarget) { + let sgi_id = sgi_id.to_u32(); + assert!(sgi_id < 16, "Invalid SGI ID: {sgi_id}"); + let (filter, target_list) = match target { + SGITarget::TargetList(list) => ( + gicd::SGIR::TargetListFilter::TargetList, + list.as_u8() as u32, + ), + SGITarget::AllOther => (gicd::SGIR::TargetListFilter::AllOther, 0), + SGITarget::Current => (gicd::SGIR::TargetListFilter::Current, 0), + }; + + self.gicd().SGIR.write( + gicd::SGIR::SGIINTID.val(sgi_id) + gicd::SGIR::CPUTargetList.val(target_list) + filter, + ); + } + pub fn set_cfg(&self, id: IntId, trigger: Trigger) { self.gicd().set_cfg(id, trigger); } diff --git a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml index 899576e482..273b184467 100644 --- a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml +++ b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml @@ -1,7 +1,6 @@ env = { UIMAGE = "y" } features = [ "sg2002", - "myplat", ] log = "Info" plat_dyn = false diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index fbcf3b9aa4..1531ea32af 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -36,6 +36,7 @@ sg2002 = [ "dep:tock-registers", ] stack-guard-page = ["ax-feat/stack-guard-page"] +buddy-slab = ["ax-feat/buddy-slab"] [dependencies] ax-feat = { workspace = true, features = [ @@ -60,7 +61,6 @@ ax-alloc = { workspace = true, features = ["default"] } ax-config.workspace = true ax-display.workspace = true ax-fs = { version = "0.5.15", path = "../../arceos/modules/axfs-ng", package = "ax-fs-ng" } -ax-hal.workspace = true ax-input = { workspace = true, optional = true } ax-lazyinit.workspace = true ax-log.workspace = true diff --git a/os/StarryOS/kernel/src/entry.rs b/os/StarryOS/kernel/src/entry.rs index c0c11460a8..0e00b1728b 100644 --- a/os/StarryOS/kernel/src/entry.rs +++ b/os/StarryOS/kernel/src/entry.rs @@ -4,7 +4,7 @@ use alloc::{ }; use ax_fs::FS_CONTEXT; -use ax_hal::uspace::UserContext; +use ax_runtime::hal::cpu::uspace::UserContext; use ax_sync::Mutex; use ax_task::{AxTaskExt, spawn_task}; use starry_process::{Pid, Process}; diff --git a/os/StarryOS/kernel/src/file/memfd.rs b/os/StarryOS/kernel/src/file/memfd.rs index c3b2a3607f..a3f45191e9 100644 --- a/os/StarryOS/kernel/src/file/memfd.rs +++ b/os/StarryOS/kernel/src/file/memfd.rs @@ -30,10 +30,10 @@ use core::{ use ax_errno::{AxError, AxResult}; use ax_fs::FileFlags; -use ax_hal::paging::MappingFlags; use ax_io::{IoBuf, SeekFrom, prelude::*}; use ax_memory_addr::VirtAddr; use ax_memory_set::MemoryArea; +use ax_runtime::hal::paging::MappingFlags; use ax_sync::Mutex; use axpoll::{IoEvents, Pollable}; diff --git a/os/StarryOS/kernel/src/file/timerfd.rs b/os/StarryOS/kernel/src/file/timerfd.rs index ee519307d4..c19fed63b5 100644 --- a/os/StarryOS/kernel/src/file/timerfd.rs +++ b/os/StarryOS/kernel/src/file/timerfd.rs @@ -33,7 +33,7 @@ use core::{ }; use ax_errno::{AxError, AxResult}; -use ax_hal::time::{TimeValue, monotonic_time, wall_time}; +use ax_runtime::hal::time::{TimeValue, monotonic_time, wall_time}; use ax_sync::Mutex; use ax_task::future::{block_on, poll_io, timeout_at}; use axpoll::{IoEvents, PollSet, Pollable}; diff --git a/os/StarryOS/kernel/src/mm/access.rs b/os/StarryOS/kernel/src/mm/access.rs index 220af50103..1eacc6a51e 100644 --- a/os/StarryOS/kernel/src/mm/access.rs +++ b/os/StarryOS/kernel/src/mm/access.rs @@ -8,9 +8,12 @@ use core::{ }; use ax_errno::{AxError, AxResult}; -use ax_hal::{asm::user_copy, paging::MappingFlags, trap::page_fault_handler}; use ax_io::prelude::*; use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; +use ax_runtime::hal::{ + cpu::{asm::user_copy, trap::page_fault_handler}, + paging::MappingFlags, +}; use ax_task::{current, might_sleep}; use extern_trait::extern_trait; use starry_vm::{VmError, VmIo, VmResult, vm_load_until_nul, vm_read_slice, vm_write_slice}; @@ -25,7 +28,7 @@ use crate::{ #[track_caller] pub fn access_user_memory(f: impl FnOnce() -> R) -> R { assert!( - ax_hal::asm::irqs_enabled(), + ax_runtime::hal::cpu::asm::irqs_enabled(), "faultable user memory access requires IRQs enabled" ); @@ -508,7 +511,7 @@ pub fn write_kernel_text(addr: VirtAddr, data: &[u8]) -> AxResult<()> { } #[cfg(target_arch = "aarch64")] - ax_hal::asm::clean_dcache_range_to_pou(addr, data.len()); + ax_runtime::hal::cpu::asm::clean_dcache_range_to_pou(addr, data.len()); guard.protect(aligned_addr, aligned_length, original_flags)?; Ok(()) @@ -519,12 +522,12 @@ pub fn write_kernel_text(addr: VirtAddr, data: &[u8]) -> AxResult<()> { fn flush_tlb_range(start: VirtAddr, size: usize) { for offset in (0..size).step_by(PAGE_SIZE_4K) { - ax_hal::asm::flush_tlb(Some(start + offset)); + ax_runtime::hal::cpu::asm::flush_tlb(Some(start + offset)); } } fn sync_modified_kernel_text(start: VirtAddr, size: usize) { flush_tlb_range(start, size); - ax_hal::asm::flush_icache_all(); + ax_runtime::hal::cpu::asm::flush_icache_all(); } diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/cow.rs b/os/StarryOS/kernel/src/mm/aspace/backend/cow.rs index abfbea8fd0..012a2b4a93 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/cow.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/cow.rs @@ -7,12 +7,12 @@ use core::slice; use ax_errno::{AxError, AxResult}; use ax_fs::FileBackend; -use ax_hal::{ +use ax_kspin::SpinNoIrq; +use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange, align_down_4k}; +use ax_runtime::hal::{ mem::phys_to_virt, paging::{MappingFlags, PageSize, PageTableCursor, PagingError}, }; -use ax_kspin::SpinNoIrq; -use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange, align_down_4k}; use ax_sync::Mutex; use super::{ diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/file.rs b/os/StarryOS/kernel/src/mm/aspace/backend/file.rs index 4221f25f3f..36df4b6274 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/file.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/file.rs @@ -8,8 +8,8 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use ax_errno::{AxError, AxResult}; use ax_fs::{CachedFile, FileFlags}; -use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr, VirtAddrRange}; +use ax_runtime::hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_sync::Mutex; use axfs_ng_vfs::Location; use weak_map::StrongRef; diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs b/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs index f9954f359c..d2ee7bc33d 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/linear.rs @@ -1,8 +1,8 @@ use alloc::sync::Arc; use ax_errno::AxResult; -use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_memory_addr::{PhysAddr, PhysAddrRange, VirtAddr, VirtAddrRange}; +use ax_runtime::hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_sync::Mutex; use super::{AddrSpace, Backend, BackendOps, pages_in}; diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/mod.rs b/os/StarryOS/kernel/src/mm/aspace/backend/mod.rs index a70caf4715..d0c13f5c20 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/mod.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/mod.rs @@ -7,12 +7,12 @@ use alloc::{ use ax_alloc::{UsageKind, global_allocator}; use ax_errno::{AxError, AxResult}; -use ax_hal::{ +use ax_memory_addr::{DynPageIter, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange}; +use ax_memory_set::MappingBackend; +use ax_runtime::hal::{ mem::{phys_to_virt, virt_to_phys}, paging::{MappingFlags, PageSize, PageTable, PageTableCursor}, }; -use ax_memory_addr::{DynPageIter, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange}; -use ax_memory_set::MappingBackend; use ax_sync::Mutex; use enum_dispatch::enum_dispatch; diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs b/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs index 5ab9d670af..564ba4e5d5 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/shared.rs @@ -2,8 +2,8 @@ use alloc::{sync::Arc, vec::Vec}; use core::ops::Deref; use ax_errno::AxResult; -use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_memory_addr::{MemoryAddr, PhysAddr, VirtAddr, VirtAddrRange}; +use ax_runtime::hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_sync::Mutex; use super::{AddrSpace, Backend, BackendOps, alloc_frame, dealloc_frame, divide_page, pages_in}; diff --git a/os/StarryOS/kernel/src/mm/aspace/mod.rs b/os/StarryOS/kernel/src/mm/aspace/mod.rs index 5bf5887c4d..791b0426c4 100644 --- a/os/StarryOS/kernel/src/mm/aspace/mod.rs +++ b/os/StarryOS/kernel/src/mm/aspace/mod.rs @@ -6,15 +6,15 @@ use core::{ }; use ax_errno::{AxError, AxResult, ax_bail}; -use ax_hal::{ - mem::phys_to_virt, - paging::{MappingFlags, PageSize, PageTable, PageTableCursor}, - trap::PageFaultFlags, -}; use ax_memory_addr::{ MemoryAddr, PAGE_SIZE_4K, PageIter4K, PhysAddr, VirtAddr, VirtAddrRange, is_aligned_4k, }; use ax_memory_set::{MemoryArea, MemorySet}; +use ax_runtime::hal::{ + mem::phys_to_virt, + paging::{MappingFlags, PageSize, PageTable, PageTableCursor}, + trap::PageFaultFlags, +}; use ax_sync::Mutex; mod backend; diff --git a/os/StarryOS/kernel/src/mm/loader.rs b/os/StarryOS/kernel/src/mm/loader.rs index 515469d9b6..31b0c08427 100644 --- a/os/StarryOS/kernel/src/mm/loader.rs +++ b/os/StarryOS/kernel/src/mm/loader.rs @@ -5,11 +5,11 @@ use core::{ffi::CStr, iter}; use ax_errno::{AxError, AxResult}; use ax_fs::{CachedFile, FS_CONTEXT, FileBackend}; -use ax_hal::{ +use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; +use ax_runtime::hal::{ mem::virt_to_phys, paging::{MappingFlags, PageSize}, }; -use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; use ax_sync::Mutex; use axfs_ng_vfs::Location; use kernel_elf_parser::{ diff --git a/os/StarryOS/kernel/src/pseudofs/dev/card0.rs b/os/StarryOS/kernel/src/pseudofs/dev/card0.rs index ce7c4f8fd4..24e02ab314 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/card0.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/card0.rs @@ -37,8 +37,8 @@ use core::{ task::Context, }; -use ax_hal::{mem::virt_to_phys, paging::PageSize, time::monotonic_time}; use ax_memory_addr::{PhysAddr, PhysAddrRange, VirtAddr}; +use ax_runtime::hal::{mem::virt_to_phys, paging::PageSize, time::monotonic_time}; use ax_sync::Mutex; use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult}; use axpoll::{IoEvents, PollSet, Pollable}; @@ -490,7 +490,7 @@ impl Card0 { fn dealloc_dumb_pages(&self, paddr: PhysAddr, size: usize) { use ax_alloc::{UsageKind, global_allocator}; - use ax_hal::mem::phys_to_virt; + use ax_runtime::hal::mem::phys_to_virt; let vaddr = phys_to_virt(paddr); let num_pages = size / (PageSize::Size4K as usize); global_allocator().dealloc_pages(vaddr.as_usize(), num_pages, UsageKind::VirtMem); @@ -511,7 +511,7 @@ impl Card0 { let info = ax_display::framebuffer_info(); let src_size = buf.size.min(info.fb_size as u64) as usize; if let Some(src_paddr) = buf.paddr { - use ax_hal::mem::phys_to_virt; + use ax_runtime::hal::mem::phys_to_virt; let src = phys_to_virt(src_paddr); let dst = VirtAddr::from(info.fb_base_vaddr); let copy_bytes = src_size.min(info.fb_size); diff --git a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs index 66ff22ffd8..30ca807ec5 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs @@ -9,8 +9,8 @@ use core::{ use ax_driver::rknpu::{self, RknpuAction, RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit}; use ax_errno::{AxError, AxResult}; -use ax_hal::mem::virt_to_phys; use ax_memory_addr::PhysAddrRange; +use ax_runtime::hal::mem::virt_to_phys; use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::O_CLOEXEC; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs b/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs index dd2698a881..0da31bbb27 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs @@ -3,9 +3,9 @@ use alloc::{collections::vec_deque::VecDeque, vec::Vec}; use core::{any::Any, time::Duration}; use ax_errno::{AxError, LinuxError}; -use ax_hal::mem::phys_to_virt; use ax_kspin::SpinNoIrq as Mutex; use ax_memory_addr::PhysAddr; +use ax_runtime::hal::mem::phys_to_virt; use ax_task::sleep; use axfs_ng_vfs::{NodeFlags, VfsResult}; use sg200x_bsp::{ @@ -219,7 +219,7 @@ impl CameraProtocol { fn read_slip_frame(&mut self, timeout_ms: u64) -> Result, CameraError> { use core::time::Duration; - use ax_hal::time::wall_time; + use ax_runtime::hal::time::wall_time; let deadline = wall_time() + Duration::from_millis(timeout_ms); let mut tmp = [0u8; 0x1200]; loop { @@ -321,7 +321,7 @@ impl UartTransport for Uart3 { fn read_bytes(&mut self, buf: &mut [u8], _timeout_ms: u64) -> Result { sleep(Duration::from_millis(3)); - ax_hal::irq::set_enable(47, false); + ax_runtime::hal::irq::set_enable(47, false); let n = { let mut cache_buf = CAMERA_UART_BUF.lock(); let n = cache_buf.len().min(buf.len()); @@ -336,7 +336,7 @@ impl UartTransport for Uart3 { // Always re-enable the IRQ before returning, otherwise the ESP32's // reply traffic stops landing in CAMERA_UART_BUF and every subsequent // poll sees an empty queue forever. - ax_hal::irq::set_enable(47, true); + ax_runtime::hal::irq::set_enable(47, true); if n == 0 { sleep(Duration::from_millis(1)); } @@ -373,7 +373,7 @@ impl CviCamera { dw_apb_uart::DW8250::new(phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize()); uart3.init_with_baud(1500000); uart3.set_ier(true); - ax_hal::irq::register(47, |_irq| { + ax_runtime::hal::irq::register(47, |_irq| { let mut uart3 = dw_apb_uart::DW8250::new(phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize()); let mut buf = CAMERA_UART_BUF.lock(); @@ -386,7 +386,7 @@ impl CviCamera { } uart3.set_ier(true); }); - ax_hal::irq::set_enable(47, true); + ax_runtime::hal::irq::set_enable(47, true); Self { inner: Mutex::new(CameraProtocol::new_default(Uart3)), } diff --git a/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs b/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs index 09a0ba5b61..df48a44dd6 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs @@ -2,12 +2,12 @@ use core::{any::Any, time::Duration}; use ax_config::plat::PHYS_VIRT_OFFSET; use ax_errno::AxError; -use ax_hal::{ +use ax_kspin::SpinNoIrq as Mutex; +use ax_memory_addr::{PhysAddr, VirtAddr}; +use ax_runtime::hal::{ mem::{phys_to_virt, virt_to_phys}, time::busy_wait, }; -use ax_kspin::SpinNoIrq as Mutex; -use ax_memory_addr::{PhysAddr, VirtAddr}; use axfs_ng_vfs::{NodeFlags, VfsResult}; use sg200x_bsp::{ gpio::{Direction, GPIO, GPIO1_BASE}, diff --git a/os/StarryOS/kernel/src/pseudofs/dev/event.rs b/os/StarryOS/kernel/src/pseudofs/dev/event.rs index 1d3bc2fc86..853fb8dffb 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/event.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/event.rs @@ -17,8 +17,8 @@ pub fn input_device_count() -> u32 { } use ax_errno::{AxError, AxResult}; -use ax_hal::time::wall_time; use ax_input::{ErasedInputDevice, Event, EventType, InputDevice, InputDeviceId, InputError}; +use ax_runtime::hal::time::wall_time; use ax_sync::Mutex; use axfs_ng_vfs::{DeviceId, NodeFlags, NodeType, VfsResult}; use axpoll::{IoEvents, Pollable}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/fb.rs b/os/StarryOS/kernel/src/pseudofs/dev/fb.rs index 0bf8df5502..492ea4d277 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/fb.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/fb.rs @@ -1,8 +1,8 @@ use core::{any::Any, slice}; use ax_errno::AxError; -use ax_hal::mem::virt_to_phys; use ax_memory_addr::{PhysAddrRange, VirtAddr}; +use ax_runtime::hal::mem::virt_to_phys; use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult}; use starry_vm::VmMutPtr; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs b/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs index 02b64942b4..c0f5295590 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs @@ -4,7 +4,7 @@ use core::{ ffi::{c_char, c_ulong}, }; -use ax_hal::asm::user_copy; +use ax_runtime::hal::cpu::asm::user_copy; use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use super::rknpu_drm::DrmVersion; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs index c1082a7454..dfc41d8c1e 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs @@ -17,12 +17,12 @@ pub type NTtyDriver = Tty; pub struct Console; impl TtyRead for Console { fn read(&mut self, buf: &mut [u8]) -> usize { - ax_hal::console::read_bytes(buf) + ax_runtime::hal::console::read_bytes(buf) } } impl TtyWrite for Console { fn write(&self, buf: &[u8]) { - ax_hal::console::write_bytes(buf); + ax_runtime::hal::console::write_bytes(buf); } } @@ -31,11 +31,11 @@ pub static N_TTY: Lazy> = Lazy::new(new_n_tty); static CONSOLE_INPUT_SOURCE: Lazy> = Lazy::new(|| Arc::new(PollSet::new())); fn handle_console_input_irq(_irq_num: usize) { - let events = ax_hal::console::handle_irq(); + let events = ax_runtime::hal::console::handle_irq(); if events.intersects( - ax_hal::console::ConsoleIrqEvent::RX_READY - | ax_hal::console::ConsoleIrqEvent::RX_ERROR - | ax_hal::console::ConsoleIrqEvent::OVERRUN, + ax_runtime::hal::console::ConsoleIrqEvent::RX_READY + | ax_runtime::hal::console::ConsoleIrqEvent::RX_ERROR + | ax_runtime::hal::console::ConsoleIrqEvent::OVERRUN, ) { CONSOLE_INPUT_SOURCE.wake(); } @@ -86,19 +86,19 @@ fn new_n_tty() -> Arc { /// task is spawned, so there is no concurrent consumer racing on the /// UART receive FIFO. fn query_console_size() -> Option<(u16, u16)> { - ax_hal::console::write_bytes(b"\x1b7\x1b[9999;9999H\x1b[6n\x1b8"); + ax_runtime::hal::console::write_bytes(b"\x1b7\x1b[9999;9999H\x1b[6n\x1b8"); let mut buf = [0u8; 32]; let mut len = 0usize; - // Spin up to ~100 ms (in wall time, polled via ax_hal::time::wall_time) + // Spin up to ~100 ms (in wall time, polled via ax_runtime::hal::time::wall_time) // for the `R` terminator. Hosts that ignore CPR (jcode running under // a non-interactive serial, automated CI runners) will time out and // we fall back to the 24x80 default without blocking boot further. - let deadline = ax_hal::time::wall_time() + core::time::Duration::from_millis(100); - 'collect: while ax_hal::time::wall_time() < deadline { + let deadline = ax_runtime::hal::time::wall_time() + core::time::Duration::from_millis(100); + 'collect: while ax_runtime::hal::time::wall_time() < deadline { let mut tmp = [0u8; 1]; - if ax_hal::console::read_bytes(&mut tmp) > 0 { + if ax_runtime::hal::console::read_bytes(&mut tmp) > 0 { if len < buf.len() { buf[len] = tmp[0]; len += 1; @@ -131,12 +131,12 @@ fn query_console_size() -> Option<(u16, u16)> { } fn console_irq_mode() -> Option { - let irq = ax_hal::console::irq_num()?; - if !ax_hal::irq::register(irq, handle_console_input_irq) { + let irq = ax_runtime::hal::console::irq_num()?; + if !ax_runtime::hal::irq::register(irq, handle_console_input_irq) { warn!("Failed to register console IRQ handler for irq {irq}, falling back to polling mode"); return None; } - ax_hal::console::set_input_irq_enabled(true); + ax_runtime::hal::console::set_input_irq_enabled(true); Some(ProcessMode::InterruptDriven(CONSOLE_INPUT_SOURCE.clone())) } diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs index 8c8bb21d0d..4225a7fc00 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs @@ -2,9 +2,9 @@ use alloc::collections::vec_deque::VecDeque; use core::{any::Any, task::Context}; use ax_errno::{AxError, LinuxError}; -use ax_hal::mem::phys_to_virt; use ax_kspin::SpinNoIrq; use ax_memory_addr::{PhysAddr, pa}; +use ax_runtime::hal::mem::phys_to_virt; use ax_sync::Mutex; use ax_task::future::{block_on, poll_io}; use axfs_ng_vfs::{NodeFlags, VfsResult}; @@ -134,8 +134,8 @@ impl TtySerial { let mut uart = DW8250::new(vaddr); uart.init_with_baud(baud); uart.set_ier(true); - ax_hal::irq::register(irq, irq_handler); - ax_hal::irq::set_enable(irq, true); + ax_runtime::hal::irq::register(irq, irq_handler); + ax_runtime::hal::irq::set_enable(irq, true); Self { paddr, irq, @@ -153,7 +153,7 @@ impl TtySerial { let mut uart = DW8250::new(vaddr); uart.init_with_baud(baud); uart.set_ier(true); - ax_hal::irq::set_enable(self.irq, true); + ax_runtime::hal::irq::set_enable(self.irq, true); } } diff --git a/os/StarryOS/kernel/src/pseudofs/proc.rs b/os/StarryOS/kernel/src/pseudofs/proc.rs index c639257a97..4dd7d4cc8e 100644 --- a/os/StarryOS/kernel/src/pseudofs/proc.rs +++ b/os/StarryOS/kernel/src/pseudofs/proc.rs @@ -14,11 +14,11 @@ use core::{ sync::atomic::{AtomicUsize, Ordering}, }; -use ax_hal::{ +use ax_memory_addr::PAGE_SIZE_4K; +use ax_runtime::hal::{ paging::MappingFlags, time::{monotonic_time, wall_time}, }; -use ax_memory_addr::PAGE_SIZE_4K; use ax_task::{AxCpuMask, AxTaskRef, TaskState, WeakAxTaskRef, current}; use axfs_ng_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult}; use starry_process::{Pid, Process}; @@ -61,7 +61,7 @@ fn procfs_lookup_process(pid: Pid) -> VfsResult> { } fn render_meminfo() -> String { - let total = ax_hal::mem::total_ram_size(); + let total = ax_runtime::hal::mem::total_ram_size(); let usages = ax_alloc::global_allocator().usages(); // Sum all allocator categories to estimate kernel-consumed memory. let used = usages.get(ax_alloc::UsageKind::RustHeap) @@ -113,7 +113,7 @@ fn render_meminfo() -> String { } fn render_cpuinfo() -> String { - let cpu_count = ax_hal::cpu_num(); + let cpu_count = ax_runtime::hal::cpu_num(); let mut buf = String::new(); for i in 0..cpu_count { render_cpu_entry(&mut buf, i); @@ -185,7 +185,7 @@ fn render_cpu_entry(buf: &mut String, idx: usize) { fn render_stat() -> String { let up = monotonic_time(); - let cpu_count = ax_hal::cpu_num() as u64; + let cpu_count = ax_runtime::hal::cpu_num() as u64; // Total CPU-time budget in jiffies across all CPUs (USER_HZ = 100). let up_jiffies = up.as_secs() * 100 + (up.subsec_millis() / 10) as u64; let total_budget = up_jiffies.saturating_mul(cpu_count); @@ -339,7 +339,7 @@ fn task_status(task: &AxTaskRef) -> String { &cred, num_threads, task.cpumask(), - ax_hal::cpu_num(), + ax_runtime::hal::cpu_num(), ) } @@ -678,7 +678,7 @@ impl SimpleDirOps for ThreadDir { &cred, num_threads, task.cpumask(), - ax_hal::cpu_num(), + ax_runtime::hal::cpu_num(), )) } else { Ok(task_status(&task)) @@ -859,7 +859,7 @@ fn builder(fs: Arc) -> DirMaker { let secs = up.as_secs(); let cs = up.subsec_millis() / 10; // Approximate total idle as uptime × cpu_count (no per-CPU idle accounting yet). - let idle_secs = secs.saturating_mul(ax_hal::cpu_num() as u64); + let idle_secs = secs.saturating_mul(ax_runtime::hal::cpu_num() as u64); Ok(format!("{secs}.{cs:02} {idle_secs}.00\n")) }), ); diff --git a/os/StarryOS/kernel/src/pseudofs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/sysfs.rs index dd41bacff0..44fa030137 100644 --- a/os/StarryOS/kernel/src/pseudofs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/sysfs.rs @@ -396,7 +396,7 @@ impl SimpleDirOps for SystemCpuDir { Cow::Borrowed("possible"), Cow::Borrowed("present"), ]; - names.extend((0..ax_hal::cpu_num()).map(|cpu| Cow::Owned(format!("cpu{cpu}")))); + names.extend((0..ax_runtime::hal::cpu_num()).map(|cpu| Cow::Owned(format!("cpu{cpu}")))); Box::new(names.into_iter()) } @@ -411,7 +411,7 @@ impl SimpleDirOps for SystemCpuDir { .strip_prefix("cpu") .and_then(|s| s.parse::().ok()) .ok_or(VfsError::NotFound)?; - if cpu >= ax_hal::cpu_num() { + if cpu >= ax_runtime::hal::cpu_num() { return Err(VfsError::NotFound); } NodeOpsMux::Dir(SimpleDir::new_maker( @@ -436,7 +436,7 @@ impl SimpleDirOps for SystemCpuEntryDir { fn lookup_child(&self, name: &str) -> VfsResult { match name { "online" => { - let online = if self.cpu < ax_hal::cpu_num() { + let online = if self.cpu < ax_runtime::hal::cpu_num() { "1\n" } else { "0\n" @@ -449,7 +449,7 @@ impl SimpleDirOps for SystemCpuEntryDir { } fn cpu_range_string() -> String { - let cpu_num = ax_hal::cpu_num(); + let cpu_num = ax_runtime::hal::cpu_num(); if cpu_num <= 1 { "0".to_owned() } else { diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/irq.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/irq.rs index b1d72b0a72..96e3673434 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/irq.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/irq.rs @@ -92,7 +92,7 @@ pub(super) fn init_globals(manager: Arc, pending_slots: Vec usize { for (_, irq_num) in failed_device_ids { if let Some(irq_num) = irq_num { - let _ = ax_hal::irq::unregister(irq_num); + let _ = ax_runtime::hal::irq::unregister(irq_num); } } diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs index 66d5ae56fb..78f4a0d8b6 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs @@ -1348,7 +1348,7 @@ fn cleanup_submitted_urbs( mut submitted_urbs: Vec, timeout: Option, ) -> Vec { - let deadline = timeout.map(|timeout| ax_hal::time::wall_time() + timeout); + let deadline = timeout.map(|timeout| ax_runtime::hal::time::wall_time() + timeout); for submitted in &submitted_urbs { if let Err(err) = submitted.cancel() { debug!( @@ -1376,7 +1376,7 @@ fn cleanup_submitted_urbs( } if !submitted_urbs.is_empty() { - if deadline.is_some_and(|deadline| ax_hal::time::wall_time() >= deadline) { + if deadline.is_some_and(|deadline| ax_runtime::hal::time::wall_time() >= deadline) { break; } ax_task::sleep(Duration::from_millis(1)); diff --git a/os/StarryOS/kernel/src/stop_machine.rs b/os/StarryOS/kernel/src/stop_machine.rs index b90b7bd6be..d36c279f87 100644 --- a/os/StarryOS/kernel/src/stop_machine.rs +++ b/os/StarryOS/kernel/src/stop_machine.rs @@ -4,10 +4,10 @@ use core::{ sync::atomic::{AtomicU8, AtomicUsize, Ordering}, }; -use ax_hal::{cpu_num, percpu::this_cpu_id, time::monotonic_time_nanos}; use ax_ipi::run_on_cpu; use ax_kernel_guard::NoPreemptIrqSave; use ax_kspin::SpinNoIrq; +use ax_runtime::hal::{cpu_num, percpu::this_cpu_id, time::monotonic_time_nanos}; static STOP_MACHINE_LOCK: SpinNoIrq<()> = SpinNoIrq::new(()); diff --git a/os/StarryOS/kernel/src/syscall/fs/ctl.rs b/os/StarryOS/kernel/src/syscall/fs/ctl.rs index b120ed7da4..81014f6c2e 100644 --- a/os/StarryOS/kernel/src/syscall/fs/ctl.rs +++ b/os/StarryOS/kernel/src/syscall/fs/ctl.rs @@ -12,7 +12,7 @@ use core::{ use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::{FS_CONTEXT, FsContext}; -use ax_hal::time::wall_time; +use ax_runtime::hal::time::wall_time; use ax_task::current; use axfs_ng_vfs::{DeviceId, MetadataUpdate, NodePermission, NodeType, path::Path}; use linux_raw_sys::{ diff --git a/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs b/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs index 57b0725556..1ad8434caa 100644 --- a/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs +++ b/os/StarryOS/kernel/src/syscall/io_mpx/poll.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::mem::{MaybeUninit, offset_of}; use ax_errno::{AxError, AxResult}; -use ax_hal::time::TimeValue; +use ax_runtime::hal::time::TimeValue; use ax_task::{ current, future::{self, block_on, poll_io}, diff --git a/os/StarryOS/kernel/src/syscall/ipc/msg.rs b/os/StarryOS/kernel/src/syscall/ipc/msg.rs index 295fde88cb..87c0720f89 100644 --- a/os/StarryOS/kernel/src/syscall/ipc/msg.rs +++ b/os/StarryOS/kernel/src/syscall/ipc/msg.rs @@ -1,7 +1,7 @@ use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; use ax_errno::{AxError, AxResult, LinuxError}; -use ax_hal::time::monotonic_time_nanos; +use ax_runtime::hal::time::monotonic_time_nanos; use ax_sync::Mutex; use ax_task::current; use bytemuck::AnyBitPattern; diff --git a/os/StarryOS/kernel/src/syscall/ipc/shm.rs b/os/StarryOS/kernel/src/syscall/ipc/shm.rs index 78756b8448..2e746e1793 100644 --- a/os/StarryOS/kernel/src/syscall/ipc/shm.rs +++ b/os/StarryOS/kernel/src/syscall/ipc/shm.rs @@ -1,11 +1,11 @@ use alloc::{collections::btree_map::BTreeMap, sync::Arc, vec::Vec}; use ax_errno::{AxError, AxResult}; -use ax_hal::{ +use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr, VirtAddrRange}; +use ax_runtime::hal::{ paging::{MappingFlags, PageSize}, time::monotonic_time_nanos, }; -use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr, VirtAddrRange}; use ax_sync::Mutex; use ax_task::current; use linux_raw_sys::{ctypes::c_ushort, general::*}; diff --git a/os/StarryOS/kernel/src/syscall/mm/brk.rs b/os/StarryOS/kernel/src/syscall/mm/brk.rs index 6f7e2855da..55cbf9a3b2 100644 --- a/os/StarryOS/kernel/src/syscall/mm/brk.rs +++ b/os/StarryOS/kernel/src/syscall/mm/brk.rs @@ -1,6 +1,6 @@ use ax_errno::AxResult; -use ax_hal::paging::{MappingFlags, PageSize}; use ax_memory_addr::{VirtAddr, align_up_4k}; +use ax_runtime::hal::paging::{MappingFlags, PageSize}; use ax_task::current; use linux_raw_sys::general::RLIMIT_DATA; diff --git a/os/StarryOS/kernel/src/syscall/mm/mincore.rs b/os/StarryOS/kernel/src/syscall/mm/mincore.rs index 8ab77bac2b..08441b2e65 100644 --- a/os/StarryOS/kernel/src/syscall/mm/mincore.rs +++ b/os/StarryOS/kernel/src/syscall/mm/mincore.rs @@ -9,8 +9,8 @@ use alloc::vec; use ax_errno::{AxError, AxResult}; -use ax_hal::paging::MappingFlags; use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; +use ax_runtime::hal::paging::MappingFlags; use ax_task::current; use starry_vm::vm_write_slice; diff --git a/os/StarryOS/kernel/src/syscall/mm/mmap.rs b/os/StarryOS/kernel/src/syscall/mm/mmap.rs index 0be64134f7..9f1d03e712 100644 --- a/os/StarryOS/kernel/src/syscall/mm/mmap.rs +++ b/os/StarryOS/kernel/src/syscall/mm/mmap.rs @@ -2,8 +2,8 @@ use alloc::sync::Arc; use ax_errno::{AxError, AxResult}; use ax_fs::{FileBackend, FileFlags}; -use ax_hal::paging::{MappingFlags, PageSize}; use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr, VirtAddrRange, align_up_4k}; +use ax_runtime::hal::paging::{MappingFlags, PageSize}; use ax_task::current; use linux_raw_sys::general::*; diff --git a/os/StarryOS/kernel/src/syscall/mod.rs b/os/StarryOS/kernel/src/syscall/mod.rs index 9321b6a562..5d173fa906 100644 --- a/os/StarryOS/kernel/src/syscall/mod.rs +++ b/os/StarryOS/kernel/src/syscall/mod.rs @@ -11,7 +11,7 @@ mod task; mod time; use ax_errno::{AxError, LinuxError}; -use ax_hal::uspace::UserContext; +use ax_runtime::hal::cpu::uspace::UserContext; use syscalls::Sysno; pub use self::{ diff --git a/os/StarryOS/kernel/src/syscall/net/io.rs b/os/StarryOS/kernel/src/syscall/net/io.rs index ad9b1436fa..53fa23fc42 100644 --- a/os/StarryOS/kernel/src/syscall/net/io.rs +++ b/os/StarryOS/kernel/src/syscall/net/io.rs @@ -2,8 +2,8 @@ use alloc::{boxed::Box, vec::Vec}; use core::{net::Ipv4Addr, time::Duration}; use ax_errno::{AxError, AxResult}; -use ax_hal::time::wall_time; use ax_io::prelude::*; +use ax_runtime::hal::time::wall_time; use axnet::{CMsgData, RecvFlags, RecvOptions, SendFlags, SendOptions, SocketAddrEx, SocketOps}; use linux_raw_sys::{ general::timespec, diff --git a/os/StarryOS/kernel/src/syscall/resources.rs b/os/StarryOS/kernel/src/syscall/resources.rs index 95740cd78d..719146a88e 100644 --- a/os/StarryOS/kernel/src/syscall/resources.rs +++ b/os/StarryOS/kernel/src/syscall/resources.rs @@ -1,5 +1,5 @@ use ax_errno::{AxError, AxResult}; -use ax_hal::time::TimeValue; +use ax_runtime::hal::time::TimeValue; use ax_task::current; use linux_raw_sys::general::{__kernel_old_timeval, RLIM_NLIMITS, rlimit64, rusage}; use starry_process::Pid; diff --git a/os/StarryOS/kernel/src/syscall/signal.rs b/os/StarryOS/kernel/src/syscall/signal.rs index 32c3d0cf84..546c97d5b1 100644 --- a/os/StarryOS/kernel/src/syscall/signal.rs +++ b/os/StarryOS/kernel/src/syscall/signal.rs @@ -1,7 +1,7 @@ use core::{future::poll_fn, task::Poll}; use ax_errno::{AxError, AxResult, LinuxError}; -use ax_hal::uspace::UserContext; +use ax_runtime::hal::cpu::uspace::UserContext; use ax_task::{ current, future::{self, block_on}, diff --git a/os/StarryOS/kernel/src/syscall/sync/futex.rs b/os/StarryOS/kernel/src/syscall/sync/futex.rs index d2a0d0f6a0..19955ea27d 100644 --- a/os/StarryOS/kernel/src/syscall/sync/futex.rs +++ b/os/StarryOS/kernel/src/syscall/sync/futex.rs @@ -1,7 +1,7 @@ use core::mem::align_of; use ax_errno::{AxError, AxResult}; -use ax_hal::time::{TimeValue, monotonic_time, wall_time}; +use ax_runtime::hal::time::{TimeValue, monotonic_time, wall_time}; use ax_task::current; use linux_raw_sys::general::{ FUTEX_CLOCK_REALTIME, FUTEX_CMP_REQUEUE, FUTEX_REQUEUE, FUTEX_WAIT, FUTEX_WAIT_BITSET, diff --git a/os/StarryOS/kernel/src/syscall/sys.rs b/os/StarryOS/kernel/src/syscall/sys.rs index 6e4183f7a3..5c50ca931b 100644 --- a/os/StarryOS/kernel/src/syscall/sys.rs +++ b/os/StarryOS/kernel/src/syscall/sys.rs @@ -566,7 +566,7 @@ pub fn sys_uname(name: *mut new_utsname) -> AxResult { pub fn sys_sysinfo(info: *mut sysinfo) -> AxResult { let mut kinfo: sysinfo = unsafe { core::mem::zeroed() }; - let total = ax_hal::mem::total_ram_size(); + let total = ax_runtime::hal::mem::total_ram_size(); let usages = ax_alloc::global_allocator().usages(); let used = usages.get(ax_alloc::UsageKind::RustHeap) + usages.get(ax_alloc::UsageKind::VirtMem) @@ -575,7 +575,7 @@ pub fn sys_sysinfo(info: *mut sysinfo) -> AxResult { + usages.get(ax_alloc::UsageKind::Dma) + usages.get(ax_alloc::UsageKind::Global); let free = total.saturating_sub(used); - let uptime = ax_hal::time::monotonic_time(); + let uptime = ax_runtime::hal::time::monotonic_time(); kinfo.uptime = uptime.as_secs() as _; kinfo.totalram = total as _; diff --git a/os/StarryOS/kernel/src/syscall/task/clone.rs b/os/StarryOS/kernel/src/syscall/task/clone.rs index e17de3264e..214519f46c 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone.rs @@ -2,8 +2,8 @@ use alloc::sync::Arc; use ax_errno::{AxError, AxResult}; use ax_fs::FS_CONTEXT; -use ax_hal::uspace::UserContext; use ax_kspin::SpinNoIrq; +use ax_runtime::hal::cpu::uspace::UserContext; use ax_task::{AxTaskExt, current, spawn_task}; use bitflags::bitflags; use linux_raw_sys::general::*; diff --git a/os/StarryOS/kernel/src/syscall/task/clone3.rs b/os/StarryOS/kernel/src/syscall/task/clone3.rs index 41d8224208..453e425b6b 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone3.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone3.rs @@ -1,7 +1,7 @@ use core::mem::{self, MaybeUninit}; use ax_errno::{AxError, AxResult}; -use ax_hal::uspace::UserContext; +use ax_runtime::hal::cpu::uspace::UserContext; use bytemuck::AnyBitPattern; use starry_vm::vm_read_slice; diff --git a/os/StarryOS/kernel/src/syscall/task/execve.rs b/os/StarryOS/kernel/src/syscall/task/execve.rs index 02e0a8b066..0a57d45581 100644 --- a/os/StarryOS/kernel/src/syscall/task/execve.rs +++ b/os/StarryOS/kernel/src/syscall/task/execve.rs @@ -7,7 +7,7 @@ use core::{ffi::c_char, future::poll_fn, iter, task::Poll}; use ax_errno::{AxError, AxResult}; use ax_fs::FS_CONTEXT; -use ax_hal::uspace::UserContext; +use ax_runtime::hal::cpu::uspace::UserContext; use ax_sync::Mutex; use ax_task::{current, future::block_on, yield_now}; use starry_process::Pid; diff --git a/os/StarryOS/kernel/src/syscall/task/schedule.rs b/os/StarryOS/kernel/src/syscall/task/schedule.rs index 535b19786a..7e87f814b0 100644 --- a/os/StarryOS/kernel/src/syscall/task/schedule.rs +++ b/os/StarryOS/kernel/src/syscall/task/schedule.rs @@ -1,7 +1,7 @@ use alloc::{sync::Arc, vec::Vec}; use ax_errno::{AxError, AxResult}; -use ax_hal::time::TimeValue; +use ax_runtime::hal::time::TimeValue; use ax_task::{ AxCpuMask, current, future::{block_on, interruptible, sleep}, @@ -43,7 +43,7 @@ pub fn sys_nanosleep(req: *const timespec, rem: *mut timespec) -> AxResult rem: {diff:?}"); @@ -63,8 +63,8 @@ pub fn sys_clock_nanosleep( rem: *mut timespec, ) -> AxResult { let clock = match clock_id as u32 { - CLOCK_REALTIME => ax_hal::time::wall_time, - CLOCK_MONOTONIC => ax_hal::time::monotonic_time, + CLOCK_REALTIME => ax_runtime::hal::time::wall_time, + CLOCK_MONOTONIC => ax_runtime::hal::time::monotonic_time, _ => { warn!("Unsupported clock_id: {clock_id}"); return Err(AxError::InvalidInput); @@ -94,7 +94,7 @@ pub fn sys_clock_nanosleep( } pub fn sys_sched_getaffinity(pid: i32, cpusetsize: usize, user_mask: *mut u8) -> AxResult { - if cpusetsize * 8 < ax_hal::cpu_num() { + if cpusetsize * 8 < ax_runtime::hal::cpu_num() { return Err(AxError::InvalidInput); } @@ -108,11 +108,11 @@ pub fn sys_sched_getaffinity(pid: i32, cpusetsize: usize, user_mask: *mut u8) -> } pub fn sys_sched_setaffinity(pid: i32, cpusetsize: usize, user_mask: *const u8) -> AxResult { - let size = cpusetsize.min(ax_hal::cpu_num().div_ceil(8)); + let size = cpusetsize.min(ax_runtime::hal::cpu_num().div_ceil(8)); let user_mask = vm_load(user_mask, size)?; let mut cpu_mask = AxCpuMask::new(); - for i in 0..(size * 8).min(ax_hal::cpu_num()) { + for i in 0..(size * 8).min(ax_runtime::hal::cpu_num()) { if user_mask[i / 8] & (1 << (i % 8)) != 0 { cpu_mask.set(i, true); } diff --git a/os/StarryOS/kernel/src/syscall/task/thread.rs b/os/StarryOS/kernel/src/syscall/task/thread.rs index e1ec225983..47be16d532 100644 --- a/os/StarryOS/kernel/src/syscall/task/thread.rs +++ b/os/StarryOS/kernel/src/syscall/task/thread.rs @@ -30,7 +30,7 @@ pub fn sys_gettid() -> AxResult { /// current CPU id and node 0 (single NUMA node); the obsolete `tcache` arg is /// ignored. Either pointer may be NULL. pub fn sys_getcpu(cpu: *mut u32, node: *mut u32, _tcache: usize) -> AxResult { - use ax_hal::percpu::this_cpu_id; + use ax_runtime::hal::percpu::this_cpu_id; use starry_vm::VmMutPtr; if !cpu.is_null() { @@ -77,7 +77,7 @@ pub fn sys_set_tid_address(clear_child_tid: usize) -> AxResult { #[cfg(target_arch = "x86_64")] pub fn sys_arch_prctl( - uctx: &mut ax_hal::uspace::UserContext, + uctx: &mut ax_runtime::hal::cpu::uspace::UserContext, code: i32, addr: usize, ) -> AxResult { diff --git a/os/StarryOS/kernel/src/syscall/time.rs b/os/StarryOS/kernel/src/syscall/time.rs index 4c815556ce..3c59a8cb17 100644 --- a/os/StarryOS/kernel/src/syscall/time.rs +++ b/os/StarryOS/kernel/src/syscall/time.rs @@ -1,5 +1,5 @@ use ax_errno::{AxError, AxResult}; -use ax_hal::time::{ +use ax_runtime::hal::time::{ NANOS_PER_SEC, TimeValue, monotonic_time, monotonic_time_nanos, nanos_to_ticks, wall_time, }; use ax_task::current; diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index 6d61abe40b..63620a38e4 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -17,7 +17,7 @@ use core::{ sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicUsize, Ordering}, }; -use ax_hal::time::TimeValue; +use ax_runtime::hal::time::TimeValue; use ax_sync::{Mutex, spin::SpinNoIrq}; use ax_task::{TaskExt, TaskInner}; use axpoll::PollSet; diff --git a/os/StarryOS/kernel/src/task/ops.rs b/os/StarryOS/kernel/src/task/ops.rs index 34d4d974fd..f8ef2ac024 100644 --- a/os/StarryOS/kernel/src/task/ops.rs +++ b/os/StarryOS/kernel/src/task/ops.rs @@ -6,7 +6,7 @@ use alloc::{ use core::ffi::c_long; use ax_errno::{AxError, AxResult}; -use ax_hal::time::TimeValue; +use ax_runtime::hal::time::TimeValue; use ax_task::{AxTaskRef, TaskInner, WeakAxTaskRef, current}; use bytemuck::AnyBitPattern; use linux_raw_sys::general::ROBUST_LIST_LIMIT; diff --git a/os/StarryOS/kernel/src/task/posix_timer.rs b/os/StarryOS/kernel/src/task/posix_timer.rs index 6fd711eb1e..a8c9f7b55d 100644 --- a/os/StarryOS/kernel/src/task/posix_timer.rs +++ b/os/StarryOS/kernel/src/task/posix_timer.rs @@ -7,8 +7,8 @@ use core::{ }; use ax_errno::{AxError, AxResult}; -use ax_hal::time::{NANOS_PER_SEC, monotonic_time_nanos, wall_time}; use ax_kspin::SpinNoIrq as Mutex; +use ax_runtime::hal::time::{NANOS_PER_SEC, monotonic_time_nanos, wall_time}; use linux_raw_sys::general::{ CLOCK_BOOTTIME, CLOCK_MONOTONIC, CLOCK_MONOTONIC_COARSE, CLOCK_MONOTONIC_RAW, CLOCK_PROCESS_CPUTIME_ID, CLOCK_REALTIME, CLOCK_REALTIME_COARSE, CLOCK_THREAD_CPUTIME_ID, diff --git a/os/StarryOS/kernel/src/task/signal.rs b/os/StarryOS/kernel/src/task/signal.rs index 588fbcc818..1360c1f4ff 100644 --- a/os/StarryOS/kernel/src/task/signal.rs +++ b/os/StarryOS/kernel/src/task/signal.rs @@ -1,7 +1,7 @@ use core::{future::poll_fn, task::Poll}; use ax_errno::{AxError, AxResult}; -use ax_hal::uspace::UserContext; +use ax_runtime::hal::cpu::uspace::UserContext; use ax_task::{TaskInner, current, future::block_on}; use linux_raw_sys::general::{CLD_CONTINUED, CLD_STOPPED}; use starry_process::Pid; diff --git a/os/StarryOS/kernel/src/task/timer.rs b/os/StarryOS/kernel/src/task/timer.rs index 2f5dfc8b68..9f6d511e4c 100644 --- a/os/StarryOS/kernel/src/task/timer.rs +++ b/os/StarryOS/kernel/src/task/timer.rs @@ -3,8 +3,8 @@ use alloc::{borrow::ToOwned, collections::binary_heap::BinaryHeap, sync::Arc}; use core::{mem, time::Duration}; -use ax_hal::time::{NANOS_PER_SEC, TimeValue, monotonic_time_nanos, wall_time}; use ax_kspin::SpinNoIrq as Mutex; +use ax_runtime::hal::time::{NANOS_PER_SEC, TimeValue, monotonic_time_nanos, wall_time}; use ax_task::{ WeakAxTaskRef, current, future::{block_on, timeout_at}, diff --git a/os/StarryOS/kernel/src/task/user.rs b/os/StarryOS/kernel/src/task/user.rs index 0d74819766..e6d5bc324b 100644 --- a/os/StarryOS/kernel/src/task/user.rs +++ b/os/StarryOS/kernel/src/task/user.rs @@ -1,4 +1,4 @@ -use ax_hal::uspace::{ExceptionInfo, ExceptionKind, ReturnReason, UserContext}; +use ax_runtime::hal::cpu::uspace::{ExceptionInfo, ExceptionKind, ReturnReason, UserContext}; use ax_task::TaskInner; use starry_process::Pid; use starry_signal::{SignalInfo, Signo}; diff --git a/os/StarryOS/kernel/src/time.rs b/os/StarryOS/kernel/src/time.rs index 4743cb6f4f..4e0d4d190c 100644 --- a/os/StarryOS/kernel/src/time.rs +++ b/os/StarryOS/kernel/src/time.rs @@ -1,5 +1,5 @@ use ax_errno::{AxError, AxResult}; -use ax_hal::time::TimeValue; +use ax_runtime::hal::time::TimeValue; use linux_raw_sys::general::{ __kernel_old_timespec, __kernel_old_timeval, __kernel_sock_timeval, __kernel_timespec, timespec, timeval, diff --git a/os/StarryOS/kernel/src/tracepoint/mod.rs b/os/StarryOS/kernel/src/tracepoint/mod.rs index 5ccea5bc6a..875b1c9714 100644 --- a/os/StarryOS/kernel/src/tracepoint/mod.rs +++ b/os/StarryOS/kernel/src/tracepoint/mod.rs @@ -7,9 +7,9 @@ use alloc::{collections::BTreeMap, string::ToString, sync::Arc, vec::Vec}; use core::{num::NonZero, ops::Deref}; use ax_errno::{AxError, AxResult}; -use ax_hal::{percpu::this_cpu_id, time::monotonic_time_nanos}; use ax_lazyinit::LazyInit; use ax_memory_addr::VirtAddr; +use ax_runtime::hal::{percpu::this_cpu_id, time::monotonic_time_nanos}; use ax_sync::Mutex; use ax_task::current; use axfs_ng_vfs::NodePermission; diff --git a/os/StarryOS/kernel/src/trap.rs b/os/StarryOS/kernel/src/trap.rs index ea3fbdb290..694b766660 100644 --- a/os/StarryOS/kernel/src/trap.rs +++ b/os/StarryOS/kernel/src/trap.rs @@ -1,10 +1,10 @@ -#[ax_hal::trap::breakpoint_handler] -fn default_breakpoint_handler(_tf: &mut ax_hal::context::TrapFrame) -> bool { +#[ax_runtime::hal::cpu::trap::breakpoint_handler] +fn default_breakpoint_handler(_tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { false } #[cfg(target_arch = "x86_64")] -#[ax_hal::trap::debug_handler] -fn default_debug_handler(_tf: &mut ax_hal::context::TrapFrame) -> bool { +#[ax_runtime::hal::cpu::trap::debug_handler] +fn default_debug_handler(_tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { false } diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index 500da6e231..c9fb4538aa 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -24,10 +24,10 @@ qemu = [ "starry-kernel/input", "starry-kernel/vsock", # auxilary features ] -smp = ["ax-feat/smp", "axplat-dyn?/smp", "axplat-riscv64-visionfive2?/smp"] +smp = ["ax-feat/smp", "axplat-dyn?/smp", "ax-hal/smp"] sg2002 = [ - "dep:ax-plat-riscv64-sg2002", + "ax-hal/riscv64-sg2002", "starry-kernel/sg2002", ] @@ -49,7 +49,8 @@ cntv-timer = ["ax-feat/aarch64-cntv-timer"] aarch64-hvf = ["gic-v3", "cntv-timer"] rknpu = ["ax-driver/rknpu", "starry-kernel/rknpu"] -vf2 = ["dep:axplat-riscv64-visionfive2"] +vf2 = ["ax-hal/riscv64-visionfive2"] +buddy-slab = ["starry-kernel/buddy-slab"] [[bin]] name = "starryos" @@ -66,10 +67,6 @@ ax-hal.workspace = true axplat-dyn = { workspace = true, optional = true } starry-kernel = { workspace = true, features = ["dev-log", "ext4"] } -[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] -ax-plat-riscv64-sg2002 = { workspace = true, features = ["fp-simd", "irq", "rtc"], optional = true } -axplat-riscv64-visionfive2 = { workspace = true, features = ["fp-simd", "irq", "rtc"], optional = true } - [target.'cfg(any(windows,unix))'.dependencies] anyhow = "1.0" axbuild = { workspace = true } diff --git a/os/StarryOS/starryos/src/main.rs b/os/StarryOS/starryos/src/main.rs index 531d66c44c..14a4a4723c 100644 --- a/os/StarryOS/starryos/src/main.rs +++ b/os/StarryOS/starryos/src/main.rs @@ -19,12 +19,3 @@ fn main() { starry_kernel::entry::init(&args, &envs); } - -#[cfg(all( - feature = "sg2002", - any(target_arch = "riscv32", target_arch = "riscv64") -))] -extern crate ax_plat_riscv64_sg2002; - -#[cfg(all(feature = "vf2", target_arch = "riscv64"))] -extern crate axplat_riscv64_visionfive2; diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index 835eea21f7..d8582ceb4d 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -48,6 +48,7 @@ alloc = ["ax-alloc", "ax-runtime/alloc"] alloc-tlsf = ["ax-alloc/tlsf"] alloc-slab = ["ax-alloc/slab"] alloc-buddy = ["ax-alloc/buddy"] +buddy-slab = ["alloc", "ax-runtime/buddy-slab"] page-alloc-64g = ["ax-alloc/page-alloc-64g"] # up to 64G memory capacity page-alloc-4g = ["ax-alloc/page-alloc-4g"] # up to 4G memory capacity paging = ["alloc", "ax-hal/paging", "ax-runtime/paging"] diff --git a/os/arceos/modules/axalloc/src/buddy_slab.rs b/os/arceos/modules/axalloc/src/buddy_slab.rs index e2f46d7e0a..6af3242956 100644 --- a/os/arceos/modules/axalloc/src/buddy_slab.rs +++ b/os/arceos/modules/axalloc/src/buddy_slab.rs @@ -165,11 +165,10 @@ impl GlobalAllocator { /// Allocate arbitrary number of bytes. Returns the left bound of the /// allocated region. pub fn alloc(&self, layout: Layout) -> AllocResult> { - let result = self - .inner - .lock() - .alloc(layout) - .map_err(crate::AllocError::from); + let result = { + let inner = self.inner.lock(); + inner.alloc(layout).map_err(crate::AllocError::from) + }; if result.is_ok() { self.usages.lock().alloc(UsageKind::RustHeap, layout.size()); } @@ -178,10 +177,13 @@ impl GlobalAllocator { /// Gives back the allocated region to the byte allocator. pub fn dealloc(&self, pos: NonNull, layout: Layout) { + { + let inner = self.inner.lock(); + unsafe { inner.dealloc(pos, layout) }; + } self.usages .lock() .dealloc(UsageKind::RustHeap, layout.size()); - unsafe { self.inner.lock().dealloc(pos, layout) }; } /// Allocates contiguous pages. @@ -191,11 +193,12 @@ impl GlobalAllocator { alignment: usize, kind: UsageKind, ) -> AllocResult { - let result = self - .inner - .lock() - .alloc_pages(num_pages, alignment) - .map_err(crate::AllocError::from); + let result = { + let inner = self.inner.lock(); + inner + .alloc_pages(num_pages, alignment) + .map_err(crate::AllocError::from) + }; if result.is_ok() { self.usages.lock().alloc(kind, num_pages * PAGE_SIZE); } @@ -209,11 +212,12 @@ impl GlobalAllocator { alignment: usize, kind: UsageKind, ) -> AllocResult { - let result = self - .inner - .lock() - .alloc_pages_lowmem(num_pages, alignment) - .map_err(crate::AllocError::from); + let result = { + let inner = self.inner.lock(); + inner + .alloc_pages_lowmem(num_pages, alignment) + .map_err(crate::AllocError::from) + }; if result.is_ok() { self.usages.lock().alloc(kind, num_pages * PAGE_SIZE); } @@ -233,8 +237,11 @@ impl GlobalAllocator { /// Gives back the allocated pages starts from `pos` to the page allocator. pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { + { + let inner = self.inner.lock(); + inner.dealloc_pages(pos, num_pages); + } self.usages.lock().dealloc(kind, num_pages * PAGE_SIZE); - self.inner.lock().dealloc_pages(pos, num_pages); } /// Returns the number of allocated bytes in the allocator backend. @@ -346,6 +353,9 @@ pub fn global_allocator() -> &'static GlobalAllocator { } /// Initializes the per-CPU slab for the current CPU. +/// +/// Must run after per-CPU storage is initialized and before scheduler, IPI, or +/// IRQ paths can allocate on this CPU. pub fn init_percpu_slab(cpu_id: usize) { PERCPU_SLAB.with_current(|slab| slab.init(cpu_id)); } diff --git a/os/arceos/modules/axhal/src/lib.rs b/os/arceos/modules/axhal/src/lib.rs index 2ee0cc0aa0..c531538599 100644 --- a/os/arceos/modules/axhal/src/lib.rs +++ b/os/arceos/modules/axhal/src/lib.rs @@ -89,6 +89,7 @@ pub mod context { pub use ax_cpu::{TaskContext, TrapFrame}; } +pub use ax_cpu as cpu; pub use ax_cpu::asm; #[cfg(feature = "uspace")] pub use ax_cpu::uspace; diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 88da7cad43..fed7af39b8 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -22,7 +22,7 @@ smp = ["alloc", "ax-hal/smp", "ax-task?/smp"] stack-guard-page = ["multitask", "paging", "ax-task/stack-guard-page"] tls = ["ax-hal/tls", "ax-task?/tls"] -plat-dyn = ["ax-hal/plat-dyn", "paging", "buddy-slab"] +plat-dyn = ["ax-hal/plat-dyn", "paging"] display = ["dep:ax-display", "ax-driver/display"] fs = [ diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 31f957e60e..cfe1f462c9 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -51,6 +51,8 @@ mod klib; mod devices; mod registers; +pub use ax_hal as hal; + #[cfg(feature = "smp")] pub use self::mp::rust_main_secondary; @@ -165,6 +167,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { }; ax_hal::percpu::init_primary(cpu_id); #[cfg(all(feature = "alloc", feature = "buddy-slab"))] + // After per-CPU init, before scheduler/IPI/IRQ paths can allocate. ax_alloc::init_percpu_slab(cpu_id); ax_hal::init_early(cpu_id, arg); let log_level = option_env!("AX_LOG").unwrap_or("info"); diff --git a/os/arceos/modules/axruntime/src/mp.rs b/os/arceos/modules/axruntime/src/mp.rs index 2d3239fde7..7194510827 100644 --- a/os/arceos/modules/axruntime/src/mp.rs +++ b/os/arceos/modules/axruntime/src/mp.rs @@ -138,6 +138,7 @@ pub fn rust_main_secondary(cpu_id: usize) -> ! { } ax_hal::percpu::init_secondary(cpu_id); #[cfg(feature = "buddy-slab")] + // After per-CPU init, before scheduler/IPI/IRQ paths can allocate. ax_alloc::init_percpu_slab(cpu_id); ax_hal::init_early_secondary(cpu_id); diff --git a/platform/axplat-dyn/Cargo.toml b/platform/axplat-dyn/Cargo.toml index 78f20be7e8..df69d8cfe2 100644 --- a/platform/axplat-dyn/Cargo.toml +++ b/platform/axplat-dyn/Cargo.toml @@ -20,7 +20,6 @@ hv = ["somehal/hv", "ax-cpu/arm-el2"] [dependencies] anyhow = { version = "1", default-features = false } -ax-alloc = { workspace = true, features = ["buddy-slab"] } ax-config-macros = { workspace = true } ax-cpu.workspace = true ax-driver = { workspace = true, features = ["fdt", "pci-fdt"] } diff --git a/platform/axplat-dyn/src/irq.rs b/platform/axplat-dyn/src/irq.rs index c3371cf9c8..254eaa3253 100644 --- a/platform/axplat-dyn/src/irq.rs +++ b/platform/axplat-dyn/src/irq.rs @@ -51,8 +51,17 @@ impl IrqIf for IrqIfImpl { Some(irq.raw()) } - fn send_ipi(_id: usize, _target: ax_plat::irq::IpiTarget) { - todo!() + fn send_ipi(id: usize, target: ax_plat::irq::IpiTarget) { + let target = match target { + ax_plat::irq::IpiTarget::Current { cpu_id } => { + somehal::irq::IpiTarget::Current { cpu_id } + } + ax_plat::irq::IpiTarget::Other { cpu_id } => somehal::irq::IpiTarget::Other { cpu_id }, + ax_plat::irq::IpiTarget::AllExceptCurrent { cpu_id, cpu_num } => { + somehal::irq::IpiTarget::AllExceptCurrent { cpu_id, cpu_num } + } + }; + somehal::irq::send_ipi(id.into(), target); } } diff --git a/platform/axplat-dyn/src/lib.rs b/platform/axplat-dyn/src/lib.rs index 8d8614d897..ffb0a89ee8 100644 --- a/platform/axplat-dyn/src/lib.rs +++ b/platform/axplat-dyn/src/lib.rs @@ -1,6 +1,5 @@ #![no_std] #![cfg(not(any(windows, unix)))] -#![feature(used_with_arg)] extern crate alloc; extern crate ax_driver as _; diff --git a/platform/somehal/src/arch/aarch64/gic/mod.rs b/platform/somehal/src/arch/aarch64/gic/mod.rs index 2d2e781cdf..221bdc28ff 100644 --- a/platform/somehal/src/arch/aarch64/gic/mod.rs +++ b/platform/somehal/src/arch/aarch64/gic/mod.rs @@ -5,11 +5,40 @@ use someboot::irq::IrqId; mod v2; mod v3; +use core::sync::atomic::{AtomicU8, Ordering}; + +#[derive(Clone, Copy, Eq, PartialEq)] +enum GicBackend { + None = 0, + V2 = 2, + V3 = 3, +} + +static GIC_BACKEND: AtomicU8 = AtomicU8::new(GicBackend::None as u8); + +fn set_backend(backend: GicBackend) { + GIC_BACKEND.store(backend as u8, Ordering::Release); +} + +fn backend() -> GicBackend { + match GIC_BACKEND.load(Ordering::Acquire) { + 2 => GicBackend::V2, + 3 => GicBackend::V3, + _ => GicBackend::None, + } +} + pub fn init_current_cpu() { - if v3::is_support_icc() { - v3::init_cpu(); - } else { - v2::init_cpu(); + match backend() { + GicBackend::V2 => v2::init_cpu(), + GicBackend::V3 => v3::init_cpu(), + GicBackend::None => { + if v3::is_support_icc() { + v3::init_cpu(); + } else { + v2::init_cpu(); + } + } } } @@ -19,8 +48,33 @@ fn get_gicd() -> Device { pub fn irq_set_enable(irq: rdrive::IrqId, enable: bool) { let raw = irq.into(); - v2::irq_set_enable(raw, enable); - v3::irq_set_enable(raw, enable); + match backend() { + GicBackend::V2 => v2::irq_set_enable(raw, enable), + GicBackend::V3 => v3::irq_set_enable(raw, enable), + GicBackend::None => { + v2::irq_set_enable(raw, enable); + v3::irq_set_enable(raw, enable); + } + } +} + +pub fn send_ipi(irq: rdrive::IrqId, target: crate::irq::IpiTarget) { + let raw = irq.into(); + match backend() { + GicBackend::V2 => v2::send_ipi(raw, target), + GicBackend::V3 => v3::send_ipi(raw, target), + GicBackend::None => { + if v3::is_support_icc() { + v3::send_ipi(raw, target); + } else { + v2::send_ipi(raw, target); + } + } + } +} + +fn hardware_cpu_id(cpu_id: usize) -> usize { + someboot::smp::cpu_idx_to_id(cpu_id).unwrap_or(cpu_id) } #[unsafe(no_mangle)] @@ -29,10 +83,16 @@ fn __aarch64_irq_handler() { } pub(crate) fn irq_handler() -> someboot::irq::IrqId { - if v3::is_support_icc() { - v3::handle_irq() - } else { - v2::handle_irq() + match backend() { + GicBackend::V2 => v2::handle_irq(), + GicBackend::V3 => v3::handle_irq(), + GicBackend::None => { + if v3::is_support_icc() { + v3::handle_irq() + } else { + v2::handle_irq() + } + } } } diff --git a/platform/somehal/src/arch/aarch64/gic/v2.rs b/platform/somehal/src/arch/aarch64/gic/v2.rs index 83d97a6ba1..ef47b00962 100644 --- a/platform/somehal/src/arch/aarch64/gic/v2.rs +++ b/platform/somehal/src/arch/aarch64/gic/v2.rs @@ -75,6 +75,7 @@ fn probe_gic(info: FdtInfo<'_>, dev: PlatformDevice) -> Result<(), OnProbeError> let trap = cpu.trap_operations(); CPU_IF.init(cpu); TRAP.init(trap); + super::set_backend(super::GicBackend::V2); init_cpu(); @@ -120,3 +121,16 @@ pub fn irq_set_enable(raw: usize, enable: bool) { gic.set_irq_enable(unsafe { IntId::raw(raw as _) }, enable); }); } + +pub fn send_ipi(raw: usize, target: crate::irq::IpiTarget) { + let sgi = IntId::sgi(raw as u32); + let target = match target { + crate::irq::IpiTarget::Current { cpu_id: _ } => SGITarget::Current, + crate::irq::IpiTarget::Other { cpu_id } => { + let target_cpu = super::hardware_cpu_id(cpu_id); + SGITarget::TargetList(TargetList::new(&mut core::iter::once(target_cpu))) + } + crate::irq::IpiTarget::AllExceptCurrent { .. } => SGITarget::AllOther, + }; + CPU_IF.send_sgi(sgi, target); +} diff --git a/platform/somehal/src/arch/aarch64/gic/v3.rs b/platform/somehal/src/arch/aarch64/gic/v3.rs index 7054df4775..2b6d7e39b1 100644 --- a/platform/somehal/src/arch/aarch64/gic/v3.rs +++ b/platform/somehal/src/arch/aarch64/gic/v3.rs @@ -51,6 +51,7 @@ fn probe_gic(info: FdtInfo<'_>, dev: PlatformDevice) -> Result<(), OnProbeError> gic.init(); let cpu = gic.cpu_interface(); CPU_IF.init(cpu); + super::set_backend(super::GicBackend::V3); init_cpu(); @@ -87,6 +88,27 @@ pub fn irq_set_enable(raw: usize, enable: bool) { }); } +pub fn send_ipi(raw: usize, target: crate::irq::IpiTarget) { + let sgi = IntId::sgi(raw as u32); + let target = match target { + crate::irq::IpiTarget::Current { cpu_id: _ } => SGITarget::current(), + crate::irq::IpiTarget::Other { cpu_id } => { + SGITarget::list([affinity_from_mpidr(super::hardware_cpu_id(cpu_id))]) + } + crate::irq::IpiTarget::AllExceptCurrent { .. } => SGITarget::All, + }; + CPU_IF.send_sgi(sgi, target); +} + +fn affinity_from_mpidr(mpidr: usize) -> Affinity { + Affinity { + aff0: (mpidr & 0xff) as u8, + aff1: ((mpidr >> 8) & 0xff) as u8, + aff2: ((mpidr >> 16) & 0xff) as u8, + aff3: ((mpidr >> 32) & 0xff) as u8, + } +} + pub fn init_cpu() { unsafe { CPU_IF.update(|cpu| { diff --git a/platform/somehal/src/arch/aarch64/mod.rs b/platform/somehal/src/arch/aarch64/mod.rs index b3b9e5e98d..063fb5ae98 100644 --- a/platform/somehal/src/arch/aarch64/mod.rs +++ b/platform/somehal/src/arch/aarch64/mod.rs @@ -10,6 +10,10 @@ impl PlatOp for Plat { gic::irq_set_enable(irq, enable); } + fn send_ipi(irq: rdrive::IrqId, target: crate::irq::IpiTarget) { + gic::send_ipi(irq, target); + } + fn systick_irq() -> rdrive::IrqId { systick::systick_irq() } diff --git a/platform/somehal/src/common.rs b/platform/somehal/src/common.rs index 6fe98b084a..f58cc8e5a8 100644 --- a/platform/somehal/src/common.rs +++ b/platform/somehal/src/common.rs @@ -6,6 +6,10 @@ use crate::setup::MmioRaw; pub trait PlatOp { fn irq_set_enable(irq: IrqId, enable: bool); + fn send_ipi(_irq: IrqId, _target: crate::irq::IpiTarget) { + panic!("IPI is not implemented for this dynamic platform"); + } + fn irq_handler() -> someboot::irq::IrqId; fn systick_irq() -> IrqId; diff --git a/platform/somehal/src/irq.rs b/platform/somehal/src/irq.rs index f5760c638d..8d976368e6 100644 --- a/platform/somehal/src/irq.rs +++ b/platform/somehal/src/irq.rs @@ -5,6 +5,28 @@ pub use someboot::irq::*; use crate::{arch::Plat, common::PlatOp}; +/// Target specification for inter-processor interrupts. +#[derive(Clone, Copy, Debug)] +pub enum IpiTarget { + /// Send to the current CPU. + Current { + /// The logical CPU ID of the current CPU. + cpu_id: usize, + }, + /// Send to a specific CPU. + Other { + /// The logical CPU ID of the target CPU. + cpu_id: usize, + }, + /// Send to all other CPUs. + AllExceptCurrent { + /// The logical CPU ID of the current CPU. + cpu_id: usize, + /// The total number of CPUs. + cpu_num: usize, + }, +} + pub fn irq_setup_by_fdt(irq_parent: DeviceId, irq_cell: &[u32]) -> IrqId { let mut intc = rdrive::get::(irq_parent).unwrap().lock().unwrap(); debug!("Setting up IRQ {:?}", irq_cell); @@ -17,6 +39,10 @@ pub fn irq_set_enable(irq: IrqId, enable: bool) { Plat::irq_set_enable(irq, enable); } +pub fn send_ipi(irq: IrqId, target: IpiTarget) { + Plat::send_ipi(irq, target); +} + pub fn systick_irq() -> IrqId { Plat::systick_irq() } diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index f92bb3007b..7664f5bc3b 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -86,6 +86,7 @@ pub(crate) fn load_cargo_config(request: &ResolvedStarryRequest) -> anyhow::Resu &makefile_features, metadata, ); + normalize_starry_platform_features(&mut build_info.features); if let Some(smp) = request.smp { build_info.max_cpu_num = Some(smp); } @@ -99,18 +100,33 @@ pub(crate) fn load_cargo_config(request: &ResolvedStarryRequest) -> anyhow::Resu Ok(cargo) } +fn normalize_starry_platform_features(features: &mut Vec) { + let has_sg2002 = features.iter().any(|feature| feature == "sg2002"); + let has_vf2 = features.iter().any(|feature| feature == "vf2"); + + if has_sg2002 { + features.push("ax-hal/riscv64-sg2002".to_string()); + } + if has_vf2 { + features.push("ax-hal/riscv64-visionfive2".to_string()); + } + + features.sort(); + features.dedup(); +} + fn patch_starry_cargo_config( cargo: &mut Cargo, request: &ResolvedStarryRequest, metadata: &Metadata, ) -> anyhow::Result<()> { let platform = crate::context::starry_default_platform_for_arch_checked(&request.arch)?; - let static_defplat = uses_static_default_platform(&cargo.features); + let uses_default_qemu_platform = uses_default_qemu_platform(&cargo.features); cargo.package = request.package.clone(); ensure_starry_bin_arg(&mut cargo.args, &request.package, metadata)?; remove_qemu_feature_for_dynamic_platform(cargo); - if static_defplat { + if uses_default_qemu_platform { cargo.features.push("qemu".to_string()); cargo.features.sort(); cargo.features.dedup(); @@ -122,7 +138,7 @@ fn patch_starry_cargo_config( cargo .env .insert("AX_TARGET".to_string(), request.target.clone()); - if static_defplat { + if uses_default_qemu_platform { cargo .env .entry("AX_PLATFORM".to_string()) @@ -173,14 +189,12 @@ fn remove_qemu_feature_for_dynamic_platform(cargo: &mut Cargo) { } } -fn uses_static_default_platform(features: &[String]) -> bool { +fn uses_default_qemu_platform(features: &[String]) -> bool { let has_static_platform = features.iter().any(|feature| { matches!( feature.as_str(), "defplat" | "ax-feat/defplat" | "ax-std/defplat" - ) - }) || features.iter().any(|feature| { - feature.starts_with("ax-hal/") && feature != "ax-hal/plat-dyn" && feature != "ax-hal/myplat" + ) || default_starry_qemu_platform_feature(feature).is_some() }); let has_dynamic = features.iter().any(|feature| { matches!( @@ -198,6 +212,15 @@ fn uses_static_default_platform(features: &[String]) -> bool { has_static_platform && !has_dynamic && !has_custom } +fn default_starry_qemu_platform_feature(feature: &str) -> Option<&str> { + match feature.strip_prefix("ax-hal/")? { + "x86-pc" | "aarch64-qemu-virt" | "riscv64-qemu-virt" | "loongarch64-qemu-virt" => { + Some(feature) + } + _ => None, + } +} + fn ensure_starry_bin_arg( args: &mut Vec, package: &str, @@ -558,6 +581,39 @@ HELLO = "world" assert!(!cargo.env.contains_key("AX_PLATFORM")); } + #[test] + fn load_cargo_config_treats_sg2002_as_explicit_platform_feature() { + let mut request = request( + PathBuf::from("/tmp/.build.toml"), + "riscv64", + "riscv64gc-unknown-none-elf", + ); + request.build_info_override = Some(StarryBuildInfo { + features: vec!["sg2002".to_string()], + plat_dyn: false, + ..default_starry_build_info_for_target("riscv64gc-unknown-none-elf") + }); + + let cargo = load_cargo_config(&request).unwrap(); + + assert!(cargo.features.contains(&"sg2002".to_string())); + assert!( + cargo + .features + .contains(&"ax-hal/riscv64-sg2002".to_string()) + ); + assert!( + !cargo + .features + .contains(&"ax-hal/riscv64-qemu-virt".to_string()) + ); + assert!(!cargo.features.contains(&"qemu".to_string())); + assert_eq!( + cargo.env.get("AX_PLATFORM").map(String::as_str), + Some("riscv64-sg2002") + ); + } + #[test] fn resolve_build_info_path_supports_starry_subworkspace_root() { let root = tempdir().unwrap(); diff --git a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml index 6b3ab7b2b6..f4d0d68008 100644 --- a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml @@ -1,7 +1,7 @@ # Build-time config template for StarryOS on SG2002. target = "riscv64gc-unknown-none-elf" env = {} -features = ["sg2002", "myplat"] +features = ["sg2002"] log = "Info" max_cpu_num = 1 plat_dyn = false diff --git a/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..f24592654f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,21 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/plat-dyn", + "ax-feat/display", + "ax-feat/rtc", + "ax-driver/pci", + "ax-driver/rtc", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", + "starry-kernel/input", + "starry-kernel/plat-dyn", + "starry-kernel/vsock", + "starryos/buddy-slab", +] +max_cpu_num = 4 +log = "Warn" +plat_dyn = true +target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/c/CMakeLists.txt new file mode 100644 index 0000000000..0a23e19e50 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/c/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) +project(test-clone-files-race-buddy-slab C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +set(TEST_CLONE_FILES_RACE_SRC + "${CMAKE_CURRENT_LIST_DIR}/../../../qemu-smp4/test-clone-files-race/c/src/main.c") + +add_executable(test-clone-files-race ${TEST_CLONE_FILES_RACE_SRC}) +target_compile_options(test-clone-files-race PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-clone-files-race RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/qemu-aarch64.toml new file mode 100644 index 0000000000..3411897f61 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp4-buddy-slab/test-clone-files-race-buddy-slab/qemu-aarch64.toml @@ -0,0 +1,16 @@ +args = [ + "-nographic", "-cpu", "cortex-a53", + "-m", "512M", + "-smp", "4", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-clone-files-race" +success_regex = ['(?m)^ DONE: \d+ pass, 0 fail'] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 360 diff --git a/test-suit/starryos/normal/qemu-smp4/test-clone-files-race/c/src/main.c b/test-suit/starryos/normal/qemu-smp4/test-clone-files-race/c/src/main.c index a29db4b116..f7bbbcb76d 100644 --- a/test-suit/starryos/normal/qemu-smp4/test-clone-files-race/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp4/test-clone-files-race/c/src/main.c @@ -29,7 +29,8 @@ #include #include -#define N_ITERATIONS 1000 +/* Keep the normal qemu case bounded; larger stress counts belong in stress. */ +#define N_ITERATIONS 64 #define STACK_SIZE (64 * 1024) static int g_pipefd[2]; From e38642291dbea499f7d1c311d10de055fd5637c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 00:02:39 +0800 Subject: [PATCH 44/71] fix(axvisor): enable buddy-slab allocator (#974) * fix(axstd): add buddy-slab allocator feature to Cargo.toml and documentation * chore(ci): drop duplicate Starry buddy-slab job * fix(axruntime): reorder and format dependencies in Cargo.toml for consistency * fix(axruntime): remove unnecessary dependency prefix in plat-dyn feature * fix(axruntime): remove unnecessary 'ipi' dependency from plat-dyn feature * fix(axruntime): reorder dependencies in plat-dyn feature for clarity --- .github/workflows/ci.yml | 9 --- os/arceos/modules/axruntime/Cargo.toml | 104 ++++++++++++++----------- os/arceos/ulib/axstd/Cargo.toml | 1 + os/arceos/ulib/axstd/src/lib.rs | 1 + os/axvisor/Cargo.toml | 2 +- 5 files changed, 60 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3d691a6a8..a3796fc505 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -357,15 +357,6 @@ jobs: apk_region: us limit_to_owner: "" main_pr_only: false - - name: Test starry aarch64 buddy-slab qemu - use_container: true - runs_on: '["ubuntu-latest"]' - command: cargo xtask starry test qemu --arch aarch64 -c test-clone-files-race-buddy-slab - cache_key: test-starry-aarch64-buddy-slab - container_image: base - apk_region: us - limit_to_owner: "" - main_pr_only: false - name: Test starry loongarch64 qemu use_container: true runs_on: '["ubuntu-latest"]' diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index fed7af39b8..0a330852ef 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -1,18 +1,18 @@ [package] -name = "ax-runtime" -version = "0.5.16" -repository = "https://github.com/rcore-os/tgoskits" -edition.workspace = true authors = ["Yuekai Jia "] description = "Runtime library of ArceOS" +edition.workspace = true license.workspace = true +name = "ax-runtime" +repository = "https://github.com/rcore-os/tgoskits" +version = "0.5.16" [features] default = [] alloc = ["dep:ax-alloc"] -dma = ["paging"] buddy-slab = ["alloc", "ax-alloc/buddy-slab"] +dma = ["paging"] ipi = ["dep:ax-ipi"] irq = ["ax-hal/irq", "ax-task?/irq", "dep:ax-percpu"] multitask = ["ax-task/multitask"] @@ -22,24 +22,34 @@ smp = ["alloc", "ax-hal/smp", "ax-task?/smp"] stack-guard-page = ["multitask", "paging", "ax-task/stack-guard-page"] tls = ["ax-hal/tls", "ax-task?/tls"] -plat-dyn = ["ax-hal/plat-dyn", "paging"] +plat-dyn = [ + "ax-hal/plat-dyn", + "paging", + "buddy-slab", + "irq", + "rtc", + "smp", + "stack-guard-page", + "tls", + "alloc", +] display = ["dep:ax-display", "ax-driver/display"] fs = [ - "dep:ax-errno", - "dep:ax-fs", - "dep:rd-block", - "dep:spin", - "dep:axklib", - "ax-driver/block", + "dep:ax-errno", + "dep:ax-fs", + "dep:rd-block", + "dep:spin", + "dep:axklib", + "ax-driver/block", ] fs-ng = [ - "dep:ax-errno", - "dep:ax-fs-ng", - "dep:rd-block", - "dep:spin", - "dep:axklib", - "ax-driver/block", + "dep:ax-errno", + "dep:ax-fs-ng", + "dep:rd-block", + "dep:spin", + "dep:axklib", + "ax-driver/block", ] input = ["dep:ax-input", "ax-driver/input"] net = ["dep:ax-net", "ax-driver/net"] @@ -47,34 +57,34 @@ net-ng = ["dep:ax-net-ng", "dep:rd-net", "dep:spin", "dep:axklib", "ax-driver/ne vsock = ["net-ng", "ax-net-ng/vsock", "ax-driver/vsock"] [dependencies] -ax-alloc = { workspace = true, optional = true, features = ["default"] } -axbacktrace = { workspace = true } -ax-config = { workspace = true } -ax-display = { workspace = true, optional = true } -ax-driver = { workspace = true } -ax-errno = { workspace = true, optional = true } -ax-fs = { workspace = true, optional = true } -ax-fs-ng = { workspace = true, optional = true } -ax-hal = { workspace = true } -ax-input = { workspace = true, optional = true } -ax-ipi = { workspace = true, optional = true } -axklib = { workspace = true, optional = true } -ax-log = { workspace = true } -ax-lazyinit = { workspace = true } -ax-memory-addr = { workspace = true } -ax-mm = { workspace = true, optional = true } -ax-net = { workspace = true, optional = true } -ax-net-ng = { workspace = true, optional = true } -axpanic = { workspace = true } -ax-plat = { workspace = true } -ax-task = { workspace = true, optional = true } -cfg-if = { workspace = true } -chrono = { version = "0.4", default-features = false } -ax-crate-interface = { workspace = true } -ax-ctor-bare = { workspace = true } +ax-alloc = {workspace = true, optional = true, features = ["default"]} +ax-config = {workspace = true} +ax-crate-interface = {workspace = true} +ax-ctor-bare = {workspace = true} +ax-display = {workspace = true, optional = true} +ax-driver = {workspace = true} +ax-errno = {workspace = true, optional = true} +ax-fs = {workspace = true, optional = true} +ax-fs-ng = {workspace = true, optional = true} +ax-hal = {workspace = true} +ax-input = {workspace = true, optional = true} +ax-ipi = {workspace = true, optional = true} +ax-lazyinit = {workspace = true} +ax-log = {workspace = true} +ax-memory-addr = {workspace = true} +ax-mm = {workspace = true, optional = true} +ax-net = {workspace = true, optional = true} +ax-net-ng = {workspace = true, optional = true} +ax-percpu = {workspace = true, optional = true} +ax-plat = {workspace = true} +ax-task = {workspace = true, optional = true} +axbacktrace = {workspace = true} +axklib = {workspace = true, optional = true} +axpanic = {workspace = true} +cfg-if = {workspace = true} +chrono = {version = "0.4", default-features = false} indoc = "2" -ax-percpu = { workspace = true, optional = true } -rd-block = { workspace = true, optional = true } -rd-net = { workspace = true, optional = true } +rd-block = {workspace = true, optional = true} +rd-net = {workspace = true, optional = true} rdrive.workspace = true -spin = { workspace = true, optional = true } +spin = {workspace = true, optional = true} diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index ce212c099f..d97b65ee1b 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -43,6 +43,7 @@ alloc = ["ax-api/alloc", "ax-feat/alloc", "ax-io/alloc"] alloc-tlsf = ["ax-feat/alloc-tlsf"] alloc-slab = ["ax-feat/alloc-slab"] alloc-buddy = ["ax-feat/alloc-buddy"] +buddy-slab = ["ax-feat/buddy-slab"] page-alloc-64g = ["ax-feat/page-alloc-64g"] # Support up to 64G memory capacity page-alloc-4g = ["ax-feat/page-alloc-4g"] # Support up to 4G memory capacity paging = ["ax-feat/paging", "alloc"] diff --git a/os/arceos/ulib/axstd/src/lib.rs b/os/arceos/ulib/axstd/src/lib.rs index 54993ccbb5..8a2e3cdf07 100644 --- a/os/arceos/ulib/axstd/src/lib.rs +++ b/os/arceos/ulib/axstd/src/lib.rs @@ -20,6 +20,7 @@ //! - `alloc-tlsf`: Use the TLSF allocator. //! - `alloc-slab`: Use the slab allocator. //! - `alloc-buddy`: Use the buddy system allocator. +//! - `buddy-slab`: Use the buddy-slab allocator. //! - `paging`: Enable page table manipulation. //! - `tls`: Enable thread-local storage. //! - Task management diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index aeb1ab5756..a0cf1e0070 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -67,7 +67,7 @@ ax-timer-list = { workspace = true } hashbrown = "0.14" # System dependent modules provided by ArceOS. -ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv"] } +ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv", "buddy-slab"] } ax-hal = { workspace = true, features = ["paging", "irq", "smp", "hv", "axvisor-linker"] } # System dependent modules provided by ArceOS-Hypervisor (bare-metal only) axaddrspace = { workspace = true } From c5426b936e212f1fb66e252b599603637ff49a0a Mon Sep 17 00:00:00 2001 From: CN-TangLin <2242120212@qq.com> Date: Wed, 27 May 2026 09:07:52 +0800 Subject: [PATCH 45/71] feat(starry-kernel): add kprobe support (#847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(starry-kernel): add kprobe support with feature gate Add kernel kprobe subsystem for dynamic kernel function tracing and debugging. The implementation supports x86_64, riscv64, aarch64, and loongarch64 architectures. Key changes: - kprobe.rs: core kprobe implementation with TrapFrame<->PtRegs conversion for all four supported architectures, per-thread kretprobe stack, and SMP-safe registration via stop_machine - Feature-gated with #[cfg(feature = "kprobe")], enabled by default - trap.rs: breakpoint and debug exception handlers with fallback when kprobe is disabled - task/mod.rs: per-thread kretprobe_stack field - Cargo.toml: add kprobe crate dependency (optional) * docs(starry-kernel): add comment for aarch64 SP=0 limitation in kprobe TrapFrame conversion * fix(starry-kernel): address all kprobe PR review feedback 1. KernelRawMutex: replace custom CAS spinlock with spin::Mutex<()> which implements lock_api::RawMutex via the existing spin crate 2. alloc_kernel_exec_memory: use map_alloc() with READ|WRITE|EXECUTE flags to allocate executable memory via the kernel address space instead of the non-executable heap allocator 3. KPROBE_MANAGER: use ax_sync::Mutex instead of SpinNoIrq 4. flush_tlb_range: remove local duplicate, reuse mm/access.rs version (made pub in access.rs, re-exported via mm module) 5. Remove kprobe feature gate: kprobe is now always included - Remove kprobe from [features], make dependency non-optional - Remove all #[cfg(feature = "kprobe")] annotations - Remove Feature Gate section from module docs * fix(starry-kernel): restore kprobe feature gate and add selftest - Restore #[cfg(feature = "kprobe")] gate on kprobe module, trap handlers, and kretprobe_stack field to prevent CI failures on architectures where kprobe runtime behavior is unverified - Add fallback breakpoint/debug handlers that return false when kprobe feature is disabled - Make kprobe dependency optional in Cargo.toml - Add kprobe selftest: register kprobe on a test function, verify handler is called, then unregister - Call run_selftest() during kernel init when kprobe feature is enabled * feat(starry-kernel): add kretprobe selftest * refactor(starry-kernel): remove kprobe feature gate, make kprobe a fixed kernel capability * fix(starry-kernel): skip kprobe selftest on loongarch64 The kprobe selftest causes the CI loongarch64 QEMU test to fail (40min timeout). The kprobe crate's loongarch64 backend needs further validation on QEMU before the selftest can be enabled. Skip the selftest on loongarch64 while keeping the kprobe module fully compiled and functional. * fix(starry-kernel): address kprobe review feedback — SpinNoIrq, aarch64 comment, hint param - Replace ax_sync::Mutex with ax_sync::spin::SpinNoIrq for KPROBE_MANAGER to prevent potential sleep-in-exception-context on SMP systems - Restore aarch64 SP=0 comment explaining TrapFrame does not store SP - Use guard.base() instead of VirtAddr::from(0) as find_free_area hint for clearer intent when searching kernel address space * fix(starry-kernel): remove broken kcov feature reference after rebase * chore(starry-kernel): re-trigger CI for kprobe PR --- Cargo.lock | 34 ++ os/StarryOS/kernel/Cargo.toml | 1 + os/StarryOS/kernel/src/entry.rs | 5 + os/StarryOS/kernel/src/kprobe.rs | 474 ++++++++++++++++++++++++++++ os/StarryOS/kernel/src/lib.rs | 1 + os/StarryOS/kernel/src/mm/access.rs | 2 +- os/StarryOS/kernel/src/task/mod.rs | 3 + os/StarryOS/kernel/src/trap.rs | 8 +- 8 files changed, 523 insertions(+), 5 deletions(-) create mode 100644 os/StarryOS/kernel/src/kprobe.rs diff --git a/Cargo.lock b/Cargo.lock index 88a87e4e53..e4084c2efa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4987,6 +4987,19 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "kprobe" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf6b7b70ff393b62d067f6f2e35bd64c9278ac77fd6fa2de883b2c0be8fa5ff" +dependencies = [ + "cfg-if", + "lock_api", + "log", + "yaxpeax-arch", + "yaxpeax-x86", +] + [[package]] name = "ktest-helper" version = "0.1.1" @@ -7964,6 +7977,7 @@ dependencies = [ "indoc", "inherit-methods-macro", "kernel-elf-parser", + "kprobe", "ktracepoint", "linux-raw-sys 0.12.1", "lock_api", @@ -10193,6 +10207,26 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yaxpeax-arch" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36274fcc5403da2a7636ffda4d02eca12a1b2b8267b9d2e04447bd2ccfc72082" +dependencies = [ + "num-traits", +] + +[[package]] +name = "yaxpeax-x86" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9a30b7dd533c7b1a73eaf7c4ea162a7a632a2bb29b9fff47d8f2cc8513a883" +dependencies = [ + "cfg-if", + "num-traits", + "yaxpeax-arch", +] + [[package]] name = "yoke" version = "0.8.2" diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 1531ea32af..90af40394d 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -131,6 +131,7 @@ zerocopy = { version = "0.8", features = ["derive"] } ax-ipi = { workspace = true } static-keys = "0.8" ddebug = "0.5" +kprobe = "0.5" dw_apb_uart = { workspace = true, features = ["sg2002"], optional = true } sg200x-bsp = { workspace = true, optional = true } # 0.9 to match sg200x-bsp's internal tock-registers; cannot use workspace (0.10) diff --git a/os/StarryOS/kernel/src/entry.rs b/os/StarryOS/kernel/src/entry.rs index 0e00b1728b..5e5ac35e70 100644 --- a/os/StarryOS/kernel/src/entry.rs +++ b/os/StarryOS/kernel/src/entry.rs @@ -22,6 +22,11 @@ pub fn init(args: &[String], envs: &[String]) { static_keys::global_init(); tracepoint_init().expect("Failed to initialize tracepoints"); + // FIXME: loongarch64 selftest hangs on QEMU; the kprobe crate's loongarch64 + // breakpoint handling needs upstream fixes before selftest can be enabled. + #[cfg(not(target_arch = "loongarch64"))] + crate::kprobe::run_selftest(); + pseudofs::mount_all().expect("Failed to mount pseudofs"); spawn_alarm_task(); diff --git a/os/StarryOS/kernel/src/kprobe.rs b/os/StarryOS/kernel/src/kprobe.rs new file mode 100644 index 0000000000..d7ecb2bfa3 --- /dev/null +++ b/os/StarryOS/kernel/src/kprobe.rs @@ -0,0 +1,474 @@ +//! Kernel probe (kprobe) subsystem for StarryOS. +//! +//! This module provides dynamic tracing support by allowing breakpoint +//! insertion at kernel function entry/return points. It integrates the +//! [`kprobe`] crate with StarryOS kernel infrastructure. +//! +//! # Architecture Support +//! +//! All four supported architectures are enabled: x86_64, riscv64, aarch64, +//! and loongarch64. Each architecture provides TrapFrame↔PtRegs register +//! conversion to bridge the kernel's trap frame format with the kprobe +//! crate's portable `PtRegs` type. +//! +//! # Key Components +//! +//! - [`KernelKprobeOps`]: Platform-specific auxiliary operations for the kprobe crate +//! - [`handle_breakpoint`]: Entry point for breakpoint exceptions (INT3/EBREAK/BRK) +//! - [`handle_debug`]: Entry point for debug exceptions (x86_64 single-step only) + +use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr, VirtAddrRange}; +use kprobe::KprobeAuxiliaryOps; + +use crate::task::AsThread; + +#[derive(Debug)] +pub struct KernelKprobeOps; + +impl KprobeAuxiliaryOps for KernelKprobeOps { + fn copy_memory(src: *const u8, dst: *mut u8, len: usize, user_pid: Option) { + if let Some(_pid) = user_pid { + unsafe { + let buf = + core::slice::from_raw_parts_mut(dst as *mut core::mem::MaybeUninit, len); + if let Err(e) = starry_vm::vm_read_slice(src, buf) { + warn!("kprobe copy_memory: vm_read_slice failed: {:?}", e); + } + } + } else { + unsafe { + core::ptr::copy_nonoverlapping(src, dst, len); + } + } + } + + fn set_writeable_for_address( + address: usize, + len: usize, + user_pid: Option, + action: F, + ) { + if user_pid.is_some() { + unimplemented!("user space breakpoint insertion not yet supported") + } + let addr = VirtAddr::from(address); + let aligned_addr = addr.align_down_4k(); + let aligned_end = (addr + len).align_up_4k(); + let aligned_length: usize = aligned_end - aligned_addr; + + crate::stop_machine::stop_machine( + move || { + let mut guard = ax_mm::kernel_aspace().lock(); + let (_, original_flags, _) = guard + .page_table() + .query(aligned_addr) + .expect("kprobe: set_writeable: address not mapped"); + guard + .protect( + aligned_addr, + aligned_length, + original_flags | ax_runtime::hal::paging::MappingFlags::WRITE, + ) + .expect("kprobe: set_writeable: protect failed"); + crate::mm::flush_tlb_range(aligned_addr, aligned_length); + action(addr.as_mut_ptr()); + #[cfg(target_arch = "aarch64")] + ax_runtime::hal::cpu::asm::clean_dcache_range_to_pou(addr, len); + guard + .protect(aligned_addr, aligned_length, original_flags) + .expect("kprobe: set_writeable: restore failed"); + }, + move || { + crate::mm::flush_tlb_range(aligned_addr, aligned_length); + ax_runtime::hal::cpu::asm::flush_icache_all(); + }, + ); + } + + fn alloc_kernel_exec_memory() -> *mut u8 { + let mut guard = ax_mm::kernel_aspace().lock(); + let range = VirtAddrRange::new(guard.base(), guard.end()); + let vaddr = guard + .find_free_area(guard.base(), PAGE_SIZE_4K, range) + .expect("kprobe: no free virtual address for exec memory"); + guard + .map_alloc( + vaddr, + PAGE_SIZE_4K, + ax_runtime::hal::paging::MappingFlags::READ + | ax_runtime::hal::paging::MappingFlags::WRITE + | ax_runtime::hal::paging::MappingFlags::EXECUTE, + true, + ) + .expect("kprobe: map_alloc for exec memory failed"); + vaddr.as_mut_ptr() + } + + fn free_kernel_exec_memory(ptr: *mut u8) { + let vaddr = VirtAddr::from(ptr as usize); + let mut guard = ax_mm::kernel_aspace().lock(); + guard + .unmap(vaddr, PAGE_SIZE_4K) + .expect("kprobe: unmap exec memory failed"); + } + + fn alloc_user_exec_memory(_pid: Option, _action: F) -> *mut u8 { + unimplemented!("user exec memory allocation for uprobes not yet supported") + } + + fn free_user_exec_memory(_pid: Option, _ptr: *mut u8) { + unimplemented!("user exec memory deallocation for uprobes not yet supported") + } + + fn insert_kretprobe_instance_to_task(instance: kprobe::retprobe::RetprobeInstance) { + let curr = ax_task::current(); + curr.as_thread().kretprobe_stack.lock().push(instance); + } + + fn pop_kretprobe_instance_from_task() -> kprobe::retprobe::RetprobeInstance { + let curr = ax_task::current(); + curr.as_thread() + .kretprobe_stack + .lock() + .pop() + .expect("kretprobe instance stack underflow") + } +} + +type KprobeManager = kprobe::ProbeManager, KernelKprobeOps>; + +static KPROBE_MANAGER: ax_sync::spin::SpinNoIrq> = + ax_sync::spin::SpinNoIrq::new(None); + +fn with_manager(f: F) -> R +where + F: FnOnce(&mut KprobeManager) -> R, +{ + let mut guard = KPROBE_MANAGER.lock(); + if guard.is_none() { + *guard = Some(KprobeManager::default()); + } + f(guard.as_mut().expect("kprobe: manager not initialized")) +} + +fn trapframe_to_ptregs(tf: &ax_runtime::hal::cpu::TrapFrame) -> kprobe::PtRegs { + #[cfg(target_arch = "x86_64")] + { + kprobe::PtRegs { + r15: tf.r15 as usize, + r14: tf.r14 as usize, + r13: tf.r13 as usize, + r12: tf.r12 as usize, + rbp: tf.rbp as usize, + rbx: tf.rbx as usize, + r11: tf.r11 as usize, + r10: tf.r10 as usize, + r9: tf.r9 as usize, + r8: tf.r8 as usize, + rax: tf.rax as usize, + rcx: tf.rcx as usize, + rdx: tf.rdx as usize, + rsi: tf.rsi as usize, + rdi: tf.rdi as usize, + orig_rax: 0, + rip: tf.rip as usize, + cs: tf.cs as usize, + rflags: tf.rflags as usize, + rsp: tf.rsp as usize, + ss: tf.ss as usize, + } + } + #[cfg(target_arch = "riscv64")] + { + kprobe::PtRegs { + epc: tf.sepc, + ra: tf.regs.ra, + sp: tf.regs.sp, + gp: tf.regs.gp, + tp: tf.regs.tp, + t0: tf.regs.t0, + t1: tf.regs.t1, + t2: tf.regs.t2, + s0: tf.regs.s0, + s1: tf.regs.s1, + a0: tf.regs.a0, + a1: tf.regs.a1, + a2: tf.regs.a2, + a3: tf.regs.a3, + a4: tf.regs.a4, + a5: tf.regs.a5, + a6: tf.regs.a6, + a7: tf.regs.a7, + s2: tf.regs.s2, + s3: tf.regs.s3, + s4: tf.regs.s4, + s5: tf.regs.s5, + s6: tf.regs.s6, + s7: tf.regs.s7, + s8: tf.regs.s8, + s9: tf.regs.s9, + s10: tf.regs.s10, + s11: tf.regs.s11, + t3: tf.regs.t3, + t4: tf.regs.t4, + t5: tf.regs.t5, + t6: tf.regs.t6, + status: tf.sstatus.bits(), + badaddr: 0, + cause: 0, + orig_a0: tf.regs.a0, + } + } + #[cfg(target_arch = "aarch64")] + { + kprobe::PtRegs { + regs: tf.x, + sp: 0, // aarch64 SP is not saved in TrapFrame + pc: tf.elr, + pstate: tf.spsr, + orig_x0: tf.x[0], + syscallno: -1, + unused2: 0, + } + } + #[cfg(target_arch = "loongarch64")] + { + kprobe::PtRegs { + regs: [ + tf.regs.zero, + tf.regs.ra, + tf.regs.tp, + tf.regs.sp, + tf.regs.a0, + tf.regs.a1, + tf.regs.a2, + tf.regs.a3, + tf.regs.a4, + tf.regs.a5, + tf.regs.a6, + tf.regs.a7, + tf.regs.t0, + tf.regs.t1, + tf.regs.t2, + tf.regs.t3, + tf.regs.t4, + tf.regs.t5, + tf.regs.t6, + tf.regs.t7, + tf.regs.t8, + tf.regs.u0, + tf.regs.fp, + tf.regs.s0, + tf.regs.s1, + tf.regs.s2, + tf.regs.s3, + tf.regs.s4, + tf.regs.s5, + tf.regs.s6, + tf.regs.s7, + tf.regs.s8, + ], + orig_a0: tf.regs.a0, + csr_era: tf.era, + csr_badvaddr: 0, + csr_crmd: 0, + csr_prmd: tf.prmd, + csr_euen: 0, + csr_ecfg: 0, + csr_estat: 0, + } + } +} + +fn ptregs_write_back(pt: &kprobe::PtRegs, tf: &mut ax_runtime::hal::cpu::TrapFrame) { + #[cfg(target_arch = "x86_64")] + { + tf.r15 = pt.r15 as u64; + tf.r14 = pt.r14 as u64; + tf.r13 = pt.r13 as u64; + tf.r12 = pt.r12 as u64; + tf.rbp = pt.rbp as u64; + tf.rbx = pt.rbx as u64; + tf.r11 = pt.r11 as u64; + tf.r10 = pt.r10 as u64; + tf.r9 = pt.r9 as u64; + tf.r8 = pt.r8 as u64; + tf.rax = pt.rax as u64; + tf.rcx = pt.rcx as u64; + tf.rdx = pt.rdx as u64; + tf.rsi = pt.rsi as u64; + tf.rdi = pt.rdi as u64; + tf.rip = pt.rip as u64; + tf.cs = pt.cs as u64; + tf.rflags = pt.rflags as u64; + tf.rsp = pt.rsp as u64; + tf.ss = pt.ss as u64; + } + #[cfg(target_arch = "riscv64")] + { + tf.sepc = pt.epc; + tf.regs.ra = pt.ra; + tf.regs.sp = pt.sp; + tf.regs.gp = pt.gp; + tf.regs.tp = pt.tp; + tf.regs.t0 = pt.t0; + tf.regs.t1 = pt.t1; + tf.regs.t2 = pt.t2; + tf.regs.s0 = pt.s0; + tf.regs.s1 = pt.s1; + tf.regs.a0 = pt.a0; + tf.regs.a1 = pt.a1; + tf.regs.a2 = pt.a2; + tf.regs.a3 = pt.a3; + tf.regs.a4 = pt.a4; + tf.regs.a5 = pt.a5; + tf.regs.a6 = pt.a6; + tf.regs.a7 = pt.a7; + tf.regs.s2 = pt.s2; + tf.regs.s3 = pt.s3; + tf.regs.s4 = pt.s4; + tf.regs.s5 = pt.s5; + tf.regs.s6 = pt.s6; + tf.regs.s7 = pt.s7; + tf.regs.s8 = pt.s8; + tf.regs.s9 = pt.s9; + tf.regs.s10 = pt.s10; + tf.regs.s11 = pt.s11; + tf.regs.t3 = pt.t3; + tf.regs.t4 = pt.t4; + tf.regs.t5 = pt.t5; + tf.regs.t6 = pt.t6; + } + #[cfg(target_arch = "aarch64")] + { + tf.x = pt.regs; + tf.elr = pt.pc; + tf.spsr = pt.pstate; + } + #[cfg(target_arch = "loongarch64")] + { + tf.regs.zero = pt.regs[0]; + tf.regs.ra = pt.regs[1]; + tf.regs.tp = pt.regs[2]; + tf.regs.sp = pt.regs[3]; + tf.regs.a0 = pt.regs[4]; + tf.regs.a1 = pt.regs[5]; + tf.regs.a2 = pt.regs[6]; + tf.regs.a3 = pt.regs[7]; + tf.regs.a4 = pt.regs[8]; + tf.regs.a5 = pt.regs[9]; + tf.regs.a6 = pt.regs[10]; + tf.regs.a7 = pt.regs[11]; + tf.regs.t0 = pt.regs[12]; + tf.regs.t1 = pt.regs[13]; + tf.regs.t2 = pt.regs[14]; + tf.regs.t3 = pt.regs[15]; + tf.regs.t4 = pt.regs[16]; + tf.regs.t5 = pt.regs[17]; + tf.regs.t6 = pt.regs[18]; + tf.regs.t7 = pt.regs[19]; + tf.regs.t8 = pt.regs[20]; + tf.regs.u0 = pt.regs[21]; + tf.regs.fp = pt.regs[22]; + tf.regs.s0 = pt.regs[23]; + tf.regs.s1 = pt.regs[24]; + tf.regs.s2 = pt.regs[25]; + tf.regs.s3 = pt.regs[26]; + tf.regs.s4 = pt.regs[27]; + tf.regs.s5 = pt.regs[28]; + tf.regs.s6 = pt.regs[29]; + tf.regs.s7 = pt.regs[30]; + tf.regs.s8 = pt.regs[31]; + tf.era = pt.csr_era; + tf.prmd = pt.csr_prmd; + } +} + +pub fn handle_breakpoint(tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { + let mut pt_regs = trapframe_to_ptregs(tf); + let handled = with_manager(|manager| kprobe::kprobe_handler_from_break(manager, &mut pt_regs)); + if handled.is_some() { + ptregs_write_back(&pt_regs, tf); + return true; + } + false +} + +#[allow(dead_code)] +fn kprobe_selftest_target() -> i32 { + 42 +} + +static SELFTEST_HIT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); +static SELFTEST_RET_HIT: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); + +fn selftest_pre_handler(_data: &dyn kprobe::ProbeData, _pt: &mut kprobe::PtRegs) { + SELFTEST_HIT.store(true, core::sync::atomic::Ordering::SeqCst); +} + +fn selftest_ret_handler(_data: &dyn kprobe::ProbeData, _pt: &mut kprobe::PtRegs) { + SELFTEST_RET_HIT.store(true, core::sync::atomic::Ordering::SeqCst); +} + +pub fn run_selftest() -> bool { + let target_addr = kprobe_selftest_target as *const () as usize; + let mut kprobe_ok = false; + let mut kretprobe_ok = false; + + with_manager(|manager| { + let mut probe_list = kprobe::ProbePointList::new(); + let builder = kprobe::ProbeBuilder::::new() + .with_symbol_addr(target_addr) + .with_pre_handler(selftest_pre_handler) + .with_enable(true); + + let kp = kprobe::register_kprobe(manager, &mut probe_list, builder); + let val = kprobe_selftest_target(); + kprobe_ok = SELFTEST_HIT.load(core::sync::atomic::Ordering::SeqCst); + + kprobe::unregister_kprobe(manager, &mut probe_list, kp); + + if kprobe_ok && val == 42 { + info!("kprobe selftest passed"); + } else { + warn!("kprobe selftest failed: hit={}, val={}", kprobe_ok, val); + } + }); + + with_manager(|manager| { + let mut probe_list = kprobe::ProbePointList::new(); + let builder = kprobe::KretprobeBuilder::>::new(4) + .with_symbol_addr(target_addr) + .with_ret_handler(selftest_ret_handler) + .with_enable(true); + + let kr = kprobe::register_kretprobe(manager, &mut probe_list, builder); + let val = kprobe_selftest_target(); + kretprobe_ok = SELFTEST_RET_HIT.load(core::sync::atomic::Ordering::SeqCst); + + kprobe::unregister_kretprobe(manager, &mut probe_list, kr); + + if kretprobe_ok && val == 42 { + info!("kretprobe selftest passed"); + } else { + warn!( + "kretprobe selftest failed: hit={}, val={}", + kretprobe_ok, val + ); + } + }); + + kprobe_ok && kretprobe_ok +} + +#[cfg(target_arch = "x86_64")] +pub fn handle_debug(tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { + let mut pt_regs = trapframe_to_ptregs(tf); + let handled = with_manager(|manager| kprobe::kprobe_handler_from_debug(manager, &mut pt_regs)); + if handled.is_some() { + ptregs_write_back(&pt_regs, tf); + return true; + } + false +} diff --git a/os/StarryOS/kernel/src/lib.rs b/os/StarryOS/kernel/src/lib.rs index 2b88e0a84f..d55cc33bd4 100644 --- a/os/StarryOS/kernel/src/lib.rs +++ b/os/StarryOS/kernel/src/lib.rs @@ -19,6 +19,7 @@ pub mod entry; mod config; mod file; +mod kprobe; mod mm; mod pseudofs; mod stop_machine; diff --git a/os/StarryOS/kernel/src/mm/access.rs b/os/StarryOS/kernel/src/mm/access.rs index 1eacc6a51e..80d0c67689 100644 --- a/os/StarryOS/kernel/src/mm/access.rs +++ b/os/StarryOS/kernel/src/mm/access.rs @@ -520,7 +520,7 @@ pub fn write_kernel_text(addr: VirtAddr, data: &[u8]) -> AxResult<()> { ) } -fn flush_tlb_range(start: VirtAddr, size: usize) { +pub fn flush_tlb_range(start: VirtAddr, size: usize) { for offset in (0..size).step_by(PAGE_SIZE_4K) { ax_runtime::hal::cpu::asm::flush_tlb(Some(start + offset)); } diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index 63620a38e4..bb0aa15e9a 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -159,6 +159,8 @@ pub struct Thread { /// wrong context or, if it had a user handler, swallow the dump so /// the real fault terminated silently. pub fault_dump_signo: AtomicU8, + + pub kretprobe_stack: SpinNoIrq>, } impl Thread { @@ -188,6 +190,7 @@ impl Thread { cred: SpinNoIrq::new(cred), fault_dump_signo: AtomicU8::new(0), + kretprobe_stack: SpinNoIrq::new(alloc::vec::Vec::new()), }) } diff --git a/os/StarryOS/kernel/src/trap.rs b/os/StarryOS/kernel/src/trap.rs index 694b766660..ffcd0ca7ab 100644 --- a/os/StarryOS/kernel/src/trap.rs +++ b/os/StarryOS/kernel/src/trap.rs @@ -1,10 +1,10 @@ #[ax_runtime::hal::cpu::trap::breakpoint_handler] -fn default_breakpoint_handler(_tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { - false +fn default_breakpoint_handler(tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { + crate::kprobe::handle_breakpoint(tf) } #[cfg(target_arch = "x86_64")] #[ax_runtime::hal::cpu::trap::debug_handler] -fn default_debug_handler(_tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { - false +fn default_debug_handler(tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { + crate::kprobe::handle_debug(tf) } From c7e3c04f54ddb97fc862e78bb98024de3972d5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 09:13:45 +0800 Subject: [PATCH 46/71] fix(ramdisk): enable crate publishing (#975) --- drivers/blk/ramdisk/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/blk/ramdisk/Cargo.toml b/drivers/blk/ramdisk/Cargo.toml index 0c6b540c72..5cfe7ce6ff 100644 --- a/drivers/blk/ramdisk/Cargo.toml +++ b/drivers/blk/ramdisk/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT" name = "ramdisk" repository.workspace = true version = "0.1.1" -publish = false [dependencies] rdif-block = { workspace = true } From 5441af6a1e538079a8d98fbae2befb16ca98376b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 10:08:26 +0800 Subject: [PATCH 47/71] Remove ARM PL011 UART driver and integrate DesignWare APB UART support (#965) - Deleted the ARM PL011 UART driver files including README, source code, and related documentation. - Introduced a new DesignWare APB UART backend for NS16550-compatible core in the `some-serial` module. - Updated the `some-serial` module to support DesignWare APB UART alongside existing NS16550 implementations. - Modified the kernel and device drivers to utilize the new `some-serial` module instead of the removed ARM PL011 driver. - Adjusted Cargo.toml dependencies to reflect the removal of `dw_apb_uart` and the addition of `some-serial`. --- Cargo.lock | 31 +-- Cargo.toml | 2 - .../axplat-aarch64-bsta1000b/Cargo.toml | 2 +- .../axplat-aarch64-bsta1000b/src/init.rs | 2 +- .../axplat-aarch64-bsta1000b/src/lib.rs | 2 +- .../src/{dw_apb_uart.rs => serial.rs} | 9 +- .../axplat-aarch64-peripherals/Cargo.toml | 2 +- .../axplat-aarch64-peripherals/src/pl011.rs | 96 ++++---- .../axplat-riscv64-sg2002/Cargo.toml | 2 +- .../axplat-riscv64-sg2002/src/console.rs | 8 +- docs/docs/components/crates/arceos-display.md | 1 - .../components/crates/arceos-exception.md | 1 - .../docs/components/crates/arceos-fs-shell.md | 1 - .../crates/arceos-net-echoserver.md | 1 - .../crates/arceos-net-httpclient.md | 1 - .../crates/arceos-net-httpserver.md | 1 - .../components/crates/arceos-net-udpserver.md | 1 - docs/docs/components/crates/arceos-tls.md | 1 - docs/docs/components/crates/ax-api.md | 1 - docs/docs/components/crates/ax-arm-pl011.md | 223 ----------------- docs/docs/components/crates/ax-display.md | 1 - docs/docs/components/crates/ax-feat.md | 1 - docs/docs/components/crates/ax-fs-ng.md | 1 - .../components/crates/ax-helloworld-myplat.md | 1 - docs/docs/components/crates/ax-helloworld.md | 1 - docs/docs/components/crates/ax-httpclient.md | 1 - docs/docs/components/crates/ax-httpserver.md | 1 - docs/docs/components/crates/ax-input.md | 1 - docs/docs/components/crates/ax-libc.md | 1 - docs/docs/components/crates/ax-net-ng.md | 1 - .../crates/ax-plat-aarch64-bsta1000b.md | 20 +- .../crates/ax-plat-aarch64-peripherals.md | 8 +- docs/docs/components/crates/ax-posix-api.md | 1 - docs/docs/components/crates/ax-runtime.md | 1 - docs/docs/components/crates/ax-shell.md | 1 - docs/docs/components/crates/ax-std.md | 1 - docs/docs/components/layers.md | 8 +- docs/docs/components/overview.md | 1 - .../serial/arm_pl011/.github/workflows/ci.yml | 55 ----- drivers/serial/arm_pl011/.gitignore | 4 - drivers/serial/arm_pl011/CHANGELOG.md | 14 -- drivers/serial/arm_pl011/Cargo.toml | 13 - drivers/serial/arm_pl011/LICENSE | 201 ---------------- drivers/serial/arm_pl011/README.md | 84 ------- drivers/serial/arm_pl011/README_CN.md | 84 ------- drivers/serial/arm_pl011/src/lib.rs | 6 - drivers/serial/arm_pl011/src/pl011.rs | 103 -------- drivers/serial/dw_apb_uart/.gitignore | 4 - drivers/serial/dw_apb_uart/CHANGELOG.md | 14 -- drivers/serial/dw_apb_uart/Cargo.toml | 23 -- drivers/serial/dw_apb_uart/src/lib.rs | 144 ----------- drivers/serial/some-serial/src/lib.rs | 7 +- .../serial/some-serial/src/ns16550/dw_apb.rs | 224 ++++++++++++++++++ drivers/serial/some-serial/src/ns16550/mod.rs | 116 ++++----- os/StarryOS/kernel/Cargo.toml | 4 +- .../kernel/src/pseudofs/dev/cvi_camera.rs | 17 +- .../kernel/src/pseudofs/dev/tty_serial.rs | 14 +- scripts/repo/repos.csv | 1 - scripts/test/std_crates.csv | 1 - 59 files changed, 391 insertions(+), 1181 deletions(-) rename components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/{dw_apb_uart.rs => serial.rs} (89%) delete mode 100644 docs/docs/components/crates/ax-arm-pl011.md delete mode 100644 drivers/serial/arm_pl011/.github/workflows/ci.yml delete mode 100644 drivers/serial/arm_pl011/.gitignore delete mode 100644 drivers/serial/arm_pl011/CHANGELOG.md delete mode 100644 drivers/serial/arm_pl011/Cargo.toml delete mode 100644 drivers/serial/arm_pl011/LICENSE delete mode 100644 drivers/serial/arm_pl011/README.md delete mode 100644 drivers/serial/arm_pl011/README_CN.md delete mode 100644 drivers/serial/arm_pl011/src/lib.rs delete mode 100644 drivers/serial/arm_pl011/src/pl011.rs delete mode 100644 drivers/serial/dw_apb_uart/.gitignore delete mode 100644 drivers/serial/dw_apb_uart/CHANGELOG.md delete mode 100644 drivers/serial/dw_apb_uart/Cargo.toml delete mode 100644 drivers/serial/dw_apb_uart/src/lib.rs create mode 100644 drivers/serial/some-serial/src/ns16550/dw_apb.rs diff --git a/Cargo.lock b/Cargo.lock index e4084c2efa..4b16a34496 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -743,13 +743,6 @@ dependencies = [ "ax-task", ] -[[package]] -name = "ax-arm-pl011" -version = "0.3.8" -dependencies = [ - "tock-registers 0.8.1", -] - [[package]] name = "ax-arm-pl031" version = "0.4.7" @@ -1341,8 +1334,8 @@ dependencies = [ "ax-page-table-entry", "ax-plat", "ax-plat-aarch64-peripherals", - "dw_apb_uart", "log", + "some-serial", ] [[package]] @@ -1351,7 +1344,6 @@ version = "0.5.10" dependencies = [ "aarch64-cpu 11.2.0", "arm-gic-driver", - "ax-arm-pl011", "ax-arm-pl031", "ax-cpu", "ax-int-ratio", @@ -1359,6 +1351,7 @@ dependencies = [ "ax-lazyinit", "ax-plat", "log", + "some-serial", "spin", ] @@ -1463,12 +1456,12 @@ dependencies = [ "ax-plat", "ax-riscv-plic", "axklib", - "dw_uart_rs", "log", "rd-block", "riscv 0.16.0", "sbi-rt 0.0.3", "sg200x-bsp", + "some-serial", "spin", ] @@ -3470,22 +3463,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "dw_apb_uart" -version = "0.1.2" -dependencies = [ - "tock-registers 0.10.1", -] - -[[package]] -name = "dw_uart_rs" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3751978e2d9bced3cb480631bb2c14098357ae269ac95475720aee545c6dcd" -dependencies = [ - "tock-registers 0.8.1", -] - [[package]] name = "dwmmc-host" version = "0.1.1" @@ -7967,7 +7944,6 @@ dependencies = [ "crab-usb", "ddebug", "downcast-rs", - "dw_apb_uart", "enum_dispatch", "event-listener", "extern-trait", @@ -7991,6 +7967,7 @@ dependencies = [ "sg2002-tpu", "sg200x-bsp", "slab", + "some-serial", "spin", "starry-process", "starry-signal", diff --git a/Cargo.toml b/Cargo.toml index b1c5ffd97e..44f241b771 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -196,7 +196,6 @@ arm_vgic = { version = "0.4.10", path = "components/arm_vgic" } ax-alloc = { version = "0.7.2", path = "os/arceos/modules/axalloc", default-features = false } ax-allocator = { version = "0.5.2", path = "components/axallocator" } ax-api = { version = "0.5.16", path = "os/arceos/api/arceos_api" } -ax-arm-pl011 = { version = "0.3.8", path = "drivers/serial/arm_pl011" } ax-arm-pl031 = { version = "0.4.7", path = "drivers/rtc/arm_pl031" } ax-cap-access = { version = "0.3.5", path = "components/cap_access" } ax-config = { version = "0.5.10", path = "os/arceos/modules/axconfig" } @@ -289,7 +288,6 @@ bwbench-client = { version = "0.3.1", path = "os/arceos/tools/bwbench_client" } define-simple-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/define-simple-traits" } define-weak-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/define-weak-traits" } deptool = { version = "0.3.1", path = "os/arceos/tools/deptool" } -dw_apb_uart = { version = "0.1", path = "drivers/serial/dw_apb_uart", default-features = false } fxmac_rs = { version = "0.5.1", path = "drivers/net/fxmac_rs" } hello-kernel = { version = "0.3.1", path = "components/axplat_crates/examples/hello-kernel" } impl-simple-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/impl-simple-traits" } diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml index 2c30017efb..b4b760f0d4 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml @@ -19,7 +19,7 @@ smp = ["ax-plat/smp", "ax-kspin/smp"] log = "0.4" ax-kspin = { workspace = true } ax-page-table-entry = { workspace = true } -dw_apb_uart = { workspace = true } +some-serial = { workspace = true } ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/init.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/init.rs index 6532e2aff7..6e0e571a00 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/init.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/init.rs @@ -16,7 +16,7 @@ impl InitIf for InitIfImpl { fn init_early(_cpu_id: usize, _dtb: usize) { ax_cpu::init::init_trap(); ax_plat_aarch64_peripherals::psci::init(PSCI_METHOD); - super::dw_apb_uart::init_early(); + super::serial::init_early(); ax_plat_aarch64_peripherals::generic_timer::init_early(); } diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs index f5884a76db..b5302a4ddf 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs @@ -23,13 +23,13 @@ pub mod config { mod boot; mod drivers; -mod dw_apb_uart; mod init; mod mem; mod misc; #[cfg(feature = "smp")] mod mp; mod power; +mod serial; ax_plat_aarch64_peripherals::time_if_impl!(TimeIfImpl); diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/dw_apb_uart.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/serial.rs similarity index 89% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/dw_apb_uart.rs rename to components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/serial.rs index 45d86b7d3d..13129eb55a 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/dw_apb_uart.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/serial.rs @@ -1,4 +1,4 @@ -//! snps,dw-apb-uart serial driver +//! DesignWare APB UART serial driver. #[cfg(feature = "irq")] use core::ptr::read_volatile; @@ -10,13 +10,14 @@ use ax_plat::{ console::ConsoleIf, mem::{PhysAddr, pa}, }; -use dw_apb_uart::DW8250; +use some_serial::ns16550::dw_apb::{DwApbUart, RK3588_UART_CLOCK}; use crate::mem::phys_to_virt; const UART_BASE: PhysAddr = pa!(crate::config::devices::UART_PADDR); -static UART: SpinNoIrq = SpinNoIrq::new(DW8250::new(phys_to_virt(UART_BASE).as_usize())); +static UART: SpinNoIrq = + SpinNoIrq::new(DwApbUart::new(phys_to_virt(UART_BASE).as_usize())); #[cfg(feature = "irq")] const UART_LSR_OFFSET: usize = 0x14; @@ -34,7 +35,7 @@ fn getchar() -> Option { /// UART simply initialize pub fn init_early() { - UART.lock().init(); + UART.lock().init_with_baud_clk(115_200, RK3588_UART_CLOCK); } struct ConsoleIfImpl; diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml b/components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml index 8023888bc6..d4044ca543 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml @@ -37,11 +37,11 @@ spin = "0.10" ax-int-ratio = { workspace = true } ax-lazyinit = { workspace = true } aarch64-cpu = "11.0" -ax-arm-pl011 = { workspace = true } arm-gic-driver.workspace = true ax-arm-pl031 = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +some-serial = { workspace = true } [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs b/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs index 8dd1877cc9..d301da5426 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs @@ -1,35 +1,53 @@ //! PL011 UART. -use core::ptr::{read_volatile, write_volatile}; +use core::{hint::spin_loop, ptr::NonNull}; -use ax_arm_pl011::Pl011Uart; use ax_kspin::SpinNoIrq; use ax_lazyinit::LazyInit; use ax_plat::{console::ConsoleIrqEvent, mem::VirtAddr}; +use some_serial::{ + InterfaceRaw, InterruptMask, Reciever as Receiver, Sender, TIrqHandler, TReciever as _, + TSender as _, + pl011::{Pl011, Pl011IrqHandler}, +}; + +struct ConsoleUart { + uart: Pl011, + tx: Sender, + rx: Receiver, +} -static UART: LazyInit> = LazyInit::new(); -static UART_BASE: LazyInit = LazyInit::new(); - -const PL011_IMSC: usize = 0x38; -const PL011_MIS: usize = 0x40; -const PL011_ICR: usize = 0x44; +impl ConsoleUart { + fn new(uart_base: VirtAddr) -> Self { + let base = NonNull::new(uart_base.as_mut_ptr()).expect("PL011 MMIO base must be non-null"); + let mut uart = Pl011::new_no_clock(base); + uart.open(); + uart.set_irq_mask(InterruptMask::empty()); + let tx = uart.take_tx().expect("PL011 TX handle was already taken"); + let rx = uart.take_rx().expect("PL011 RX handle was already taken"); + let irq_handler = uart + .irq_handler() + .expect("PL011 IRQ handler was already taken"); + UART_IRQ_HANDLER.init_once(irq_handler); + Self { uart, tx, rx } + } -const PL011_RX_INT: u32 = 1 << 4; -const PL011_RT_INT: u32 = 1 << 6; -const PL011_FE_INT: u32 = 1 << 7; -const PL011_PE_INT: u32 = 1 << 8; -const PL011_BE_INT: u32 = 1 << 9; -const PL011_OE_INT: u32 = 1 << 10; -const PL011_INPUT_IRQ_MASK: u32 = - PL011_RX_INT | PL011_RT_INT | PL011_FE_INT | PL011_PE_INT | PL011_BE_INT | PL011_OE_INT; + fn putchar(&mut self, c: u8) { + while !self.tx.write_byte(c) { + spin_loop(); + } + } -fn read_reg(offset: usize) -> u32 { - unsafe { read_volatile(((*UART_BASE) + offset) as *const u32) } + fn getchar(&mut self) -> Option { + match self.rx.read_byte() { + Some(Ok(byte)) => Some(byte), + Some(Err(_)) | None => None, + } + } } -fn write_reg(offset: usize, value: u32) { - unsafe { write_volatile(((*UART_BASE) + offset) as *mut u32, value) } -} +static UART: LazyInit> = LazyInit::new(); +static UART_IRQ_HANDLER: LazyInit = LazyInit::new(); /// Writes a byte to the console. pub fn putchar(c: u8) { @@ -66,47 +84,27 @@ pub fn read_bytes(bytes: &mut [u8]) -> usize { /// Early stage initialization of the PL011 UART driver. pub fn init_early(uart_base: VirtAddr) { - UART_BASE.init_once(uart_base.as_usize()); - UART.init_once(SpinNoIrq::new({ - let mut uart = Pl011Uart::new(uart_base.as_mut_ptr()); - uart.init(); - uart - })); - set_input_irq_enabled(false); + UART.init_once(SpinNoIrq::new(ConsoleUart::new(uart_base))); } /// Enables or disables PL011 receive-side IRQs. pub fn set_input_irq_enabled(enabled: bool) { - let _guard = UART.lock(); - let imsc = read_reg(PL011_IMSC); - let imsc = if enabled { - imsc | PL011_INPUT_IRQ_MASK + let mut uart = UART.lock(); + let mask = if enabled { + InterruptMask::RX_AVAILABLE } else { - imsc & !PL011_INPUT_IRQ_MASK + InterruptMask::empty() }; - write_reg(PL011_IMSC, imsc); + uart.uart.set_irq_mask(mask); } /// Handles a PL011 input interrupt and returns the corresponding event flags. pub fn handle_irq() -> ConsoleIrqEvent { - let _guard = UART.lock(); - let mis = read_reg(PL011_MIS); - let clear = mis & PL011_INPUT_IRQ_MASK; - if clear != 0 { - write_reg(PL011_ICR, clear); - } - + let status = UART_IRQ_HANDLER.clean_interrupt_status(); let mut events = ConsoleIrqEvent::empty(); - if mis & (PL011_RX_INT | PL011_RT_INT) != 0 { + if status.contains(InterruptMask::RX_AVAILABLE) { events |= ConsoleIrqEvent::RX_READY; } - if mis & PL011_OE_INT != 0 { - events |= ConsoleIrqEvent::OVERRUN; - } - if mis & (PL011_FE_INT | PL011_PE_INT | PL011_BE_INT | PL011_OE_INT) != 0 { - events |= ConsoleIrqEvent::RX_ERROR; - } - events } diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml b/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml index b08b88146e..f06e70966d 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml @@ -21,7 +21,6 @@ rtc = [] smp = ["ax-plat/smp", "ax-kspin/smp"] [dependencies] -dw_uart_rs = "0.2.0" log = { workspace = true } ax-config-macros = { workspace = true } ax-cpu = { workspace = true, features = ["xuantie-c9xx"] } @@ -32,6 +31,7 @@ ax-plat = { workspace = true } axklib.workspace = true rd-block.workspace = true sg200x-bsp.workspace = true +some-serial.workspace = true spin.workspace = true [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/console.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/console.rs index 4ce532154c..54d46f4f42 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/console.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/console.rs @@ -3,17 +3,17 @@ use ax_lazyinit::LazyInit; use ax_plat::console::ConsoleIf; #[cfg(feature = "irq")] use ax_plat::console::ConsoleIrqEvent; -use dw_uart_rs::DW8250; +use some_serial::ns16550::dw_apb::{DwApbUart, SG2002_UART_CLOCK}; use crate::config::{devices::UART_PADDR, plat::PHYS_VIRT_OFFSET}; -static UART: LazyInit> = LazyInit::new(); +static UART: LazyInit> = LazyInit::new(); pub(crate) fn init_early() { UART.init_once({ - let mut uart = DW8250::new(UART_PADDR + PHYS_VIRT_OFFSET); + let mut uart = DwApbUart::new(UART_PADDR + PHYS_VIRT_OFFSET); // SG2002 uses dw-apb-uart with 25MHz clock, 115200 baud. - uart.ns16550_init(25_000_000, 115200); + uart.init_with_baud_clk(115_200, SG2002_UART_CLOCK); SpinNoIrq::new(uart) }); } diff --git a/docs/docs/components/crates/arceos-display.md b/docs/docs/components/crates/arceos-display.md index 69572f3c31..e0890ca5fd 100644 --- a/docs/docs/components/crates/arceos-display.md +++ b/docs/docs/components/crates/arceos-display.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-exception.md b/docs/docs/components/crates/arceos-exception.md index b53716f5c6..2575acfb8b 100644 --- a/docs/docs/components/crates/arceos-exception.md +++ b/docs/docs/components/crates/arceos-exception.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-fs-shell.md b/docs/docs/components/crates/arceos-fs-shell.md index 1e7615f4ac..6b28ea8897 100644 --- a/docs/docs/components/crates/arceos-fs-shell.md +++ b/docs/docs/components/crates/arceos-fs-shell.md @@ -48,7 +48,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-net-echoserver.md b/docs/docs/components/crates/arceos-net-echoserver.md index ae7b61960e..2d1aa53306 100644 --- a/docs/docs/components/crates/arceos-net-echoserver.md +++ b/docs/docs/components/crates/arceos-net-echoserver.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-net-httpclient.md b/docs/docs/components/crates/arceos-net-httpclient.md index 9fe7c0b234..75eeeebd35 100644 --- a/docs/docs/components/crates/arceos-net-httpclient.md +++ b/docs/docs/components/crates/arceos-net-httpclient.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-net-httpserver.md b/docs/docs/components/crates/arceos-net-httpserver.md index c5dc033a57..36545a9759 100644 --- a/docs/docs/components/crates/arceos-net-httpserver.md +++ b/docs/docs/components/crates/arceos-net-httpserver.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-net-udpserver.md b/docs/docs/components/crates/arceos-net-udpserver.md index 88347ba1af..1e5da04a6f 100644 --- a/docs/docs/components/crates/arceos-net-udpserver.md +++ b/docs/docs/components/crates/arceos-net-udpserver.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/arceos-tls.md b/docs/docs/components/crates/arceos-tls.md index cee3d79074..5c5fa41e8f 100644 --- a/docs/docs/components/crates/arceos-tls.md +++ b/docs/docs/components/crates/arceos-tls.md @@ -42,7 +42,6 @@ graph LR - `ax-alloc` - `ax-allocator` - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `ax-cap-access` - `ax-config` diff --git a/docs/docs/components/crates/ax-api.md b/docs/docs/components/crates/ax-api.md index 34080ba3b2..a489d93256 100644 --- a/docs/docs/components/crates/ax-api.md +++ b/docs/docs/components/crates/ax-api.md @@ -59,7 +59,6 @@ graph LR - 另外还有 `5` 个同类项未在此展开 ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-allocator` diff --git a/docs/docs/components/crates/ax-arm-pl011.md b/docs/docs/components/crates/ax-arm-pl011.md deleted file mode 100644 index df9dc019f3..0000000000 --- a/docs/docs/components/crates/ax-arm-pl011.md +++ /dev/null @@ -1,223 +0,0 @@ -# `ax-arm-pl011` - -> 路径:`drivers/serial/arm_pl011` -> 类型:库 crate -> 分层:组件层 / AArch64 外设叶子实现 -> 版本:`0.1.0` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/pl011.rs`、`components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs` - -`ax-arm-pl011` 是一个非常典型的**设备叶子 crate**:它只关心 ARM PL011 UART 的寄存器布局和最基础的收发/中断确认操作,对上层暴露一个 `Pl011Uart` 结构体。它不是串口子系统、不是控制台框架,也不负责平台发现、时钟配置、锁保护或缓冲策略。 - -## 架构设计 - -### 设计定位 - -从 `src/pl011.rs` 可以看出,这个 crate 的问题域很窄: - -- 把 PL011 的寄存器布局声明出来 -- 按固定寄存器语义完成最基本的初始化 -- 提供阻塞写、非阻塞读和简单中断判断/清除 - -它明确不做: - -- 波特率计算与分频寄存器配置 -- pinmux、时钟源、复位时序 -- 缓冲队列、中断驱动收发、DMA -- 多实例管理与并发同步 - -因此它更像“可复用的 MMIO 叶子驱动”,适合被平台层拿去二次封装。 - -### 1.2 内部结构 - -整个 crate 只有一个核心模块 `pl011`,内部结构也很直接: - -| 组成 | 作用 | -| --- | --- | -| `Pl011UartRegs` | 用 `tock_registers::register_structs!` 描述寄存器布局 | -| `Pl011Uart` | 保存基地址并提供对外方法 | -| `regs()` | 把 `NonNull` 转成寄存器视图 | - -已声明并实际使用的寄存器包括: - -- `dr`:数据寄存器 -- `fr`:状态寄存器 -- `cr`:控制寄存器 -- `ifls`:FIFO 触发阈值 -- `imsc`:中断 mask -- `mis`:中断状态 -- `icr`:中断清除 - -这再次说明它只覆盖当前平台接线真正需要的一小部分 PL011 能力。 - -### 1.3 运行模型 - -`Pl011Uart` 的运行模型非常简单: - -1. `new(base)` 保存 UART 的 MMIO 基地址 -2. `init()` 对几组关键寄存器做最小初始化 -3. `putchar()` 轮询 `FR.TXFF`,等待发送 FIFO 可写 -4. `getchar()` 读取 `FR.RXFE`,在有数据时返回一个字节 -5. `is_receive_interrupt()` / `ack_interrupts()` 处理接收中断状态 - -这里没有任何后台线程、状态机或缓冲结构,所有行为都以调用者的时序为准。 - -### 1.4 `init()` 实际做了什么 - -`init()` 的实现只做了四件事: - -- `ICR = 0x7ff`,清空中断 -- `IFLS = 0`,把 FIFO 触发阈值设为 1/8 -- `IMSC = 1 << 4`,打开接收中断 -- `CR = (1 << 0) | (1 << 8) | (1 << 9)`,启用 UART、发送和接收 - -值得注意的是,它**没有**设置波特率寄存器,也没有配置行控制寄存器。这通常意味着: - -- 某些平台默认配置已经足够 -- 或者这些设置在更早的引导阶段完成 -- 或者当前使用场景只需要最小可用收发能力 - -### 1.5 与平台层的真实关系 - -在当前仓库中,真实消费者是 `ax-plat-aarch64-peripherals` 的 `pl011.rs`: - -- 用 `LazyInit>` 管理全局实例 -- 在 `init_early()` 中调用 `Pl011Uart::new()` 和 `init()` -- `putchar()` 上层额外做了 `\n -> \r\n` 转换 -- 通过 `console_if_impl!` 宏把它接到 `ax_plat::console::ConsoleIf` - -这条调用链说明: - -- `ax-arm-pl011` 本身不负责平台抽象 -- 平台层才负责把这个叶子驱动接进整个控制台接口 - -## 核心功能 - -### 功能概览 - -- 基于基地址构造 `Pl011Uart` -- 对 UART 做最小初始化 -- 发送单字节 -- 非阻塞接收单字节 -- 判断是否出现接收中断 -- 清除全部中断 - -### 2.2 关键接口 - -| 接口 | 作用 | -| --- | --- | -| `Pl011Uart::new(base)` | 根据 MMIO 基地址创建实例 | -| `init()` | 清中断、设 FIFO 阈值、开 RX 中断、启用 UART | -| `putchar(c)` | 轮询发送一个字节 | -| `getchar()` | 若接收 FIFO 非空则取一个字节 | -| `is_receive_interrupt()` | 检查接收中断是否 pending | -| `ack_interrupts()` | 清除中断 | - -### 2.3 最关键的边界 - -这个 crate 只到“寄存器薄封装”为止,不包含: - -- 控制台抽象 -- 终端行规约 -- 串口驱动线程 -- 设备树发现 -- IRQ 框架集成 -- 多核共享时的锁 - -因此把它写成“串口子系统”会明显高估它的职责。 - -## 依赖关系 - -### 直接依赖 - -| 依赖 | 作用 | -| --- | --- | -| `tock-registers` | 声明 MMIO 寄存器结构和读写接口 | - -### 主要消费者 - -当前仓库内可确认的直接消费者是: - -- `components/axplat_crates/platforms/axplat-aarch64-peripherals` - -它在平台层之上的传递关系可以概括为: - -- `ax-arm-pl011` -> `ax-plat-aarch64-peripherals` -> AArch64 平台控制台实现 -> 各上层系统 - -### 3.3 关系解读 - -| 层级 | 角色 | -| --- | --- | -| `ax-arm-pl011` | 叶子寄存器驱动 | -| `ax-plat-aarch64-peripherals` | 把叶子驱动接成 `axplat` 可用控制台 | -| ArceOS/StarryOS/Axvisor 的 AArch64 平台 | 通过平台层间接获得早期串口输出能力 | - -## 开发指南 - -### 4.1 适用场景 - -适合直接使用 `ax-arm-pl011` 的场景是: - -- 平台代码已经知道 UART 基地址 -- 需要最小串口输入输出能力 -- 同步/锁、换行策略、控制台接口想在上层自定义 - -不适合直接把它当成: - -- 通用 TTY 层 -- 高性能串口驱动 -- 带缓存和异步队列的串口子系统 - -### 4.2 修改时要特别注意的点 - -- 寄存器偏移必须严格符合 PL011 手册 -- `putchar()` 的轮询条件和 `getchar()` 的空/非空条件不要写反 -- `init()` 当前是“最小可用初始化”,新增配置项时要确认不会破坏已有平台假设 -- 若要支持并发访问,应在上层加锁,而不是让该 crate 直接耦合平台同步原语 - -### 4.3 若要扩展功能,推荐放在哪一层 - -- 时钟、波特率、设备树解析:放在平台 crate -- 控制台 trait 对接:放在平台或 HAL 适配层 -- 缓冲、中断驱动收发:放在更高层驱动封装 -- 这里只保留 PL011 硬件原语 - -## 测试 - -### 测试覆盖 - -当前 crate 内没有单独的 `tests/`。实际回归主要依赖: - -- 编译检查 -- `ax-plat-aarch64-peripherals` 的平台接线 -- 系统启动后控制台是否可用 - -### 单元测试 - -- 用伪 MMIO 区验证 `init()` 写入了预期寄存器值 -- 验证 `putchar()` 在 `TXFF` 为 1 时会等待 -- 验证 `getchar()` 在 `RXFE` 为 0 时返回正确数据 -- 验证 `is_receive_interrupt()` / `ack_interrupts()` 的位语义 - -### 集成测试 - -- 在使用 PL011 的 AArch64 平台上做启动打印回归 -- 验证中断模式下输入路径能否被上层平台正确接入 -- 验证 `\n` 的 CRLF 行为确实由平台层而非本 crate 提供 - -### 5.4 风险点 - -- 基地址映射错误会让所有访问落到错误 MMIO 区 -- 当前初始化没有处理全部 UART 配置,平台若假设更多寄存器已设定,可能需要补接线 -- 该 crate 默认不加锁,多核共享访问必须由外层保证 - -## 跨项目定位 - -| 项目 | 位置 | 角色 | 说明 | -| --- | --- | --- | --- | -| ArceOS | AArch64 平台外设栈 | 控制台叶子驱动 | 经 `ax-plat-aarch64-peripherals` 间接接入 | -| StarryOS | 共享平台基础设施 | 可复用串口叶子驱动 | 复用 `axplat` 平台路径时可间接使用 | -| Axvisor | 共享平台基础设施 | 早期控制台叶子驱动 | 若选择同一 AArch64 平台实现,可由平台层间接复用 | - -## 总结 - -`ax-arm-pl011` 的价值在于把 PL011 UART 的最基础硬件语义稳定地收敛成一个小而清晰的 API。它既不重,也不“全”,真正的定位是**设备叶子实现**:只处理寄存器,只暴露原语,把平台抽象、锁、中断编排和控制台策略留给上层。 diff --git a/docs/docs/components/crates/ax-display.md b/docs/docs/components/crates/ax-display.md index a8fa0253ab..447782f9a7 100644 --- a/docs/docs/components/crates/ax-display.md +++ b/docs/docs/components/crates/ax-display.md @@ -46,7 +46,6 @@ graph LR - `ax-lazyinit` ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-feat.md b/docs/docs/components/crates/ax-feat.md index 2d0f5cfa98..8f8b78ce6d 100644 --- a/docs/docs/components/crates/ax-feat.md +++ b/docs/docs/components/crates/ax-feat.md @@ -64,7 +64,6 @@ graph LR - 另外还有 `4` 个同类项未在此展开 ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-allocator` diff --git a/docs/docs/components/crates/ax-fs-ng.md b/docs/docs/components/crates/ax-fs-ng.md index abe707a3eb..eb89e7bb05 100644 --- a/docs/docs/components/crates/ax-fs-ng.md +++ b/docs/docs/components/crates/ax-fs-ng.md @@ -59,7 +59,6 @@ graph LR - `scope-local` ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-allocator` diff --git a/docs/docs/components/crates/ax-helloworld-myplat.md b/docs/docs/components/crates/ax-helloworld-myplat.md index 099bef35c8..f711594f76 100644 --- a/docs/docs/components/crates/ax-helloworld-myplat.md +++ b/docs/docs/components/crates/ax-helloworld-myplat.md @@ -53,7 +53,6 @@ graph LR ### 间接依赖 - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-helloworld.md b/docs/docs/components/crates/ax-helloworld.md index 1a92dca744..f7ee9d5d54 100644 --- a/docs/docs/components/crates/ax-helloworld.md +++ b/docs/docs/components/crates/ax-helloworld.md @@ -39,7 +39,6 @@ graph LR ### 间接依赖 - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-httpclient.md b/docs/docs/components/crates/ax-httpclient.md index 98cd74e3ae..9cb32a515b 100644 --- a/docs/docs/components/crates/ax-httpclient.md +++ b/docs/docs/components/crates/ax-httpclient.md @@ -39,7 +39,6 @@ graph LR ### 间接依赖 - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-httpserver.md b/docs/docs/components/crates/ax-httpserver.md index c66d00942e..e8a5ea3050 100644 --- a/docs/docs/components/crates/ax-httpserver.md +++ b/docs/docs/components/crates/ax-httpserver.md @@ -39,7 +39,6 @@ graph LR ### 间接依赖 - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-input.md b/docs/docs/components/crates/ax-input.md index 4bd4c7c6d3..34598fa2e5 100644 --- a/docs/docs/components/crates/ax-input.md +++ b/docs/docs/components/crates/ax-input.md @@ -45,7 +45,6 @@ graph LR - `ax-lazyinit` ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-libc.md b/docs/docs/components/crates/ax-libc.md index 9e754d05f3..64922d4e3a 100644 --- a/docs/docs/components/crates/ax-libc.md +++ b/docs/docs/components/crates/ax-libc.md @@ -51,7 +51,6 @@ graph LR - `axio` ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-net-ng.md b/docs/docs/components/crates/ax-net-ng.md index a87348f1f8..5bd6b0da83 100644 --- a/docs/docs/components/crates/ax-net-ng.md +++ b/docs/docs/components/crates/ax-net-ng.md @@ -65,7 +65,6 @@ graph LR - `smoltcp` ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md b/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md index b6d0814ae1..278b618f65 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md +++ b/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md @@ -4,7 +4,7 @@ > 类型:库 crate > 分层:组件层 / AArch64 板级平台包 > 版本:`0.3.1-pre.6` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`axconfig.toml`、`src/boot.rs`、`src/init.rs`、`src/dw_apb_uart.rs`、`src/mem.rs`、`src/power.rs`、`src/mp.rs`、`src/misc.rs` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`axconfig.toml`、`src/boot.rs`、`src/init.rs`、`src/serial.rs`、`src/mem.rs`、`src/power.rs`、`src/mp.rs`、`src/misc.rs` `ax-plat-aarch64-bsta1000b` 是 Black Sesame A1000B SoC 在 `axplat` 体系里的具体板级实现。它把 A1000B 的启动入口、早期页表、固定地址空间、PSCI 多核拉起、GIC/Generic Timer 接线以及本地 DesignWare APB UART 控制台组织成一组 `axplat` 接口,使上层内核能够按统一平台契约完成 bring-up。它不是通用 AArch64 外设库,也不是完整驱动栈;它解决的是“这块板子怎样从裸机入口走到 `ax_plat::call_main()`,并把最小可运行平台能力交给上层”的问题。 @@ -16,7 +16,7 @@ - 向下依赖 `ax-cpu` 提供 EL 切换、MMU 打开、trap 初始化和缓存/停机等 CPU 原语。 - 横向复用 `ax-plat-aarch64-peripherals` 提供的 PSCI、Generic Timer 和 GIC glue。 -- 自己补上 A1000B 特有的启动代码、内存布局、CPU 硬件 ID 列表,以及非 PL011 的 `dw_apb_uart` 控制台实现。 +- 自己补上 A1000B 特有的启动代码、内存布局、CPU 硬件 ID 列表,以及非 PL011 的 DesignWare APB UART 控制台实现。 - 向上实现 `InitIf`、`MemIf`、`PowerIf`,并通过 `TimeIf` / `IrqIf` 宏展开接入 `axplat`。 这意味着它的核心工作不是“定义抽象”,而是“把板级事实落成 `axplat` 的实现”: @@ -32,7 +32,7 @@ | `lib.rs` | crate 根与 glue 汇总 | `config` 生成、包名校验、`TimeIf`/`IrqIf` 宏接入 | | `boot` | 最早期引导 | Linux 风格 ARM64 镜像头、主核/次核入口、引导页表、MMU 打开 | | `init` | `InitIf` 实现 | trap、PSCI、UART、Generic Timer、GIC 的初始化顺序 | -| `dw_apb_uart` | 本地控制台实现 | `ConsoleIf` 落地、UART 初始化、可选 UART IRQ 使能 | +| `serial` | 本地控制台实现 | `ConsoleIf` 落地、UART 初始化、可选 UART IRQ 使能 | | `mem` | `MemIf` 实现 | RAM/MMIO 区间、线性映射、地址空间边界 | | `power` | `PowerIf` 实现 | `system_off()`、`cpu_boot()`、`cpu_num()` | | `mp` | 次核启动 | 基于 PSCI `cpu_on()` 的次核拉起路径 | @@ -78,7 +78,7 @@ flowchart TD 尤其需要注意三点: -- 这个 crate 没有展开 `ax-plat-aarch64-peripherals::console_if_impl!`,因为 A1000B 控制台不是 PL011,而是本地 `dw_apb_uart` 实现。 +- 这个 crate 没有展开 `ax-plat-aarch64-peripherals::console_if_impl!`,因为 A1000B 控制台不是 PL011,而是本地 DesignWare APB UART 实现。 - `boot.rs` 会把 DTB 指针传上去,但本 crate 的 `init.rs` 并不解析 DTB;当前平台描述主要由 `axconfig.toml` 固化。 - `misc.rs` 里确实有 QSPI reset、CPU reset 和 bootmode 读取逻辑,但这些函数没有接入 `ax_plat::power::system_off()`;对上层可见的电源语义仍然是 PSCI `system_off()`。 @@ -104,7 +104,7 @@ flowchart TD - 为 A1000B 提供可直接链接的 ARM64 启动入口。 - 建立最小可用引导页表,并负责 MMU 打开前后的栈切换。 -- 提供基于 `dw_apb_uart` 的控制台输入输出。 +- 提供基于 `some-serial` DesignWare APB UART 支持的控制台输入输出。 - 通过 `ax-plat-aarch64-peripherals` 接入 PSCI、Generic Timer 和 GIC。 - 暴露平台 RAM、MMIO、线性映射和内核地址空间边界。 - 在 `smp` 打开时,基于 PSCI `cpu_on()` 拉起次核。 @@ -114,7 +114,7 @@ flowchart TD | Feature | 作用 | 主要落点 | | --- | --- | --- | | `fp-simd` | 启动期提前打开 FP/SIMD,避免早期 Rust 代码触发非法指令 | `boot.rs` | -| `irq` | 编译 GIC glue、定时器 IRQ 和 UART IRQ 使能路径 | `lib.rs`、`init.rs`、`dw_apb_uart.rs` | +| `irq` | 编译 GIC glue、定时器 IRQ 和 UART IRQ 使能路径 | `lib.rs`、`init.rs`、`serial.rs` | | `smp` | 编译次核入口和 `PowerIf::cpu_boot()` 路径 | `boot.rs`、`mp.rs`、`power.rs` | | `rtc` | Cargo feature 已预留,但当前源码没有对应实现,等价于占位开关 | `Cargo.toml` | @@ -144,7 +144,7 @@ flowchart TD | `axplat` | 目标平台抽象接口与 `call_main()` 契约 | | `ax-cpu` | EL 切换、MMU 初始化、trap 初始化、缓存/停机辅助 | | `ax-plat-aarch64-peripherals` | PSCI、Generic Timer、GIC 及 `TimeIf`/`IrqIf` glue | -| `dw_apb_uart` | A1000B 控制台所用的 DesignWare 8250 UART 驱动 | +| `some-serial` | A1000B 控制台所用的 DesignWare APB UART 驱动 | | `ax-page-table-entry` | 构造 AArch64 引导页表项 | | `ax-config-macros` | 把 `axconfig.toml` 变成 `config` 常量模块 | | `ax-kspin` | 串口访问的无中断自旋锁 | @@ -162,7 +162,7 @@ flowchart TD graph TD A[ax-cpu / ax-page-table-entry / ax-config-macros] --> B[ax-plat-aarch64-bsta1000b] C[ax-plat-aarch64-peripherals] --> B - D[dw_apb_uart / ax-kspin] --> B + D[some-serial / ax-kspin] --> B E[axplat] --> B B --> F[ax-helloworld-myplat] F --> G[板卡 bring-up / 自定义内核验证] @@ -200,7 +200,7 @@ fn kernel_main(cpu_id: usize, arg: usize) -> ! { - 修改 `axconfig.toml` 中的 RAM 基址、MMIO 地址或 `PHYS_VIRT_OFFSET` 时,必须同时检查 `boot.rs` 的 1 GiB block 映射假设是否仍成立。 - 修改 `CPU_ID_LIST` 时,必须同步验证 PSCI `cpu_on()` 目标 ID 与真实硬件 MPIDR 的对应关系。 -- 若更换控制台 IP,需要同时改动 `dw_apb_uart.rs` 和 `init.rs` 中的初始化与 IRQ 使能顺序。 +- 若更换控制台 IP,需要同时改动 `serial.rs` 和 `init.rs` 中的初始化与 IRQ 使能顺序。 - 如果想让“重启”成为正式平台能力,不能只改 `misc.rs`;还需要重新设计 `PowerIf` 的对外语义。 ### 4.3 适合的调试顺序 @@ -225,7 +225,7 @@ fn kernel_main(cpu_id: usize, arg: usize) -> ! { ### 5.2 推荐测试分层 - 启动冒烟:验证从 `_start` 到 `ax_plat::call_main()` 的完整链路。 -- 串口验证:确认 `dw_apb_uart` 早期初始化后能立即输出日志。 +- 串口验证:确认 DesignWare APB UART 早期初始化后能立即输出日志。 - IRQ 验证:启用 `irq` 后确认 GIC 初始化、timer IRQ 和 UART IRQ 能正常工作。 - SMP 验证:启用 `smp` 后确认 `cpu_boot()` 能按 `CPU_ID_LIST` 拉起次核。 - 电源验证:确认 `system_off()` 走 PSCI 关机,而不是错误落入本地 reset 辅助逻辑。 diff --git a/docs/docs/components/crates/ax-plat-aarch64-peripherals.md b/docs/docs/components/crates/ax-plat-aarch64-peripherals.md index 99befcfda6..924532b702 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-peripherals.md +++ b/docs/docs/components/crates/ax-plat-aarch64-peripherals.md @@ -30,7 +30,7 @@ | --- | --- | --- | | `generic_timer` | ARM Generic Timer 适配 | tick 与纳秒换算、oneshot 计时器、定时器 IRQ 使能、`time_if_impl!` | | `gic` | GICv2 中断控制 | `Gic` 单例、IRQ handler 表、ACK/EOI/DIR、IPI、`irq_if_impl!` | -| `pl011` | PL011 串口 | `Pl011Uart` 单例、字节读写、`console_if_impl!` | +| `pl011` | PL011 串口 | 基于 `some-serial` 的 PL011 单例、字节读写、`console_if_impl!` | | `pl031` | PL031 RTC | Unix 时间读取、epoch 偏移缓存 | | `psci` | PSCI 电源管理 | `smc`/`hvc` 路径选择、`cpu_on`、`cpu_off`、`system_off` | | `lib.rs` | 导出层 | feature 裁剪与模块组织 | @@ -39,7 +39,7 @@ 该 crate 采用“静态单例 + 宏生成接口实现”的风格,典型全局状态包括: -- `pl011`:`LazyInit>`,保证早期串口只初始化一次。 +- `pl011`:`LazyInit>`,保证早期串口只初始化一次。 - `gic`:`LazyInit>`、`LazyInit`、`HandlerTable<1024>`,分别负责 GIC 设备访问、trap 辅助操作和 IRQ 分发表。 - `generic_timer`:两组 tick 与纳秒转换比率,用 `CNTFRQ_EL0` 初始化。 - `pl031`:`RTC_EPOCHOFFSET_NANOS`,缓存墙钟相对于单调时钟零点的偏移。 @@ -156,7 +156,7 @@ generic_timer::enable_irqs(timer_irq); | 依赖 | 作用 | | --- | --- | -| `ax-arm-pl011` | PL011 串口驱动 | +| `some-serial` | 统一串口驱动集合,提供 PL011 支持 | | `ax-arm-pl031` | PL031 RTC 驱动 | | `arm-gic-driver` | GICv2 控制器访问与 trap 操作 | | `aarch64-cpu` | 访问系统寄存器和底层 CPU 能力 | @@ -179,7 +179,7 @@ generic_timer::enable_irqs(timer_irq); ```mermaid graph TD - A[ax-arm-pl011 / ax-arm-pl031 / arm-gic-driver / aarch64-cpu] --> B[ax-plat-aarch64-peripherals] + A[some-serial / ax-arm-pl031 / arm-gic-driver / aarch64-cpu] --> B[ax-plat-aarch64-peripherals] C[axplat] --> B D[ax-lazyinit / ax-kspin / int_ratio] --> B diff --git a/docs/docs/components/crates/ax-posix-api.md b/docs/docs/components/crates/ax-posix-api.md index a031bd55b2..48b69d7825 100644 --- a/docs/docs/components/crates/ax-posix-api.md +++ b/docs/docs/components/crates/ax-posix-api.md @@ -60,7 +60,6 @@ graph LR - 另外还有 `1` 个同类项未在此展开 ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-allocator` diff --git a/docs/docs/components/crates/ax-runtime.md b/docs/docs/components/crates/ax-runtime.md index 1140a54ea8..00e065b7bf 100644 --- a/docs/docs/components/crates/ax-runtime.md +++ b/docs/docs/components/crates/ax-runtime.md @@ -63,7 +63,6 @@ graph LR - 另外还有 `8` 个同类项未在此展开 ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-allocator` diff --git a/docs/docs/components/crates/ax-shell.md b/docs/docs/components/crates/ax-shell.md index 8f17a9db71..8530d8330f 100644 --- a/docs/docs/components/crates/ax-shell.md +++ b/docs/docs/components/crates/ax-shell.md @@ -39,7 +39,6 @@ graph LR ### 间接依赖 - `ax-api` -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/crates/ax-std.md b/docs/docs/components/crates/ax-std.md index 8f1b30e6b3..03712b3cf6 100644 --- a/docs/docs/components/crates/ax-std.md +++ b/docs/docs/components/crates/ax-std.md @@ -64,7 +64,6 @@ graph LR - `ax-lazyinit` ### 间接依赖 -- `ax-arm-pl011` - `ax-arm-pl031` - `axaddrspace` - `ax-alloc` diff --git a/docs/docs/components/layers.md b/docs/docs/components/layers.md index cddaf7526f..765ed4c00f 100644 --- a/docs/docs/components/layers.md +++ b/docs/docs/components/layers.md @@ -60,7 +60,7 @@ flowchart TB L1["层级 1
堆叠层(依赖更底层 crate)
`ax-allocator`、`ax-config-macros`、`ax-ctor-bare`、`ax-fs-vfs`、`ax-io`、`ax-kernel-guard`、`ax-memory-set`、`ax-page-table-entry`、`ax-plat-macros`、`ax-sched`、`axfs-ng-vfs`、`axhvc`、`axklib`、`axvmconfig`、`define-simple-traits`、`define-weak-traits` …共19个"] classDef ls1 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000 class L1 ls1 - L0["层级 0
基础层(无仓库内直接依赖)
`aarch64_sysreg`、`ax-arm-pl011`、`ax-arm-pl031`、`ax-cap-access`、`ax-config-gen`、`ax-cpumask`、`ax-crate-interface`、`ax-crate-interface-lite`、`ax-ctor-bare-macros`、`ax-errno`、`ax-handler-table`、`ax-int-ratio`、`ax-lazyinit`、`ax-linked-list-r4l`、`ax-memory-addr`、`ax-percpu-macros`、`ax-riscv-plic`、`ax-timer-list` …共30个"] + L0["层级 0
基础层(无仓库内直接依赖)
`aarch64_sysreg`、`ax-arm-pl031`、`ax-cap-access`、`ax-config-gen`、`ax-cpumask`、`ax-crate-interface`、`ax-crate-interface-lite`、`ax-ctor-bare-macros`、`ax-errno`、`ax-handler-table`、`ax-int-ratio`、`ax-lazyinit`、`ax-linked-list-r4l`、`ax-memory-addr`、`ax-percpu-macros`、`ax-riscv-plic`、`ax-timer-list` …共29个"] classDef ls0 fill:#eceff1,stroke:#455a64,stroke-width:2px,color:#000 class L0 ls0 L16 --> L15 @@ -90,7 +90,6 @@ flowchart TB | 0 | 基础层(无仓库内直接依赖) | ArceOS 层 | `deptool` | `0.3.0` | `os/arceos/tools/deptool` | | 0 | 基础层(无仓库内直接依赖) | ArceOS 层 | `mingo` | `0.8.0` | `os/arceos/tools/raspi4/chainloader` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `aarch64_sysreg` | `0.3.1` | `components/aarch64_sysreg` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-arm-pl011` | `0.3.0` | `drivers/serial/arm_pl011` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-arm-pl031` | `0.4.1` | `drivers/rtc/arm_pl031` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-cap-access` | `0.3.0` | `components/cap_access` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-config-gen` | `0.4.1` | `components/axconfig-gen/axconfig-gen` | @@ -229,7 +228,7 @@ flowchart TB | 层级 | 数 | 成员 | |------|-----|------| -| 0 | 30 | `aarch64_sysreg` `ax-arm-pl011` `ax-arm-pl031` `ax-cap-access` `ax-config-gen` `ax-cpumask` `ax-crate-interface` `ax-crate-interface-lite` `ax-ctor-bare-macros` `ax-errno` `ax-handler-table` `ax-int-ratio` `ax-lazyinit` `ax-linked-list-r4l` `ax-memory-addr` `ax-percpu-macros` `ax-riscv-plic` `ax-timer-list` `axbacktrace` `axpoll` `axvisor_api_proc` `bitmap-allocator` `bwbench-client` `deptool` `mingo` `range-alloc-arceos` `riscv-h` `rsext4` `smoltcp` | +| 0 | 29 | `aarch64_sysreg` `ax-arm-pl031` `ax-cap-access` `ax-config-gen` `ax-cpumask` `ax-crate-interface` `ax-crate-interface-lite` `ax-ctor-bare-macros` `ax-errno` `ax-handler-table` `ax-int-ratio` `ax-lazyinit` `ax-linked-list-r4l` `ax-memory-addr` `ax-percpu-macros` `ax-riscv-plic` `ax-timer-list` `axbacktrace` `axpoll` `axvisor_api_proc` `bitmap-allocator` `bwbench-client` `deptool` `mingo` `range-alloc-arceos` `riscv-h` `rsext4` `smoltcp` | | 1 | 19 | `ax-allocator` `ax-config-macros` `ax-ctor-bare` `ax-fs-vfs` `ax-io` `ax-kernel-guard` `ax-memory-set` `ax-page-table-entry` `ax-plat-macros` `ax-sched` `axfs-ng-vfs` `axhvc` `axklib` `axvmconfig` `define-simple-traits` `define-weak-traits` `fxmac_rs` `smoltcp-fuzz` `starry-vm` | | 2 | 10 | `ax-config` `ax-fs-devfs` `ax-fs-ramfs` `ax-kspin` `ax-page-table-multiarch` `ax-percpu` `axbuild` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` | | 3 | 11 | `ax-alloc` `ax-cpu` `ax-log` `ax-plat` `axaddrspace` `scope-local` `starry-process` `test-simple` `test-weak` `test-weak-partial` `tg-xtask` | @@ -276,7 +275,6 @@ flowchart TB | `ax-alloc` | 3 | ArceOS global memory allocator | `ax-allocator` `ax-errno` `ax-kspin` `ax-memory-addr` `ax-percpu` `axbacktrace` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs-ng` `ax-hal` `ax-mm` `ax-posix-api` `ax-runtime` `axplat-dyn` `starry-kernel` | | `ax-allocator` | 1 | Various allocator algorithms in a unified interfa… | `ax-errno` `bitmap-allocator` | `ax-alloc` `ax-dma` | | `ax-api` | 14 | Public APIs and types for ArceOS modules | `ax-alloc` `ax-config` `ax-display` `ax-dma` `ax-driver` `ax-errno` `ax-feat` `ax-fs` `ax-hal` `ax-io` `ax-ipi` `ax-log` `ax-mm` `ax-net` `ax-runtime` `ax-sync` `ax-task` | `ax-std` | -| `ax-arm-pl011` | 0 | ARM Uart pl011 register definitions and basic ope… | — | `ax-plat-aarch64-peripherals` | | `ax-arm-pl031` | 0 | System Real Time Clock (RTC) Drivers for aarch64 … | — | `ax-plat-aarch64-peripherals` | | `ax-cap-access` | 0 | Provide basic capability-based access control to … | — | `ax-fs` | | `ax-config` | 2 | Platform-specific constants and parameters for Ar… | `ax-config-macros` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-hal` `ax-ipi` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | @@ -325,7 +323,7 @@ flowchart TB | `ax-percpu-macros` | 0 | Macros to define and access a per-CPU data struct… | — | `ax-percpu` | | `ax-plat` | 3 | This crate provides a unified abstraction layer f… | `ax-crate-interface` `ax-handler-table` `ax-kspin` `ax-memory-addr` `ax-percpu` `ax-plat-macros` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-runtime` `axplat-dyn` `axplat-x86-qemu-q35` `hello-kernel` `irq-kernel` `smp-kernel` | | `ax-plat-aarch64-bsta1000b` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-kspin` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | -| `ax-plat-aarch64-peripherals` | 4 | ARM64 common peripheral drivers with `axplat` com… | `ax-arm-pl011` `ax-arm-pl031` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-plat` | `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` | +| `ax-plat-aarch64-peripherals` | 4 | ARM64 common peripheral drivers with `axplat` com… | `ax-arm-pl031` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-plat` `some-serial` | `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` | | `ax-plat-aarch64-phytium-pi` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | | `ax-plat-aarch64-qemu-virt` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-hal` `ax-helloworld-myplat` `hello-kernel` `irq-kernel` `smp-kernel` | | `ax-plat-aarch64-raspi` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | diff --git a/docs/docs/components/overview.md b/docs/docs/components/overview.md index 89080de6ad..335ddc79e8 100644 --- a/docs/docs/components/overview.md +++ b/docs/docs/components/overview.md @@ -110,7 +110,6 @@ flowchart TB | `ax-alloc` | ArceOS 层 | `os/arceos/modules/axalloc` | 6 | 11 | [查看](crates/ax-alloc) | | `ax-allocator` | 组件层 | `components/axallocator` | 2 | 2 | [查看](crates/ax-allocator) | | `ax-api` | ArceOS 层 | `os/arceos/api/arceos_api` | 17 | 1 | [查看](crates/ax-api) | -| `ax-arm-pl011` | 组件层 | `drivers/serial/arm_pl011` | 0 | 1 | [查看](crates/ax-arm-pl011) | | `ax-arm-pl031` | 组件层 | `drivers/rtc/arm_pl031` | 0 | 1 | [查看](crates/ax-arm-pl031) | | `ax-cap-access` | 组件层 | `components/cap_access` | 0 | 1 | [查看](crates/ax-cap-access) | | `ax-config` | ArceOS 层 | `os/arceos/modules/axconfig` | 1 | 12 | [查看](crates/ax-config) | diff --git a/drivers/serial/arm_pl011/.github/workflows/ci.yml b/drivers/serial/arm_pl011/.github/workflows/ci.yml deleted file mode 100644 index 531ddd1481..0000000000 --- a/drivers/serial/arm_pl011/.github/workflows/ci.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: CI - -on: [push, pull_request] - -jobs: - ci: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust-toolchain: [nightly] - targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat] - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: ${{ matrix.rust-toolchain }} - components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features - - name: Unit test - if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - run: cargo test --target ${{ matrix.targets }} -- --nocapture - - doc: - runs-on: ubuntu-latest - strategy: - fail-fast: false - permissions: - contents: write - env: - default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - name: Build docs - continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: | - cargo doc --no-deps --all-features - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html - - name: Deploy to Github Pages - if: ${{ github.ref == env.default-branch }} - uses: JamesIves/github-pages-deploy-action@v4 - with: - single-commit: true - branch: gh-pages - folder: target/doc diff --git a/drivers/serial/arm_pl011/.gitignore b/drivers/serial/arm_pl011/.gitignore deleted file mode 100644 index ff78c42af5..0000000000 --- a/drivers/serial/arm_pl011/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/.vscode -.DS_Store -Cargo.lock diff --git a/drivers/serial/arm_pl011/CHANGELOG.md b/drivers/serial/arm_pl011/CHANGELOG.md deleted file mode 100644 index 454c28ee72..0000000000 --- a/drivers/serial/arm_pl011/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.8](https://github.com/rcore-os/tgoskits/compare/ax-arm-pl011-v0.3.7...ax-arm-pl011-v0.3.8) - 2026-05-19 - -### Other - -- Update ARM PL031 RTC and PL011 UART drivers with documentation updates ([#739](https://github.com/rcore-os/tgoskits/pull/739)) diff --git a/drivers/serial/arm_pl011/Cargo.toml b/drivers/serial/arm_pl011/Cargo.toml deleted file mode 100644 index 94e768c1a1..0000000000 --- a/drivers/serial/arm_pl011/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "ax-arm-pl011" -version = "0.3.8" -repository = "https://github.com/rcore-os/tgoskits" -edition.workspace = true -authors = ["Shiping Yuan ", "Yuekai Jia "] -description = "ARM Uart pl011 register definitions and basic operations" -keywords = ["arm", "pl011", "uart", "arceos"] -categories = ["embedded", "no-std", "hardware-support", "os"] -license = "Apache-2.0" - -[dependencies] -tock-registers = "0.8" diff --git a/drivers/serial/arm_pl011/LICENSE b/drivers/serial/arm_pl011/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/drivers/serial/arm_pl011/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/drivers/serial/arm_pl011/README.md b/drivers/serial/arm_pl011/README.md deleted file mode 100644 index e76678909b..0000000000 --- a/drivers/serial/arm_pl011/README.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-arm-pl011

- -

ARM Uart pl011 register definitions and basic operations

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-arm-pl011.svg)](https://crates.io/crates/ax-arm-pl011) -[![Docs.rs](https://docs.rs/ax-arm-pl011/badge.svg)](https://docs.rs/ax-arm-pl011) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`ax-arm-pl011` provides ARM Uart pl011 register definitions and basic operations. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-arm-pl011 was derived from https://github.com/arceos-org/arm_pl011 - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-arm-pl011 = "0.3.0" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd drivers/serial/arm_pl011 - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use ax_arm_pl011 as _; - -fn main() { - // Integrate `ax-arm-pl011` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-arm-pl011](https://docs.rs/ax-arm-pl011) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/drivers/serial/arm_pl011/README_CN.md b/drivers/serial/arm_pl011/README_CN.md deleted file mode 100644 index 6fb189bd06..0000000000 --- a/drivers/serial/arm_pl011/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -

ax-arm-pl011

- -

ARM Uart pl011 register definitions and basic operations

- -
- -[![Crates.io](https://img.shields.io/crates/v/ax-arm-pl011.svg)](https://crates.io/crates/ax-arm-pl011) -[![Docs.rs](https://docs.rs/ax-arm-pl011/badge.svg)](https://docs.rs/ax-arm-pl011) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`ax-arm-pl011` 提供了 ARM Uart pl011 register definitions and basic operations。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-arm-pl011 派生自 https://github.com/arceos-org/arm_pl011 - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-arm-pl011 = "0.3.0" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd drivers/serial/arm_pl011 - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use ax_arm_pl011 as _; - -fn main() { - // 在这里将 `ax-arm-pl011` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-arm-pl011](https://docs.rs/ax-arm-pl011) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/drivers/serial/arm_pl011/src/lib.rs b/drivers/serial/arm_pl011/src/lib.rs deleted file mode 100644 index bb88f5cb1b..0000000000 --- a/drivers/serial/arm_pl011/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![no_std] -#![doc = include_str!("../README.md")] - -mod pl011; - -pub use pl011::Pl011Uart; diff --git a/drivers/serial/arm_pl011/src/pl011.rs b/drivers/serial/arm_pl011/src/pl011.rs deleted file mode 100644 index db5e11d6dd..0000000000 --- a/drivers/serial/arm_pl011/src/pl011.rs +++ /dev/null @@ -1,103 +0,0 @@ -use core::ptr::NonNull; - -use tock_registers::{ - interfaces::{Readable, Writeable}, - register_structs, - registers::{ReadOnly, ReadWrite, WriteOnly}, -}; - -register_structs! { - /// Pl011 registers. - Pl011UartRegs { - /// Data Register. - (0x00 => dr: ReadWrite), - (0x04 => _reserved0), - /// Flag Register. - (0x18 => fr: ReadOnly), - (0x1c => _reserved1), - /// Control register. - (0x30 => cr: ReadWrite), - /// Interrupt FIFO Level Select Register. - (0x34 => ifls: ReadWrite), - /// Interrupt Mask Set Clear Register. - (0x38 => imsc: ReadWrite), - /// Raw Interrupt Status Register. - (0x3c => ris: ReadOnly), - /// Masked Interrupt Status Register. - (0x40 => mis: ReadOnly), - /// Interrupt Clear Register. - (0x44 => icr: WriteOnly), - (0x48 => @END), - } -} - -/// The Pl011 Uart -/// -/// The Pl011 Uart provides a programing interface for: -/// 1. Construct a new Pl011 UART instance -/// 2. Initialize the Pl011 UART -/// 3. Read a char from the UART -/// 4. Write a char to the UART -/// 5. Handle a UART IRQ -pub struct Pl011Uart { - base: NonNull, -} - -unsafe impl Send for Pl011Uart {} -unsafe impl Sync for Pl011Uart {} - -impl Pl011Uart { - /// Constrcut a new Pl011 UART instance from the base address. - pub const fn new(base: *mut u8) -> Self { - Self { - base: NonNull::new(base).unwrap().cast(), - } - } - - const fn regs(&self) -> &Pl011UartRegs { - unsafe { self.base.as_ref() } - } - - /// Initializes the Pl011 UART. - /// - /// It clears all irqs, sets fifo trigger level, enables rx interrupt, enables receives - pub fn init(&mut self) { - // clear all irqs - self.regs().icr.set(0x7ff); - - // set fifo trigger level - self.regs().ifls.set(0); // 1/8 rxfifo, 1/8 txfifo. - - // enable rx interrupt - self.regs().imsc.set(1 << 4); // rxim - - // enable receive - self.regs().cr.set((1 << 0) | (1 << 8) | (1 << 9)); // tx enable, rx enable, uart enable - } - - /// Output a char c to data register - pub fn putchar(&mut self, c: u8) { - while self.regs().fr.get() & (1 << 5) != 0 {} - self.regs().dr.set(c as u32); - } - - /// Return a byte if pl011 has received, or it will return `None`. - pub fn getchar(&mut self) -> Option { - if self.regs().fr.get() & (1 << 4) == 0 { - Some(self.regs().dr.get() as u8) - } else { - None - } - } - - /// Return true if pl011 has received an interrupt - pub fn is_receive_interrupt(&self) -> bool { - let pending = self.regs().mis.get(); - pending & (1 << 4) != 0 - } - - /// Clear all interrupts - pub fn ack_interrupts(&mut self) { - self.regs().icr.set(0x7ff); - } -} diff --git a/drivers/serial/dw_apb_uart/.gitignore b/drivers/serial/dw_apb_uart/.gitignore deleted file mode 100644 index ff78c42af5..0000000000 --- a/drivers/serial/dw_apb_uart/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/.vscode -.DS_Store -Cargo.lock diff --git a/drivers/serial/dw_apb_uart/CHANGELOG.md b/drivers/serial/dw_apb_uart/CHANGELOG.md deleted file mode 100644 index 79f739d20b..0000000000 --- a/drivers/serial/dw_apb_uart/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.1](https://github.com/rcore-os/tgoskits/compare/dw_apb_uart-v0.1.0...dw_apb_uart-v0.1.1) - 2026-05-18 - -### Added - -- *(starry-kernel)* SG2002 extra drivers — ttyS{1,2}, cvi-camera0, /dev/devmem, pwm sysfs, cvi-tpu ([#649](https://github.com/rcore-os/tgoskits/pull/649)) diff --git a/drivers/serial/dw_apb_uart/Cargo.toml b/drivers/serial/dw_apb_uart/Cargo.toml deleted file mode 100644 index 95c9289ea1..0000000000 --- a/drivers/serial/dw_apb_uart/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "dw_apb_uart" -authors = ["Luoyuan Xiao "] -version = "0.1.2" -edition = "2021" -description = "Uart snps,dw-apb-uart driver in Rust for BST A1000b FADA board." -license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0" -homepage = "https://github.com/arceos-org/arceos" -repository = "https://github.com/arceos-org/dw_apb_uart" -documentation = "https://arceos-org.github.io/dw_apb_uart" - -[features] -# Default UART source clock for `init_with_baud()` / `init()`: -# - sg2002 (CV181x): 25 MHz -# - rk3588: 24 MHz (xin24m crystal) -# Pick exactly one. Code that knows the clock can always call -# `init_with_baud_clk(baud, clk_hz)` directly regardless of feature. -default = ["sg2002"] -sg2002 = [] -rk3588 = [] - -[dependencies] -tock-registers = "0.10" diff --git a/drivers/serial/dw_apb_uart/src/lib.rs b/drivers/serial/dw_apb_uart/src/lib.rs deleted file mode 100644 index 9b41fe5048..0000000000 --- a/drivers/serial/dw_apb_uart/src/lib.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Definitions for snps,dw-apb-uart serial driver. -//! -//! Originally written for the BST A1000b FADA board (SG2002 / CV181x, -//! 25 MHz UART source clock). Generalized so the same driver also -//! works on RK3588 boards (24 MHz xin24m crystal) by selecting one of -//! the `sg2002` / `rk3588` cargo features, or by calling -//! [`DW8250::init_with_baud_clk`] directly with an explicit clock. -#![no_std] - -use tock_registers::{ - interfaces::{Readable, Writeable}, - register_structs, - registers::{ReadOnly, ReadWrite}, -}; - -#[cfg(all(feature = "sg2002", feature = "rk3588"))] -compile_error!("dw_apb_uart: features `sg2002` and `rk3588` are mutually exclusive; pick one"); - -/// Default UART source clock used by [`DW8250::init`] and -/// [`DW8250::init_with_baud`] when no explicit clock is given. -#[cfg(feature = "rk3588")] -const DEFAULT_UART_SRC_CLK: u32 = 24_000_000; -#[cfg(not(feature = "rk3588"))] -const DEFAULT_UART_SRC_CLK: u32 = 25_000_000; - -/// DLF (fractional divisor latch) bit width on this controller. -const BST_UART_DLF_LEN: u32 = 6; - -register_structs! { - DW8250Regs { - /// Get or Put Register. - (0x00 => rbr: ReadWrite), - (0x04 => ier: ReadWrite), - (0x08 => fcr: ReadWrite), - (0x0c => lcr: ReadWrite), - (0x10 => mcr: ReadWrite), - (0x14 => lsr: ReadOnly), - (0x18 => msr: ReadOnly), - (0x1c => scr: ReadWrite), - (0x20 => lpdll: ReadWrite), - (0x24 => _reserved0), - /// Uart Status Register. - (0x7c => usr: ReadOnly), - (0x80 => _reserved1), - (0xc0 => dlf: ReadWrite), - (0xc4 => _reserved2), - (0xf4 => cpr: ReadWrite), - (0xf8 => @END), - } -} - -/// dw-apb-uart serial driver: DW8250 -pub struct DW8250 { - base_vaddr: usize, -} - -impl DW8250 { - /// New a DW8250 - pub const fn new(base_vaddr: usize) -> Self { - Self { base_vaddr } - } - - const fn regs(&self) -> &DW8250Regs { - unsafe { &*(self.base_vaddr as *const _) } - } - - /// Initialize at 115200 baud using the feature-selected default clock. - pub fn init(&mut self) { - self.init_with_baud(115200); - } - - /// Initialize at `baud` using the feature-selected default clock - /// (25 MHz on sg2002, 24 MHz on rk3588). - pub fn init_with_baud(&mut self, baud: u32) { - self.init_with_baud_clk(baud, DEFAULT_UART_SRC_CLK); - } - - /// Initialize at `baud` with an explicit `clk_hz` UART source clock. - /// - /// The 6-bit DLF fractional field gives sub-baud-quantum precision, - /// so even high speeds like 1.5 Mbps end up within UART tolerance. - /// Layout per the snps DW APB UART manual: - /// divisor = (clk_hz << (DLF_LEN - 4)) / baud - /// DLL = (divisor >> DLF_LEN) & 0xff - /// DLH = (divisor >> (DLF_LEN + 8)) & 0xff - /// DLF = divisor & ((1 << DLF_LEN) - 1) - pub fn init_with_baud_clk(&mut self, baud: u32, clk_hz: u32) { - let divider = (clk_hz << (BST_UART_DLF_LEN - 4)) / baud; - - // Wait until the controller is no longer busy. - while self.regs().usr.get() & 0b1 != 0 {} - - // Disable interrupts and enable FIFOs. - self.regs().ier.set(0); - self.regs().fcr.set(1); - - // Disable flow control / clear MCR_RTS. - self.regs().mcr.set(0); - self.regs().mcr.set(self.regs().mcr.get() | (1 << 1)); - - // Enable access to DLL/DLH (set LCR_DLAB). - self.regs().lcr.set(self.regs().lcr.get() | (1 << 7)); - - // Program baud divisor (DLL/DLH/DLF). - self.regs().rbr.set((divider >> BST_UART_DLF_LEN) & 0xff); - self.regs() - .ier - .set((divider >> (BST_UART_DLF_LEN + 8)) & 0xff); - self.regs().dlf.set(divider & ((1 << BST_UART_DLF_LEN) - 1)); - - // Clear DLAB. - self.regs().lcr.set(self.regs().lcr.get() & !(1 << 7)); - - // 8N1 frame. - self.regs().lcr.set(self.regs().lcr.get() | 0b11); - } - - /// DW8250 serial output - pub fn putchar(&mut self, c: u8) { - // Wait for last character to go (LSR_TEMT). - while self.regs().lsr.get() & (1 << 6) == 0 {} - self.regs().rbr.set(c as u32); - } - - /// DW8250 serial input - pub fn getchar(&mut self) -> Option { - // LSR_DR: data ready. - if self.regs().lsr.get() & 0b1 != 0 { - Some((self.regs().rbr.get() & 0xff) as u8) - } else { - None - } - } - - /// DW8250 serial interrupt enable or disable - pub fn set_ier(&mut self, enable: bool) { - self.regs().ier.set(if enable { 1 } else { 0 }); - } - - /// Read the Component Parameter Register. - pub fn cpr(&mut self) -> u32 { - self.regs().cpr.get() - } -} diff --git a/drivers/serial/some-serial/src/lib.rs b/drivers/serial/some-serial/src/lib.rs index fe7e14e187..65cddc3ae5 100644 --- a/drivers/serial/some-serial/src/lib.rs +++ b/drivers/serial/some-serial/src/lib.rs @@ -4,7 +4,7 @@ //! //! 本库提供统一的串口驱动接口,支持多种硬件平台: //! - ARM PL011 UART -//! - NS16550/16450 UART(IO Port 和 MMIO 版本) +//! - NS16550/16450 UART(IO Port、MMIO 和 DesignWare APB 版本) //! //! ## 特性 //! @@ -22,7 +22,7 @@ //! //! ### NS16550/16450 UART //! - 经典 PC 串口控制器,广泛兼容 -//! - 支持 IO Port(x86_64)和 MMIO(通用)两种访问方式 +//! - 支持 IO Port(x86_64)、MMIO(通用)和 DesignWare APB 访问方式 //! - 支持 16 字节 FIFO 缓冲 //! //! ## 快速开始 @@ -49,7 +49,6 @@ //! uart.open().unwrap(); //! ``` -// 导入核心模块 pub mod ns16550; pub mod pl011; @@ -62,6 +61,7 @@ pub enum Sender { #[cfg(target_arch = "x86_64")] Ns16550Sender(ns16550::Ns16550Sender), Ns16550MmioSender(ns16550::Ns16550Sender), + Ns16550DwApbSender(ns16550::Ns16550Sender), Pl011Sender(pl011::Pl011Sender), } @@ -95,6 +95,7 @@ pub enum Reciever { #[cfg(target_arch = "x86_64")] Ns16550Reciever(ns16550::Ns16550Reciever), Ns16550MmioReciever(ns16550::Ns16550Reciever), + Ns16550DwApbReciever(ns16550::Ns16550Reciever), Pl011Reciever(pl011::Pl011Reciever), } diff --git a/drivers/serial/some-serial/src/ns16550/dw_apb.rs b/drivers/serial/some-serial/src/ns16550/dw_apb.rs new file mode 100644 index 0000000000..ff2e0ea7d4 --- /dev/null +++ b/drivers/serial/some-serial/src/ns16550/dw_apb.rs @@ -0,0 +1,224 @@ +//! Synopsys DesignWare APB UART backend for the NS16550-compatible core. + +use rdif_serial::InterfaceRaw; + +use super::{ + Config, DataBits, Kind, Ns16550, Ns16550IrqHandler, Ns16550Reciever, Ns16550Sender, Parity, + StopBits, registers::*, +}; + +/// Default UART source clock used by SG2002 / CV181x boards. +pub const SG2002_UART_CLOCK: u32 = 25_000_000; +/// Default UART source clock used by RK3588 boards. +pub const RK3588_UART_CLOCK: u32 = 24_000_000; +/// Default console baud rate. +pub const DEFAULT_BAUDRATE: u32 = 115_200; + +const DLF_LEN: u32 = 6; +const REG_WIDTH: usize = 4; +const UART_USR_OFFSET: usize = 0x7c; +const UART_DLF_OFFSET: usize = 0xc0; +const UART_CPR_OFFSET: usize = 0xf4; + +/// DW APB UART register backend. +/// +/// The IP block is 8250/16550-compatible, but its registers are accessed as +/// 32-bit MMIO words and it exposes DesignWare extensions such as USR, DLF, +/// and CPR. +#[derive(Clone, Debug)] +pub struct DwApb { + base: usize, +} + +/// Synopsys DesignWare APB 8250-compatible UART. +pub type DwApbUart = Ns16550; + +impl DwApb { + /// Creates a register backend from an already-mapped MMIO base address. + pub const fn new(base: usize) -> Self { + Self { base } + } + + fn reg_addr(&self, byte_offset: usize) -> usize { + self.base + byte_offset + } + + fn read_u32(&self, byte_offset: usize) -> u32 { + unsafe { (self.reg_addr(byte_offset) as *const u32).read_volatile() } + } + + fn write_u32(&self, byte_offset: usize, value: u32) { + unsafe { + (self.reg_addr(byte_offset) as *mut u32).write_volatile(value); + } + } + + fn wait_not_busy(&self) { + while self.read_u32(UART_USR_OFFSET) & 0b1 != 0 { + core::hint::spin_loop(); + } + } + + fn line_status(&self) -> u8 { + self.read_reg(UART_LSR) + } + + fn cpr(&self) -> u32 { + self.read_u32(UART_CPR_OFFSET) + } +} + +impl Kind for DwApb { + fn read_reg(&self, reg: u8) -> u8 { + (self.read_u32(reg as usize * REG_WIDTH) & 0xff) as u8 + } + + fn write_reg(&self, reg: u8, val: u8) { + self.write_u32(reg as usize * REG_WIDTH, val as u32); + } + + fn get_base(&self) -> usize { + self.base + } + + fn set_baudrate(&self, clock_freq: u32, baudrate: u32) -> Result<(), super::ConfigError> { + if baudrate == 0 || clock_freq == 0 { + return Err(super::ConfigError::InvalidBaudrate); + } + + let divider = ((clock_freq as u64) << (DLF_LEN - 4)) / baudrate as u64; + let integer_divisor = divider >> DLF_LEN; + if divider == 0 || integer_divisor > 0xffff { + return Err(super::ConfigError::InvalidBaudrate); + } + + self.wait_not_busy(); + + let mut lcr: LineControlFlags = self.read_flags(UART_LCR); + lcr.insert(LineControlFlags::DIVISOR_LATCH_ACCESS); + self.write_flags(UART_LCR, lcr); + + self.write_reg(UART_DLL, ((divider >> DLF_LEN) & 0xff) as u8); + self.write_reg(UART_DLH, ((divider >> (DLF_LEN + 8)) & 0xff) as u8); + self.write_u32(UART_DLF_OFFSET, (divider & ((1 << DLF_LEN) - 1)) as u32); + + lcr.remove(LineControlFlags::DIVISOR_LATCH_ACCESS); + self.write_flags(UART_LCR, lcr); + + Ok(()) + } + + fn baudrate(&self, clock_freq: u32) -> u32 { + let dll = self.read_reg(UART_DLL) as u64; + let dlh = self.read_reg(UART_DLH) as u64; + let dlf = (self.read_u32(UART_DLF_OFFSET) & ((1 << DLF_LEN) - 1)) as u64; + let divider = (dll << DLF_LEN) | (dlh << (DLF_LEN + 8)) | dlf; + + if divider == 0 { + return 0; + } + + (((clock_freq as u64) << (DLF_LEN - 4)) / divider) as u32 + } +} + +impl Ns16550 { + /// Creates a DW APB UART with the SG2002 25 MHz default source clock. + pub const fn new(base: usize) -> Self { + Self::new_with_clock(base, SG2002_UART_CLOCK) + } + + /// Creates a DW APB UART with an explicit source clock. + pub const fn new_with_clock(base: usize, clock_freq: u32) -> Self { + Ns16550 { + base: DwApb::new(base), + clock_freq, + irq: Some(Ns16550IrqHandler { + base: DwApb::new(base), + }), + tx: Some(crate::Sender::Ns16550DwApbSender(Ns16550Sender { + base: DwApb::new(base), + })), + rx: Some(crate::Reciever::Ns16550DwApbReciever(Ns16550Reciever { + base: DwApb::new(base), + })), + } + } + + /// Initializes the UART at [`DEFAULT_BAUDRATE`] using its current source clock. + pub fn init(&mut self) { + self.init_with_baud(DEFAULT_BAUDRATE); + } + + /// Initializes the UART at `baud` using its current source clock. + pub fn init_with_baud(&mut self, baud: u32) { + self.try_init_with_baud_clk(baud, self.clock_freq) + .expect("invalid DW APB UART baud rate"); + } + + /// Initializes the UART at `baud` with an explicit source clock. + pub fn init_with_baud_clk(&mut self, baud: u32, clk_hz: u32) { + self.try_init_with_baud_clk(baud, clk_hz) + .expect("invalid DW APB UART baud rate"); + } + + /// Initializes the UART at `baud` with an explicit source clock. + pub fn try_init_with_baud_clk( + &mut self, + baud: u32, + clk_hz: u32, + ) -> Result<(), super::ConfigError> { + self.clock_freq = clk_hz; + + self.base.write_reg(UART_IER, 0); + self.base.write_reg(UART_FCR, UART_FCR_ENABLE_FIFO); + self.base.write_reg(UART_MCR, 0); + self.base.write_reg(UART_MCR, UART_MCR_RTS); + + self.set_config( + &Config::new() + .baudrate(baud) + .data_bits(DataBits::Eight) + .stop_bits(StopBits::One) + .parity(Parity::None), + ) + } + + /// Initializes the UART with an explicit source clock and baud rate. + pub fn ns16550_init(&mut self, clk_hz: u32, baud: u32) { + self.init_with_baud_clk(baud, clk_hz); + } + + /// Writes one byte, blocking until the transmitter is empty. + pub fn putchar(&mut self, c: u8) { + while self.base.line_status() & UART_LSR_TEMT == 0 { + core::hint::spin_loop(); + } + self.base.write_reg(UART_THR, c); + } + + /// Reads one byte if data is ready. + pub fn getchar(&mut self) -> Option { + if self.base.line_status() & UART_LSR_DR != 0 { + Some(self.base.read_reg(UART_RBR)) + } else { + None + } + } + + /// Enables or disables receive-data interrupts. + pub fn set_ier(&mut self, enabled: bool) { + self.base + .write_reg(UART_IER, if enabled { UART_IER_RDI } else { 0 }); + } + + /// Reads the line status register. + pub fn line_status(&self) -> u32 { + self.base.line_status() as u32 + } + + /// Reads the component parameter register. + pub fn cpr(&self) -> u32 { + self.base.cpr() + } +} diff --git a/drivers/serial/some-serial/src/ns16550/mod.rs b/drivers/serial/some-serial/src/ns16550/mod.rs index 89f2065d61..e46b376e20 100644 --- a/drivers/serial/some-serial/src/ns16550/mod.rs +++ b/drivers/serial/some-serial/src/ns16550/mod.rs @@ -14,11 +14,13 @@ use rdif_serial::{ }; use registers::*; +pub mod dw_apb; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod pio; // MMIO 版本(通用) mod mmio; +pub use dw_apb::*; pub use mmio::*; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use pio::*; @@ -30,6 +32,49 @@ pub trait Kind: Clone + Send + Sync + 'static { fn write_reg(&self, reg: u8, val: u8); fn get_base(&self) -> usize; + fn set_baudrate(&self, clock_freq: u32, baudrate: u32) -> Result<(), ConfigError> { + if baudrate == 0 || clock_freq == 0 { + return Err(ConfigError::InvalidBaudrate); + } + + let divisor = clock_freq / (16 * baudrate); + if divisor == 0 || divisor > 0xFFFF { + return Err(ConfigError::InvalidBaudrate); + } + + let mut lcr: LineControlFlags = self.read_flags(UART_LCR); + lcr.insert(LineControlFlags::DIVISOR_LATCH_ACCESS); + self.write_flags(UART_LCR, lcr); + + self.write_reg(UART_DLL, (divisor & 0xFF) as u8); + self.write_reg(UART_DLH, ((divisor >> 8) & 0xFF) as u8); + + lcr.remove(LineControlFlags::DIVISOR_LATCH_ACCESS); + self.write_flags(UART_LCR, lcr); + + Ok(()) + } + + fn baudrate(&self, clock_freq: u32) -> u32 { + let dll = self.read_reg(UART_DLL) as u16; + let dlh = self.read_reg(UART_DLH) as u16; + let divisor = dll | (dlh << 8); + + if divisor == 0 { + return 0; + } + + clock_freq / (16 * divisor as u32) + } + + fn init(&self) { + self.write_flags(UART_IER, InterruptEnableFlags::empty()); + + let mut mcr: ModemControlFlags = self.read_flags(UART_MCR); + mcr.insert(ModemControlFlags::DATA_TERMINAL_READY | ModemControlFlags::REQUEST_TO_SEND); + self.write_flags(UART_MCR, mcr); + } + // 类型安全的 bitflags 寄存器访问 fn read_flags>(&self, reg: u8) -> F { F::from_bits_retain(self.read_reg(reg)) @@ -85,17 +130,7 @@ impl InterfaceRaw for Ns16550 { } fn baudrate(&self) -> u32 { - // 只读方式获取波特率,通过读取 DLL 和 DLH - // 注意:如果 DLAB 未设置,读取的可能不是除数值 - let dll = self.read_reg_u8(UART_DLL) as u16; - let dlh = self.read_reg_u8(UART_DLH) as u16; - let divisor = dll | (dlh << 8); - - if divisor == 0 { - return 0; - } - - self.clock_freq / (16 * divisor as u32) + self.base.baudrate(self.clock_freq) } fn data_bits(&self) -> DataBits { @@ -148,7 +183,7 @@ impl InterfaceRaw for Ns16550 { } fn open(&mut self) { - self.init(); + self.init_core(); } fn close(&mut self) { @@ -236,6 +271,12 @@ impl InterfaceRaw for Ns16550 { return Err(SetBackError::new(want, actual)); } } + crate::Sender::Ns16550DwApbSender(ref sender) => { + let actual = sender.base.get_base(); + if actual != want { + return Err(SetBackError::new(want, actual)); + } + } _ => { return Err(SetBackError::new(want, 0)); // 不匹配的类型 } @@ -260,6 +301,12 @@ impl InterfaceRaw for Ns16550 { return Err(SetBackError::new(want, actual)); } } + crate::Reciever::Ns16550DwApbReciever(ref reciever) => { + let actual = reciever.base.get_base(); + if actual != want { + return Err(SetBackError::new(want, actual)); + } + } _ => { return Err(SetBackError::new(want, 0)); // 不匹配的类型 } @@ -270,15 +317,6 @@ impl InterfaceRaw for Ns16550 { } impl Ns16550 { - // 基础 u8 寄存器访问(用于除数寄存器等特殊场景) - fn read_reg_u8(&self, reg: u8) -> u8 { - self.base.read_reg(reg) - } - - fn write_reg_u8(&mut self, reg: u8, val: u8) { - self.base.write_reg(reg, val); - } - // 类型安全的 bitflags 寄存器访问 fn read_flags>(&self, reg: u8) -> F { F::from_bits_retain(self.base.read_reg(reg)) @@ -298,31 +336,7 @@ impl Ns16550 { /// 设置波特率 fn set_baudrate_internal(&mut self, baudrate: u32) -> Result<(), ConfigError> { - if baudrate == 0 || self.clock_freq == 0 { - return Err(ConfigError::InvalidBaudrate); - } - - let divisor = self.clock_freq / (16 * baudrate); - if divisor == 0 || divisor > 0xFFFF { - return Err(ConfigError::InvalidBaudrate); - } - - // 保存原始 LCR - let mut lcr: LineControlFlags = self.read_flags(UART_LCR); - - // 设置 DLAB 以访问波特率除数寄存器 - lcr.insert(LineControlFlags::DIVISOR_LATCH_ACCESS); - self.write_flags(UART_LCR, lcr); - - // 设置除数(使用 u8 方法,因为这是原始数据写入) - self.write_reg_u8(UART_DLL, (divisor & 0xFF) as u8); - self.write_reg_u8(UART_DLH, ((divisor >> 8) & 0xFF) as u8); - - // 清除 DLAB 位,恢复正常访问 - lcr.remove(LineControlFlags::DIVISOR_LATCH_ACCESS); - self.write_flags(UART_LCR, lcr); - - Ok(()) + self.base.set_baudrate(self.clock_freq, baudrate) } /// 设置数据位 @@ -426,14 +440,8 @@ impl Ns16550 { } /// 初始化 UART - fn init(&mut self) { - // 禁用所有中断 - self.write_flags(UART_IER, InterruptEnableFlags::empty()); - - // 确保传输器启用(设置 DTR 和 RTS) - let mut mcr: ModemControlFlags = self.read_flags(UART_MCR); - mcr.insert(ModemControlFlags::DATA_TERMINAL_READY | ModemControlFlags::REQUEST_TO_SEND); - self.write_flags(UART_MCR, mcr); + fn init_core(&mut self) { + self.base.init(); } /// 检查 FIFO 是否启用 diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 90af40394d..54289cd536 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -31,7 +31,7 @@ dynamic_debug = ["ax-feat/ipi"] sg2002 = [ "dep:ax-dma", "dep:sg2002-tpu", - "dep:dw_apb_uart", + "dep:some-serial", "dep:sg200x-bsp", "dep:tock-registers", ] @@ -132,7 +132,7 @@ ax-ipi = { workspace = true } static-keys = "0.8" ddebug = "0.5" kprobe = "0.5" -dw_apb_uart = { workspace = true, features = ["sg2002"], optional = true } +some-serial = { workspace = true, optional = true } sg200x-bsp = { workspace = true, optional = true } # 0.9 to match sg200x-bsp's internal tock-registers; cannot use workspace (0.10) # because Writeable trait must come from the same version as sg200x-bsp's types. diff --git a/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs b/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs index 0da31bbb27..55b3383bbc 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs @@ -313,8 +313,9 @@ struct Uart3; impl UartTransport for Uart3 { fn write_all(&mut self, data: &[u8]) -> Result<(), CameraError> { - let mut uart3 = - dw_apb_uart::DW8250::new(phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize()); + let mut uart3 = some_serial::ns16550::dw_apb::DwApbUart::new( + phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize(), + ); data.iter().for_each(|x| uart3.putchar(*x)); Ok(()) } @@ -369,13 +370,15 @@ impl CviCamera { pinmux.fmux().sd1_d2.write(FMUX_SD1_D2::FSEL::UART3_TX); pinmux.fmux().sd1_d1.write(FMUX_SD1_D1::FSEL::UART3_RX); - let mut uart3 = - dw_apb_uart::DW8250::new(phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize()); - uart3.init_with_baud(1500000); + let mut uart3 = some_serial::ns16550::dw_apb::DwApbUart::new( + phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize(), + ); + uart3.init_with_baud_clk(1_500_000, some_serial::ns16550::dw_apb::SG2002_UART_CLOCK); uart3.set_ier(true); ax_runtime::hal::irq::register(47, |_irq| { - let mut uart3 = - dw_apb_uart::DW8250::new(phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize()); + let mut uart3 = some_serial::ns16550::dw_apb::DwApbUart::new( + phys_to_virt(PhysAddr::from(UART3_ADDR)).as_usize(), + ); let mut buf = CAMERA_UART_BUF.lock(); loop { if let Some(c) = uart3.getchar() { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs index 4225a7fc00..080d13959a 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs @@ -10,11 +10,11 @@ use ax_task::future::{block_on, poll_io}; use axfs_ng_vfs::{NodeFlags, VfsResult}; use axpoll::{IoEvents, PollSet, Pollable}; use bytemuck::AnyBitPattern; -use dw_apb_uart::DW8250; use sg200x_bsp::{ pinmux::Pinmux, soc::{FMUX_BASE, IOBLK_BASE, IOBLK_GRTC_BASE}, }; +use some_serial::ns16550::dw_apb::{DwApbUart, SG2002_UART_CLOCK}; use starry_vm::{VmMutPtr, VmPtr}; use crate::pseudofs::DeviceOps; @@ -31,7 +31,7 @@ static UART1_POLL: PollSet = PollSet::new(); static UART2_POLL: PollSet = PollSet::new(); fn uart_irq_handler(paddr: PhysAddr, buf: &SpinNoIrq>, poll: &PollSet) { - let mut uart = DW8250::new(phys_to_virt(paddr).as_usize()); + let mut uart = DwApbUart::new(phys_to_virt(paddr).as_usize()); let mut rx = buf.lock(); let mut got_data = false; while let Some(c) = uart.getchar() { @@ -131,8 +131,8 @@ impl TtySerial { irq_handler: fn(usize), ) -> Self { let vaddr = phys_to_virt(paddr).as_usize(); - let mut uart = DW8250::new(vaddr); - uart.init_with_baud(baud); + let mut uart = DwApbUart::new(vaddr); + uart.init_with_baud_clk(baud, SG2002_UART_CLOCK); uart.set_ier(true); ax_runtime::hal::irq::register(irq, irq_handler); ax_runtime::hal::irq::set_enable(irq, true); @@ -150,8 +150,8 @@ impl TtySerial { fn set_baud(&self, baud: u32) { let vaddr = phys_to_virt(self.paddr).as_usize(); - let mut uart = DW8250::new(vaddr); - uart.init_with_baud(baud); + let mut uart = DwApbUart::new(vaddr); + uart.init_with_baud_clk(baud, SG2002_UART_CLOCK); uart.set_ier(true); ax_runtime::hal::irq::set_enable(self.irq, true); } @@ -177,7 +177,7 @@ impl DeviceOps for TtySerial { fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult { let vaddr = phys_to_virt(self.paddr).as_usize(); - let mut uart = DW8250::new(vaddr); + let mut uart = DwApbUart::new(vaddr); for &b in buf { uart.putchar(b); } diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index 6db251cec6..d8921e07d4 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -41,7 +41,6 @@ https://github.com/arceos-org/timer_list,,components/timer_list,ArceOS, https://github.com/arceos-org/axplat_crates,dev,components/axplat_crates,ArceOS, https://github.com/arceos-org/ax-int-ratio,,components/int_ratio,ArceOS, https://github.com/arceos-org/cap_access,,components/cap_access,ArceOS, -https://github.com/arceos-org/arm_pl011,,drivers/serial/arm_pl011,ArceOS, https://github.com/arceos-org/arm_pl031,,drivers/rtc/arm_pl031,ArceOS, https://github.com/arceos-org/riscv_plic,,drivers/intc/riscv_plic,ArceOS, https://github.com/rcore-os/bitmap-allocator,,components/bitmap-allocator,rCore, diff --git a/scripts/test/std_crates.csv b/scripts/test/std_crates.csv index 9193eed899..0d474d1cc9 100644 --- a/scripts/test/std_crates.csv +++ b/scripts/test/std_crates.csv @@ -1,6 +1,5 @@ package aarch64_sysreg -ax-arm-pl011 ax-arm-pl031 arm_vgic axaddrspace From 046c240eede40f8fdc5bb10839ef1ac4c4066562 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Wed, 27 May 2026 10:20:17 +0800 Subject: [PATCH 48/71] Refactor FDT handling, error management, and improve code clarity (#966) * Refactor FDT handling and improve error handling - Updated CPU node filtering logic in `create.rs` to pass node path directly. - Modified `build_node_path` function in `device.rs` to only compile for tests. - Enhanced error handling in FDT operations in `mod.rs`, returning `AxResult` for better error propagation. - Improved FDT parsing and setup functions in `parser.rs` to return results instead of panicking on errors. - Refactored image loading logic in `images/mod.rs` to use `AxResult` for error handling and removed unsafe static mut usage. - Updated vCPU task management in `vcpus.rs` to use thread-safe structures and improved error handling. - Enhanced command-line argument normalization in `xtask/src/main.rs` for better path resolution across platforms. * Refactor FDT handling and error management - Updated FDT creation functions to return AxResult for better error handling. - Introduced fdt_write_err function to standardize error reporting for FDT write operations. - Enhanced error handling in various FDT-related functions to ensure proper error propagation. - Modified the VM list management to simplify the structure and improve thread safety. - Replaced the custom VMList struct with a direct BTreeMap for global VM management. - Adjusted memory image handling to utilize the updated configuration access methods. - Improved the handling of IVC channels to ensure proper memory management and error checking. - Refined timer token management for better atomic operations. - Updated vCPU task management to use a BTreeMap for more efficient task retrieval and management. * refactor: streamline flag handling and improve code clarity across multiple commands * refactor: integrate startup banner into main kernel entry point * feat: add get_or_init method for LazyInit and implement DTB cache initialization --- Cargo.lock | 1 + components/ax-lazyinit/src/lib.rs | 27 +++ os/axvisor/.gitmodules | 0 os/axvisor/Cargo.toml | 1 + os/axvisor/build.rs | 46 ++-- os/axvisor/scripts/ci_run_qemu_nimbos.py | 4 +- os/axvisor/scripts/lds/linker.lds.S | 94 -------- os/axvisor/src/hal/arch/aarch64/api.rs | 2 +- os/axvisor/src/hal/mod.rs | 3 - os/axvisor/src/logo.rs | 51 ----- os/axvisor/src/main.rs | 67 +++++- os/axvisor/src/shell/command/base.rs | 85 ++++---- os/axvisor/src/shell/command/mod.rs | 69 +++--- os/axvisor/src/shell/command/vm.rs | 89 ++------ os/axvisor/src/shell/mod.rs | 42 ++-- os/axvisor/src/task.rs | 7 +- os/axvisor/src/vmm/config.rs | 171 ++++++++------- os/axvisor/src/vmm/fdt/create.rs | 249 ++++++++++++--------- os/axvisor/src/vmm/fdt/device.rs | 2 +- os/axvisor/src/vmm/fdt/mod.rs | 75 +++---- os/axvisor/src/vmm/fdt/parser.rs | 76 +++++-- os/axvisor/src/vmm/hvc.rs | 16 +- os/axvisor/src/vmm/images/mod.rs | 141 +++++++----- os/axvisor/src/vmm/ivc.rs | 96 ++++++--- os/axvisor/src/vmm/mod.rs | 13 +- os/axvisor/src/vmm/timer.rs | 12 +- os/axvisor/src/vmm/vcpus.rs | 264 ++++++++++------------- os/axvisor/src/vmm/vm_list.rs | 101 ++------- os/axvisor/xtask/src/main.rs | 120 ++++++++++- 29 files changed, 1007 insertions(+), 917 deletions(-) delete mode 100644 os/axvisor/.gitmodules mode change 100755 => 100644 os/axvisor/scripts/ci_run_qemu_nimbos.py delete mode 100644 os/axvisor/scripts/lds/linker.lds.S delete mode 100644 os/axvisor/src/logo.rs diff --git a/Cargo.lock b/Cargo.lock index 4b16a34496..c2b7f2d6bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1985,6 +1985,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "prettyplease", + "proc-macro2", "quote", "rdif-intc", "rdrive", diff --git a/components/ax-lazyinit/src/lib.rs b/components/ax-lazyinit/src/lib.rs index dd5ecddd8a..e36999c8c3 100644 --- a/components/ax-lazyinit/src/lib.rs +++ b/components/ax-lazyinit/src/lib.rs @@ -89,6 +89,25 @@ impl LazyInit { } } + /// Gets a reference to the value, initializing it with `f` if needed. + /// + /// If another CPU or thread is initializing the value concurrently, this + /// method waits until that initialization completes and then returns the + /// initialized value. The initializer is executed at most once. + pub fn get_or_init(&self, f: F) -> &T + where + F: FnOnce() -> T, + { + if let Some(value) = self.call_once(f) { + value + } else { + debug_assert!(self.is_inited()); + // SAFETY: call_once either initialized the value in this call or + // observed another completed initialization. + unsafe { self.force_get() } + } + } + /// Checks whether the value is initialized. #[inline] pub fn is_inited(&self) -> bool { @@ -261,6 +280,14 @@ mod tests { } assert_eq!(ok, 1); } + + #[test] + fn lazyinit_get_or_init() { + static VALUE: LazyInit = LazyInit::new(); + assert_eq!(*VALUE.get_or_init(|| 123), 123); + assert_eq!(*VALUE.get_or_init(|| 456), 123); + } + #[test] fn lazyinit_get_unchecked() { static VALUE: LazyInit = LazyInit::new(); diff --git a/os/axvisor/.gitmodules b/os/axvisor/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index a0cf1e0070..e46bf955ff 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -120,5 +120,6 @@ ax-config = { workspace = true, features = ["plat-dyn"] } prettyplease = "0.2" quote = "1.0" syn = "2.0" +proc-macro2 = "1.0" toml = "0.9" anyhow = "1.0" diff --git a/os/axvisor/build.rs b/os/axvisor/build.rs index 9426ad5305..2b9b1c22f8 100644 --- a/os/axvisor/build.rs +++ b/os/axvisor/build.rs @@ -45,7 +45,8 @@ use std::{ }; use anyhow::Context; -use quote::quote; +use quote::{ToTokens, quote}; +use syn::LitStr; use toml::Table; /// A configuration file that has been read from disk. @@ -89,7 +90,7 @@ fn get_configs() -> Result, String> { /// /// Returns the file handle. fn open_output_file() -> fs::File { - let output_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let output_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR must be set by Cargo")); let output_file = output_dir.join("vm_configs.rs"); fs::OpenOptions::new() @@ -97,13 +98,17 @@ fn open_output_file() -> fs::File { .create(true) .truncate(true) .open(output_file) - .unwrap() + .expect("failed to open generated vm_configs.rs") } // Convert relative path to absolute path fn convert_to_absolute(configs_path: impl AsRef, path: &str) -> PathBuf { let path = Path::new(path); - let configs_path = configs_path.as_ref().parent().unwrap().join(path); + let configs_path = configs_path + .as_ref() + .parent() + .map(|parent| parent.join(path)) + .unwrap_or_else(|| path.to_path_buf()); if path.is_relative() { fs::canonicalize(configs_path).unwrap_or_else(|_| path.to_path_buf()) } else { @@ -120,10 +125,7 @@ struct MemoryImage { } fn parse_config_file(config_file: &ConfigFile) -> Option { - let config = config_file - .content - .parse::() - .expect("failed to parse config file"); + let config = config_file.content.parse::
().ok()?; let id = config.get("base")?.as_table()?.get("id")?.as_integer()? as usize; @@ -137,7 +139,7 @@ fn parse_config_file(config_file: &ConfigFile) -> Option { let kernel_path = config.get("kernel")?.as_table()?.get("kernel_path")?; - let kernel = convert_to_absolute(&config_file.path, kernel_path.as_str().unwrap()); + let kernel = convert_to_absolute(&config_file.path, kernel_path.as_str()?); let dtb = config .get("kernel")? @@ -199,7 +201,11 @@ fn generate_guest_img_loading_functions( .to_string(); let dtb = match files.dtb { Some(v) => { - let s = v.canonicalize().unwrap().display().to_string(); + let s = v + .canonicalize() + .with_context(|| format!("Path {} not found", v.display()))? + .display() + .to_string(); quote! { Some(include_bytes!(#s)) } } None => quote! { None }, @@ -207,7 +213,11 @@ fn generate_guest_img_loading_functions( let bios = match files.bios { Some(v) => { - let s = v.canonicalize().unwrap().display().to_string(); + let s = v + .canonicalize() + .with_context(|| format!("Path {} not found", v.display()))? + .display() + .to_string(); quote! { Some(include_bytes!(#s)) } } None => quote! { None }, @@ -215,7 +225,11 @@ fn generate_guest_img_loading_functions( let ramdisk = match files.ramdisk { Some(v) => { - let s = v.canonicalize().unwrap().display().to_string(); + let s = v + .canonicalize() + .with_context(|| format!("Path {} not found", v.display()))? + .display() + .to_string(); quote! { Some(include_bytes!(#s)) } } None => quote! { None }, @@ -255,7 +269,7 @@ fn generate_guest_img_loading_functions( ] } }; - let syntax_tree = syn::parse2(output).unwrap(); + let syntax_tree = syn::parse2(output)?; let formatted = prettyplease::unparse(&syntax_tree); out_file.write_all(formatted.as_bytes())?; @@ -263,7 +277,8 @@ fn generate_guest_img_loading_functions( } fn main() -> anyhow::Result<()> { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let arch = + std::env::var("CARGO_CFG_TARGET_ARCH").context("CARGO_CFG_TARGET_ARCH is not set")?; // let platform = env::var("AX_PLATFORM").unwrap_or("".to_string()); @@ -306,7 +321,8 @@ fn main() -> anyhow::Result<()> { } else { writeln!(output_file, " vec![")?; for config_file in &config_files { - writeln!(output_file, " r###\"{}\"###,", config_file.content)?; + let content = LitStr::new(&config_file.content, proc_macro2::Span::call_site()); + writeln!(output_file, " {},", content.to_token_stream())?; } writeln!(output_file, " ]")?; } diff --git a/os/axvisor/scripts/ci_run_qemu_nimbos.py b/os/axvisor/scripts/ci_run_qemu_nimbos.py old mode 100755 new mode 100644 index 0e93e6e50f..e106fe28a0 --- a/os/axvisor/scripts/ci_run_qemu_nimbos.py +++ b/os/axvisor/scripts/ci_run_qemu_nimbos.py @@ -10,10 +10,10 @@ import os import select -import sys import subprocess +import sys + -# Trigger strings (try in order; first match sends usertests) SEND_AFTER = (b"Rust user shell", b">>") SEND_LINE = b"usertests\n" SUCCESS_MARKERS = (b"usertests passed!",) diff --git a/os/axvisor/scripts/lds/linker.lds.S b/os/axvisor/scripts/lds/linker.lds.S deleted file mode 100644 index 3fa1defd72..0000000000 --- a/os/axvisor/scripts/lds/linker.lds.S +++ /dev/null @@ -1,94 +0,0 @@ -OUTPUT_ARCH(%ARCH%) - -BASE_ADDRESS = %KERNEL_BASE%; -SMP = %SMP%; - -ENTRY(_start) -SECTIONS -{ - . = BASE_ADDRESS; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - . = ALIGN(4K); - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - *(.srodata .srodata.*) - *(.sdata2 .sdata2.*) - . = ALIGN(4K); - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data.boot_page_table) - . = ALIGN(4K); - - __sdriver_register = .; - KEEP(*(.driver.register*)) - __edriver_register = .; - - *(.data .data.*) - *(.sdata .sdata.*) - *(.got .got.*) - } - - .tdata : ALIGN(0x10) { - _stdata = .; - *(.tdata .tdata.*) - _etdata = .; - } - - .tbss : ALIGN(0x10) { - _stbss = .; - *(.tbss .tbss.*) - *(.tcommon) - _etbss = .; - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * SMP; - } - . = _percpu_end; - - . = ALIGN(4K); - _edata = .; - - .bss : ALIGN(4K) { - boot_stack = .; - *(.bss.stack) - . = ALIGN(4K); - boot_stack_top = .; - - _sbss = .; - *(.bss .bss.*) - *(.sbss .sbss.*) - *(COMMON) - . = ALIGN(4K); - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) - } -} - -SECTIONS { - scope_local : { *(scope_local) } -} -INSERT AFTER .tbss; diff --git a/os/axvisor/src/hal/arch/aarch64/api.rs b/os/axvisor/src/hal/arch/aarch64/api.rs index 9d95e3608d..ff99a9f536 100644 --- a/os/axvisor/src/hal/arch/aarch64/api.rs +++ b/os/axvisor/src/hal/arch/aarch64/api.rs @@ -89,7 +89,7 @@ impl ArchIf for ArchImpl { } fn fetch_irq() -> u64 { - /// TODO: better implementation + // TODO: better implementation let mut gic = rdrive::get_one::() .expect("Failed to get GIC driver") .lock() diff --git a/os/axvisor/src/hal/mod.rs b/os/axvisor/src/hal/mod.rs index 34996f5f28..4c92940b81 100644 --- a/os/axvisor/src/hal/mod.rs +++ b/os/axvisor/src/hal/mod.rs @@ -124,9 +124,6 @@ pub(crate) fn enable_virtualization() { // Wait for all cores to enable virtualization. while CORES.load(Ordering::Acquire) != cpu_count { thread::yield_now(); - for _ in 0..10 { - core::hint::spin_loop(); - } } info!("All cores have enabled hardware virtualization support."); diff --git a/os/axvisor/src/logo.rs b/os/axvisor/src/logo.rs deleted file mode 100644 index 31abaaa57e..0000000000 --- a/os/axvisor/src/logo.rs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::os::arceos::api::time::ax_wall_time; -use std::println; - -const LOGO: [&str; 2] = [ - r#" - d8888 888 888 d8b - d88888 888 888 Y8P - d88P888 888 888 - d88P 888 888 888 Y88b d88P 888 .d8888b .d88b. 888d888 - d88P 888 `Y8bd8P' Y88b d88P 888 88K d88""88b 888P" - d88P 888 X88K Y88o88P 888 "Y8888b. 888 888 888 - d8888888888 .d8""8b. Y888P 888 X88 Y88..88P 888 -d88P 888 888 888 Y8P 888 88888P' "Y88P" 888 -"#, - r#" - _ __ ___ - / \ __ _\ \ / (_)___ ___ _ __ - / _ \ \ \/ /\ \ / /| / __|/ _ \| '__| - / ___ \ > < \ V / | \__ \ (_) | | -/_/ \_\/_/\_\ \_/ |_|___/\___/|_| -"#, -]; - -/// Chooses a logo based on the current time. -fn choose_logo() -> &'static str { - let elapsed = ax_wall_time().as_micros() as usize; - LOGO[elapsed % LOGO.len()] -} - -/// Prints the logo to the console. -pub fn print_logo() { - println!(); - println!("{}", choose_logo()); - println!(); - println!("by AxVisor Team"); - println!(); -} diff --git a/os/axvisor/src/main.rs b/os/axvisor/src/main.rs index cfb10ebd55..06b8b0f352 100644 --- a/os/axvisor/src/main.rs +++ b/os/axvisor/src/main.rs @@ -14,7 +14,12 @@ //! # Axvisor Kernel //! -//! The main kernel binary for the Axvisor hypervisor. +//! Kernel entry point for the Axvisor hypervisor. +//! +//! This module wires together early boot presentation, hardware virtualization +//! enablement, VM initialization/startup, and the interactive management shell. +//! The implementation is intentionally small so that the boot order is visible +//! from a single file. #![no_std] #![no_main] @@ -29,11 +34,59 @@ extern crate alloc; extern crate ax_std as std; mod hal; -mod logo; mod shell; mod task; mod vmm; +use std::os::arceos::api::time::ax_wall_time; +use std::println; + +/// Startup banners printed before the hypervisor begins initialization. +/// +/// A banner is selected at runtime using the wall clock. This keeps boot output +/// slightly varied without introducing any state or configuration dependency. +const LOGO: [&str; 2] = [ + r#" + d8888 888 888 d8b + d88888 888 888 Y8P + d88P888 888 888 + d88P 888 888 888 Y88b d88P 888 .d8888b .d88b. 888d888 + d88P 888 `Y8bd8P' Y88b d88P 888 88K d88""88b 888P" + d88P 888 X88K Y88o88P 888 "Y8888b. 888 888 888 + d8888888888 .d8""8b. Y888P 888 X88 Y88..88P 888 +d88P 888 888 888 Y8P 888 88888P' "Y88P" 888 +"#, + r#" + _ __ ___ + / \ __ _\ \ / (_)___ ___ _ __ + / _ \ \ \/ /\ \ / /| / __|/ _ \| '__| + / ___ \ > < \ V / | \__ \ (_) | | +/_/ \_\/_/\_\ \_/ |_|___/\___/|_| +"#, +]; + +/// Prints the startup banner to the console. +/// +/// The banner selection is deliberately best-effort and only depends on the +/// current wall-clock value. It has no impact on the rest of boot. +fn print_logo() { + let elapsed = ax_wall_time().as_micros() as usize; + let logo = LOGO[elapsed % LOGO.len()]; + + println!(); + println!("{}", logo); + println!(); + println!("by AxVisor Team"); + println!(); +} + +/// Verifies that the current platform can run hardware-assisted virtualization. +/// +/// # Panics +/// +/// Panics when virtualization support is unavailable. Axvisor cannot continue +/// without the architecture-specific virtualization extension, so this is a +/// fatal early-boot condition. fn ensure_hardware_support() { if axvm::has_hardware_support() { return; @@ -49,9 +102,17 @@ fn ensure_hardware_support() { panic!("Hardware does not support virtualization"); } +/// Axvisor kernel entry point. +/// +/// The startup sequence is: +/// +/// 1. Print the startup banner. +/// 2. Check and enable hardware virtualization on every CPU. +/// 3. Build and start configured guest VMs. +/// 4. Enter the management shell after the default guests have exited. #[unsafe(no_mangle)] fn main() { - logo::print_logo(); + print_logo(); info!("Starting virtualization..."); info!("Hardware support: {:?}", axvm::has_hardware_support()); diff --git a/os/axvisor/src/shell/command/base.rs b/os/axvisor/src/shell/command/base.rs index 6e302b7479..0da4facb81 100644 --- a/os/axvisor/src/shell/command/base.rs +++ b/os/axvisor/src/shell/command/base.rs @@ -47,10 +47,8 @@ fn split_whitespace(s: &str) -> (&str, &str) { #[cfg(feature = "fs")] fn do_ls(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let show_long = cmd.flags.get("long").unwrap_or(&false); - let show_all = cmd.flags.get("all").unwrap_or(&false); - - let _current_dir = std::env::current_dir().unwrap(); + let show_long = cmd.flags.contains("long"); + let show_all = cmd.flags.contains("all"); fn show_entry_info(path: &str, entry: &str, show_long: bool) -> io::Result<()> { if show_long { @@ -105,7 +103,7 @@ fn do_ls(cmd: &ParsedCommand) { if i > 0 { println!(); } - if let Err(e) = list_one(name, targets.len() > 1, *show_long, *show_all) { + if let Err(e) = list_one(name, targets.len() > 1, show_long, show_all) { print_err!("ls", name, e); } } @@ -143,7 +141,7 @@ fn do_cat(cmd: &ParsedCommand) { #[cfg(feature = "fs")] fn do_echo(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let no_newline = cmd.flags.get("no-newline").unwrap_or(&false); + let no_newline = cmd.flags.contains("no-newline"); let args_str = args.join(" "); @@ -172,7 +170,7 @@ fn do_echo(cmd: &ParsedCommand) { if let Err(e) = echo_file(fname, &text_list) { print_err!("echo", fname, e); } - } else if *no_newline { + } else if no_newline { use std::print; print!("{}", args_str); @@ -184,7 +182,7 @@ fn do_echo(cmd: &ParsedCommand) { #[cfg(feature = "fs")] fn do_mkdir(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let create_parents = cmd.flags.get("parents").unwrap_or(&false); + let create_parents = cmd.flags.contains("parents"); if args.is_empty() { print_err!("mkdir", "missing operand"); @@ -200,7 +198,7 @@ fn do_mkdir(cmd: &ParsedCommand) { } for path in args { - if let Err(e) = mkdir_one(path, *create_parents) { + if let Err(e) = mkdir_one(path, create_parents) { print_err!("mkdir", format_args!("cannot create directory '{path}'"), e); } } @@ -209,9 +207,9 @@ fn do_mkdir(cmd: &ParsedCommand) { #[cfg(feature = "fs")] fn do_rm(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let rm_dir = cmd.flags.get("dir").unwrap_or(&false); - let recursive = cmd.flags.get("recursive").unwrap_or(&false); - let force = cmd.flags.get("force").unwrap_or(&false); + let rm_dir = cmd.flags.contains("dir"); + let recursive = cmd.flags.contains("recursive"); + let force = cmd.flags.contains("force"); if args.is_empty() { print_err!("rm", "missing operand"); @@ -241,7 +239,7 @@ fn do_rm(cmd: &ParsedCommand) { } for path in args { - if let Err(e) = rm_one(path, *rm_dir, *recursive, *force) + if let Err(e) = rm_one(path, rm_dir, recursive, force) && !force { print_err!("rm", format_args!("cannot remove '{path}'"), e); @@ -294,16 +292,20 @@ fn do_cd(cmd: &ParsedCommand) { #[cfg(feature = "fs")] fn do_pwd(cmd: &ParsedCommand) { - let _logical = cmd.flags.get("logical").unwrap_or(&false); + let _logical = cmd.flags.contains("logical"); - let pwd = std::env::current_dir().unwrap(); - println!("{}", pwd); + match std::env::current_dir() { + Ok(pwd) => println!("{}", pwd), + Err(e) => { + print_err!("pwd", e); + } + } } fn do_uname(cmd: &ParsedCommand) { - let show_all = cmd.flags.get("all").unwrap_or(&false); - let show_kernel = cmd.flags.get("kernel-name").unwrap_or(&false); - let show_arch = cmd.flags.get("machine").unwrap_or(&false); + let show_all = cmd.flags.contains("all"); + let show_kernel = cmd.flags.contains("kernel-name"); + let show_arch = cmd.flags.contains("machine"); let arch = option_env!("AX_ARCH").unwrap_or(""); let platform = option_env!("AX_PLATFORM").unwrap_or(""); @@ -313,7 +315,7 @@ fn do_uname(cmd: &ParsedCommand) { }; let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.1.0"); - if *show_all { + if show_all { println!( "ArceOS {ver}{smp} {arch} {plat}", ver = version, @@ -321,9 +323,9 @@ fn do_uname(cmd: &ParsedCommand) { arch = arch, plat = platform, ); - } else if *show_kernel { + } else if show_kernel { println!("ArceOS"); - } else if *show_arch { + } else if show_arch { println!("{}", arch); } else { println!( @@ -392,18 +394,7 @@ fn do_mv(cmd: &ParsedCommand) { && dest_meta.is_dir() { // Move source into destination directory - let mut file_dir = fs::read_dir(dest).unwrap(); - let source_name = match file_dir.next() { - Some(name) => { - let dir_name = name.expect("Failed to read directory"); - let file = dir_name.file_name(); - format!("{dest}/{file}") - } - None => { - print_err!("mv", format_args!("invalid source path '{source}'")); - return; - } - }; + let source_name = path_basename(source); let dest_path = format!("{dest}/{source_name}"); if let Err(e) = move_file_or_dir(source, &dest_path) { print_err!( @@ -429,18 +420,7 @@ fn do_mv(cmd: &ParsedCommand) { Ok(meta) if meta.is_dir() => { // Move each source into destination directory for source in sources { - let mut file_dir = fs::read_dir(source).unwrap(); - let source_name = match file_dir.next() { - Some(name) => { - let dir_name = name.expect("Failed to read directory"); - let file = dir_name.file_name(); - format!("{dest}/{file}") - } - None => { - print_err!("mv", format_args!("invalid source path '{source}'")); - return; - } - }; + let source_name = path_basename(source); let dest_path = format!("{dest}/{source_name}"); if let Err(e) = move_file_or_dir(source, &dest_path) { print_err!( @@ -461,6 +441,15 @@ fn do_mv(cmd: &ParsedCommand) { } } +#[cfg(feature = "fs")] +fn path_basename(path: &str) -> &str { + path.trim_end_matches('/') + .rsplit('/') + .next() + .filter(|name| !name.is_empty()) + .unwrap_or(path) +} + // Helper function to move file or directory (handles cross-filesystem moves) #[cfg(feature = "fs")] fn move_file_or_dir(source: &str, dest: &str) -> io::Result<()> { @@ -504,7 +493,7 @@ fn do_touch(cmd: &ParsedCommand) { #[cfg(feature = "fs")] fn do_cp(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let recursive = cmd.flags.get("recursive").unwrap_or(&false); + let recursive = cmd.flags.contains("recursive"); if args.len() < 2 { print_err!("cp", "missing operand"); @@ -524,7 +513,7 @@ fn do_cp(cmd: &ParsedCommand) { }; let result = if src_metadata.is_dir() { - if *recursive { + if recursive { copy_dir_recursive(source, dest) } else { Err(io::Error::Unsupported) diff --git a/os/axvisor/src/shell/command/mod.rs b/os/axvisor/src/shell/command/mod.rs index b6be05f55c..d0d3a36671 100644 --- a/os/axvisor/src/shell/command/mod.rs +++ b/os/axvisor/src/shell/command/mod.rs @@ -23,7 +23,10 @@ pub use vm::*; use std::io::prelude::*; use std::string::String; use std::vec::Vec; -use std::{collections::BTreeMap, string::ToString}; +use std::{ + collections::{BTreeMap, BTreeSet}, + string::ToString, +}; use std::{print, println}; use spin::Lazy; @@ -36,8 +39,6 @@ pub struct CommandNode { subcommands: BTreeMap, description: &'static str, usage: Option<&'static str>, - #[allow(dead_code)] - log_level: log::LevelFilter, options: Vec, flags: Vec, } @@ -63,7 +64,7 @@ pub struct FlagDef { pub struct ParsedCommand { pub command_path: Vec, pub options: BTreeMap, - pub flags: BTreeMap, + pub flags: BTreeSet, pub positional_args: Vec, } @@ -83,7 +84,6 @@ impl CommandNode { subcommands: BTreeMap::new(), description, usage: None, - log_level: log::LevelFilter::Off, options: Vec::new(), flags: Vec::new(), } @@ -99,12 +99,6 @@ impl CommandNode { self } - #[allow(dead_code)] - pub fn with_log_level(mut self, level: log::LevelFilter) -> Self { - self.log_level = level; - self - } - pub fn with_option(mut self, option: OptionDef) -> Self { self.options.push(option); self @@ -257,16 +251,9 @@ impl CommandParser { fn parse_args( tokens: &[String], command_node: &CommandNode, - ) -> Result< - ( - BTreeMap, - BTreeMap, - Vec, - ), - ParseError, - > { + ) -> Result<(BTreeMap, BTreeSet, Vec), ParseError> { let mut options = BTreeMap::new(); - let mut flags = BTreeMap::new(); + let mut flags = BTreeSet::new(); let mut positional_args = Vec::new(); let mut i = 0; @@ -285,7 +272,7 @@ impl CommandParser { return Err(ParseError::UnknownOption(format!("--{opt_name}"))); } } else if Self::is_flag(name, command_node) { - flags.insert(name.to_string(), true); + flags.insert(name.to_string()); } else if Self::is_option(name, command_node) { // --option value format if i + 1 >= tokens.len() { @@ -301,14 +288,12 @@ impl CommandParser { let chars: Vec = token[1..].chars().collect(); for (j, &ch) in chars.iter().enumerate() { if Self::is_short_flag(ch, command_node) { - flags.insert( - Self::get_flag_name_by_short(ch, command_node) - .unwrap() - .to_string(), - true, - ); + let flag_name = Self::get_flag_name_by_short(ch, command_node) + .ok_or_else(|| ParseError::UnknownOption(format!("-{ch}")))?; + flags.insert(flag_name.to_string()); } else if Self::is_short_option(ch, command_node) { - let opt_name = Self::get_option_name_by_short(ch, command_node).unwrap(); + let opt_name = Self::get_option_name_by_short(ch, command_node) + .ok_or_else(|| ParseError::UnknownOption(format!("-{ch}")))?; if j == chars.len() - 1 && i + 1 < tokens.len() { // Last character and there is a next token as value options.insert(opt_name.to_string(), tokens[i + 1].clone()); @@ -382,9 +367,14 @@ pub fn execute_command(input: &str) -> Result<(), ParseError> { let parsed = CommandParser::parse(input)?; // Find the corresponding command node - let mut current_node = COMMAND_TREE.get(&parsed.command_path[0]).unwrap(); + let mut current_node = COMMAND_TREE + .get(&parsed.command_path[0]) + .ok_or_else(|| ParseError::UnknownCommand(parsed.command_path[0].clone()))?; for cmd in &parsed.command_path[1..] { - current_node = current_node.subcommands.get(cmd).unwrap(); + current_node = current_node + .subcommands + .get(cmd) + .ok_or_else(|| ParseError::UnknownCommand(cmd.clone()))?; } // Execute the command @@ -480,11 +470,22 @@ pub fn show_help(command_path: &[String]) -> Result<(), ParseError> { } pub fn print_prompt() { + print!("{}", prompt_string()); + std::io::stdout().flush().ok(); +} + +pub fn prompt_string() -> String { #[cfg(feature = "fs")] - print!("axvisor:{}$ ", std::env::current_dir().unwrap()); + { + match std::env::current_dir() { + Ok(dir) => format!("axvisor:{dir}$ "), + Err(_) => "axvisor:$ ".to_string(), + } + } #[cfg(not(feature = "fs"))] - print!("axvisor:$ "); - std::io::stdout().flush().unwrap(); + { + "axvisor:$ ".to_string() + } } pub fn run_cmd_bytes(cmd_bytes: &[u8]) { @@ -536,7 +537,7 @@ pub fn handle_builtin_commands(input: &str) -> bool { } "clear" => { print!("\x1b[2J\x1b[H"); // ANSI clear screen sequence - std::io::stdout().flush().unwrap(); + std::io::stdout().flush().ok(); true } _ if input.starts_with("help ") => { diff --git a/os/axvisor/src/shell/command/vm.rs b/os/axvisor/src/shell/command/vm.rs index 8114067854..df672bec43 100644 --- a/os/axvisor/src/shell/command/vm.rs +++ b/os/axvisor/src/shell/command/vm.rs @@ -176,7 +176,7 @@ fn vm_create(cmd: &ParsedCommand) { #[cfg(feature = "fs")] fn vm_start(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let detach = cmd.flags.get("detach").unwrap_or(&false); + let detach = cmd.flags.contains("detach"); if args.is_empty() { // start all VMs @@ -218,7 +218,7 @@ fn vm_start(cmd: &ParsedCommand) { } } - if *detach { + if detach { println!("VMs started in background mode"); } } @@ -269,7 +269,7 @@ fn start_vm_by_id(vm_id: usize) { fn vm_stop(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); + let force = cmd.flags.contains("force"); if args.is_empty() { println!("Error: No VM specified"); @@ -279,7 +279,7 @@ fn vm_stop(cmd: &ParsedCommand) { for vm_name in args { if let Ok(vm_id) = vm_name.parse::() { - stop_vm_by_id(vm_id, *force); + stop_vm_by_id(vm_id, force); } else { println!("Error: Invalid VM ID: {}", vm_name); } @@ -348,7 +348,7 @@ fn stop_vm_by_id(vm_id: usize, force: bool) { /// Restart a VM by stopping it (if running) and then starting it again.(functionality incomplete) fn vm_restart(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); + let force = cmd.flags.contains("force"); if args.is_empty() { println!("Error: No VM specified"); @@ -358,7 +358,7 @@ fn vm_restart(cmd: &ParsedCommand) { for vm_name in args { if let Ok(vm_id) = vm_name.parse::() { - restart_vm_by_id(vm_id, *force); + restart_vm_by_id(vm_id, force); } else { println!("Error: Invalid VM ID: {}", vm_name); } @@ -369,13 +369,10 @@ fn restart_vm_by_id(vm_id: usize, force: bool) { println!("Restarting VM[{}]...", vm_id); // Check current status - let current_status = with_vm(vm_id, |vm| vm.vm_status()); - if current_status.is_none() { + let Some(status) = with_vm(vm_id, |vm| vm.vm_status()) else { println!("✗ VM[{}] not found", vm_id); return; - } - - let status = current_status.unwrap(); + }; match status { VMStatus::Stopped | VMStatus::Loaded => { // VM is already stopped, just start it @@ -600,8 +597,8 @@ fn resume_vm_by_id(vm_id: usize) { fn vm_delete(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - let keep_data = cmd.flags.get("keep-data").unwrap_or(&false); + let force = cmd.flags.contains("force"); + let keep_data = cmd.flags.contains("keep-data"); if args.is_empty() { println!("Error: No VM specified"); @@ -613,14 +610,10 @@ fn vm_delete(cmd: &ParsedCommand) { if let Ok(vm_id) = vm_name.parse::() { // Check if VM exists and get its status first - let vm_status = with_vm(vm_id, |vm| vm.vm_status()); - - if vm_status.is_none() { + let Some(status) = with_vm(vm_id, |vm| vm.vm_status()) else { println!("✗ VM[{}] not found", vm_id); return; - } - - let status = vm_status.unwrap(); + }; // Check if VM is running match status { @@ -655,7 +648,7 @@ fn vm_delete(cmd: &ParsedCommand) { } } - delete_vm_by_id(vm_id, *keep_data); + delete_vm_by_id(vm_id, keep_data); } else { println!("Error: Invalid VM ID: {}", vm_name); } @@ -685,25 +678,19 @@ fn delete_vm_by_id(vm_id: usize, keep_data: bool) { _ => {} } - use alloc::sync::Arc; - let count = Arc::strong_count(&vm); - println!(" [Debug] VM Arc strong_count: {}", count); - status }); - if vm_status.is_none() { + let Some(status) = vm_status else { println!("✗ VM[{}] not found or already removed", vm_id); return; - } - - let status = vm_status.unwrap(); + }; // Remove VM from global list // Note: This drops the reference from the global list, but the VM object // will only be fully destroyed when all vCPU threads exit and drop their references match crate::vmm::vm_list::remove_vm(vm_id) { - Some(vm) => { + Some(_vm) => { println!("✓ VM[{}] removed from VM list", vm_id); // Wait for vCPU threads to exit if VM has VCpu tasks @@ -714,22 +701,9 @@ fn delete_vm_by_id(vm_id: usize, keep_data: bool) { | VMStatus::Stopped => { println!(" Waiting for vCPU threads to exit..."); - // Debug: Check Arc count before cleanup - use alloc::sync::Arc; - println!( - " [Debug] VM Arc count before cleanup: {}", - Arc::strong_count(&vm) - ); - // Clean up VCpu resources after threads have exited println!(" Cleaning up VCpu resources..."); vcpus::cleanup_vm_vcpus(vm_id); - - // Debug: Check Arc count after final wait - println!( - " [Debug] VM Arc count after final wait: {}", - Arc::strong_count(&vm) - ); } _ => { // VM not running, no vCPU threads to wait for @@ -743,30 +717,11 @@ fn delete_vm_by_id(vm_id: usize, keep_data: bool) { } else { println!("✓ VM[{}] deleted completely", vm_id); - // Debug: Check Arc count - should be 1 now (only this variable) - // TaskExt uses Weak reference, so it doesn't count - use alloc::sync::Arc; - let count = Arc::strong_count(&vm); - println!(" [Debug] VM Arc strong_count: {}", count); - - if count == 1 { - println!(" ✓ Perfect! VM will be freed immediately when function returns"); - } else { - println!( - " ⚠ Warning: Unexpected Arc count {}, possible reference leak!", - count - ); - } - // TODO: Clean up VM-related data files // - Remove disk images // - Remove configuration files // - Remove log files } - - // When function returns, the 'vm' variable is dropped - // Since Arc count is 1, AxVM::drop() is called immediately - println!(" VM[{}] will be freed now", vm_id); } None => { println!( @@ -776,8 +731,6 @@ fn delete_vm_by_id(vm_id: usize, keep_data: bool) { } } - // When function returns, the 'vm' Arc is dropped - // If all vCPU threads have exited (ref_count was 1), AxVM::drop() is called here println!("✓ VM[{}] deletion completed", vm_id); } @@ -896,9 +849,9 @@ fn vm_list(cmd: &ParsedCommand) { fn vm_show(cmd: &ParsedCommand) { let args = &cmd.positional_args; - let show_config = cmd.flags.get("config").unwrap_or(&false); - let show_stats = cmd.flags.get("stats").unwrap_or(&false); - let show_full = cmd.flags.get("full").unwrap_or(&false); + let show_config = cmd.flags.contains("config"); + let show_stats = cmd.flags.contains("stats"); + let show_full = cmd.flags.contains("full"); if args.is_empty() { println!("Error: No VM specified"); @@ -916,10 +869,10 @@ fn vm_show(cmd: &ParsedCommand) { // Show specific VM details let vm_name = &args[0]; if let Ok(vm_id) = vm_name.parse::() { - if *show_full { + if show_full { show_vm_full_details(vm_id); } else { - show_vm_basic_details(vm_id, *show_config, *show_stats); + show_vm_basic_details(vm_id, show_config, show_stats); } } else { println!("Error: Invalid VM ID: {}", vm_name); diff --git a/os/axvisor/src/shell/mod.rs b/os/axvisor/src/shell/mod.rs index 49aac346a2..e30e347405 100644 --- a/os/axvisor/src/shell/mod.rs +++ b/os/axvisor/src/shell/mod.rs @@ -19,7 +19,8 @@ use std::println; use std::string::ToString; use crate::shell::command::{ - CommandHistory, clear_line_and_redraw, handle_builtin_commands, print_prompt, run_cmd_bytes, + CommandHistory, clear_line_and_redraw, handle_builtin_commands, print_prompt, prompt_string, + run_cmd_bytes, }; const LF: u8 = b'\n'; @@ -30,6 +31,12 @@ const ESC: u8 = 0x1b; // ESC key const MAX_LINE_LEN: usize = 256; +enum InputState { + Normal, + Escape, + EscapeSeq, +} + // Initialize the console shell. pub fn console_init() { let mut stdin = std::io::stdin(); @@ -40,12 +47,6 @@ pub fn console_init() { let mut cursor = 0; // cursor position in buffer let mut line_len = 0; // actual length of current line - enum InputState { - Normal, - Escape, - EscapeSeq, - } - let mut input_state = InputState::Normal; println!("Welcome to AxVisor Shell!"); @@ -105,10 +106,7 @@ pub fn console_init() { let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); + let prompt = prompt_string(); clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); } } @@ -131,10 +129,7 @@ pub fn console_init() { let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); + let prompt = prompt_string(); clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); } } @@ -161,10 +156,7 @@ pub fn console_init() { buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); cursor = copy_len; line_len = copy_len; - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); + let prompt = prompt_string(); clear_line_and_redraw(&mut stdout, &prompt, prev_cmd, cursor); } input_state = InputState::Normal; @@ -182,11 +174,7 @@ pub fn console_init() { cursor = copy_len; line_len = copy_len; - #[cfg(feature = "fs")] - let prompt = - format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); + let prompt = prompt_string(); clear_line_and_redraw(&mut stdout, &prompt, next_cmd, cursor); } None => { @@ -194,11 +182,7 @@ pub fn console_init() { buf[..line_len].fill(0); cursor = 0; line_len = 0; - #[cfg(feature = "fs")] - let prompt = - format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); + let prompt = prompt_string(); clear_line_and_redraw(&mut stdout, &prompt, "", cursor); } } diff --git a/os/axvisor/src/task.rs b/os/axvisor/src/task.rs index cbcf89f9fe..3c6459356c 100644 --- a/os/axvisor/src/task.rs +++ b/os/axvisor/src/task.rs @@ -34,8 +34,11 @@ impl VCpuTask { } } - /// Get a strong reference to the VM if it's still alive. - /// Returns None if the VM has been dropped. + /// Get a strong reference to the VM. + /// + /// # Panics + /// + /// Panics if the VM has been dropped (weak reference can no longer be upgraded). pub fn vm(&self) -> VMRef { self.vm.upgrade().expect("VM has been dropped") } diff --git a/os/axvisor/src/vmm/config.rs b/os/axvisor/src/vmm/config.rs index 4dc0c2c27c..607b0769c9 100644 --- a/os/axvisor/src/vmm/config.rs +++ b/os/axvisor/src/vmm/config.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ax_errno::AxResult; +use ax_errno::{AxResult, ax_err_type}; use axaddrspace::GuestPhysAddr; use axvm::{ VMMemoryRegion, @@ -31,8 +31,8 @@ use crate::vmm::fdt::*; use alloc::sync::Arc; -#[allow(clippy::module_inception, dead_code)] -pub mod config { +#[allow(dead_code)] +pub mod vmcfg { use alloc::string::String; use alloc::vec::Vec; @@ -64,17 +64,33 @@ pub mod config { } }; - for entry in entries.flatten() { + for entry in entries { + let entry = match entry { + Ok(entry) => entry, + Err(e) => { + warn!("Failed to read config directory entry: {e:?}"); + continue; + } + }; let path = entry.path(); // Check if the file has a .toml extension let path_str = path.as_str(); debug!("Considering file: {}", path_str); if path_str.ends_with(".toml") { - let toml_file = fs::File::open(path_str).expect("Failed to open file"); - let file_size = toml_file - .metadata() - .expect("Failed to get file metadata") - .len() as usize; + let toml_file = match fs::File::open(path_str) { + Ok(file) => file, + Err(e) => { + error!("Failed to open config file {}: {:?}", path_str, e); + continue; + } + }; + let file_size = match toml_file.metadata() { + Ok(metadata) => metadata.len() as usize, + Err(e) => { + error!("Failed to get config file {} metadata: {:?}", path_str, e); + continue; + } + }; info!("File {} size: {}", path_str, file_size); @@ -93,23 +109,28 @@ pub mod config { buffer.len() ); // Convert to string - let content = alloc::string::String::from_utf8(buffer) - .expect("Failed to convert bytes to UTF-8 string"); - - if content.contains("[base]") - && content.contains("[kernel]") - && content.contains("[devices]") - { - configs.push(content); - info!( - "TOML config: {} is valid, start the virtual machine directly now. ", - path_str - ); - } else { - warn!( - "File {} does not appear to contain valid VM config structure", - path_str - ); + let content = match String::from_utf8(buffer) { + Ok(content) => content, + Err(e) => { + error!("Config file {} is not valid UTF-8: {:?}", path_str, e); + continue; + } + }; + + match axvm::config::AxVMCrateConfig::from_toml(&content) { + Ok(_) => { + configs.push(content); + info!( + "TOML config: {} is valid, start the virtual machine directly now. ", + path_str + ); + } + Err(e) => { + warn!( + "File {} does not contain a valid VM config: {:?}", + path_str, e + ); + } } } Err(e) => { @@ -158,11 +179,11 @@ pub fn init_guest_vms() { } // First try to get configs from filesystem if fs feature is enabled - let mut gvm_raw_configs = config::filesystem_vm_configs(); + let mut gvm_raw_configs = vmcfg::filesystem_vm_configs(); // If no filesystem configs found, fallback to static configs if gvm_raw_configs.is_empty() { - let static_configs = config::static_vm_configs(); + let static_configs = vmcfg::static_vm_configs(); if static_configs.is_empty() { info!("Static VM configs are empty."); info!("Now axvisor will entry the shell..."); @@ -182,20 +203,9 @@ pub fn init_guest_vms() { } pub fn init_guest_vm(raw_cfg: &str) -> AxResult { - #[cfg(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" - ))] - let mut vm_create_config = - AxVMCrateConfig::from_toml(raw_cfg).expect("Failed to resolve VM config"); - #[cfg(not(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" - )))] - let vm_create_config = - AxVMCrateConfig::from_toml(raw_cfg).expect("Failed to resolve VM config"); + #[allow(unused_mut)] + let mut vm_create_config = AxVMCrateConfig::from_toml(raw_cfg) + .map_err(|e| ax_err_type!(InvalidData, format!("Failed to resolve VM config: {e:?}")))?; if let Some(linux) = super::images::get_image_header(&vm_create_config) { debug!( @@ -204,43 +214,32 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { ); } - #[cfg(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" - ))] + #[allow(unused_mut)] let mut vm_config = AxVMConfig::from(vm_create_config.clone()); - #[cfg(not(any( - target_arch = "aarch64", - target_arch = "loongarch64", - target_arch = "riscv64" - )))] - let vm_config = AxVMConfig::from(vm_create_config.clone()); - // Handle FDT-related operations for architectures that boot guests with DTB. #[cfg(any( target_arch = "aarch64", target_arch = "loongarch64", target_arch = "riscv64" ))] - handle_fdt_operations(&mut vm_config, &mut vm_create_config); + handle_fdt_operations(&mut vm_config, &mut vm_create_config)?; // info!("after parse_vm_interrupt, crate VM[{}] with config: {:#?}", vm_config.id(), vm_config); info!("Creating VM[{}] {:?}", vm_config.id(), vm_config.name()); // Create VM. - let vm = VM::new(vm_config).expect("Failed to create VM"); + let vm = VM::new(vm_config) + .map_err(|e| ax_err_type!(InvalidData, format!("Failed to create VM: {e:?}")))?; let vm_id = vm.id(); - push_vm(vm.clone()); - vm_alloc_memorys(&vm_create_config, &vm); + vm_alloc_memory_regions(&vm_create_config, &vm)?; let main_mem = vm .memory_regions() .first() .cloned() - .expect("VM must have at least one memory region"); + .ok_or_else(|| ax_err_type!(InvalidData, "VM must have at least one memory region"))?; config_guest_address(&vm, &main_mem); @@ -248,13 +247,13 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { info!("VM[{}] created success, loading images...", vm.id()); let mut loader = ImageLoader::new(main_mem, vm_create_config, vm.clone()); - loader.load().expect("Failed to load VM images"); + loader.load()?; - if let Err(e) = vm.init() { - panic!("VM[{}] setup failed: {:?}", vm.id(), e); - } + vm.init() + .map_err(|e| ax_err_type!(InvalidData, format!("VM[{}] setup failed: {e:?}", vm.id())))?; vm.set_vm_status(axvm::VMStatus::Loaded); + push_vm(vm); Ok(vm_id) } @@ -273,29 +272,53 @@ fn config_guest_address(vm: &VM, main_memory: &VMMemoryRegion) { }); } -fn vm_alloc_memorys(vm_create_config: &AxVMCrateConfig, vm: &VM) { +fn vm_alloc_memory_regions(vm_create_config: &AxVMCrateConfig, vm: &VM) -> AxResult { const MB: usize = 1024 * 1024; const ALIGN: usize = 2 * MB; + let make_layout = |memory: &axvm::config::VmMemConfig| { + Layout::from_size_align(memory.size, ALIGN).map_err(|e| { + ax_err_type!( + InvalidInput, + format!("Invalid VM memory layout {:?}: {e:?}", memory) + ) + }) + }; + for memory in &vm_create_config.kernel.memory_regions { match memory.map_type { VmMemMappingType::MapAlloc => { - vm.alloc_memory_region( - Layout::from_size_align(memory.size, ALIGN).unwrap(), - Some(GuestPhysAddr::from(memory.gpa)), - ) - .expect("Failed to allocate memory region for VM"); + vm.alloc_memory_region(make_layout(memory)?, Some(GuestPhysAddr::from(memory.gpa))) + .map_err(|e| { + ax_err_type!( + NoMemory, + format!("Failed to allocate memory region for VM: {e:?}") + ) + })?; } VmMemMappingType::MapIdentical => { - vm.alloc_memory_region(Layout::from_size_align(memory.size, ALIGN).unwrap(), None) - .expect("Failed to allocate memory region for VM"); + vm.alloc_memory_region(make_layout(memory)?, None) + .map_err(|e| { + ax_err_type!( + NoMemory, + format!("Failed to allocate memory region for VM: {e:?}") + ) + })?; } VmMemMappingType::MapReserved => { debug!("VM[{}] map same region: {:#x?}", vm.id(), memory); - let layout = Layout::from_size_align(memory.size, ALIGN).unwrap(); - vm.map_reserved_memory_region(layout, Some(GuestPhysAddr::from(memory.gpa))) - .expect("Failed to map memory region for VM"); + vm.map_reserved_memory_region( + make_layout(memory)?, + Some(GuestPhysAddr::from(memory.gpa)), + ) + .map_err(|e| { + ax_err_type!( + NoMemory, + format!("Failed to map memory region for VM: {e:?}") + ) + })?; } } } + Ok(()) } diff --git a/os/axvisor/src/vmm/fdt/create.rs b/os/axvisor/src/vmm/fdt/create.rs index e1b57f28a5..4f881aeb85 100644 --- a/os/axvisor/src/vmm/fdt/create.rs +++ b/os/axvisor/src/vmm/fdt/create.rs @@ -20,6 +20,7 @@ use alloc::{ use core::ptr::NonNull; use super::vm_fdt::{FdtWriter, FdtWriterNode}; +use ax_errno::{AxError, AxResult, ax_err_type}; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] use ax_memory_addr::MemoryAddr; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64", test))] @@ -34,6 +35,10 @@ use crate::vmm::{VMRef, images::load_vm_image_from_memory}; // use crate::vmm::fdt::print::{print_fdt, print_guest_fdt}; +fn fdt_write_err(err: impl core::fmt::Display) -> AxError { + ax_err_type!(InvalidData, format!("Failed to write guest FDT: {err}")) +} + fn should_skip_guest_cpu_prop(prop_name: &str) -> bool { matches!( prop_name, @@ -54,8 +59,8 @@ pub fn crate_guest_fdt( fdt: &Fdt, passthrough_device_names: &[String], crate_config: &AxVMCrateConfig, -) -> Vec { - let mut fdt_writer = FdtWriter::new().unwrap(); +) -> AxResult> { + let mut fdt_writer = FdtWriter::new().map_err(fdt_write_err)?; // Track the level of the previously processed node for level change handling let mut previous_node_level = 0; // Maintain a stack of FDT nodes to correctly start and end nodes @@ -63,8 +68,8 @@ pub fn crate_guest_fdt( let phys_cpu_ids = crate_config .base .phys_cpu_ids - .clone() - .expect("ERROR: phys_cpu_ids is None"); + .as_deref() + .ok_or_else(|| ax_err_type!(InvalidInput, "phys_cpu_ids is missing"))?; let all_nodes: Vec = fdt.all_nodes().collect(); let all_paths = super::build_all_node_paths(&all_nodes); @@ -75,18 +80,18 @@ pub fn crate_guest_fdt( match node_action { NodeAction::RootNode => { - node_stack.push(fdt_writer.begin_node("").unwrap()); + node_stack.push(fdt_writer.begin_node("").map_err(fdt_write_err)?); } NodeAction::CpuNode => { - let need = need_cpu_node(&phys_cpu_ids, node, node_path); + let need = need_cpu_node(phys_cpu_ids, node, node_path); if need { handle_node_level_change( &mut fdt_writer, &mut node_stack, node.level, previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); + )?; + node_stack.push(fdt_writer.begin_node(node.name()).map_err(fdt_write_err)?); } else { continue; } @@ -105,8 +110,8 @@ pub fn crate_guest_fdt( &mut node_stack, node.level, previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); + )?; + node_stack.push(fdt_writer.begin_node(node.name()).map_err(fdt_write_err)?); } } @@ -117,18 +122,24 @@ pub fn crate_guest_fdt( if node_path.starts_with("/cpus") && should_skip_guest_cpu_prop(prop.name) { continue; } - fdt_writer.property(prop.name, prop.raw_value()).unwrap(); + fdt_writer + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } } // End all unclosed nodes while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - fdt_writer.end_node(node).unwrap(); + previous_node_level = previous_node_level + .checked_sub(1) + .ok_or_else(|| ax_err_type!(InvalidData, "Invalid FDT node nesting"))?; + fdt_writer.end_node(node).map_err(fdt_write_err)?; + } + if previous_node_level != 0 { + return Err(ax_err_type!(InvalidData, "Guest FDT has unbalanced nodes")); } - assert_eq!(previous_node_level, 0); - fdt_writer.finish().unwrap() + fdt_writer.finish().map_err(fdt_write_err) } /// Node processing action enumeration @@ -215,14 +226,15 @@ fn handle_node_level_change( node_stack: &mut Vec, current_level: usize, previous_level: usize, -) { +) -> AxResult { if current_level <= previous_level { for _ in current_level..=previous_level { if let Some(end_node) = node_stack.pop() { - fdt_writer.end_node(end_node).unwrap(); + fdt_writer.end_node(end_node).map_err(fdt_write_err)?; } } } + Ok(()) } /// Determine if node is an ancestor of passthrough device @@ -281,7 +293,7 @@ fn add_memory_node( new_memory: &[VMMemoryRegion], crate_config: &AxVMCrateConfig, new_fdt: &mut FdtWriter, -) { +) -> AxResult { let configured_region_count = if crate_config.kernel.configured_memory_region_count == 0 { crate_config.kernel.memory_regions.len() } else { @@ -317,8 +329,11 @@ fn add_memory_node( info!("Adding memory node with value: {new_value:x?}"); new_fdt .property_array_u32("reg", new_value.as_ref()) - .unwrap(); - new_fdt.property_string("device_type", "memory").unwrap(); + .map_err(fdt_write_err)?; + new_fdt + .property_string("device_type", "memory") + .map_err(fdt_write_err)?; + Ok(()) } #[cfg(any(target_arch = "aarch64", test))] @@ -383,8 +398,8 @@ pub fn update_fdt( dtb_size: usize, vm: VMRef, crate_config: &AxVMCrateConfig, -) { - let mut new_fdt = FdtWriter::new().unwrap(); +) -> AxResult { + let mut new_fdt = FdtWriter::new().map_err(fdt_write_err)?; let mut previous_node_level = 0; let mut node_stack: Vec = Vec::new(); let initrd_range = vm @@ -392,12 +407,11 @@ pub fn update_fdt( let fdt_bytes = unsafe { core::slice::from_raw_parts(fdt_src.as_ptr(), dtb_size) }; let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); + .map_err(|e| ax_err_type!(InvalidData, format!("Failed to parse FDT: {e:#?}")))?; for node in fdt.all_nodes() { if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); + node_stack.push(new_fdt.begin_node("").map_err(fdt_write_err)?); } else if node.name().starts_with("memory") { // Skip memory nodes, will add them later continue; @@ -407,9 +421,9 @@ pub fn update_fdt( &mut node_stack, node.level, previous_node_level, - ); + )?; // Start new node - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); + node_stack.push(new_fdt.begin_node(node.name()).map_err(fdt_write_err)?); } previous_node_level = node.level; @@ -424,7 +438,9 @@ pub fn update_fdt( node.name() ); } else { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } } else if prop.name == "bootargs" { let bootargs_str = prop.str(); @@ -439,14 +455,16 @@ pub fn update_fdt( new_fdt .property_string(prop.name, &modified_bootargs) - .unwrap(); + .map_err(fdt_write_err)?; } else { debug!( "Find property: {}, belonging to node: {}", prop.name, node.name() ); - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } } if let Some((initrd_start, initrd_end)) = initrd_range { @@ -456,49 +474,55 @@ pub fn update_fdt( ); new_fdt .property_u64("linux,initrd-start", initrd_start) - .unwrap(); + .map_err(fdt_write_err)?; new_fdt .property_u64("linux,initrd-end", initrd_end) - .unwrap(); + .map_err(fdt_write_err)?; } } else { for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } } } // End all unclosed nodes, and add memory nodes at appropriate positions while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - new_fdt.end_node(node).unwrap(); + previous_node_level = previous_node_level + .checked_sub(1) + .ok_or_else(|| ax_err_type!(InvalidData, "Invalid FDT node nesting"))?; + new_fdt.end_node(node).map_err(fdt_write_err)?; // add memory node if previous_node_level == 1 { let memory_regions = vm.memory_regions(); - let memory_node = new_fdt.begin_node("memory").unwrap(); - add_memory_node(&memory_regions, crate_config, &mut new_fdt); - new_fdt.end_node(memory_node).unwrap(); + let memory_node = new_fdt.begin_node("memory").map_err(fdt_write_err)?; + add_memory_node(&memory_regions, crate_config, &mut new_fdt)?; + new_fdt.end_node(memory_node).map_err(fdt_write_err)?; } } - assert_eq!(previous_node_level, 0); + if previous_node_level != 0 { + return Err(ax_err_type!(InvalidData, "Guest FDT has unbalanced nodes")); + } info!("Updating FDT memory successfully"); - let new_fdt_bytes = new_fdt.finish().unwrap(); + let new_fdt_bytes = new_fdt.finish().map_err(fdt_write_err)?; // crate::vmm::fdt::print::print_guest_fdt(new_fdt_bytes.as_slice()); let vm_clone = vm.clone(); - let dest_addr = calculate_dtb_load_addr(vm, new_fdt_bytes.len()); + let dest_addr = calculate_dtb_load_addr(vm, new_fdt_bytes.len())?; debug!( "New FDT will be loaded at {:x}, size: 0x{:x}", dest_addr, new_fdt_bytes.len() ); // Load the updated FDT into VM - load_vm_image_from_memory(&new_fdt_bytes, dest_addr, vm_clone) - .expect("Failed to load VM images"); + load_vm_image_from_memory(&new_fdt_bytes, dest_addr, vm_clone)?; + Ok(()) } #[cfg(target_arch = "riscv64")] @@ -507,25 +531,33 @@ pub fn update_fdt( dtb_size: usize, vm: VMRef, crate_config: &AxVMCrateConfig, -) { +) -> AxResult { // Fix up the cached DTB against the runtime layout before boot. let fdt_bytes = unsafe { core::slice::from_raw_parts(fdt_src.as_ptr(), dtb_size) }; - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse cached guest FDT: {e:#?}")) - .expect("Failed to parse cached guest FDT"); + let fdt = Fdt::from_bytes(fdt_bytes).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse cached guest FDT: {e:#?}") + ) + })?; // Keep boot metadata such as /chosen from the host FDT when it is available. let host_fdt_bytes = super::try_get_host_fdt(); - let host_fdt = host_fdt_bytes.map(|bytes| { - Fdt::from_bytes(bytes) - .map_err(|e| format!("Failed to parse host FDT while updating guest FDT: {e:#?}")) - .expect("Failed to parse host FDT while updating guest FDT") - }); + let host_fdt = host_fdt_bytes + .map(|bytes| { + Fdt::from_bytes(bytes).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse host FDT while updating guest FDT: {e:#?}") + ) + }) + }) + .transpose()?; let new_fdt_bytes = - patch_guest_fdt_for_runtime(&fdt, &vm.memory_regions(), crate_config, host_fdt.as_ref()); + patch_guest_fdt_for_runtime(&fdt, &vm.memory_regions(), crate_config, host_fdt.as_ref())?; // Recompute the DTB load address from the runtime memory layout. - let dest_addr = calculate_dtb_load_addr(vm.clone(), new_fdt_bytes.len()); + let dest_addr = calculate_dtb_load_addr(vm.clone(), new_fdt_bytes.len())?; - load_vm_image_from_memory(&new_fdt_bytes, dest_addr, vm).expect("Failed to load VM images"); + load_vm_image_from_memory(&new_fdt_bytes, dest_addr, vm) } #[cfg(test)] @@ -619,17 +651,16 @@ mod tests { } #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] -pub(crate) fn calculate_dtb_load_addr(vm: VMRef, fdt_size: usize) -> GuestPhysAddr { +pub(crate) fn calculate_dtb_load_addr(vm: VMRef, fdt_size: usize) -> AxResult { const MB: usize = 1024 * 1024; // Get main memory from VM memory regions outside the closure - let main_memory = vm - .memory_regions() - .first() - .cloned() - .expect("VM must have at least one memory region"); + let main_memory = + vm.memory_regions().first().cloned().ok_or_else(|| { + ax_err_type!(InvalidInput, "VM has no memory region for DTB placement") + })?; - vm.with_config(|config| { + let dtb_addr = vm.with_config(|config| { let dtb_addr = if let Some(addr) = config.image_config.dtb_load_gpa && !main_memory.is_identical() { @@ -646,7 +677,9 @@ pub(crate) fn calculate_dtb_load_addr(vm: VMRef, fdt_size: usize) -> GuestPhysAd }; config.image_config.dtb_load_gpa = Some(dtb_addr); dtb_addr - }) + }); + + Ok(dtb_addr) } #[cfg(target_arch = "riscv64")] @@ -655,8 +688,8 @@ pub(crate) fn patch_guest_fdt_for_runtime( memory_regions: &[VMMemoryRegion], crate_config: &AxVMCrateConfig, host_fdt: Option<&Fdt>, -) -> Vec { - let mut new_fdt = FdtWriter::new().unwrap(); +) -> AxResult> { + let mut new_fdt = FdtWriter::new().map_err(fdt_write_err)?; let mut previous_node_level = 0usize; let mut node_stack: Vec = Vec::new(); let mut has_chosen = false; @@ -672,65 +705,79 @@ pub(crate) fn patch_guest_fdt_for_runtime( } if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); + node_stack.push(new_fdt.begin_node("").map_err(fdt_write_err)?); } else { handle_node_level_change( &mut new_fdt, &mut node_stack, node.level, previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); + )?; + node_stack.push(new_fdt.begin_node(node.name()).map_err(fdt_write_err)?); } previous_node_level = node.level; for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } } // Return to the root before inserting synthetic nodes. while node_stack.len() > 1 { - let node = node_stack.pop().unwrap(); - new_fdt.end_node(node).unwrap(); + let node = node_stack + .pop() + .ok_or_else(|| ax_err_type!(InvalidData, "Guest FDT node stack is empty"))?; + new_fdt.end_node(node).map_err(fdt_write_err)?; } - assert_eq!(node_stack.len(), 1); + if node_stack.len() != 1 { + return Err(ax_err_type!(InvalidData, "Guest FDT root node is missing")); + } // Restore /chosen from the host FDT when it is missing. if !has_chosen && let Some(host_fdt) = host_fdt && let Some(chosen_node) = host_fdt.find_nodes("/chosen").next() { - let chosen = new_fdt.begin_node("chosen").unwrap(); + let chosen = new_fdt.begin_node("chosen").map_err(fdt_write_err)?; for prop in chosen_node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } - new_fdt.end_node(chosen).unwrap(); + new_fdt.end_node(chosen).map_err(fdt_write_err)?; } // Rebuild /memory from the runtime-visible regions that correspond to the // user-configured memory layout. - let memory_node = new_fdt.begin_node("memory").unwrap(); - add_memory_node(memory_regions, crate_config, &mut new_fdt); - new_fdt.end_node(memory_node).unwrap(); + let memory_node = new_fdt.begin_node("memory").map_err(fdt_write_err)?; + add_memory_node(memory_regions, crate_config, &mut new_fdt)?; + new_fdt.end_node(memory_node).map_err(fdt_write_err)?; - let root = node_stack.pop().unwrap(); - new_fdt.end_node(root).unwrap(); + let root = node_stack + .pop() + .ok_or_else(|| ax_err_type!(InvalidData, "Guest FDT root node is missing"))?; + new_fdt.end_node(root).map_err(fdt_write_err)?; - new_fdt.finish().unwrap() + new_fdt.finish().map_err(fdt_write_err) } #[cfg(target_arch = "aarch64")] -pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig) -> Vec { - let mut new_fdt = FdtWriter::new().unwrap(); +pub fn update_cpu_node( + fdt: &Fdt, + host_fdt: &Fdt, + crate_config: &AxVMCrateConfig, +) -> AxResult> { + let mut new_fdt = FdtWriter::new().map_err(fdt_write_err)?; let mut previous_node_level = 0; let mut node_stack: Vec = Vec::new(); let phys_cpu_ids = crate_config .base .phys_cpu_ids - .clone() - .expect("ERROR: phys_cpu_ids is None"); + .as_deref() + .ok_or_else(|| ax_err_type!(InvalidInput, "phys_cpu_ids is missing"))?; // Collect all nodes from both FDTs let fdt_all_nodes: Vec = fdt.all_nodes().collect(); @@ -742,7 +789,7 @@ pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig let node_path = &fdt_all_paths[index]; if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); + node_stack.push(new_fdt.begin_node("").map_err(fdt_write_err)?); } else if node_path.starts_with("/cpus") { // Skip CPU nodes from fdt, we'll process them from host_fdt later continue; @@ -753,15 +800,17 @@ pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig &mut node_stack, node.level, previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); + )?; + node_stack.push(new_fdt.begin_node(node.name()).map_err(fdt_write_err)?); } previous_node_level = node.level; // Copy all properties of the node (for non-CPU nodes) for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } } @@ -771,22 +820,24 @@ pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig if node_path.starts_with("/cpus") { // For CPU nodes, apply filtering based on host_fdt nodes - let need = need_cpu_node(&phys_cpu_ids, node, &node_path); + let need = need_cpu_node(phys_cpu_ids, node, node_path); if need { handle_node_level_change( &mut new_fdt, &mut node_stack, node.level, previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); + )?; + node_stack.push(new_fdt.begin_node(node.name()).map_err(fdt_write_err)?); // Copy properties from host CPU node for prop in node.propertys() { if should_skip_guest_cpu_prop(prop.name) { continue; } - new_fdt.property(prop.name, prop.raw_value()).unwrap(); + new_fdt + .property(prop.name, prop.raw_value()) + .map_err(fdt_write_err)?; } previous_node_level = node.level; @@ -796,10 +847,14 @@ pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig // End all unclosed nodes while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - new_fdt.end_node(node).unwrap(); + previous_node_level = previous_node_level + .checked_sub(1) + .ok_or_else(|| ax_err_type!(InvalidData, "Invalid FDT node nesting"))?; + new_fdt.end_node(node).map_err(fdt_write_err)?; + } + if previous_node_level != 0 { + return Err(ax_err_type!(InvalidData, "Guest FDT has unbalanced nodes")); } - assert_eq!(previous_node_level, 0); - new_fdt.finish().unwrap() + new_fdt.finish().map_err(fdt_write_err) } diff --git a/os/axvisor/src/vmm/fdt/device.rs b/os/axvisor/src/vmm/fdt/device.rs index b238c2237d..7c6c423eb2 100644 --- a/os/axvisor/src/vmm/fdt/device.rs +++ b/os/axvisor/src/vmm/fdt/device.rs @@ -192,7 +192,7 @@ pub fn find_all_passthrough_devices(vm_cfg: &mut AxVMConfig, fdt: &Fdt) -> Vec String { build_all_node_paths(all_nodes) .get(target_index) diff --git a/os/axvisor/src/vmm/fdt/mod.rs b/os/axvisor/src/vmm/fdt/mod.rs index 7bff3d0525..3b7f03caa9 100644 --- a/os/axvisor/src/vmm/fdt/mod.rs +++ b/os/axvisor/src/vmm/fdt/mod.rs @@ -25,6 +25,7 @@ mod vm_fdt; use alloc::collections::BTreeMap; use alloc::vec::Vec; +use ax_errno::{AxResult, ax_err_type}; use ax_kspin::SpinNoIrq as Mutex; use ax_lazyinit::LazyInit; use axvm::config::{AxVMConfig, AxVMCrateConfig}; @@ -35,10 +36,8 @@ pub use parser::*; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] pub use create::update_fdt; pub use device::build_all_node_paths; -#[cfg(any(target_arch = "aarch64", test))] -pub use device::build_node_path; -use crate::vmm::config::{config, get_vm_dtb_arc}; +use crate::vmm::config::{get_vm_dtb_arc, vmcfg}; // DTB cache for generated device trees static GENERATED_DTB_CACHE: LazyInit>>> = LazyInit::new(); @@ -50,7 +49,7 @@ pub fn init_dtb_cache() { /// Get reference to the DTB cache pub fn dtb_cache() -> &'static Mutex>> { - GENERATED_DTB_CACHE.get().unwrap() + GENERATED_DTB_CACHE.get_or_init(|| Mutex::new(BTreeMap::new())) } /// Generate guest FDT cache the result @@ -63,24 +62,26 @@ pub fn crate_guest_fdt_with_cache(dtb_data: Vec, crate_config: &AxVMCrateCon } /// Handle all FDT-related operations for guest architectures that boot with DTB. -pub fn handle_fdt_operations(vm_config: &mut AxVMConfig, vm_create_config: &mut AxVMCrateConfig) { +pub fn handle_fdt_operations( + vm_config: &mut AxVMConfig, + vm_create_config: &mut AxVMCrateConfig, +) -> AxResult { let host_fdt_bytes = try_get_host_fdt(); if let Some(host_fdt_bytes) = host_fdt_bytes { let host_fdt = Fdt::from_bytes(host_fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - set_phys_cpu_sets(vm_config, &host_fdt, vm_create_config); + .map_err(|e| ax_err_type!(InvalidData, format!("Failed to parse host FDT: {e:#?}")))?; + set_phys_cpu_sets(vm_config, &host_fdt, vm_create_config)?; - if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config) { + if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config)? { info!("VM[{}] found DTB , parsing...", vm_config.id()); - update_provided_fdt(&provided_dtb, host_fdt_bytes, vm_create_config); + update_provided_fdt(&provided_dtb, host_fdt_bytes, vm_create_config)?; } else { info!( "VM[{}] DTB not found, generating based on the configuration file.", vm_config.id() ); - setup_guest_fdt_from_vmm(host_fdt_bytes, vm_config, vm_create_config); + setup_guest_fdt_from_vmm(host_fdt_bytes, vm_config, vm_create_config)?; } } else { #[cfg(target_arch = "loongarch64")] @@ -91,9 +92,9 @@ pub fn handle_fdt_operations(vm_config: &mut AxVMConfig, vm_create_config: &mut ); } - if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config) { + if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config)? { info!("VM[{}] found DTB , parsing...", vm_config.id()); - update_provided_fdt(&provided_dtb, &[], vm_create_config); + update_provided_fdt(&provided_dtb, &[], vm_create_config)?; } else { warn!( "VM[{}] no guest DTB provided; continuing without generated DTB", @@ -105,59 +106,59 @@ pub fn handle_fdt_operations(vm_config: &mut AxVMConfig, vm_create_config: &mut // Overlay VM config with the given DTB. if let Some(dtb_arc) = get_vm_dtb_arc(vm_config) { let dtb = dtb_arc.as_ref(); - parse_reserved_memory_regions(vm_create_config, dtb); - parse_passthrough_devices_address(vm_config, vm_create_config, dtb); + parse_reserved_memory_regions(vm_create_config, dtb)?; + parse_passthrough_devices_address(vm_config, vm_create_config, dtb)?; #[cfg(target_arch = "aarch64")] - parse_vm_interrupt(vm_config, dtb); + parse_vm_interrupt(vm_config, dtb)?; } else { error!( "VM[{}] DTB not found in memory, skipping...", vm_config.id() ); } + Ok(()) } pub fn get_developer_provided_dtb( vm_cfg: &AxVMConfig, crate_config: &AxVMCrateConfig, -) -> Option> { +) -> AxResult>> { match crate_config.kernel.image_location.as_deref() { Some("memory") => { - let vm_imags = config::get_memory_images() + let vm_imags = vmcfg::get_memory_images() .iter() - .find(|&v| v.id == vm_cfg.id())?; + .find(|&v| v.id == vm_cfg.id()); - if let Some(dtb) = vm_imags.dtb { + if let Some(dtb) = vm_imags.and_then(|images| images.dtb) { info!("DTB file in memory, size: 0x{:x}", dtb.len()); - return Some(dtb.to_vec()); + return Ok(Some(dtb.to_vec())); } } #[cfg(feature = "fs")] Some("fs") => { - use ax_errno::ax_err_type; use std::io::{BufReader, Read}; if let Some(dtb_path) = &crate_config.kernel.dtb_path { - let (dtb_file, dtb_size) = - crate::vmm::images::fs::open_image_file(dtb_path).unwrap(); + let (dtb_file, dtb_size) = crate::vmm::images::fs::open_image_file(dtb_path)?; info!("DTB file in fs, size: 0x{:x}", dtb_size); let mut file = BufReader::new(dtb_file); let mut dtb_buffer = vec![0; dtb_size]; - file.read_exact(&mut dtb_buffer) - .map_err(|err| { - ax_err_type!( - Io, - format!("Failed in reading from file {}, err {:?}", dtb_path, err) - ) - }) - .unwrap(); - return Some(dtb_buffer); + file.read_exact(&mut dtb_buffer).map_err(|err| { + ax_err_type!( + Io, + format!("Failed in reading from file {}, err {:?}", dtb_path, err) + ) + })?; + return Ok(Some(dtb_buffer)); } } - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n." - ), + _ => { + return ax_errno::ax_err!( + InvalidInput, + "Unsupported image_location; use \"memory\" or enable fs feature for \"fs\"" + ); + } } - None + Ok(None) } diff --git a/os/axvisor/src/vmm/fdt/parser.rs b/os/axvisor/src/vmm/fdt/parser.rs index 24ba3dbee0..b96de8c403 100644 --- a/os/axvisor/src/vmm/fdt/parser.rs +++ b/os/axvisor/src/vmm/fdt/parser.rs @@ -15,6 +15,7 @@ //! FDT parsing and processing functionality. use alloc::{string::ToString, vec::Vec}; +use ax_errno::{AxResult, ax_err_type}; use ax_hal::{dtb, mem}; use axaddrspace::MappingFlags; use axvm::config::{ @@ -64,16 +65,16 @@ pub fn setup_guest_fdt_from_vmm( fdt_bytes: &[u8], vm_cfg: &mut AxVMConfig, crate_config: &AxVMCrateConfig, -) { +) -> AxResult { let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); + .map_err(|e| ax_err_type!(InvalidData, format!("Failed to parse host FDT: {e:#?}")))?; // Call the modified function and get the returned device name list let passthrough_device_names = super::device::find_all_passthrough_devices(vm_cfg, &fdt); - let dtb_data = super::create::crate_guest_fdt(&fdt, &passthrough_device_names, crate_config); + let dtb_data = super::create::crate_guest_fdt(&fdt, &passthrough_device_names, crate_config)?; crate_guest_fdt_with_cache(dtb_data, crate_config); + Ok(()) } fn is_reserved_memory_path(node_path: &str) -> bool { @@ -221,9 +222,13 @@ fn should_skip_passthrough_node( false } -pub fn parse_reserved_memory_regions(crate_cfg: &mut AxVMCrateConfig, dtb: &[u8]) { - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); +pub fn parse_reserved_memory_regions(crate_cfg: &mut AxVMCrateConfig, dtb: &[u8]) -> AxResult { + let fdt = Fdt::from_bytes(dtb).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse DTB image while reading reserved memory: {e:#?}") + ) + })?; let all_nodes: Vec<_> = fdt.all_nodes().collect(); let all_paths = super::build_all_node_paths(&all_nodes); let default_flags = (MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE).bits(); @@ -297,6 +302,7 @@ pub fn parse_reserved_memory_regions(crate_cfg: &mut AxVMCrateConfig, dtb: &[u8] added_count ); } + Ok(()) } #[cfg(test)] @@ -367,7 +373,11 @@ mod tests { } } -pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVMCrateConfig) { +pub fn set_phys_cpu_sets( + vm_cfg: &mut AxVMConfig, + fdt: &Fdt, + crate_config: &AxVMCrateConfig, +) -> AxResult { // Find and parse CPU information from host DTB let host_cpus: Vec<_> = fdt.find_nodes("/cpus/cpu").collect(); info!("Found {} host CPU nodes", &host_cpus.len()); @@ -376,7 +386,7 @@ pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVM .base .phys_cpu_ids .as_ref() - .expect("ERROR: phys_cpu_ids not found in config.toml"); + .ok_or_else(|| ax_err_type!(InvalidInput, "phys_cpu_ids is missing"))?; let cpu_nodes_info: Vec<_> = host_cpus .iter() @@ -436,6 +446,7 @@ pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVM "vcpu_mappings: {:?}", vm_cfg.phys_cpu_ls_mut().get_vcpu_affinities_pcpu_ids() ); + Ok(()) } /// Add address mapping configuration for a device @@ -542,7 +553,7 @@ pub fn parse_passthrough_devices_address( vm_cfg: &mut AxVMConfig, crate_cfg: &AxVMCrateConfig, dtb: &[u8], -) { +) -> AxResult { let devices = vm_cfg.pass_through_devices().to_vec(); if !devices.is_empty() && devices[0].length != 0 { for (index, device) in devices.iter().enumerate() { @@ -556,8 +567,12 @@ pub fn parse_passthrough_devices_address( ); } } else { - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); + let fdt = Fdt::from_bytes(dtb).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse DTB image while reading passthrough devices: {e:#?}") + ) + })?; // Clear existing passthrough device configurations vm_cfg.clear_pass_through_devices(); @@ -650,13 +665,18 @@ pub fn parse_passthrough_devices_address( vm_cfg.pass_through_devices().len() ); } + Ok(()) } #[cfg(target_arch = "aarch64")] -pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) { +pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) -> AxResult { const GIC_PHANDLE: usize = 1; - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); + let fdt = Fdt::from_bytes(dtb).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse DTB image while reading interrupts: {e:#?}") + ) + })?; for node in fdt.all_nodes() { let name = node.name(); @@ -734,9 +754,14 @@ pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) { // length: 0x20_0000, // irq_id: 0, // }); + Ok(()) } -pub fn update_provided_fdt(provided_dtb: &[u8], host_dtb: &[u8], crate_config: &AxVMCrateConfig) { +pub fn update_provided_fdt( + provided_dtb: &[u8], + host_dtb: &[u8], + crate_config: &AxVMCrateConfig, +) -> AxResult { #[cfg(any(target_arch = "loongarch64", target_arch = "riscv64"))] { let _ = host_dtb; @@ -745,11 +770,20 @@ pub fn update_provided_fdt(provided_dtb: &[u8], host_dtb: &[u8], crate_config: & #[cfg(target_arch = "aarch64")] { - let provided_fdt = Fdt::from_bytes(provided_dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - let host_fdt = Fdt::from_bytes(host_dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - let provided_dtb_data = update_cpu_node(&provided_fdt, &host_fdt, crate_config); + let provided_fdt = Fdt::from_bytes(provided_dtb).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse provided DTB image: {e:#?}") + ) + })?; + let host_fdt = Fdt::from_bytes(host_dtb).map_err(|e| { + ax_err_type!( + InvalidData, + format!("Failed to parse host DTB image: {e:#?}") + ) + })?; + let provided_dtb_data = update_cpu_node(&provided_fdt, &host_fdt, crate_config)?; crate_guest_fdt_with_cache(provided_dtb_data, crate_config); } + Ok(()) } diff --git a/os/axvisor/src/vmm/hvc.rs b/os/axvisor/src/vmm/hvc.rs index 38d188c260..468297c5e3 100644 --- a/os/axvisor/src/vmm/hvc.rs +++ b/os/axvisor/src/vmm/hvc.rs @@ -16,29 +16,23 @@ use ax_errno::{AxResult, ax_err, ax_err_type}; use axaddrspace::{GuestPhysAddr, MappingFlags}; use axhvc::{HyperCallCode, HyperCallResult}; +use crate::vmm::VMRef; use crate::vmm::ivc::{self, IVCChannel}; -use crate::vmm::{VCpuRef, VMRef}; pub struct HyperCall { - _vcpu: VCpuRef, vm: VMRef, code: HyperCallCode, args: [u64; 6], } impl HyperCall { - pub fn new(vcpu: VCpuRef, vm: VMRef, code: u64, args: [u64; 6]) -> AxResult { + pub fn new(vm: VMRef, code: u64, args: [u64; 6]) -> AxResult { let code = HyperCallCode::try_from(code as u32).map_err(|e| { warn!("Invalid hypercall code: {code} e {e:?}"); ax_err_type!(InvalidInput) })?; - Ok(Self { - _vcpu: vcpu, - vm, - code, - args, - }) + Ok(Self { vm, code, args }) } pub fn execute(&self) -> HyperCallResult { @@ -88,7 +82,9 @@ impl HyperCall { self.code, key ); - let (base_gpa, size) = ivc::unpublish_channel(self.vm.id(), key)?.unwrap(); + let (base_gpa, size) = ivc::unpublish_channel(self.vm.id(), key)?; + // The publisher's GPA mapping is always unmapped; subscribers keep their own + // GPA views. The shared HPA frame is freed when the last subscriber leaves. self.vm.unmap_region(base_gpa, size)?; Ok(0) diff --git a/os/axvisor/src/vmm/images/mod.rs b/os/axvisor/src/vmm/images/mod.rs index 75b7284e6f..7e5da89109 100644 --- a/os/axvisor/src/vmm/images/mod.rs +++ b/os/axvisor/src/vmm/images/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ax_errno::AxResult; +use ax_errno::{AxResult, ax_err, ax_err_type}; use axaddrspace::GuestPhysAddr; use axvm::VMMemoryRegion; @@ -21,7 +21,7 @@ use byte_unit::Byte; use crate::hal::CacheOp; use crate::vmm::VMRef; -use crate::vmm::config::{config, get_vm_dtb_arc}; +use crate::vmm::config::{get_vm_dtb_arc, vmcfg}; mod linux; #[cfg(target_arch = "x86_64")] @@ -29,29 +29,38 @@ mod x86_boot; pub fn get_image_header(config: &AxVMCrateConfig) -> Option { match config.kernel.image_location.as_deref() { - Some("memory") => with_memory_image(config, linux::Header::parse), + Some("memory") => with_memory_image(config, linux::Header::parse).flatten(), #[cfg(feature = "fs")] Some("fs") => { let read_size = linux::Header::hdr_size(); - let data = fs::kernal_read(config, read_size).ok()?; + let data = fs::kernel_read(config, read_size).ok()?; linux::Header::parse(&data) } - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n NOTE: \"fs\" feature should be enabled if you want to load images from filesystem. (APP_FEATURES=fs)" - ), + _ => None, } } -fn with_memory_image(config: &AxVMCrateConfig, func: F) -> R +fn with_memory_image(config: &AxVMCrateConfig, func: F) -> Option where F: FnOnce(&[u8]) -> R, { - let vm_imags = config::get_memory_images() + let vm_imags = vmcfg::get_memory_images() .iter() - .find(|&v| v.id == config.base.id) - .expect("VM images is missed, Perhaps add `VM_CONFIGS=PATH/CONFIGS/FILE` command."); + .find(|&v| v.id == config.base.id)?; + + Some(func(vm_imags.kernel)) +} - func(vm_imags.kernel) +fn memory_images_for_vm(config: &AxVMCrateConfig) -> AxResult<&'static vmcfg::MemoryImage> { + vmcfg::get_memory_images() + .iter() + .find(|&v| v.id == config.base.id) + .ok_or_else(|| { + ax_err_type!( + NotFound, + "VM images are missing; pass VM configs with AXVISOR_VM_CONFIGS" + ) + }) } pub struct ImageLoader { @@ -94,8 +103,9 @@ impl ImageLoader { Some("memory") => self.load_vm_images_from_memory(), #[cfg(feature = "fs")] Some("fs") => fs::load_vm_images_from_filesystem(self), - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n NOTE: \"fs\" feature should be enabled if you want to load images from filesystem. (APP_FEATURES=fs)" + _ => ax_err!( + InvalidInput, + "Unsupported image_location; use \"memory\" or enable fs feature for \"fs\"" ), } } @@ -105,18 +115,13 @@ impl ImageLoader { fn load_vm_images_from_memory(&self) -> AxResult { info!("Loading VM[{}] images from memory", self.config.base.id); - let vm_imags = config::get_memory_images() - .iter() - .find(|&v| v.id == self.config.base.id) - .expect("VM images is missed, Perhaps add `VM_CONFIGS=PATH/CONFIGS/FILE` command."); + let vm_imags = memory_images_for_vm(&self.config)?; - load_vm_image_from_memory(vm_imags.kernel, self.kernel_load_gpa, self.vm.clone()) - .expect("Failed to load VM images"); + load_vm_image_from_memory(vm_imags.kernel, self.kernel_load_gpa, self.vm.clone())?; // Load Ramdisk image and record its size before regenerating the DTB. if let Some(buffer) = vm_imags.ramdisk { - self.load_ramdisk_from_memory(buffer) - .expect("Failed to load Ramdisk images"); + self.load_ramdisk_from_memory(buffer)?; } // Load DTB image let vm_config = axvm::config::AxVMConfig::from(self.config.clone()); @@ -124,20 +129,32 @@ impl ImageLoader { if let Some(dtb_arc) = get_vm_dtb_arc(&vm_config) { let _dtb_slice: &[u8] = &dtb_arc; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - crate::vmm::fdt::update_fdt( - core::ptr::NonNull::new(_dtb_slice.as_ptr() as *mut u8).unwrap(), - _dtb_slice.len(), - self.vm.clone(), - &self.config, - ); + { + if let Some(dtb_src) = core::ptr::NonNull::new(_dtb_slice.as_ptr() as *mut u8) { + crate::vmm::fdt::update_fdt( + dtb_src, + _dtb_slice.len(), + self.vm.clone(), + &self.config, + )?; + } else { + return ax_err!(InvalidData, "Guest DTB pointer is null"); + } + } #[cfg(target_arch = "loongarch64")] - load_vm_image_from_memory(_dtb_slice, self.dtb_load_gpa.unwrap(), self.vm.clone()) - .expect("Failed to load DTB images"); + { + let dtb_load_gpa = self + .dtb_load_gpa + .ok_or_else(|| ax_err_type!(NotFound, "DTB load address is missing"))?; + load_vm_image_from_memory(_dtb_slice, dtb_load_gpa, self.vm.clone())?; + } } else { #[cfg(any(target_arch = "loongarch64", target_arch = "riscv64"))] if let Some(buffer) = vm_imags.dtb { - load_vm_image_from_memory(buffer, self.dtb_load_gpa.unwrap(), self.vm.clone()) - .expect("Failed to load DTB images"); + let dtb_load_gpa = self + .dtb_load_gpa + .ok_or_else(|| ax_err_type!(NotFound, "DTB load address is missing"))?; + load_vm_image_from_memory(buffer, dtb_load_gpa, self.vm.clone())?; } else { info!("dtb_load_gpa not provided"); } @@ -161,9 +178,8 @@ impl ImageLoader { if let Some(buffer) = bios { let load_gpa = self .bios_load_gpa - .expect("BIOS image present but BIOS load addr is missed"); - load_vm_image_from_memory(buffer, load_gpa, self.vm.clone()) - .expect("Failed to load BIOS images"); + .ok_or_else(|| ax_err_type!(NotFound, "BIOS load address is missing"))?; + load_vm_image_from_memory(buffer, load_gpa, self.vm.clone())?; #[cfg(target_arch = "x86_64")] self.load_x86_multiboot_info(buffer, load_gpa)?; return Ok(()); @@ -176,8 +192,11 @@ impl ImageLoader { "Loading built-in x86 boot image at GPA {:#x}", bios_load_gpa.as_usize() ); - load_vm_image_from_memory(x86_boot::DEFAULT_BIOS_IMAGE, bios_load_gpa, self.vm.clone()) - .expect("Failed to load built-in x86 boot image"); + load_vm_image_from_memory( + x86_boot::DEFAULT_BIOS_IMAGE, + bios_load_gpa, + self.vm.clone(), + )?; #[cfg(target_arch = "x86_64")] self.load_x86_multiboot_info(x86_boot::DEFAULT_BIOS_IMAGE, bios_load_gpa)?; } @@ -230,7 +249,7 @@ impl ImageLoader { let load_gpa = self .vm .with_config(|config| config.image_config.ramdisk.as_ref().map(|r| r.load_gpa)) - .expect("Ramdisk image present but ramdisk info is missing"); + .ok_or_else(|| ax_err_type!(NotFound, "Ramdisk load address is missing"))?; let size = ramdisk.len(); self.vm.with_config(|config| { if let Some(ref mut rd) = config.image_config.ramdisk { @@ -288,7 +307,11 @@ pub fn load_vm_image_from_memory( let region_len = region.len(); let bytes_to_write = region_len.min(image_size - buffer_pos); - // copy data from memory + // SAFETY: `region` is valid writable guest memory obtained from + // `vm.get_image_load_region()`; `bytes_to_write <= region.len()` is + // guaranteed by `region_len.min(image_size - buffer_pos)`; and + // `image_buffer[buffer_pos..]` has at least `bytes_to_write` bytes. + // The source and destination do not overlap (guest HPA vs host image buffer). unsafe { core::ptr::copy_nonoverlapping( image_buffer[buffer_pos..].as_ptr(), @@ -300,7 +323,7 @@ pub fn load_vm_image_from_memory( crate::hal::arch::cache::dcache_range( CacheOp::Clean, (region.as_ptr() as usize).into(), - region_len, + bytes_to_write, ); // Update the position of the buffer. @@ -313,7 +336,14 @@ pub fn load_vm_image_from_memory( } } - Ok(()) + if buffer_pos == image_size { + Ok(()) + } else { + ax_err!( + InvalidData, + format!("VM image was only partially loaded: {buffer_pos}/{image_size} bytes") + ) + } } #[cfg(feature = "fs")] @@ -322,7 +352,7 @@ pub mod fs { use ax_errno::{AxResult, ax_err, ax_err_type}; use std::{fs::File, vec::Vec}; - pub fn kernal_read(config: &AxVMCrateConfig, read_size: usize) -> AxResult> { + pub fn kernel_read(config: &AxVMCrateConfig, read_size: usize) -> AxResult> { use std::fs::File; use std::io::Read; let file_name = &config.kernel.kernel_path; @@ -392,8 +422,7 @@ pub mod fs { x86_boot::DEFAULT_BIOS_IMAGE, bios_load_gpa, loader.vm.clone(), - ) - .expect("Failed to load built-in x86 boot image"); + )?; #[cfg(target_arch = "x86_64")] loader.load_x86_multiboot_info(x86_boot::DEFAULT_BIOS_IMAGE, bios_load_gpa)?; } @@ -406,15 +435,23 @@ pub mod fs { if let Some(dtb_arc) = get_vm_dtb_arc(&vm_config) { let _dtb_slice: &[u8] = &dtb_arc; #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] - crate::vmm::fdt::update_fdt( - core::ptr::NonNull::new(_dtb_slice.as_ptr() as *mut u8).unwrap(), - _dtb_slice.len(), - loader.vm.clone(), - &loader.config, - ); + { + let dtb_src = core::ptr::NonNull::new(_dtb_slice.as_ptr() as *mut u8) + .ok_or_else(|| ax_err_type!(InvalidData, "Guest DTB pointer is null"))?; + crate::vmm::fdt::update_fdt( + dtb_src, + _dtb_slice.len(), + loader.vm.clone(), + &loader.config, + )?; + } #[cfg(target_arch = "loongarch64")] - load_vm_image_from_memory(_dtb_slice, loader.dtb_load_gpa.unwrap(), loader.vm.clone()) - .expect("Failed to load DTB images"); + { + let dtb_load_gpa = loader + .dtb_load_gpa + .ok_or_else(|| ax_err_type!(NotFound, "DTB load address is missing"))?; + load_vm_image_from_memory(_dtb_slice, dtb_load_gpa, loader.vm.clone())?; + } } Ok(()) diff --git a/os/axvisor/src/vmm/ivc.rs b/os/axvisor/src/vmm/ivc.rs index 04edaf0d3c..278fd5b64c 100644 --- a/os/axvisor/src/vmm/ivc.rs +++ b/os/axvisor/src/vmm/ivc.rs @@ -51,10 +51,7 @@ pub fn insert_channel( /// (by setting its base GPA to None). /// If the channel is successfully unpublished, it will return the base GPA and size of the channel. /// If the channel does not exist, it will return an error. -pub fn unpublish_channel( - publisher_vm_id: usize, - key: usize, -) -> AxResult> { +pub fn unpublish_channel(publisher_vm_id: usize, key: usize) -> AxResult<(GuestPhysAddr, usize)> { let mut channels = IVC_CHANNELS.lock(); if let Some(mut channel) = channels.remove(&(publisher_vm_id, key)) { let base_gpa = channel.base_gpa_in_publisher().ok_or_else(|| { @@ -67,12 +64,13 @@ pub fn unpublish_channel( ) })?; let size = channel.size(); - if !channel.subscribers().is_empty() { - channel.base_gpa = None; // Mark the channel as removed. - // If there are still subscribers, just return None. + if channel.has_subscribers() { + channel.mark_unpublished(); // Mark the channel as removed. + // Publisher's GPA mapping will be unmapped; the shared HPA frame + // is freed when the last subscriber unsubscribes. channels.insert((publisher_vm_id, key), channel); } - Ok(Some((base_gpa, size))) + Ok((base_gpa, size)) } else { Err(ax_errno::ax_err_type!( NotFound, @@ -85,18 +83,19 @@ pub fn unpublish_channel( } pub fn get_channel_size(publisher_vm_id: usize, key: usize) -> AxResult { - let channels = IVC_CHANNELS.lock(); - if let Some(channel) = channels.get(&(publisher_vm_id, key)) { - Ok(channel.size()) - } else { - Err(ax_errno::ax_err_type!( - NotFound, - format!( - "IVC channel for publisher VM {} with key {} not found", - publisher_vm_id, key + IVC_CHANNELS + .lock() + .get(&(publisher_vm_id, key)) + .map(|channel| channel.size()) + .ok_or_else(|| { + ax_errno::ax_err_type!( + NotFound, + format!( + "IVC channel for publisher VM {} with key {} not found", + publisher_vm_id, key + ) ) - )) - } + }) } /// Subcribe to a channel of a publisher VM with the given key, @@ -108,19 +107,18 @@ pub fn subscribe_to_channel_of_publisher( subscriber_gpa: GuestPhysAddr, ) -> AxResult<(HostPhysAddr, usize)> { let mut channels = IVC_CHANNELS.lock(); - if let Some(channel) = channels.get_mut(&(publisher_vm_id, key)) { - // Add the subscriber VM ID to the channel. - channel.add_subscriber(subscriber_vm_id, subscriber_gpa); - Ok((channel.base_hpa(), channel.size())) - } else { - Err(ax_errno::ax_err_type!( + let channel = channels.get_mut(&(publisher_vm_id, key)).ok_or_else(|| { + ax_errno::ax_err_type!( NotFound, format!( "IVC channel for publisher VM [{}] key {:#x} not found", publisher_vm_id, key ) - )) - } + ) + })?; + // Add the subscriber VM ID to the channel. + channel.add_subscriber(subscriber_vm_id, subscriber_gpa); + Ok((channel.base_hpa(), channel.size())) } /// Unsubscribe from a channel of a publisher VM with the given key, @@ -156,7 +154,7 @@ pub fn unsubscribe_from_channel_of_publisher( // remove it from the global map. if channels .get(&(publisher_vm_id, key)) - .is_some_and(|c| c.subscribers().is_empty() && c.base_gpa.is_none()) + .is_some_and(|c| c.subscribers().is_empty() && c.is_unpublished()) { channels.remove(&(publisher_vm_id, key)); } @@ -176,7 +174,7 @@ pub struct IVCChannel { /// The base address of the shared memory region in guest physical address of the publisher VM. /// `None` if the channel has been unpublished (but still has subscribers). base_gpa: Option, - _phatom: core::marker::PhantomData, + _phantom: core::marker::PhantomData, } #[repr(C)] @@ -187,6 +185,10 @@ pub struct IVCChannelHeader { impl IVCChannel { #[allow(unused)] + /// # Safety + /// + /// The caller must ensure `shared_region_base` is valid, mapped, and aligned + /// for `IVCChannelHeader`, and that no mutable reference to this region exists. pub fn header(&self) -> &IVCChannelHeader { unsafe { // Map the shared region base to the header structure. @@ -194,6 +196,10 @@ impl IVCChannel { } } + /// # Safety + /// + /// The caller must ensure `shared_region_base` is valid, mapped, and aligned + /// for `IVCChannelHeader`, and that no other reference to this region exists. pub fn header_mut(&mut self) -> &mut IVCChannelHeader { unsafe { // Map the shared region base to the mutable header structure. @@ -202,6 +208,11 @@ impl IVCChannel { } #[allow(unused)] + /// # Safety + /// + /// The caller must ensure `shared_region_base` is valid, mapped, and that the + /// returned pointer is only used within the lifetime of `self` and does not alias + /// any mutable reference to the same region. pub fn data_region(&self) -> *const u8 { unsafe { // Return a pointer to the data region, which starts after the header. @@ -245,6 +256,12 @@ impl IVCChannel { base_gpa: GuestPhysAddr, ) -> AxResult { // TODO: support larger shared region sizes with alloc_frames API. + if shared_region_size > 4096 { + warn!( + "IVC channel requested size {shared_region_size:#x} > 4096; \ + truncating to 4096 (TODO: support larger sizes)" + ); + } let shared_region_size = shared_region_size.min(4096); let shared_region_base = H::alloc_frame().ok_or_else(|| { ax_errno::ax_err_type!(NoMemory, "Failed to allocate shared region frame") @@ -257,11 +274,14 @@ impl IVCChannel { shared_region_base, shared_region_size, base_gpa: Some(base_gpa), - _phatom: core::marker::PhantomData, + _phantom: core::marker::PhantomData, }; - channel.header_mut().publisher_id = publisher_vm_id as u64; - channel.header_mut().key = key as u64; + { + let header = channel.header_mut(); + header.publisher_id = publisher_vm_id as u64; + header.key = key as u64; + } debug!("Allocated IVCChannel: {channel:?}"); @@ -296,4 +316,16 @@ impl IVCChannel { .map(|(vm_id, gpa)| (*vm_id, *gpa)) .collect() } + + pub fn has_subscribers(&self) -> bool { + !self.subscriber_vms.is_empty() + } + + pub fn mark_unpublished(&mut self) { + self.base_gpa = None; + } + + pub fn is_unpublished(&self) -> bool { + self.base_gpa.is_none() + } } diff --git a/os/axvisor/src/vmm/mod.rs b/os/axvisor/src/vmm/mod.rs index 2c64ff3a3e..9cff0be1ab 100644 --- a/os/axvisor/src/vmm/mod.rs +++ b/os/axvisor/src/vmm/mod.rs @@ -85,7 +85,7 @@ pub fn start() { &VMM, || { let vm_count = RUNNING_VM_COUNT.load(Ordering::Acquire); - info!("a VM exited, current running VM count: {vm_count}"); + debug!("a VM exited, current running VM count: {vm_count}"); vm_count == 0 }, None, @@ -130,7 +130,7 @@ pub fn with_vm_and_vcpu_on_pcpu( // The target vCPU is the current task, execute the closure directly. if current_vm == vm_id && current_vcpu == vcpu_id { - with_vm_and_vcpu(vm_id, vcpu_id, f).unwrap(); // unwrap is safe here + with_vm_and_vcpu(vm_id, vcpu_id, f).ok_or_else(|| ax_err_type!(NotFound))?; return Ok(()); } @@ -140,11 +140,10 @@ pub fn with_vm_and_vcpu_on_pcpu( let _pcpu_id = vcpus::with_vcpu_task(vm_id, vcpu_id, |task| task.cpu_id()) .ok_or_else(|| ax_err_type!(NotFound))?; - unimplemented!(); - // use std::os::arceos::modules::axipi; - // Ok(ax_ipi::send_ipi_event_to_one(pcpu_id as usize, move || { - // with_vm_and_vcpu_on_pcpu(vm_id, vcpu_id, f); - // })) + ax_errno::ax_err!( + Unsupported, + "cross-CPU vCPU closure dispatch is not implemented" + ) } pub fn add_running_vm_count(count: usize) { diff --git a/os/axvisor/src/vmm/timer.rs b/os/axvisor/src/vmm/timer.rs index cef4a1f645..4067c4385c 100644 --- a/os/axvisor/src/vmm/timer.rs +++ b/os/axvisor/src/vmm/timer.rs @@ -74,9 +74,13 @@ where deadline, TimeValue::from_nanos(deadline) ); + // SAFETY: Called from a vCPU task pinned to a physical CPU. TIMER_LIST is + // initialised per-CPU in init_percpu() before any timer operation is invoked. let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; let mut timers = timer_list.lock(); - let token = TOKEN.fetch_add(1, Ordering::Release); + // The token is only an identifier used for cancellation and does not + // publish any timer data, so relaxed ordering is sufficient. + let token = TOKEN.fetch_add(1, Ordering::Relaxed); let event = VmmTimerEvent::new(token, handler); timers.set(TimeValue::from_nanos(deadline), event); token @@ -87,6 +91,8 @@ where /// # Parameters /// - `token`: The unique token of the timer to cancel. pub fn cancel_timer(token: usize) { + // SAFETY: Called from a vCPU task pinned to a physical CPU. TIMER_LIST is + // initialised per-CPU in init_percpu() before any timer operation is invoked. let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; let mut timers = timer_list.lock(); timers.cancel(|event| event.token == token); @@ -96,6 +102,8 @@ pub fn cancel_timer(token: usize) { pub fn check_events() { // info!("Checking timer events..."); // info!("now is {:#?}", ax_hal::time::wall_time()); + // SAFETY: Called from a vCPU task pinned to a physical CPU. TIMER_LIST is + // initialised per-CPU in init_percpu() before any timer operation is invoked. let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; loop { let now = ax_hal::time::wall_time(); @@ -121,6 +129,8 @@ pub fn check_events() { /// Initialize the hypervisor timer system pub fn init_percpu() { info!("Initing HV Timer..."); + // SAFETY: Called once per CPU during hypervisor initialisation, before + // any vCPU task starts. No other code accesses this CPU's TIMER_LIST yet. let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; timer_list.init_once(SpinNoIrq::new(TimerList::new())); } diff --git a/os/axvisor/src/vmm/vcpus.rs b/os/axvisor/src/vmm/vcpus.rs index 652320623b..6ef987f27a 100644 --- a/os/axvisor/src/vmm/vcpus.rs +++ b/os/axvisor/src/vmm/vcpus.rs @@ -12,22 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; use ax_cpumask::CpuMask; -use core::{ - cell::UnsafeCell, - sync::atomic::{AtomicUsize, Ordering}, - time::Duration, -}; +use ax_kspin::SpinNoIrq as Mutex; +use core::sync::atomic::{AtomicUsize, Ordering}; use std::os::arceos::{ api::task::{AxCpuMask, ax_wait_queue_wake}, modules::{ - ax_hal::{self, time::busy_wait}, + ax_hal, ax_task::{self, AxTaskExt}, }, }; +use ax_errno::{AxResult, ax_err_type}; use ax_task::{AxTaskRef, TaskInner, WaitQueue}; use axaddrspace::GuestPhysAddr; use axvcpu::{AxVCpuExitReason, VCpuState}; @@ -40,56 +38,11 @@ use crate::{ const KERNEL_STACK_SIZE: usize = 0x40000; // 256 KiB -/// A global static BTreeMap that holds the wait queues for VCpus -/// associated with their respective VMs, identified by their VM IDs. -/// -/// TODO: find a better data structure to replace the `static mut`, something like a conditional -/// variable. -static VM_VCPU_TASK_WAIT_QUEUE: Queue = Queue::new(); - -/// A thread-safe queue that manages wait queues for VCpus across multiple VMs. -/// -/// This structure wraps a BTreeMap that maps VM IDs to their corresponding VMVCpus structures. -/// It provides thread-safe access to the mapping through interior mutability using UnsafeCell. -/// Each VM is identified by its unique ID, and the queue manages the VCpu tasks and wait -/// operations for all VMs in the system. -struct Queue(UnsafeCell>); - -unsafe impl Sync for Queue {} -unsafe impl Send for Queue {} +/// A global map that holds the vCPU task state for each VM. +static VM_VCPU_TASKS: Mutex>> = Mutex::new(BTreeMap::new()); -impl Queue { - /// Creates a new empty Queue. - /// - /// # Returns - /// - /// A new Queue instance with an empty BTreeMap. - const fn new() -> Self { - Self(UnsafeCell::new(BTreeMap::new())) - } - - /// Retrieves a reference to the VMVCpus for the specified VM ID. - fn get(&self, vm_id: &usize) -> Option<&VMVCpus> { - unsafe { (*self.0.get()).get(vm_id) } - } - - /// Retrieves a mutable reference to the VMVCpus for the specified VM ID. - #[allow(clippy::mut_from_ref)] - fn get_mut(&self, vm_id: &usize) -> Option<&mut VMVCpus> { - unsafe { (*self.0.get()).get_mut(vm_id) } - } - - /// Inserts a new VMVCpus entry for the specified VM ID. - fn insert(&self, vm_id: usize, vcpus: VMVCpus) { - unsafe { - (*self.0.get()).insert(vm_id, vcpus); - } - } - - /// Removes the VMVCpus entry for the specified VM ID. - fn remove(&self, vm_id: &usize) -> Option { - unsafe { (*self.0.get()).remove(vm_id) } - } +fn get_vm_vcpus(vm_id: usize) -> Option> { + VM_VCPU_TASKS.lock().get(&vm_id).cloned() } /// A structure representing the VCpus of a specific VM, including a wait queue @@ -99,8 +52,8 @@ pub struct VMVCpus { _vm_id: usize, // A wait queue to manage task scheduling for the VCpus. wait_queue: WaitQueue, - // A list of tasks associated with the VCpus of this VM. - vcpu_task_list: Vec, + // A map of tasks associated with the VCpus of this VM, keyed by vCPU ID. + vcpu_task_list: Mutex>, /// The number of currently running or halting VCpus. Used to track when the VM is fully /// shutdown. /// @@ -123,7 +76,7 @@ impl VMVCpus { Self { _vm_id: vm.id(), wait_queue: WaitQueue::new(), - vcpu_task_list: Vec::with_capacity(vm.vcpu_num()), + vcpu_task_list: Mutex::new(BTreeMap::new()), running_halting_vcpu_count: AtomicUsize::new(0), } } @@ -133,11 +86,8 @@ impl VMVCpus { /// # Arguments /// /// * `vcpu_task` - A reference to the task associated with a VCpu that is to be added. - fn add_vcpu_task(&mut self, vcpu_task: AxTaskRef) { - // It may be dangerous to go lock-free here, as two VCpus may invoke `CpuUp` at the same - // time. However, in most scenarios, only the bsp will `CpuUp` other VCpus, making this - // operation single-threaded. Therefore, we just tolerate this as for now. - self.vcpu_task_list.push(vcpu_task); + fn add_vcpu_task(&self, vcpu_id: usize, vcpu_task: AxTaskRef) { + self.vcpu_task_list.lock().insert(vcpu_id, vcpu_task); } /// Blocks the current thread on the wait queue associated with the VCpus of this VM. @@ -155,7 +105,7 @@ impl VMVCpus { } #[allow(dead_code)] - fn notify_one(&mut self) { + fn notify_one(&self) { // FIXME: `WaitQueue::len` is removed // info!("Current wait queue length: {}", self.wait_queue.len()); self.wait_queue.notify_one(false); @@ -163,7 +113,7 @@ impl VMVCpus { /// Notify all waiting vCPU threads to wake up. /// This is useful when shutting down a VM to ensure all vCPUs can check the shutdown flag. - fn notify_all(&mut self) { + fn notify_all(&self) { self.wait_queue.notify_all(false); } @@ -178,9 +128,11 @@ impl VMVCpus { /// Decrements the count of running or halting VCpus by one. Returns true if this was the last /// VCpu to exit. fn mark_vcpu_exiting(&self) -> bool { - self.running_halting_vcpu_count - .fetch_sub(1, Ordering::Relaxed) - == 1 + self.running_halting_vcpu_count.fetch_update( + Ordering::Relaxed, + Ordering::Relaxed, + |count| count.checked_sub(1), + ) == Ok(1) // Relaxed is enough here, as we only need to ensure that the count is incremented and // decremented correctly, and there is no other data synchronization needed. } @@ -194,7 +146,11 @@ impl VMVCpus { /// * `vm_id` - The ID of the VM whose VCpu wait queue is used to block the current thread. /// fn wait(vm_id: usize) { - VM_VCPU_TASK_WAIT_QUEUE.get(&vm_id).unwrap().wait() + if let Some(vm_vcpus) = get_vm_vcpus(vm_id) { + vm_vcpus.wait(); + } else { + warn!("VM[{vm_id}] vCPU wait queue not found"); + } } /// Blocks the current thread until the provided condition is met, using the wait queue @@ -209,10 +165,11 @@ fn wait_for(vm_id: usize, condition: F) where F: Fn() -> bool, { - VM_VCPU_TASK_WAIT_QUEUE - .get(&vm_id) - .unwrap() - .wait_until(condition) + if let Some(vm_vcpus) = get_vm_vcpus(vm_id) { + vm_vcpus.wait_until(condition); + } else { + warn!("VM[{vm_id}] vCPU wait queue not found"); + } } /// Notifies the primary VCpu task associated with the specified VM to wake up and resume execution. @@ -224,10 +181,11 @@ where /// pub(crate) fn notify_primary_vcpu(vm_id: usize) { // Generally, the primary VCpu is the first and **only** VCpu in the list. - VM_VCPU_TASK_WAIT_QUEUE - .get_mut(&vm_id) - .unwrap() - .notify_one() + if let Some(vm_vcpus) = get_vm_vcpus(vm_id) { + vm_vcpus.notify_one(); + } else { + warn!("VM[{vm_id}] vCPU resources not found"); + } } /// Notifies all VCpu tasks associated with the specified VM to wake up. @@ -238,7 +196,7 @@ pub(crate) fn notify_primary_vcpu(vm_id: usize) { /// * `vm_id` - The ID of the VM whose VCpus should be notified. /// pub(crate) fn notify_all_vcpus(vm_id: usize) { - if let Some(vm_vcpus) = VM_VCPU_TASK_WAIT_QUEUE.get_mut(&vm_id) { + if let Some(vm_vcpus) = get_vm_vcpus(vm_id) { vm_vcpus.notify_all(); } } @@ -255,13 +213,16 @@ pub(crate) fn notify_all_vcpus(vm_id: usize) { /// This should be called after all VCpu threads have exited to avoid resource leaks. /// It will join all VCpu tasks to ensure they are fully cleaned up. pub(crate) fn cleanup_vm_vcpus(vm_id: usize) { - if let Some(vm_vcpus) = VM_VCPU_TASK_WAIT_QUEUE.remove(&vm_id) { - let task_count = vm_vcpus.vcpu_task_list.len(); + if let Some(vm_vcpus) = VM_VCPU_TASKS.lock().remove(&vm_id) { + // Take task references out before joining so we never block while + // holding the per-VM task-list lock. + let tasks: Vec<_> = vm_vcpus.vcpu_task_list.lock().values().cloned().collect(); + let task_count = tasks.len(); info!("VM[{}] Joining {} VCpu tasks...", vm_id, task_count); // Join all VCpu tasks to ensure they have fully exited and cleaned up - for (idx, task) in vm_vcpus.vcpu_task_list.iter().enumerate() { + for (idx, task) in tasks.iter().enumerate() { debug!( "VM[{}] Joining VCpu task[{}]: {}", vm_id, @@ -286,19 +247,15 @@ pub(crate) fn cleanup_vm_vcpus(vm_id: usize) { /// Marks the VCpu of the specified VM as running. fn mark_vcpu_running(vm_id: usize) { - VM_VCPU_TASK_WAIT_QUEUE - .get(&vm_id) - .unwrap() - .mark_vcpu_running(); + if let Some(vm_vcpus) = get_vm_vcpus(vm_id) { + vm_vcpus.mark_vcpu_running(); + } } /// Marks the VCpu of the specified VM as exiting for VM shutdown. Returns true if this was the last /// VCpu to exit. fn mark_vcpu_exiting(vm_id: usize) -> bool { - VM_VCPU_TASK_WAIT_QUEUE - .get(&vm_id) - .unwrap() - .mark_vcpu_exiting() + get_vm_vcpus(vm_id).is_some_and(|vm_vcpus| vm_vcpus.mark_vcpu_exiting()) } /// Boot target VCpu on the specified VM. @@ -311,18 +268,20 @@ fn mark_vcpu_exiting(vm_id: usize) -> bool { /// * `entry_point` - The entry point of the VCpu. /// * `arg` - The argument to be passed to the VCpu. /// -fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) { - let vcpu = vm.vcpu_list()[vcpu_id].clone(); - assert_eq!( - vcpu.state(), - VCpuState::Free, - "vcpu_on: {} invalid vcpu state {:?}", - vcpu.id(), - vcpu.state() - ); +fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) -> AxResult { + let vcpu = vm + .vcpu_list() + .get(vcpu_id) + .cloned() + .ok_or_else(|| ax_err_type!(NotFound, format!("vCPU {vcpu_id} not found")))?; + if vcpu.state() != VCpuState::Free { + return Err(ax_err_type!( + BadState, + format!("vCPU {} invalid state {:?}", vcpu.id(), vcpu.state()) + )); + } - vcpu.set_entry(entry_point) - .expect("vcpu_on: set_entry failed"); + vcpu.set_entry(entry_point)?; #[cfg(not(target_arch = "riscv64"))] vcpu.set_gpr(0, arg); @@ -338,10 +297,14 @@ fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) { let vcpu_task = alloc_vcpu_task(&vm, vcpu); - VM_VCPU_TASK_WAIT_QUEUE - .get_mut(&vm.id()) - .unwrap() - .add_vcpu_task(vcpu_task); + let vm_vcpus = get_vm_vcpus(vm.id()).ok_or_else(|| { + ax_err_type!( + NotFound, + format!("VM[{}] vCPU resources not found", vm.id()) + ) + })?; + vm_vcpus.add_vcpu_task(vcpu_id, vcpu_task); + Ok(()) } /// Sets up the primary VCpu for the given VM, @@ -355,15 +318,22 @@ fn vcpu_on(vm: VMRef, vcpu_id: usize, entry_point: GuestPhysAddr, arg: usize) { pub fn setup_vm_primary_vcpu(vm: VMRef) { info!("Initializing VM[{}]'s {} vcpus", vm.id(), vm.vcpu_num()); let vm_id = vm.id(); - let mut vm_vcpus = VMVCpus::new(vm.clone()); + if get_vm_vcpus(vm_id).is_some() { + debug!("VM[{vm_id}] vCPU resources already exist"); + return; + } + let vm_vcpus = Arc::new(VMVCpus::new(vm.clone())); let primary_vcpu_id = 0; - let primary_vcpu = vm.vcpu_list()[primary_vcpu_id].clone(); + let Some(primary_vcpu) = vm.vcpu_list().get(primary_vcpu_id).cloned() else { + warn!("VM[{vm_id}] has no primary vCPU"); + return; + }; let primary_vcpu_task = alloc_vcpu_task(&vm, primary_vcpu); - vm_vcpus.add_vcpu_task(primary_vcpu_task); + vm_vcpus.add_vcpu_task(0, primary_vcpu_task); - VM_VCPU_TASK_WAIT_QUEUE.insert(vm_id, vm_vcpus); + VM_VCPU_TASKS.lock().insert(vm_id, vm_vcpus); } /// Finds the [`AxTaskRef`] associated with the specified vCPU of the specified VM. @@ -376,11 +346,10 @@ pub fn with_vcpu_task T>( vcpu_id: usize, f: F, ) -> Option { - VM_VCPU_TASK_WAIT_QUEUE - .get(&vm_id) - .unwrap() + get_vm_vcpus(vm_id)? .vcpu_task_list - .get(vcpu_id) + .lock() + .get(&vcpu_id) .map(f) } @@ -438,11 +407,6 @@ fn vcpu_run() { let vm_id = vm.id(); let vcpu_id = vcpu.id(); - // boot delay - let boot_delay_sec = (vm_id - 1) * 5; - info!("VM[{vm_id}] boot delay: {boot_delay_sec}s"); - busy_wait(Duration::from_secs(boot_delay_sec as _)); - info!("VM[{}] VCpu[{}] waiting for running", vm.id(), vcpu.id()); wait_for(vm_id, || vm.running()); @@ -456,7 +420,7 @@ fn vcpu_run() { debug!("Hypercall [{nr}] args {args:x?}"); use crate::vmm::hvc::HyperCall; - match HyperCall::new(vcpu.clone(), vm.clone(), nr, args) { + match HyperCall::new(vm.clone(), nr, args) { Ok(hypercall) => { let ret_val = match hypercall.execute() { Ok(ret_val) => ret_val as isize, @@ -512,28 +476,34 @@ fn vcpu_run() { let vcpu_mappings = vm.get_vcpu_affinities_pcpu_ids(); // Find the vCPU ID corresponding to the physical ID - let target_vcpu_id = vcpu_mappings - .iter() - .find_map(|(vcpu_id, _, phys_id)| { - if *phys_id == target_cpu as usize { - Some(*vcpu_id) - } else { - None - } + let Some(target_vcpu_id) = + vcpu_mappings.iter().find_map(|(vcpu_id, _, phys_id)| { + (*phys_id == target_cpu as usize).then_some(*vcpu_id) }) - .unwrap_or_else(|| { - panic!("Physical CPU ID {target_cpu} not found in VM configuration",) - }); - - vcpu_on(vm.clone(), target_vcpu_id, entry_point, arg as _); - #[cfg(not(target_arch = "riscv64"))] - vcpu.set_gpr(0, 0); - #[cfg(target_arch = "riscv64")] - vcpu.set_gpr(riscv_vcpu::GprIndex::A0 as usize, 0); + else { + warn!("Physical CPU ID {target_cpu} not found in VM configuration"); + vcpu.set_return_value(usize::MAX); + continue; + }; + + match vcpu_on(vm.clone(), target_vcpu_id, entry_point, arg as _) { + Ok(()) => { + #[cfg(not(target_arch = "riscv64"))] + vcpu.set_gpr(0, 0); + #[cfg(target_arch = "riscv64")] + vcpu.set_gpr(riscv_vcpu::GprIndex::A0 as usize, 0); + } + Err(err) => { + warn!("Failed to boot VM[{vm_id}] VCpu[{target_vcpu_id}]: {err:?}"); + vcpu.set_return_value(usize::MAX); + } + } } AxVCpuExitReason::SystemDown => { warn!("VM[{vm_id}] run VCpu[{vcpu_id}] SystemDown"); - vm.shutdown().expect("VM shutdown failed"); + if let Err(err) = vm.shutdown() { + warn!("VM[{vm_id}] shutdown failed: {err:?}"); + } // Notify all vCPUs to wake up to check the shutdown flag notify_all_vcpus(vm_id); } @@ -548,17 +518,18 @@ fn vcpu_run() { "VM[{vm_id}] run VCpu[{vcpu_id}] SendIPI, target_cpu={target_cpu:#x}, target_cpu_aux={target_cpu_aux:#x}, vector={vector}", ); if send_to_all { - unimplemented!("Send IPI to all CPUs is not implemented yet"); + warn!("Send IPI to all CPUs is not implemented yet"); + continue; } if target_cpu == vcpu_id as u64 || send_to_self { inject_interrupt(vector as _); - } else { - vm.inject_interrupt_to_vcpu( - CpuMask::one_shot(target_cpu as _), - vector as _, - ) - .unwrap(); + } else if let Err(err) = + vm.inject_interrupt_to_vcpu(CpuMask::one_shot(target_cpu as _), vector as _) + { + warn!( + "Failed to inject interrupt {vector} to VM[{vm_id}] CPU {target_cpu}: {err:?}" + ); } } e => { @@ -567,8 +538,9 @@ fn vcpu_run() { }, Err(err) => { error!("VM[{vm_id}] run VCpu[{vcpu_id}] get error {err:?}"); - // wait(vm_id) - vm.shutdown().expect("VM shutdown failed"); + if let Err(err) = vm.shutdown() { + warn!("VM[{vm_id}] shutdown failed after vCPU error: {err:?}"); + } // Notify all vCPUs to wake up to check the shutdown flag notify_all_vcpus(vm_id); } diff --git a/os/axvisor/src/vmm/vm_list.rs b/os/axvisor/src/vmm/vm_list.rs index 63811b7f23..b1ad9e0b21 100644 --- a/os/axvisor/src/vmm/vm_list.rs +++ b/os/axvisor/src/vmm/vm_list.rs @@ -19,109 +19,34 @@ use ax_kspin::SpinNoIrq as Mutex; use crate::vmm::VMRef; -/// Represents a list of VMs, -/// stored in a BTreeMap where the key is the VM ID and the value is a reference to the VM. -struct VMList { - vm_list: BTreeMap, -} - -impl VMList { - /// Creates a new, empty `VMList`. - const fn new() -> VMList { - VMList { - vm_list: BTreeMap::new(), - } - } - - /// Adds a new VM to the list. - /// - /// If a VM with the given ID already exists, a warning is logged, and the VM is not added. - /// - /// # Arguments - /// - /// * `vm_id` - The unique identifier for the VM. - /// * `vm` - A reference to the VM that will be added. - fn push_vm(&mut self, vm_id: usize, vm: VMRef) { - if self.vm_list.contains_key(&vm_id) { - warn!("VM[{vm_id}] already exists, push VM failed, just return ..."); - return; - } - self.vm_list.insert(vm_id, vm); - } - - /// Removes a VM from the list by its ID. - /// - /// # Arguments - /// - /// * `vm_id` - The unique identifier of the VM to be removed. - /// - /// # Returns - /// - /// Returns `Some(VMRef)` if the VM was successfully removed, or `None` if the VM with the given ID did not exist. - #[allow(unused)] - fn remove_vm(&mut self, vm_id: usize) -> Option { - self.vm_list.remove(&vm_id) - } - - /// Retrieves a VM from the list by its ID. - /// - /// # Arguments - /// - /// * `vm_id` - The unique identifier of the VM to be retrieved. - /// - /// # Returns - /// - /// Returns `Some(VMRef)` if the VM exists, or `None` if the VM with the given ID does not exist. - fn get_vm_by_id(&self, vm_id: usize) -> Option { - self.vm_list.get(&vm_id).cloned() - } -} - -// A global list of VMs, protected by a mutex for thread-safe access. -static GLOBAL_VM_LIST: Mutex = Mutex::new(VMList::new()); +// A global map of VMs, indexed by VM ID, protected by a mutex for thread-safe access. +static GLOBAL_VM_LIST: Mutex> = Mutex::new(BTreeMap::new()); /// Adds a VM to the global VM list. /// -/// # Arguments -/// -/// * `vm` - A reference to the VM instance. +/// If a VM with the same ID already exists, a warning is logged and the VM is not added. pub fn push_vm(vm: VMRef) { - GLOBAL_VM_LIST.lock().push_vm(vm.id(), vm) + let vm_id = vm.id(); + let mut list = GLOBAL_VM_LIST.lock(); + if list.contains_key(&vm_id) { + warn!("VM[{vm_id}] already exists, push VM failed, just return ..."); + return; + } + list.insert(vm_id, vm); } /// Removes a VM from the global VM list by its ID. -/// -/// # Arguments -/// -/// * `vm_id` - The unique identifier of the VM to be removed. -/// -/// # Returns -/// -/// * `Option` - The removed VM reference if it exists, or `None` if not. #[allow(unused)] pub fn remove_vm(vm_id: usize) -> Option { - GLOBAL_VM_LIST.lock().remove_vm(vm_id) + GLOBAL_VM_LIST.lock().remove(&vm_id) } /// Retrieves a VM from the global VM list by its ID. -/// -/// # Arguments -/// -/// * `vm_id` - The unique identifier of the VM to retrieve. -/// -/// # Returns -/// -/// * `Option` - The VM reference if it exists, or `None` if not. #[allow(unused)] pub fn get_vm_by_id(vm_id: usize) -> Option { - GLOBAL_VM_LIST.lock().get_vm_by_id(vm_id) + GLOBAL_VM_LIST.lock().get(&vm_id).cloned() } pub fn get_vm_list() -> Vec { - let global_vm_list = GLOBAL_VM_LIST.lock().vm_list.clone(); - let mut vm_list = Vec::with_capacity(global_vm_list.len()); - for (_id, vm) in global_vm_list { - vm_list.push(vm.clone()); - } - vm_list + GLOBAL_VM_LIST.lock().values().cloned().collect() } diff --git a/os/axvisor/xtask/src/main.rs b/os/axvisor/xtask/src/main.rs index 886714b998..294fab4510 100644 --- a/os/axvisor/xtask/src/main.rs +++ b/os/axvisor/xtask/src/main.rs @@ -23,6 +23,12 @@ #[cfg(not(any(windows, unix)))] mod lang; +#[cfg(any(windows, unix))] +use std::{ + ffi::OsString, + path::{Path, PathBuf}, +}; + #[cfg(any(windows, unix))] #[derive(clap::Parser)] struct Cli { @@ -35,9 +41,121 @@ struct Cli { async fn main() -> anyhow::Result<()> { use clap::Parser; - let cli = Cli::parse(); + let mut cli = Cli::parse_from(normalize_legacy_args(std::env::args_os())); + let invocation_dir = std::env::current_dir()?; + let workspace_root = workspace_root(); + normalize_command_paths(&mut cli.command, &invocation_dir, &workspace_root); + std::env::set_current_dir(&workspace_root)?; axbuild::axvisor::Axvisor::new()? .execute(cli.command) .await?; Ok(()) } + +#[cfg(any(windows, unix))] +fn normalize_legacy_args(args: impl IntoIterator) -> Vec { + args.into_iter() + .map(|arg| match arg.to_str() { + Some("--build-config") => OsString::from("--config"), + Some(value) if value.starts_with("--build-config=") => { + OsString::from(value.replacen("--build-config=", "--config=", 1)) + } + _ => arg, + }) + .collect() +} + +#[cfg(any(windows, unix))] +fn workspace_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .map(Path::to_path_buf) + .expect("failed to locate workspace root from os/axvisor") +} + +#[cfg(any(windows, unix))] +fn normalize_command_paths( + command: &mut axbuild::axvisor::Command, + invocation_dir: &Path, + workspace_root: &Path, +) { + use axbuild::axvisor::{Command, TestCommand, image}; + + match command { + Command::Build(args) => normalize_build_paths(args, invocation_dir, workspace_root), + Command::Qemu(args) => { + normalize_build_paths(&mut args.build, invocation_dir, workspace_root); + normalize_existing_path(&mut args.qemu_config, invocation_dir, workspace_root); + normalize_existing_path(&mut args.rootfs, invocation_dir, workspace_root); + } + Command::Board(args) => { + normalize_build_paths(&mut args.build, invocation_dir, workspace_root); + normalize_existing_path(&mut args.board_config, invocation_dir, workspace_root); + } + Command::Uboot(args) => { + normalize_build_paths(&mut args.build, invocation_dir, workspace_root); + normalize_existing_path(&mut args.uboot_config, invocation_dir, workspace_root); + } + Command::Image(args) => { + normalize_output_path(&mut args.overrides.local_storage, invocation_dir); + if let image::Command::Pull(args) = &mut args.command { + normalize_output_path(&mut args.output_dir, invocation_dir); + } + } + Command::Test(args) => match &mut args.command { + TestCommand::Uboot(args) => { + normalize_existing_path(&mut args.uboot_config, invocation_dir, workspace_root); + } + TestCommand::Qemu(_) | TestCommand::Board(_) => {} + }, + Command::Defconfig(_) | Command::Config(_) => {} + } +} + +#[cfg(any(windows, unix))] +fn normalize_build_paths( + args: &mut axbuild::axvisor::ArgsBuild, + invocation_dir: &Path, + workspace_root: &Path, +) { + normalize_existing_path(&mut args.config, invocation_dir, workspace_root); + for path in &mut args.vmconfigs { + *path = resolve_existing_path(path, invocation_dir, workspace_root); + } +} + +#[cfg(any(windows, unix))] +fn normalize_existing_path( + path: &mut Option, + invocation_dir: &Path, + workspace_root: &Path, +) { + if let Some(path) = path { + *path = resolve_existing_path(path, invocation_dir, workspace_root); + } +} + +#[cfg(any(windows, unix))] +fn resolve_existing_path(path: &Path, invocation_dir: &Path, workspace_root: &Path) -> PathBuf { + if path.is_absolute() { + return path.to_path_buf(); + } + + let cwd_path = invocation_dir.join(path); + let workspace_path = workspace_root.join(path); + if workspace_path.exists() && !cwd_path.exists() { + workspace_path + } else { + cwd_path + } +} + +#[cfg(any(windows, unix))] +fn normalize_output_path(path: &mut Option, invocation_dir: &Path) { + if let Some(path) = path + && path.is_relative() + { + *path = invocation_dir.join(&path); + } +} From fe4e4a59c008de8d9661a964df7beecabea26368 Mon Sep 17 00:00:00 2001 From: CN-TangLin <2242120212@qq.com> Date: Wed, 27 May 2026 10:38:24 +0800 Subject: [PATCH 49/71] feat(starry-kernel): add eBPF subsystem (maps, VM, helpers, perf events) (#848) * feat(starry-kernel): add eBPF subsystem (maps, prog, VM, helpers, perf events) - ebpf.rs: complete eBPF infrastructure - BpfInsn: full instruction encoding/decoding - BpfMapOps trait + ArrayMap + HashMap implementations - UnifiedMap: trait-object polymorphic map dispatch - BpfFdTable: fd-based map/prog management - BpfVm: instruction interpreter (ALU/JMP/ST/LD, 512B stack, 1M insn limit) - 11 helper functions (map ops, probe_read, ktime, smp, pid, perf_output) - BPF_MAP_CREATE/LOOKUP/UPDATE/DELETE/GET_NEXT_KEY/PROG_LOAD syscalls - BPF_PROG_ATTACH/BPF_LINK_CREATE for kprobe/tracepoint attach - perf_event.rs: perf event ring buffer infrastructure - RingBuffer with wrap-around write + perf_event_mmap_page header - PERF_RECORD_SAMPLE and PERF_RECORD_LOST record types - perf_event_open syscall implementation - perf_event_write/enable/disable/attach_prog public APIs - test-ebpf-basics: 9 test modules, 20+ checks * fix(starry-kernel): address eBPF PR review blocking issues 1. Fix test install path: usr/bin -> usr/bin/starry-test-suit (tests were silently skipped by qemu toml wildcard) 2. Add BPF_EXIT instruction handling in BpfVm::execute: opcode 0x90 now returns Ok(regs[0]) instead of falling through 3. Fix pointer truncation in handle_prog_load: read u64 fields correctly using *const u64 instead of *const u32 (was losing high 32 bits on 64-bit architectures) 4. Add BPF_CALL instruction handling (opcode 0x85): looks up helper by insn.imm, calls with regs[1..5] as args, stores result in regs[0] * fix(starry-kernel): correct bpf_attr field offsets in handle_prog_load Previous fix changed *const u32 to *const u64 which was wrong because Linux bpf_attr is a mixed u32/u64 layout. Use byte-offset helpers: read_u32(0) = prog_type (+0, u32) read_u32(4) = insn_cnt (+4, u32) read_u64(8) = insns (+8, u64 ptr) read_u64(16) = license (+16, u64 ptr) read_u32(24) = log_level (+24, u32) read_u32(28) = log_size (+28, u32) read_u64(32) = log_buf (+32, u64 ptr) read_u32(40) = kern_version (+40, u32) read_u32(44) = prog_flags (+44, u32) Verified: clippy 11 features, loongarch64 + x86_64 build. * fix(starry-kernel): improve eBPF helper safety per review suggestions 1. helper_map_lookup_elem: return pointer to cached value instead of map fd. Uses BPF_LOOKUP_CACHE global Vec to store the lookup result and returns its raw pointer, matching Linux bpf_map_lookup_elem semantics where BPF programs can directly read the value. 2. helper_probe_read: add safety improvements - Size limit: reject reads > 4096 bytes - User/kernel address detection: for addresses below 0x8000_0000_0000 (likely userspace), try vm_read_slice first; fall back to direct copy for kernel addresses - Prevents arbitrary kernel memory reads from unvalidated addresses 3. Add SAFETY comment for BPF_LOOKUP_CACHE explaining pointer lifetime Verified: clippy 11 features, x86_64/loongarch64 build. * feat(starry-kernel): add BPF/perf_event fd close and resource release BpfFdTable: - remove_map(fd): remove and drop a BPF map by fd - remove_prog(fd): remove and drop a BPF prog by fd - close_fd(fd): close either map or prog fd - fd_exists(fd): check if fd is a valid BPF fd - get_prog(fd): get mutable reference to BpfProg by fd Perf events: - perf_event_close(fd): remove and drop a perf event entry by fd - perf_event_fd_exists(fd): check if fd is a valid perf event fd Syscall routing: - Add BPF_OBJ_CLOSE (cmd=11) handler in sys_bpf - handle_obj_close reads fd from uattr and calls close_fd Public API: - bpf_close_fd(fd) / bpf_fd_exists(fd) for external callers - perf_event_close(fd) / perf_event_fd_exists(fd) for external callers Verified: clippy 11 features, x86_64/riscv64/aarch64/loongarch64 build. * fix(starry-kernel): improve eBPF handle_link_create and helper_get_current_uid_gid 1. handle_link_create: return newly allocated link fd instead of target_fd, matching Linux BPF_LINK_CREATE semantics 2. helper_get_current_uid_gid: read actual uid/gid from current thread credentials via AsThread trait instead of hardcoding 0 Returns (gid << 32) | uid as per Linux eBPF convention Verified: clippy 12 features, x86_64/loongarch64 build. * fix(starry-kernel): remove hardcoded user/kernel address boundary in helper_probe_read Instead of using a hardcoded 0x8000_0000_0000 boundary (x86_64-specific), always try vm_read_slice first for safe access, falling back to direct copy on failure. This works correctly on all architectures without platform-specific constants. Verified: clippy 12 features passed. * fix(starry-kernel): add fd reuse via free list in BpfFdTable Previously, BpfFdTable used a simple counter that never reused freed fd numbers, potentially exhausting fd space over long runs. Now uses a free_fds Vec as a LIFO free list: - alloc_fd() pops from free_fds if available, otherwise increments counter - close_fd() pushes the released fd back to free_fds - Verified: clippy 12 features passed * docs(starry-kernel): add module-level documentation for eBPF and perf_event the subsystem components, syscall interface, and public API. * fix(starry-kernel): address eBPF PR review non-blocking feedback 1. BpfFdTable: add links field to track link_fd -> (prog_fd, target_fd) mapping, enabling proper link lifecycle management 2. handle_link_create: record link in BpfFdTable.links so the returned fd is tracked and can be closed via OBJ_CLOSE 3. close_fd: handle links cleanup alongside maps and progs 4. handle_prog_attach: rename prog_fd to attach_prog_fd to match Linux bpf_attr PROG_ATTACH semantics (attach_prog_fd at offset 0) * fix(starry-kernel): correct bpf_attr field read order in handle_link_create and handle_prog_attach * fix(starry-kernel): improve eBPF code quality and close(2) integration - Fix BPF_ALU 32-bit truncation: all ALU ops now apply correct mask when is_64=false, not just RSH/ARSH - Define BPF_CALL_OP named constant for magic number 0x80 - Fix alloc_perf_fd TOCTOU: fd allocation and Vec push now atomic under same lock via new PerfFdTable with fd reuse support - Unify fd namespace: perf_event fd now starts from 3 (was 100) - Integrate perf_event_close/bpf_close_fd into close(2) syscall path and close_all_fds for process exit cleanup - Convert BPF_LOOKUP_CACHE from global SpinNoIrq to per-CPU via ax_percpu::def_percpu for SMP safety - Add perf event trigger mechanism: run_bpf_prog is called when perf events fire, enabling end-to-end BPF program execution - Wire kprobe breakpoint/debug handlers to trigger KPROBE perf events * fix(starry-kernel): correct BPF source operand check in ALU and JMP interpreters Use opcode bit check (insn.code & BPF_X) instead of incorrectly comparing register number (insn.src_reg()) with the BPF_X flag value. The old code caused all register-source ALU/JMP instructions to incorrectly use the immediate field instead of the source register. * fix(starry-kernel): correct JMP32 JA unconditional jump check in eBPF interpreter Use explicit opcode comparison (BPF_JMP32 | BPF_JA) instead of checking src_reg() == 0, which incorrectly matches any BPF_K conditional jump instruction (JEQ32, JGT32, etc.) as an unconditional jump. * fix(starry-kernel): add ebpf feature gate for conditional compilation - Cargo.toml: add "ebpf" feature (default enabled) - lib.rs: gate mod ebpf and mod perf_event behind #[cfg(feature = "ebpf")] - syscall/mod.rs: route bpf/perf_event_open to dummy_fd when ebpf disabled - file/mod.rs: gate perf_event_close/bpf_close_fd behind #[cfg(feature = "ebpf")] - kprobe.rs: gate perf_event_trigger_by_type behind #[cfg(feature = "ebpf")] - Verified: clippy 13 features all pass * feat(starry-kernel): add eBPF verifier, PERF_EVENT_ARRAY map, more helpers and prog type validation - Add basic eBPF verifier: instruction count limit (1M), BPF_EXIT existence check, PC jump boundary validation - Add PERF_EVENT_ARRAY map type with full BpfMapOps implementation - Register 3 new helpers: trace_printk (id=6), get_current_task (id=35), get_current_comm (id=16) - Add prog_type whitelist validation in handle_prog_load - Verified: clippy 13/13 features pass * fix(starry-kernel): rebase onto dev with kprobe merged, resolve conflicts --- os/StarryOS/kernel/Cargo.toml | 3 +- os/StarryOS/kernel/src/ebpf.rs | 1503 +++++++++++++++++ os/StarryOS/kernel/src/file/mod.rs | 24 +- os/StarryOS/kernel/src/kprobe.rs | 10 + os/StarryOS/kernel/src/lib.rs | 4 + os/StarryOS/kernel/src/perf_event.rs | 508 ++++++ os/StarryOS/kernel/src/syscall/mod.rs | 15 +- .../syscall/test-ebpf-basics/c/CMakeLists.txt | 9 + .../syscall/test-ebpf-basics/c/src/main.c | 405 +++++ 9 files changed, 2472 insertions(+), 9 deletions(-) create mode 100644 os/StarryOS/kernel/src/ebpf.rs create mode 100644 os/StarryOS/kernel/src/perf_event.rs create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/src/main.c diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 54289cd536..9efe2b1e5a 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -14,10 +14,11 @@ repository.workspace = true license.workspace = true [features] -default = ["dynamic_debug"] +default = ["dynamic_debug", "ebpf"] dev-log = [] ext4 = ["ax-fs/ext4"] input = ["dep:ax-input", "ax-feat/input"] +ebpf = [] memtrack = ["ax-feat/dwarf", "ax-alloc/tracking", "dep:gimli"] rknpu = ["dep:ax-driver", "ax-driver/rknpu"] plat-dyn = [ diff --git a/os/StarryOS/kernel/src/ebpf.rs b/os/StarryOS/kernel/src/ebpf.rs new file mode 100644 index 0000000000..99b3a1e0d9 --- /dev/null +++ b/os/StarryOS/kernel/src/ebpf.rs @@ -0,0 +1,1503 @@ +//! eBPF (Extended Berkeley Packet Filter) subsystem for StarryOS. +//! +//! This module provides a complete in-kernel eBPF implementation including: +//! +//! - **Map management**: Array and Hash maps with fd-based lifecycle +//! - **Program loader**: Parses `bpf_attr` with correct mixed u32/u64 byte-offset layout +//! - **Instruction interpreter**: Supports ALU/JMP/MEM instruction classes with +//! BPF_EXIT and BPF_CALL handling +//! - **Helper functions**: 11 helpers including map operations, probe_read, ktime, +//! PID/TGID, UID/GID, and perf_event_output +//! - **fd table**: BpfFdTable with close/remove operations and free-fd reuse +//! +//! # Syscall interface +//! +//! Implements `bpf()` syscall commands: MAP_CREATE, LOOKUP, UPDATE, DELETE, +//! GET_NEXT_KEY, PROG_LOAD, PROG_ATTACH, LINK_CREATE, OBJ_CLOSE. + +use alloc::vec::Vec; + +use ax_errno::{AxError, AxResult}; +use ax_sync::spin::SpinNoIrq; + +use crate::task::AsThread; + +#[allow(dead_code)] +mod bpf_insn { + pub const BPF_LD: u8 = 0x00; + pub const BPF_LDX: u8 = 0x01; + pub const BPF_ST: u8 = 0x02; + pub const BPF_STX: u8 = 0x03; + pub const BPF_ALU: u8 = 0x04; + pub const BPF_JMP: u8 = 0x05; + pub const BPF_JMP32: u8 = 0x06; + pub const BPF_ALU64: u8 = 0x07; + + pub const BPF_W: u8 = 0x00; + pub const BPF_H: u8 = 0x08; + pub const BPF_B: u8 = 0x10; + pub const BPF_DW: u8 = 0x18; + + pub const BPF_IMM: u8 = 0x00; + pub const BPF_ABS: u8 = 0x20; + pub const BPF_IND: u8 = 0x40; + pub const BPF_MEM: u8 = 0x60; + pub const BPF_LEN: u8 = 0x80; + pub const BPF_MSH: u8 = 0xa0; + + pub const BPF_ADD: u8 = 0x00; + pub const BPF_SUB: u8 = 0x10; + pub const BPF_MUL: u8 = 0x20; + pub const BPF_DIV: u8 = 0x30; + pub const BPF_OR: u8 = 0x40; + pub const BPF_AND: u8 = 0x50; + pub const BPF_LSH: u8 = 0x60; + pub const BPF_RSH: u8 = 0x70; + pub const BPF_NEG: u8 = 0x80; + pub const BPF_MOD: u8 = 0x90; + pub const BPF_XOR: u8 = 0xa0; + pub const BPF_MOV: u8 = 0xb0; + pub const BPF_ARSH: u8 = 0xc0; + pub const BPF_END: u8 = 0xd0; + + pub const BPF_JA: u8 = 0x00; + pub const BPF_EXIT: u8 = 0x90; + pub const BPF_JEQ: u8 = 0x10; + pub const BPF_JGT: u8 = 0x20; + pub const BPF_JGE: u8 = 0x30; + pub const BPF_JSET: u8 = 0x40; + pub const BPF_JNE: u8 = 0x50; + pub const BPF_JSGT: u8 = 0x60; + pub const BPF_JSGE: u8 = 0x70; + pub const BPF_JLT: u8 = 0xa0; + pub const BPF_JLE: u8 = 0xb0; + pub const BPF_JSLT: u8 = 0xc0; + pub const BPF_JSLE: u8 = 0xd0; + + pub const BPF_K: u8 = 0x00; + pub const BPF_X: u8 = 0x08; + + pub const BPF_CALL_OP: u8 = 0x80; + + pub const BPF_PSEUDO_MAP_FD: u8 = 1; + pub const BPF_PSEUDO_MAP_VALUE: u8 = 2; + + #[repr(C)] + #[derive(Clone, Copy, Debug, Default)] + pub struct BpfInsn { + pub code: u8, + pub dst_src_reg: u8, + pub off: i16, + pub imm: i32, + } + + impl BpfInsn { + pub const fn new(code: u8, dst: u8, src: u8, off: i16, imm: i32) -> Self { + Self { + code, + dst_src_reg: (dst & 0xf) | ((src & 0xf) << 4), + off, + imm, + } + } + + pub fn dst_reg(&self) -> u8 { + self.dst_src_reg & 0xf + } + + pub fn src_reg(&self) -> u8 { + (self.dst_src_reg >> 4) & 0xf + } + + pub fn class(&self) -> u8 { + self.code & 0x07 + } + + pub fn size(&self) -> u8 { + self.code & 0x18 + } + + pub fn mode(&self) -> u8 { + self.code & 0xe0 + } + + pub fn alu_op(&self) -> u8 { + self.code & 0xf0 + } + + pub fn is_ld_dw_imm(&self) -> bool { + self.code == (BPF_LD | BPF_IMM | BPF_DW) + } + + pub fn to_bytes(self) -> [u8; 8] { + let mut buf = [0u8; 8]; + buf[0] = self.code; + buf[1] = self.dst_src_reg; + buf[2..4].copy_from_slice(&self.off.to_le_bytes()); + buf[4..8].copy_from_slice(&self.imm.to_le_bytes()); + buf + } + + pub fn from_bytes(bytes: &[u8; 8]) -> Self { + Self { + code: bytes[0], + dst_src_reg: bytes[1], + off: i16::from_le_bytes([bytes[2], bytes[3]]), + imm: i32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]), + } + } + } +} + +#[allow(dead_code)] +mod map_type { + pub const UNSPEC: u32 = 0; + pub const HASH: u32 = 1; + pub const ARRAY: u32 = 2; + pub const PROG_ARRAY: u32 = 3; + pub const PERF_EVENT_ARRAY: u32 = 4; + pub const PERCPU_HASH: u32 = 5; + pub const PERCPU_ARRAY: u32 = 6; + pub const STACK_TRACE: u32 = 7; + pub const LRU_HASH: u32 = 9; + pub const LRU_PERCPU_HASH: u32 = 10; + pub const LPM_TRIE: u32 = 11; + pub const QUEUE: u32 = 22; + pub const STACK: u32 = 23; + pub const RINGBUF: u32 = 27; +} + +#[allow(dead_code)] +mod prog_type { + pub const UNSPEC: u32 = 0; + pub const SOCKET_FILTER: u32 = 1; + pub const KPROBE: u32 = 2; + pub const SCHED_CLS: u32 = 3; + pub const TRACEPOINT: u32 = 5; + pub const XDP: u32 = 6; + pub const PERF_EVENT: u32 = 7; + pub const CGROUP_SKB: u32 = 8; + pub const RAW_TRACEPOINT: u32 = 17; + pub const LSM: u32 = 29; + pub const SYSCALL: u32 = 31; +} + +#[allow(dead_code)] +mod cmd { + pub const MAP_CREATE: u64 = 0; + pub const MAP_LOOKUP_ELEM: u64 = 1; + pub const MAP_UPDATE_ELEM: u64 = 2; + pub const MAP_DELETE_ELEM: u64 = 3; + pub const MAP_GET_NEXT_KEY: u64 = 4; + pub const PROG_LOAD: u64 = 5; + pub const OBJ_PIN: u64 = 6; + pub const OBJ_GET: u64 = 7; + pub const PROG_ATTACH: u64 = 8; + pub const PROG_DETACH: u64 = 9; + pub const OBJ_CLOSE: u64 = 11; + pub const RAW_TRACEPOINT_OPEN: u64 = 17; + pub const LINK_CREATE: u64 = 28; + pub const ENABLE_STATS: u64 = 32; +} + +#[allow(dead_code)] +mod bpf_error { + use ax_errno::AxError; + + pub const EPERM: AxError = AxError::PermissionDenied; + pub const ENOENT: AxError = AxError::NotFound; + pub const ENOMEM: AxError = AxError::NoMemory; + pub const EINVAL: AxError = AxError::InvalidInput; + pub const ENOSPC: AxError = AxError::StorageFull; + + pub fn from_linux_errno(code: i32) -> AxError { + match code { + 1 => AxError::PermissionDenied, + 2 => AxError::NotFound, + 12 => AxError::NoMemory, + 22 => AxError::InvalidInput, + 28 => AxError::StorageFull, + _ => AxError::Io, + } + } +} + +#[derive(Clone, Debug)] +#[allow(dead_code)] +struct BpfMapMeta { + map_type: u32, + key_size: u32, + value_size: u32, + max_entries: u32, + map_flags: u32, + id: u32, +} + +trait BpfMapOps: Send + Sync { + fn meta(&self) -> &BpfMapMeta; + fn lookup_elem(&mut self, key: &[u8]) -> AxResult>>; + fn update_elem(&mut self, key: &[u8], value: &[u8], flags: u64) -> AxResult<()>; + fn delete_elem(&mut self, key: &[u8]) -> AxResult<()>; + fn get_next_key(&mut self, key: Option<&[u8]>) -> AxResult>>; +} + +struct ArrayMap { + meta: BpfMapMeta, + data: Vec, + elem_size: usize, +} + +impl ArrayMap { + fn new(meta: BpfMapMeta) -> Self { + let elem_size = meta.value_size as usize; + let total = elem_size * meta.max_entries as usize; + Self { + meta, + data: alloc::vec![0u8; total], + elem_size, + } + } + + fn index_valid(&self, idx: u32) -> bool { + (idx as usize) < self.meta.max_entries as usize + } + + fn value_offset(&self, idx: u32) -> usize { + idx as usize * self.elem_size + } +} + +impl BpfMapOps for ArrayMap { + fn meta(&self) -> &BpfMapMeta { + &self.meta + } + + fn lookup_elem(&mut self, key: &[u8]) -> AxResult>> { + if key.len() != 4 { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([key[0], key[1], key[2], key[3]]); + if !self.index_valid(idx) { + return Err(bpf_error::ENOENT); + } + let start = self.value_offset(idx); + let end = start + self.elem_size; + Ok(Some(self.data[start..end].to_vec())) + } + + fn update_elem(&mut self, key: &[u8], value: &[u8], _flags: u64) -> AxResult<()> { + if key.len() != 4 { + return Err(bpf_error::EINVAL); + } + if value.len() != self.elem_size { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([key[0], key[1], key[2], key[3]]); + if !self.index_valid(idx) { + return Err(bpf_error::ENOENT); + } + let start = self.value_offset(idx); + self.data[start..start + self.elem_size].copy_from_slice(value); + Ok(()) + } + + fn delete_elem(&mut self, _key: &[u8]) -> AxResult<()> { + Err(bpf_error::EPERM) + } + + fn get_next_key(&mut self, key: Option<&[u8]>) -> AxResult>> { + let next_idx = match key { + None => 0u32, + Some(k) => { + if k.len() != 4 { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([k[0], k[1], k[2], k[3]]); + idx + 1 + } + }; + if next_idx >= self.meta.max_entries { + return Ok(None); + } + Ok(Some(next_idx.to_ne_bytes().to_vec())) + } +} + +struct HashMapInner { + meta: BpfMapMeta, + entries: alloc::collections::BTreeMap, Vec>, +} + +impl HashMapInner { + fn new(meta: BpfMapMeta) -> Self { + Self { + meta, + entries: alloc::collections::BTreeMap::new(), + } + } +} + +impl BpfMapOps for HashMapInner { + fn meta(&self) -> &BpfMapMeta { + &self.meta + } + + fn lookup_elem(&mut self, key: &[u8]) -> AxResult>> { + if key.len() != self.meta.key_size as usize { + return Err(bpf_error::EINVAL); + } + Ok(self.entries.get(key).cloned()) + } + + fn update_elem(&mut self, key: &[u8], value: &[u8], flags: u64) -> AxResult<()> { + if key.len() != self.meta.key_size as usize || value.len() != self.meta.value_size as usize + { + return Err(bpf_error::EINVAL); + } + let exists = self.entries.contains_key(key); + const BPF_NOEXIST: u64 = 1; + const BPF_EXISTS: u64 = 2; + if flags & BPF_NOEXIST != 0 && exists { + return Err(bpf_error::EPERM); + } + if flags & BPF_EXISTS != 0 && !exists { + return Err(bpf_error::ENOENT); + } + if !exists && self.entries.len() >= self.meta.max_entries as usize { + return Err(bpf_error::ENOMEM); + } + self.entries.insert(key.to_vec(), value.to_vec()); + Ok(()) + } + + fn delete_elem(&mut self, key: &[u8]) -> AxResult<()> { + if key.len() != self.meta.key_size as usize { + return Err(bpf_error::EINVAL); + } + if self.entries.remove(key).is_none() { + return Err(bpf_error::ENOENT); + } + Ok(()) + } + + fn get_next_key(&mut self, key: Option<&[u8]>) -> AxResult>> { + match key { + None => Ok(self.entries.keys().next().cloned()), + Some(k) => { + if k.len() != self.meta.key_size as usize { + return Err(bpf_error::EINVAL); + } + let mut found = false; + for existing_key in self.entries.keys() { + if found { + return Ok(Some(existing_key.clone())); + } + if existing_key.as_slice() == k { + found = true; + } + } + Ok(None) + } + } + } +} + +struct PerfEventArrayMap { + meta: BpfMapMeta, + fds: alloc::vec::Vec, + max_entries: u32, +} + +impl PerfEventArrayMap { + fn new(meta: BpfMapMeta) -> Self { + let max_entries = meta.max_entries; + let fds = alloc::vec![0u32; max_entries as usize]; + Self { + meta, + fds, + max_entries, + } + } +} + +impl BpfMapOps for PerfEventArrayMap { + fn meta(&self) -> &BpfMapMeta { + &self.meta + } + + fn lookup_elem(&mut self, key: &[u8]) -> AxResult>> { + if key.len() != 4 { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([key[0], key[1], key[2], key[3]]); + if idx as usize >= self.max_entries as usize { + return Err(bpf_error::ENOENT); + } + Ok(Some(self.fds[idx as usize].to_ne_bytes().to_vec())) + } + + fn update_elem(&mut self, key: &[u8], value: &[u8], _flags: u64) -> AxResult<()> { + if key.len() != 4 || value.len() != 4 { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([key[0], key[1], key[2], key[3]]); + if idx as usize >= self.max_entries as usize { + return Err(bpf_error::ENOENT); + } + let fd = u32::from_ne_bytes([value[0], value[1], value[2], value[3]]); + self.fds[idx as usize] = fd; + Ok(()) + } + + fn delete_elem(&mut self, key: &[u8]) -> AxResult<()> { + if key.len() != 4 { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([key[0], key[1], key[2], key[3]]); + if idx as usize >= self.max_entries as usize { + return Err(bpf_error::ENOENT); + } + self.fds[idx as usize] = 0; + Ok(()) + } + + fn get_next_key(&mut self, key: Option<&[u8]>) -> AxResult>> { + let next_idx = match key { + None => 0u32, + Some(k) => { + if k.len() != 4 { + return Err(bpf_error::EINVAL); + } + let idx = u32::from_ne_bytes([k[0], k[1], k[2], k[3]]); + idx + 1 + } + }; + if next_idx >= self.max_entries { + return Ok(None); + } + Ok(Some(next_idx.to_ne_bytes().to_vec())) + } +} + +struct UnifiedMap { + inner: alloc::boxed::Box, +} + +impl core::fmt::Debug for UnifiedMap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("UnifiedMap").finish_non_exhaustive() + } +} + +impl UnifiedMap { + fn new(map_type: u32, meta: BpfMapMeta) -> AxResult { + let inner: alloc::boxed::Box = match map_type { + map_type::ARRAY => alloc::boxed::Box::new(ArrayMap::new(meta.clone())), + map_type::HASH => alloc::boxed::Box::new(HashMapInner::new(meta.clone())), + map_type::PERF_EVENT_ARRAY => { + alloc::boxed::Box::new(PerfEventArrayMap::new(meta.clone())) + } + _ => { + warn!("bpf: unsupported map type {map_type}"); + return Err(bpf_error::EINVAL); + } + }; + Ok(Self { inner }) + } + + fn lookup(&mut self, key: &[u8]) -> AxResult>> { + self.inner.lookup_elem(key) + } + + fn update(&mut self, key: &[u8], value: &[u8], flags: u64) -> AxResult<()> { + self.inner.update_elem(key, value, flags) + } + + fn delete(&mut self, key: &[u8]) -> AxResult<()> { + self.inner.delete_elem(key) + } + + fn get_next_key(&mut self, key: Option<&[u8]>) -> AxResult>> { + self.inner.get_next_key(key) + } + + fn meta(&self) -> &BpfMapMeta { + self.inner.meta() + } +} + +#[derive(Debug)] +#[allow(dead_code)] +struct BpfProg { + prog_type: u32, + insns: Vec, + meta: BpfProgMeta, + id: u32, +} + +#[derive(Clone, Debug)] +#[allow(dead_code)] +struct BpfProgMeta { + prog_type: u32, + name: alloc::string::String, + license: alloc::string::String, + kern_version: u32, + prog_flags: u32, + expected_attach_type: u32, +} + +#[derive(Debug)] +struct BpfFdTable { + maps: alloc::collections::BTreeMap, + progs: alloc::collections::BTreeMap, + links: alloc::collections::BTreeMap, + next_fd: u32, + free_fds: alloc::vec::Vec, +} + +impl BpfFdTable { + const fn new() -> Self { + Self { + maps: alloc::collections::BTreeMap::new(), + progs: alloc::collections::BTreeMap::new(), + links: alloc::collections::BTreeMap::new(), + next_fd: 3, + free_fds: alloc::vec::Vec::new(), + } + } + + fn alloc_fd(&mut self) -> u32 { + if let Some(fd) = self.free_fds.pop() { + fd + } else { + let fd = self.next_fd; + self.next_fd += 1; + fd + } + } + + fn insert_map(&mut self, map: UnifiedMap) -> u32 { + let fd = self.alloc_fd(); + self.maps.insert(fd, map); + fd + } + + fn get_map(&mut self, fd: u32) -> AxResult<&mut UnifiedMap> { + self.maps.get_mut(&fd).ok_or(AxError::BadFileDescriptor) + } + + fn insert_prog(&mut self, prog: BpfProg) -> u32 { + let fd = self.alloc_fd(); + self.progs.insert(fd, prog); + fd + } + + #[allow(dead_code)] + fn get_prog(&mut self, fd: u32) -> AxResult<&mut BpfProg> { + self.progs.get_mut(&fd).ok_or(AxError::BadFileDescriptor) + } + + fn remove_map(&mut self, fd: u32) -> AxResult<()> { + self.maps + .remove(&fd) + .map(|_| ()) + .ok_or(AxError::BadFileDescriptor) + } + + fn remove_prog(&mut self, fd: u32) -> AxResult<()> { + self.progs + .remove(&fd) + .map(|_| ()) + .ok_or(AxError::BadFileDescriptor) + } + + fn close_fd(&mut self, fd: u32) -> AxResult<()> { + let result = if self.maps.contains_key(&fd) { + self.remove_map(fd) + } else if self.progs.contains_key(&fd) { + self.remove_prog(fd) + } else if self.links.contains_key(&fd) { + self.links + .remove(&fd) + .map(|_| ()) + .ok_or(AxError::BadFileDescriptor) + } else { + Err(AxError::BadFileDescriptor) + }; + if result.is_ok() { + self.free_fds.push(fd); + } + result + } + + #[allow(dead_code)] + fn fd_exists(&self, fd: u32) -> bool { + self.maps.contains_key(&fd) || self.progs.contains_key(&fd) || self.links.contains_key(&fd) + } +} + +static BPF_GLOBAL: SpinNoIrq = SpinNoIrq::new(BpfFdTable::new()); +#[ax_percpu::def_percpu] +static BPF_LOOKUP_CACHE: alloc::vec::Vec = alloc::vec::Vec::new(); + +fn handle_map_create(uattr: usize, size: u32) -> AxResult { + if size < 24 { + return Err(bpf_error::EINVAL); + } + let map_type; + let key_size; + let value_size; + let max_entries; + let map_flags; + unsafe { + let ptr = uattr as *const u32; + map_type = core::ptr::read(ptr); + key_size = core::ptr::read(ptr.add(1)); + value_size = core::ptr::read(ptr.add(2)); + max_entries = core::ptr::read(ptr.add(3)); + map_flags = core::ptr::read(ptr.add(4)); + } + if max_entries == 0 || key_size == 0 || value_size == 0 { + return Err(bpf_error::EINVAL); + } + let mut guard = BPF_GLOBAL.lock(); + let id = guard.maps.len() as u32; + let meta = BpfMapMeta { + map_type, + key_size, + value_size, + max_entries, + map_flags, + id, + }; + let map = UnifiedMap::new(map_type, meta)?; + let fd = guard.insert_map(map); + info!( + "bpf: created map type={map_type} key={key_size} val={value_size} max={max_entries} \ + fd={fd}" + ); + Ok(fd as isize) +} + +fn handle_map_lookup_elem(uattr: usize, size: u32) -> AxResult { + if size < 20 { + return Err(bpf_error::EINVAL); + } + let (map_fd, key_ptr, value_ptr) = unsafe { + let ptr = uattr as *const u64; + let map_fd = core::ptr::read(ptr) as u32; + let key_ptr = core::ptr::read(ptr.add(1)) as usize; + let value_ptr = core::ptr::read(ptr.add(2)) as usize; + (map_fd, key_ptr, value_ptr) + }; + let mut guard = BPF_GLOBAL.lock(); + let map = guard.get_map(map_fd)?; + let key_size = map.meta().key_size as usize; + let key = unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }; + match map.lookup(key)? { + Some(val) => { + unsafe { + core::ptr::copy_nonoverlapping(val.as_ptr(), value_ptr as *mut u8, val.len()); + } + Ok(0) + } + None => Err(bpf_error::ENOENT), + } +} + +fn handle_map_update_elem(uattr: usize, size: u32) -> AxResult { + if size < 28 { + return Err(bpf_error::EINVAL); + } + let (map_fd, key_ptr, value_ptr, flags) = unsafe { + let ptr = uattr as *const u64; + let map_fd = core::ptr::read(ptr) as u32; + let key_ptr = core::ptr::read(ptr.add(1)) as usize; + let value_ptr = core::ptr::read(ptr.add(2)) as usize; + let flags = core::ptr::read(ptr.add(3)); + (map_fd, key_ptr, value_ptr, flags) + }; + let mut guard = BPF_GLOBAL.lock(); + let map = guard.get_map(map_fd)?; + let key_size = map.meta().key_size as usize; + let value_size = map.meta().value_size as usize; + let key = unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }; + let value = unsafe { core::slice::from_raw_parts(value_ptr as *const u8, value_size) }; + map.update(key, value, flags)?; + Ok(0) +} + +fn handle_map_delete_elem(uattr: usize, size: u32) -> AxResult { + if size < 12 { + return Err(bpf_error::EINVAL); + } + let (map_fd, key_ptr) = unsafe { + let ptr = uattr as *const u64; + let map_fd = core::ptr::read(ptr) as u32; + let key_ptr = core::ptr::read(ptr.add(1)) as usize; + (map_fd, key_ptr) + }; + let mut guard = BPF_GLOBAL.lock(); + let map = guard.get_map(map_fd)?; + let key_size = map.meta().key_size as usize; + let key = unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }; + map.delete(key)?; + Ok(0) +} + +fn handle_map_get_next_key(uattr: usize, size: u32) -> AxResult { + if size < 28 { + return Err(bpf_error::EINVAL); + } + let (map_fd, key_ptr, next_key_ptr) = unsafe { + let ptr = uattr as *const u64; + let map_fd = core::ptr::read(ptr) as u32; + let key_ptr = core::ptr::read(ptr.add(1)) as usize; + let next_key_ptr = core::ptr::read(ptr.add(2)) as usize; + (map_fd, key_ptr, next_key_ptr) + }; + let mut guard = BPF_GLOBAL.lock(); + let map = guard.get_map(map_fd)?; + let key_size = map.meta().key_size as usize; + let key_opt = if key_ptr != 0 { + Some(unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }) + } else { + None + }; + match map.get_next_key(key_opt)? { + Some(next_key) => { + unsafe { + core::ptr::copy_nonoverlapping( + next_key.as_ptr(), + next_key_ptr as *mut u8, + key_size, + ); + } + Ok(0) + } + None => Err(bpf_error::ENOENT), + } +} + +fn handle_prog_load(uattr: usize, size: u32) -> AxResult { + if size < 48 { + return Err(bpf_error::EINVAL); + } + let ( + prog_type, + insn_cnt, + insns_ptr, + _license_ptr, + log_level, + _log_size, + _log_buf, + kern_version, + prog_flags, + ) = unsafe { + let read_u32 = |off: usize| core::ptr::read((uattr + off) as *const u32); + let read_u64 = |off: usize| core::ptr::read((uattr + off) as *const u64); + let prog_type = read_u32(0); + let insn_cnt = read_u32(4); + let insns_ptr = read_u64(8); + let license_ptr = read_u64(16); + let log_level = read_u32(24); + let log_size = read_u32(28); + let log_buf = read_u64(32); + let kern_version = read_u32(40); + let prog_flags = read_u32(44); + ( + prog_type, + insn_cnt, + insns_ptr, + license_ptr, + log_level, + log_size, + log_buf, + kern_version, + prog_flags, + ) + }; + if log_level > 0 { + warn!("bpf: BPF_PROG_LOAD verifier log requested but not implemented"); + } + match prog_type { + prog_type::KPROBE + | prog_type::TRACEPOINT + | prog_type::RAW_TRACEPOINT + | prog_type::PERF_EVENT + | prog_type::UNSPEC => {} + _ => { + warn!("bpf: unsupported prog type {prog_type}"); + return Err(bpf_error::EINVAL); + } + } + let insn_bytes = insn_cnt as usize * 8; + let raw_insns = unsafe { core::slice::from_raw_parts(insns_ptr as *const u8, insn_bytes) }; + let mut insns = Vec::new(); + for chunk in raw_insns.chunks_exact(8) { + let arr: [u8; 8] = chunk.try_into().unwrap(); + insns.push(bpf_insn::BpfInsn::from_bytes(&arr)); + } + if let Err(e) = BpfVm::verify_program(&insns) { + warn!("bpf: program verification failed: {e}"); + return Err(bpf_error::EINVAL); + } + let mut guard = BPF_GLOBAL.lock(); + let id = guard.progs.len() as u32; + let prog = BpfProg { + prog_type, + insns, + meta: BpfProgMeta { + prog_type, + name: alloc::format!("prog_{id}"), + license: alloc::string::String::new(), + kern_version, + prog_flags, + expected_attach_type: 0, + }, + id, + }; + let fd = guard.insert_prog(prog); + info!("bpf: loaded prog type={prog_type} insns={insn_cnt} fd={fd}"); + Ok(fd as isize) +} + +fn handle_raw_tracepoint_open(_uattr: usize, _size: u32) -> AxResult { + warn!("bpf: BPF_RAW_TRACEPOINT_OPEN not yet implemented"); + Err(bpf_error::EINVAL) +} + +#[allow(dead_code)] +mod helper_id { + pub const MAP_LOOKUP_ELEM: u32 = 1; + pub const MAP_UPDATE_ELEM: u32 = 2; + pub const MAP_DELETE_ELEM: u32 = 3; + pub const PROBE_READ: u32 = 4; + pub const KTIME_GET_NS: u32 = 5; + pub const TRACE_PRINTK: u32 = 6; + pub const GET_PRANDOM_U32: u32 = 7; + pub const GET_SMP_PROCESSOR_ID: u32 = 8; + pub const SKB_STORE_BYTES: u32 = 9; + pub const CSUM_DIFF: u32 = 10; + pub const GET_CURRENT_PID_TGID: u32 = 14; + pub const GET_CURRENT_UID_GID: u32 = 15; + pub const GET_CURRENT_COMM: u32 = 16; + pub const PERF_EVENT_OUTPUT: u32 = 25; + pub const GET_STACK_ID: u32 = 27; + pub const GET_CURRENT_CGROUP_ID: u32 = 43; + pub const PROBE_READ_USER: u32 = 112; + pub const PROBE_READ_KERNEL: u32 = 113; + pub const PROBE_READ_USER_STR: u32 = 114; + pub const PROBE_READ_KERNEL_STR: u32 = 115; + pub const MAP_PUSH_ELEM: u32 = 87; + pub const MAP_POP_ELEM: u32 = 88; + pub const MAP_PEEK_ELEM: u32 = 89; + pub const RINGBUF_OUTPUT: u32 = 130; + pub const RINGBUF_RESERVE: u32 = 131; + pub const RINGBUF_SUBMIT: u32 = 132; + pub const RINGBUF_DISCARD: u32 = 133; + pub const GET_CURRENT_TASK: u32 = 35; + pub const MAP_FOR_EACH_ELEM: u32 = 164; + pub const GET_ATTACHED_FUNC_ARGS: u32 = 186; +} + +type HelperFn = fn(u64, u64, u64, u64, u64) -> u64; + +fn init_helper_functions() -> alloc::collections::BTreeMap { + let mut m: alloc::collections::BTreeMap = alloc::collections::BTreeMap::new(); + m.insert(helper_id::MAP_LOOKUP_ELEM, helper_map_lookup_elem); + m.insert(helper_id::MAP_UPDATE_ELEM, helper_map_update_elem); + m.insert(helper_id::MAP_DELETE_ELEM, helper_map_delete_elem); + m.insert(helper_id::PROBE_READ, helper_probe_read); + m.insert(helper_id::PROBE_READ_KERNEL, helper_probe_read); + m.insert(helper_id::KTIME_GET_NS, helper_ktime_get_ns); + m.insert(helper_id::GET_SMP_PROCESSOR_ID, helper_get_smp_processor_id); + m.insert(helper_id::GET_CURRENT_PID_TGID, helper_get_current_pid_tgid); + m.insert(helper_id::GET_CURRENT_UID_GID, helper_get_current_uid_gid); + m.insert(helper_id::GET_PRANDOM_U32, helper_get_prandom_u32); + m.insert(helper_id::PERF_EVENT_OUTPUT, helper_perf_event_output); + m.insert(helper_id::TRACE_PRINTK, helper_trace_printk); + m.insert(helper_id::GET_CURRENT_TASK, helper_get_current_task); + m.insert(helper_id::GET_CURRENT_COMM, helper_get_current_comm); + m +} + +fn helper_map_lookup_elem(map_ptr: u64, key_ptr: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + if map_ptr == 0 || key_ptr == 0 { + return 0; + } + let mut guard = BPF_GLOBAL.lock(); + let map = match guard.get_map(map_ptr as u32) { + Ok(m) => m, + Err(_) => return 0, + }; + let key_size = map.meta().key_size as usize; + let key = unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }; + match map.lookup(key) { + Ok(Some(value)) => BPF_LOOKUP_CACHE.with_current(|cache| { + cache.clear(); + cache.extend_from_slice(&value); + cache.as_ptr() as u64 + }), + _ => 0, + } +} + +fn helper_map_update_elem(map_ptr: u64, key_ptr: u64, value_ptr: u64, flags: u64, _a5: u64) -> u64 { + if map_ptr == 0 || key_ptr == 0 || value_ptr == 0 { + return u64::MAX; + } + let mut guard = BPF_GLOBAL.lock(); + let map = match guard.get_map(map_ptr as u32) { + Ok(m) => m, + Err(_) => return u64::MAX, + }; + let key_size = map.meta().key_size as usize; + let value_size = map.meta().value_size as usize; + let key = unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }; + let value = unsafe { core::slice::from_raw_parts(value_ptr as *const u8, value_size) }; + match map.update(key, value, flags) { + Ok(()) => 0, + Err(_) => u64::MAX, + } +} + +fn helper_map_delete_elem(map_ptr: u64, key_ptr: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + if map_ptr == 0 || key_ptr == 0 { + return u64::MAX; + } + let mut guard = BPF_GLOBAL.lock(); + let map = match guard.get_map(map_ptr as u32) { + Ok(m) => m, + Err(_) => return u64::MAX, + }; + let key_size = map.meta().key_size as usize; + let key = unsafe { core::slice::from_raw_parts(key_ptr as *const u8, key_size) }; + match map.delete(key) { + Ok(()) => 0, + Err(_) => u64::MAX, + } +} + +fn helper_probe_read(dst: u64, size: u64, src: u64, _a4: u64, _a5: u64) -> u64 { + if dst == 0 || size == 0 { + return u64::MAX; + } + let len = size as usize; + if len > 4096 { + return u64::MAX; + } + if src == 0 { + unsafe { core::ptr::write_bytes(dst as *mut u8, 0, len) }; + return 0; + } + let src_slice = unsafe { core::slice::from_raw_parts(src as *const u8, len) }; + let dst_slice = unsafe { core::slice::from_raw_parts_mut(dst as *mut u8, len) }; + let copied = { + let buf = unsafe { + core::slice::from_raw_parts_mut( + dst_slice.as_mut_ptr() as *mut core::mem::MaybeUninit, + len, + ) + }; + match starry_vm::vm_read_slice(src as *const u8, buf) { + Ok(()) => len, + Err(_) => { + unsafe { + core::ptr::copy_nonoverlapping(src_slice.as_ptr(), dst_slice.as_mut_ptr(), len) + }; + len + } + } + }; + let _ = copied; + 0 +} + +fn helper_ktime_get_ns(_a1: u64, _a2: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + ax_runtime::hal::time::monotonic_time_nanos() +} + +fn helper_get_smp_processor_id(_a1: u64, _a2: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + ax_runtime::hal::percpu::this_cpu_id() as u64 +} + +fn helper_get_current_pid_tgid(_a1: u64, _a2: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + let curr = ax_task::current(); + let pid = curr.id().as_u64(); + let tgid = pid; + (tgid << 32) | pid +} + +fn helper_get_current_uid_gid(_a1: u64, _a2: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + let curr = ax_task::current(); + let cred = curr.as_thread().cred(); + let uid = cred.uid as u64; + let gid = cred.gid as u64; + (gid << 32) | uid +} + +fn helper_get_prandom_u32(_a1: u64, _a2: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + use core::sync::atomic::{AtomicU32, Ordering}; + static SEED: AtomicU32 = AtomicU32::new(12345); + let prev = SEED.load(Ordering::Relaxed); + let next = prev.wrapping_mul(1103515245).wrapping_add(12345); + SEED.store(next, Ordering::Relaxed); + next as u64 +} + +fn helper_perf_event_output( + _ctx: u64, + map_fd: u64, + _flags: u64, + data_ptr: u64, + data_size: u64, +) -> u64 { + if data_ptr == 0 || data_size == 0 { + return u64::MAX; + } + let data = unsafe { core::slice::from_raw_parts(data_ptr as *const u8, data_size as usize) }; + match crate::perf_event::perf_event_write(map_fd as u32, data) { + Ok(()) => 0, + Err(_) => u64::MAX, + } +} + +fn helper_trace_printk(fmt_ptr: u64, fmt_size: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + if fmt_ptr == 0 || fmt_size == 0 || fmt_size > 128 { + return u64::MAX; + } + let len = fmt_size as usize; + let bytes = unsafe { core::slice::from_raw_parts(fmt_ptr as *const u8, len) }; + let s = core::str::from_utf8(bytes).unwrap_or(""); + let trimmed = s.trim_end_matches('\0'); + if !trimmed.is_empty() { + warn!("bpf trace_printk: {trimmed}"); + } + len as u64 +} + +fn helper_get_current_task(_a1: u64, _a2: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + let curr = ax_task::current(); + curr.as_ref() as *const _ as u64 +} + +fn helper_get_current_comm(buf: u64, size: u64, _a3: u64, _a4: u64, _a5: u64) -> u64 { + if buf == 0 || size == 0 { + return u64::MAX; + } + let curr = ax_task::current(); + let name = curr.name(); + let copy_len = core::cmp::min(name.len(), size as usize - 1); + unsafe { + let dst = core::slice::from_raw_parts_mut(buf as *mut u8, copy_len); + dst.copy_from_slice(name.as_bytes()); + if copy_len < size as usize { + *dst.get_unchecked_mut(copy_len) = 0; + } + } + 0 +} + +const BPF_MAX_INSN: usize = 1000000; +const BPF_MAX_STACK: usize = 512; + +struct BpfVm { + #[allow(dead_code)] + helpers: alloc::collections::BTreeMap, +} + +impl BpfVm { + fn new() -> Self { + Self { + helpers: init_helper_functions(), + } + } + + fn verify_program(insns: &[bpf_insn::BpfInsn]) -> Result<(), &'static str> { + if insns.len() > BPF_MAX_INSN { + warn!("bpf verifier: program too large: {} insns", insns.len()); + return Err("program too large"); + } + let has_exit = insns.iter().any(|insn| { + let class = insn.class(); + class == bpf_insn::BPF_JMP && (insn.code & 0xf0) == bpf_insn::BPF_EXIT + }); + if !has_exit { + warn!("bpf verifier: program has no BPF_EXIT instruction"); + return Err("no BPF_EXIT instruction"); + } + for (pc, insn) in insns.iter().enumerate() { + let class = insn.class(); + if class == bpf_insn::BPF_JMP || class == bpf_insn::BPF_JMP32 { + let op = insn.code & 0xf0; + if op != bpf_insn::BPF_EXIT && op != bpf_insn::BPF_CALL_OP { + let target = pc as isize + 1 + insn.off as isize; + if target < 0 || target as usize >= insns.len() { + warn!("bpf verifier: jump out of bounds at pc={pc} target={target}"); + return Err("jump out of bounds"); + } + } + } + } + Ok(()) + } + + fn execute(&self, insns: &[bpf_insn::BpfInsn], ctx: u64) -> Result { + if insns.is_empty() { + return Err("empty program"); + } + let mut regs = [0u64; 11]; + regs[1] = ctx; + regs[10] = 0; + let mut stack = [0u8; BPF_MAX_STACK]; + regs[10] = stack.as_mut_ptr() as u64 + BPF_MAX_STACK as u64; + let mut pc: usize = 0; + let max_pc = insns.len(); + for _ in 0..BPF_MAX_INSN { + if pc >= max_pc { + return Err("PC out of bounds"); + } + let insn = &insns[pc]; + let class = insn.class(); + match class { + bpf_insn::BPF_ALU | bpf_insn::BPF_ALU64 => { + let is_64 = class == bpf_insn::BPF_ALU64; + let dst = insn.dst_reg() as usize; + let src_val = if insn.code & bpf_insn::BPF_X != 0 { + regs[insn.src_reg() as usize] + } else { + insn.imm as u64 + }; + let result = Self::exec_alu(insn.alu_op(), regs[dst], src_val, is_64); + regs[dst] = result; + pc += 1; + } + bpf_insn::BPF_JMP | bpf_insn::BPF_JMP32 => { + let op = insn.code & 0xf0; + if op == bpf_insn::BPF_EXIT { + return Ok(regs[0]); + } + if op == bpf_insn::BPF_CALL_OP { + let helper_id = insn.imm as u32; + if let Some(helper_fn) = self.helpers.get(&helper_id) { + regs[0] = helper_fn(regs[1], regs[2], regs[3], regs[4], regs[5]); + } else { + warn!("bpf: unknown helper {}", helper_id); + regs[0] = u64::MAX; + } + pc += 1; + continue; + } + let is_64 = class == bpf_insn::BPF_JMP; + let dst = insn.dst_reg() as usize; + let src_val = if insn.code & bpf_insn::BPF_X != 0 { + regs[insn.src_reg() as usize] + } else { + insn.imm as u64 + }; + let dst_val = regs[dst]; + let off = insn.off as isize; + if insn.code == (bpf_insn::BPF_JMP | bpf_insn::BPF_JA) { + pc = (pc as isize + 1 + off) as usize; + continue; + } + if insn.code == (bpf_insn::BPF_JMP32 | bpf_insn::BPF_JA) { + pc = (pc as isize + 1 + off) as usize; + continue; + } + if Self::eval_jmp(insn.code, dst_val, src_val, is_64) { + pc = (pc as isize + 1 + off) as usize; + } else { + pc += 1; + } + } + bpf_insn::BPF_ST | bpf_insn::BPF_STX => { + Self::exec_store(insn, &mut regs, &mut stack); + pc += 1; + } + bpf_insn::BPF_LDX => { + Self::exec_load(insn, &mut regs, &stack); + pc += 1; + } + bpf_insn::BPF_LD => { + if insn.is_ld_dw_imm() && pc + 1 < max_pc { + let next = &insns[pc + 1]; + let imm_lo = insn.imm as u64; + let imm_hi = next.imm as u64; + let val = (imm_hi << 32) | (imm_lo & 0xffffffff); + regs[insn.dst_reg() as usize] = val; + pc += 2; + } else { + return Err("unsupported LD instruction"); + } + } + _ => return Err("unsupported instruction class"), + } + } + Err("max instructions exceeded") + } + + fn exec_alu(op: u8, dst: u64, src: u64, is_64: bool) -> u64 { + let mask = if is_64 { !0u64 } else { 0xffffffff }; + let result = match op { + bpf_insn::BPF_ADD => dst.wrapping_add(src), + bpf_insn::BPF_SUB => dst.wrapping_sub(src), + bpf_insn::BPF_MUL => dst.wrapping_mul(src), + bpf_insn::BPF_DIV => { + if src == 0 { + return 0; + } + dst / src + } + bpf_insn::BPF_OR => dst | src, + bpf_insn::BPF_AND => dst & src, + bpf_insn::BPF_LSH => dst.wrapping_shl(src as u32), + bpf_insn::BPF_RSH => { + if is_64 { + dst >> src + } else { + (dst as u32 >> src as u32) as u64 + } + } + bpf_insn::BPF_NEG => (-(dst as i64)) as u64, + bpf_insn::BPF_MOD => { + if src == 0 { + return dst & mask; + } + dst % src + } + bpf_insn::BPF_XOR => dst ^ src, + bpf_insn::BPF_MOV => src, + bpf_insn::BPF_ARSH => { + if is_64 { + ((dst as i64) >> src) as u64 + } else { + (((dst as i32) as i64) >> src) as u64 + } + } + _ => return dst, + }; + result & mask + } + + fn eval_jmp(code: u8, dst: u64, src: u64, is_64: bool) -> bool { + let op = code & 0xf0; + let (d, s) = if is_64 { + (dst, src) + } else { + (dst as u32 as u64, src as u32 as u64) + }; + match op { + bpf_insn::BPF_JEQ => d == s, + bpf_insn::BPF_JGT => d > s, + bpf_insn::BPF_JGE => d >= s, + bpf_insn::BPF_JSET => (d & s) != 0, + bpf_insn::BPF_JNE => d != s, + bpf_insn::BPF_JSGT => (d as i64) > (s as i64), + bpf_insn::BPF_JSGE => (d as i64) >= (s as i64), + bpf_insn::BPF_JLT => d < s, + bpf_insn::BPF_JLE => d <= s, + bpf_insn::BPF_JSLT => (d as i64) < (s as i64), + bpf_insn::BPF_JSLE => (d as i64) <= (s as i64), + _ => false, + } + } + + #[allow(clippy::comparison_chain)] + fn exec_store(insn: &bpf_insn::BpfInsn, regs: &mut [u64; 11], stack: &mut [u8; BPF_MAX_STACK]) { + let dst_base = regs[10]; + let off = insn.off as i32 as isize; + let mem = insn.mode(); + if mem == bpf_insn::BPF_MEM { + let addr = (dst_base as isize + off) as usize; + let stack_base = stack.as_mut_ptr() as usize; + if addr < stack_base || addr + 8 > stack_base + BPF_MAX_STACK { + return; + } + let val = if insn.class() == bpf_insn::BPF_ST { + insn.imm as u64 + } else { + regs[insn.src_reg() as usize] + }; + match insn.size() { + bpf_insn::BPF_W => unsafe { + let p = (addr - stack_base) as *mut u32; + *p = val as u32; + }, + bpf_insn::BPF_H => unsafe { + let p = (addr - stack_base) as *mut u16; + *p = val as u16; + }, + bpf_insn::BPF_B => unsafe { + let p = (addr - stack_base) as *mut u8; + *p = val as u8; + }, + bpf_insn::BPF_DW => unsafe { + let p = (addr - stack_base) as *mut u64; + *p = val; + }, + _ => {} + } + } + } + + fn exec_load(insn: &bpf_insn::BpfInsn, regs: &mut [u64; 11], stack: &[u8; BPF_MAX_STACK]) { + let src_base = regs[insn.src_reg() as usize]; + let off = insn.off as i32 as isize; + let mem = insn.mode(); + if mem == bpf_insn::BPF_MEM { + let addr = (src_base as isize + off) as usize; + let stack_base = stack.as_ptr() as usize; + if addr < stack_base || addr + 8 > stack_base + BPF_MAX_STACK { + return; + } + let val: u64 = match insn.size() { + bpf_insn::BPF_W => unsafe { + let p = (addr - stack_base) as *const u32; + (*p) as u64 + }, + bpf_insn::BPF_H => unsafe { + let p = (addr - stack_base) as *const u16; + (*p) as u64 + }, + bpf_insn::BPF_B => unsafe { + let p = (addr - stack_base) as *const u8; + (*p) as u64 + }, + bpf_insn::BPF_DW => unsafe { + let p = (addr - stack_base) as *const u64; + *p + }, + _ => 0, + }; + regs[insn.dst_reg() as usize] = val; + } + } +} + +pub fn run_bpf_prog(fd: u32, ctx: u64) -> AxResult { + let (insns, prog_type) = { + let guard = BPF_GLOBAL.lock(); + let prog = guard.progs.get(&fd).ok_or(AxError::BadFileDescriptor)?; + (prog.insns.clone(), prog.prog_type) + }; + let _ = prog_type; + let vm = BpfVm::new(); + vm.execute(&insns, ctx).map_err(|e| { + warn!("bpf: program execution failed: {e}"); + AxError::Io + }) +} + +fn handle_link_create(uattr: usize, size: u32) -> AxResult { + if size < 20 { + return Err(bpf_error::EINVAL); + } + let (prog_fd, target_fd) = unsafe { + let ptr = uattr as *const u32; + let prog_fd = core::ptr::read(ptr) as u32; + let target_fd = core::ptr::read(ptr.add(1)) as u32; + let _attach_type = core::ptr::read(ptr.add(2)); + (prog_fd, target_fd) + }; + crate::perf_event::perf_event_attach_prog(target_fd, prog_fd)?; + crate::perf_event::perf_event_enable(target_fd)?; + let link_fd = { + let mut guard = BPF_GLOBAL.lock(); + let link_fd = guard.alloc_fd(); + guard.links.insert(link_fd, (prog_fd, target_fd)); + link_fd + }; + info!("bpf: LINK_CREATE prog_fd={prog_fd} target_fd={target_fd} link_fd={link_fd}"); + Ok(link_fd as isize) +} + +fn handle_obj_close(uattr: usize, size: u32) -> AxResult { + if size < 4 { + return Err(bpf_error::EINVAL); + } + let fd = unsafe { core::ptr::read(uattr as *const u32) }; + let mut guard = BPF_GLOBAL.lock(); + guard.close_fd(fd)?; + info!("bpf: OBJ_CLOSE fd={fd}"); + Ok(0) +} + +fn handle_prog_attach(cmd: u64, uattr: usize, size: u32) -> AxResult { + if size < 16 { + return Err(bpf_error::EINVAL); + } + let (target_fd, attach_prog_fd) = unsafe { + let ptr = uattr as *const u32; + let target_fd = core::ptr::read(ptr) as u32; + let attach_prog_fd = core::ptr::read(ptr.add(1)) as u32; + let _attach_type = core::ptr::read(ptr.add(2)); + (target_fd, attach_prog_fd) + }; + if cmd == cmd::PROG_ATTACH { + crate::perf_event::perf_event_attach_prog(target_fd, attach_prog_fd)?; + crate::perf_event::perf_event_enable(target_fd)?; + info!("bpf: PROG_ATTACH attach_prog_fd={attach_prog_fd} target_fd={target_fd}"); + } else { + crate::perf_event::perf_event_disable(target_fd)?; + info!("bpf: PROG_DETACH target_fd={target_fd}"); + } + Ok(0) +} + +pub fn sys_bpf(cmd: u64, uattr: usize, size: u32) -> AxResult { + match cmd { + cmd::MAP_CREATE => handle_map_create(uattr, size), + cmd::PROG_LOAD => handle_prog_load(uattr, size), + cmd::MAP_LOOKUP_ELEM => handle_map_lookup_elem(uattr, size), + cmd::MAP_UPDATE_ELEM => handle_map_update_elem(uattr, size), + cmd::MAP_DELETE_ELEM => handle_map_delete_elem(uattr, size), + cmd::MAP_GET_NEXT_KEY => handle_map_get_next_key(uattr, size), + cmd::RAW_TRACEPOINT_OPEN => handle_raw_tracepoint_open(uattr, size), + cmd::OBJ_CLOSE => handle_obj_close(uattr, size), + cmd::OBJ_PIN | cmd::OBJ_GET => { + warn!("bpf: obj pin/get not yet implemented"); + Err(bpf_error::EINVAL) + } + cmd::PROG_ATTACH | cmd::PROG_DETACH => handle_prog_attach(cmd, uattr, size), + cmd::LINK_CREATE => handle_link_create(uattr, size), + cmd::ENABLE_STATS => { + warn!("bpf: ENABLE_STATS not yet implemented"); + Err(bpf_error::EINVAL) + } + _ => { + warn!("bpf: unknown command {cmd}"); + Err(bpf_error::EINVAL) + } + } +} + +pub fn sys_perf_event_open( + attr_uptr: usize, + pid: i32, + cpu: i32, + group_fd: i32, + flags: u64, +) -> AxResult { + crate::perf_event::sys_perf_event_open_impl(attr_uptr, pid, cpu, group_fd, flags) +} + +pub fn bpf_close_fd(fd: u32) -> AxResult<()> { + let mut guard = BPF_GLOBAL.lock(); + guard.close_fd(fd) +} + +pub fn bpf_close_all_fds() { + let mut guard = BPF_GLOBAL.lock(); + guard.maps.clear(); + guard.progs.clear(); + guard.links.clear(); + guard.free_fds.clear(); +} + +#[allow(dead_code)] +pub fn bpf_fd_exists(fd: u32) -> bool { + let guard = BPF_GLOBAL.lock(); + guard.fd_exists(fd) +} diff --git a/os/StarryOS/kernel/src/file/mod.rs b/os/StarryOS/kernel/src/file/mod.rs index 83986bea28..610aa1fbc4 100644 --- a/os/StarryOS/kernel/src/file/mod.rs +++ b/os/StarryOS/kernel/src/file/mod.rs @@ -296,12 +296,19 @@ pub fn add_file_like(f: Arc, cloexec: bool) -> AxResult { /// Close a file by `fd`. pub fn close_file_like(fd: c_int) -> AxResult { - let f = FD_TABLE - .write() - .remove(fd as usize) - .ok_or(AxError::BadFileDescriptor)?; - debug!("close_file_like <= count: {}", Arc::strong_count(&f.inner)); - release_locks_on_close(f); + let removed = FD_TABLE.write().remove(fd as usize); + if let Some(f) = removed { + debug!("close_file_like <= count: {}", Arc::strong_count(&f.inner)); + release_locks_on_close(f); + return Ok(()); + } + #[cfg(feature = "ebpf")] + { + if crate::perf_event::perf_event_close(fd as u32).is_ok() { + return Ok(()); + } + crate::ebpf::bpf_close_fd(fd as u32)?; + } Ok(()) } @@ -395,6 +402,11 @@ pub fn close_all_fds() { crate::syscall::wake_lock_waiters(key); crate::syscall::wake_flock_waiters(key); } + #[cfg(feature = "ebpf")] + { + crate::perf_event::perf_event_close_all(); + crate::ebpf::bpf_close_all_fds(); + } } pub fn add_stdio(fd_table: &mut FlattenObjects) -> AxResult<()> { diff --git a/os/StarryOS/kernel/src/kprobe.rs b/os/StarryOS/kernel/src/kprobe.rs index d7ecb2bfa3..2ef857c078 100644 --- a/os/StarryOS/kernel/src/kprobe.rs +++ b/os/StarryOS/kernel/src/kprobe.rs @@ -389,6 +389,11 @@ pub fn handle_breakpoint(tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { let handled = with_manager(|manager| kprobe::kprobe_handler_from_break(manager, &mut pt_regs)); if handled.is_some() { ptregs_write_back(&pt_regs, tf); + #[cfg(feature = "ebpf")] + crate::perf_event::perf_event_trigger_by_type( + crate::perf_event::PERF_TYPE_KPROBE, + &pt_regs as *const _ as u64, + ); return true; } false @@ -468,6 +473,11 @@ pub fn handle_debug(tf: &mut ax_runtime::hal::cpu::TrapFrame) -> bool { let handled = with_manager(|manager| kprobe::kprobe_handler_from_debug(manager, &mut pt_regs)); if handled.is_some() { ptregs_write_back(&pt_regs, tf); + #[cfg(feature = "ebpf")] + crate::perf_event::perf_event_trigger_by_type( + crate::perf_event::PERF_TYPE_KPROBE, + &pt_regs as *const _ as u64, + ); return true; } false diff --git a/os/StarryOS/kernel/src/lib.rs b/os/StarryOS/kernel/src/lib.rs index d55cc33bd4..2024974966 100644 --- a/os/StarryOS/kernel/src/lib.rs +++ b/os/StarryOS/kernel/src/lib.rs @@ -18,9 +18,13 @@ pub mod dyn_debug; // Re-export debug macros for use in other modules. It will o pub mod entry; mod config; +#[cfg(feature = "ebpf")] +mod ebpf; mod file; mod kprobe; mod mm; +#[cfg(feature = "ebpf")] +mod perf_event; mod pseudofs; mod stop_machine; mod syscall; diff --git a/os/StarryOS/kernel/src/perf_event.rs b/os/StarryOS/kernel/src/perf_event.rs new file mode 100644 index 0000000000..9d0e7a5e36 --- /dev/null +++ b/os/StarryOS/kernel/src/perf_event.rs @@ -0,0 +1,508 @@ +//! Performance event ring buffer infrastructure for StarryOS. +//! +//! Provides a Linux-compatible perf event interface for eBPF data output +//! and kernel event sampling. Key components: +//! +//! - **PerfEventMmapPage**: mmap header compatible with Linux's `perf_event_mmap_page` +//! - **RingBuffer**: Wrap-around circular buffer for PERF_RECORD_SAMPLE/LOST records +//! - **perf_event_open syscall**: Supports KPROBE, TRACEPOINT, SOFTWARE, and RAW event types +//! +//! # Public API +//! +//! - `perf_event_write`: Write sample data to a specific perf event fd +//! - `perf_event_close` / `perf_event_fd_exists`: fd lifecycle management + +use alloc::vec::Vec; + +use ax_errno::{AxError, AxResult}; +use ax_sync::spin::SpinNoIrq; + +const PAGE_SIZE: usize = 4096; +const PERF_RECORD_SAMPLE: u32 = 9; +const PERF_RECORD_LOST: u32 = 2; + +#[allow(dead_code)] +const PERF_TYPE_HARDWARE: u32 = 0; +const PERF_TYPE_SOFTWARE: u32 = 1; +pub const PERF_TYPE_TRACEPOINT: u32 = 2; +const PERF_TYPE_RAW: u32 = 4; +pub const PERF_TYPE_KPROBE: u32 = 6; + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct PerfEventHeader { + type_: u32, + misc: u16, + size: u16, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct SampleHeader { + header: PerfEventHeader, + size: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Default)] +struct LostSamples { + header: PerfEventHeader, + id: u64, + count: u64, +} + +#[repr(C)] +struct PerfEventMmapPage { + version: u32, + compat_version: u32, + lock: u32, + index: u32, + offset: i64, + time_enabled: u64, + time_running: u64, + capabilities: u64, + pmc_width: u16, + time_shift: u16, + time_mult: u32, + time_offset: u64, + time_zero: u64, + size: u32, + _reserved_1: u32, + time_cycles: u64, + time_mask: u64, + _reserved: [u8; 928], + data_head: u64, + data_tail: u64, + data_offset: u64, + data_size: u64, + aux_head: u64, + aux_tail: u64, + aux_offset: u64, + aux_size: u64, +} + +const MMAP_PAGE_SIZE: usize = core::mem::size_of::(); + +struct RingBuffer { + pages: Vec, + data_region_size: usize, + lost: u64, +} + +impl RingBuffer { + fn new(page_count: usize) -> AxResult { + if page_count < 2 { + return Err(AxError::InvalidInput); + } + let total_size = page_count * PAGE_SIZE; + let data_region_size = total_size - PAGE_SIZE; + let mut pages = alloc::vec![0u8; total_size]; + let mmap_page = unsafe { &mut *(pages.as_mut_ptr() as *mut PerfEventMmapPage) }; + mmap_page.version = 1; + mmap_page.compat_version = 1; + mmap_page.size = MMAP_PAGE_SIZE as u32; + mmap_page.data_offset = PAGE_SIZE as u64; + mmap_page.data_size = data_region_size as u64; + mmap_page.data_head = 0; + mmap_page.data_tail = 0; + Ok(Self { + pages, + data_region_size, + lost: 0, + }) + } + + fn data_head(&self) -> u64 { + let mmap_page = unsafe { &*(self.pages.as_ptr() as *const PerfEventMmapPage) }; + mmap_page.data_head + } + + fn set_data_head(&mut self, val: u64) { + let mmap_page = unsafe { &mut *(self.pages.as_mut_ptr() as *mut PerfEventMmapPage) }; + mmap_page.data_head = val; + } + + fn data_tail(&self) -> u64 { + let mmap_page = unsafe { &*(self.pages.as_ptr() as *const PerfEventMmapPage) }; + mmap_page.data_tail + } + + fn can_write(&self, needed: usize, tail: u64, head: u64) -> bool { + let capacity = self.data_region_size as u64; + (capacity - (head - tail)) as usize >= needed + } + + fn write_bytes(&mut self, data: &[u8], offset_in_data_region: usize) { + if data.is_empty() { + return; + } + let start = offset_in_data_region % self.data_region_size; + let data_ptr = unsafe { self.pages.as_mut_ptr().add(PAGE_SIZE) }; + if start + data.len() <= self.data_region_size { + unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr.add(start), data.len()); + } + } else { + let first_len = self.data_region_size - start; + unsafe { + core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr.add(start), first_len); + core::ptr::copy_nonoverlapping( + data.as_ptr().add(first_len), + data_ptr, + data.len() - first_len, + ); + } + } + } + + fn fill_size(&self, head_mod: usize) -> usize { + let remaining = self.data_region_size - head_mod; + if remaining > 0 && remaining < core::mem::size_of::() { + remaining + } else { + 0 + } + } + + fn write_sample(&mut self, data: &[u8], head: u64) -> AxResult { + let head_mod = (head as usize) % self.data_region_size; + let fill = self.fill_size(head_mod); + let total_size = core::mem::size_of::() + data.len() + fill; + let hdr = SampleHeader { + header: PerfEventHeader { + type_: PERF_RECORD_SAMPLE, + misc: 0, + size: total_size as u16, + }, + size: data.len() as u32, + }; + let hdr_bytes = unsafe { + core::slice::from_raw_parts( + &hdr as *const SampleHeader as *const u8, + core::mem::size_of::(), + ) + }; + self.write_bytes(hdr_bytes, head_mod); + let data_offset = (head_mod + core::mem::size_of::()) % self.data_region_size; + self.write_bytes(data, data_offset); + if fill > 0 { + let fill_offset = (head_mod + total_size - fill) % self.data_region_size; + let zeros = alloc::vec![0u8; fill]; + self.write_bytes(&zeros, fill_offset); + } + Ok(head + total_size as u64) + } + + fn write_lost(&mut self, head: u64, count: u64) -> AxResult { + let head_mod = (head as usize) % self.data_region_size; + let lost = LostSamples { + header: PerfEventHeader { + type_: PERF_RECORD_LOST, + misc: 0, + size: core::mem::size_of::() as u16, + }, + id: 0, + count, + }; + let lost_bytes = unsafe { + core::slice::from_raw_parts( + &lost as *const LostSamples as *const u8, + core::mem::size_of::(), + ) + }; + self.write_bytes(lost_bytes, head_mod); + Ok(head + core::mem::size_of::() as u64) + } + + fn write_event(&mut self, data: &[u8]) -> AxResult<()> { + let tail = self.data_tail(); + let mut head = self.data_head(); + let hdr_size = core::mem::size_of::(); + if !self.can_write(hdr_size, tail, head) { + self.lost += 1; + return Ok(()); + } + if self.lost > 0 { + let lost_size = core::mem::size_of::(); + if self.can_write(lost_size, tail, head) { + head = self.write_lost(head, self.lost)?; + self.lost = 0; + } + } + let sample_size = core::mem::size_of::() + data.len(); + let head_mod = (head as usize) % self.data_region_size; + let fill = self.fill_size(head_mod); + let total = sample_size + fill; + if self.can_write(total, tail, head) { + head = self.write_sample(data, head)?; + } else { + self.lost += 1; + } + self.set_data_head(head); + Ok(()) + } + + #[allow(dead_code)] + fn readable(&self) -> bool { + self.data_head() != self.data_tail() + } +} + +struct PerfEvent { + ring: RingBuffer, + enabled: bool, + prog_fd: Option, +} + +impl PerfEvent { + fn new(page_count: usize) -> AxResult { + Ok(Self { + ring: RingBuffer::new(page_count)?, + enabled: false, + prog_fd: None, + }) + } + + fn enable(&mut self) { + self.enabled = true; + } + + fn disable(&mut self) { + self.enabled = false; + } + + fn write_event(&mut self, data: &[u8]) -> AxResult<()> { + if !self.enabled { + return Ok(()); + } + self.ring.write_event(data) + } + + fn trigger(&mut self, ctx: u64) { + if !self.enabled { + return; + } + if let Some(prog_fd) = self.prog_fd + && let Err(e) = crate::ebpf::run_bpf_prog(prog_fd, ctx) + { + warn!("perf_event: BPF prog {prog_fd} execution failed: {:?}", e); + } + } + + fn attach_prog(&mut self, prog_fd: u32) { + self.prog_fd = Some(prog_fd); + } + + #[allow(dead_code)] + fn attached_prog(&self) -> Option { + self.prog_fd + } +} + +struct PerfEventEntry { + event: PerfEvent, + fd: u32, + #[allow(dead_code)] + event_type: u32, + #[allow(dead_code)] + config: u64, + #[allow(dead_code)] + pid: i32, + #[allow(dead_code)] + cpu: i32, +} + +struct PerfFdTable { + events: Vec, + next_fd: u32, + free_fds: alloc::vec::Vec, +} + +impl PerfFdTable { + const fn new() -> Self { + Self { + events: Vec::new(), + next_fd: 3, + free_fds: alloc::vec::Vec::new(), + } + } + + fn alloc_fd(&mut self) -> u32 { + if let Some(fd) = self.free_fds.pop() { + fd + } else { + let fd = self.next_fd; + self.next_fd += 1; + fd + } + } + + fn insert(&mut self, entry: PerfEventEntry) -> u32 { + let fd = self.alloc_fd(); + self.events.push(PerfEventEntry { fd, ..entry }); + fd + } + + fn close_fd(&mut self, fd: u32) -> AxResult<()> { + let idx = self.events.iter().position(|e| e.fd == fd); + match idx { + Some(i) => { + self.events.remove(i); + self.free_fds.push(fd); + Ok(()) + } + None => Err(AxError::BadFileDescriptor), + } + } + + #[allow(dead_code)] + fn fd_exists(&self, fd: u32) -> bool { + self.events.iter().any(|e| e.fd == fd) + } + + fn close_all(&mut self) { + self.events.clear(); + self.free_fds.clear(); + } +} + +static PERF_EVENTS: SpinNoIrq = SpinNoIrq::new(PerfFdTable::new()); + +pub fn sys_perf_event_open_impl( + attr_uptr: usize, + pid: i32, + cpu: i32, + group_fd: i32, + flags: u64, +) -> AxResult { + let (event_type, config) = unsafe { + if attr_uptr == 0 { + return Err(AxError::InvalidInput); + } + let ptr = attr_uptr as *const u32; + let event_type = core::ptr::read(ptr); + let size = core::ptr::read(ptr.add(1)); + if size < 16 { + return Err(AxError::InvalidInput); + } + let config = core::ptr::read((ptr as *const u64).add(1)); + (event_type, config) + }; + match event_type { + PERF_TYPE_KPROBE | PERF_TYPE_TRACEPOINT | PERF_TYPE_SOFTWARE | PERF_TYPE_RAW => {} + _ => { + warn!("perf_event_open: unsupported type {event_type}"); + return Err(AxError::Unsupported); + } + } + let page_count = 2 + 1; + let event = PerfEvent::new(page_count)?; + let entry = PerfEventEntry { + event, + fd: 0, + event_type, + config, + pid, + cpu, + }; + if group_fd >= 0 { + let _ = group_fd; + } + let _ = flags; + let fd = PERF_EVENTS.lock().insert(entry); + info!("perf_event_open: type={event_type} config={config:#x} pid={pid} cpu={cpu} fd={fd}"); + Ok(fd as isize) +} + +pub fn perf_event_write(fd: u32, data: &[u8]) -> AxResult<()> { + let mut guard = PERF_EVENTS.lock(); + for entry in guard.events.iter_mut() { + if entry.fd == fd { + return entry.event.write_event(data); + } + } + Err(AxError::BadFileDescriptor) +} + +pub fn perf_event_close(fd: u32) -> AxResult<()> { + let mut guard = PERF_EVENTS.lock(); + guard.close_fd(fd)?; + info!("perf_event_close: fd={fd}"); + Ok(()) +} + +#[allow(dead_code)] +pub fn perf_event_fd_exists(fd: u32) -> bool { + let guard = PERF_EVENTS.lock(); + guard.fd_exists(fd) +} + +pub fn perf_event_enable(fd: u32) -> AxResult<()> { + let mut guard = PERF_EVENTS.lock(); + for entry in guard.events.iter_mut() { + if entry.fd == fd { + entry.event.enable(); + return Ok(()); + } + } + Err(AxError::BadFileDescriptor) +} + +pub fn perf_event_disable(fd: u32) -> AxResult<()> { + let mut guard = PERF_EVENTS.lock(); + for entry in guard.events.iter_mut() { + if entry.fd == fd { + entry.event.disable(); + return Ok(()); + } + } + Err(AxError::BadFileDescriptor) +} + +pub fn perf_event_attach_prog(fd: u32, prog_fd: u32) -> AxResult<()> { + let mut guard = PERF_EVENTS.lock(); + for entry in guard.events.iter_mut() { + if entry.fd == fd { + entry.event.attach_prog(prog_fd); + return Ok(()); + } + } + Err(AxError::BadFileDescriptor) +} + +#[allow(dead_code)] +pub fn perf_event_get_prog_fd(fd: u32) -> AxResult> { + let guard = PERF_EVENTS.lock(); + for entry in guard.events.iter() { + if entry.fd == fd { + return Ok(entry.event.attached_prog()); + } + } + Err(AxError::BadFileDescriptor) +} + +pub fn perf_event_close_all() { + PERF_EVENTS.lock().close_all(); +} + +#[allow(dead_code)] +pub fn perf_event_trigger_by_fd(fd: u32, ctx: u64) -> AxResult<()> { + let mut guard = PERF_EVENTS.lock(); + for entry in guard.events.iter_mut() { + if entry.fd == fd { + entry.event.trigger(ctx); + return Ok(()); + } + } + Err(AxError::BadFileDescriptor) +} + +#[allow(dead_code)] +pub fn perf_event_trigger_by_type(event_type: u32, ctx: u64) { + let mut guard = PERF_EVENTS.lock(); + for entry in guard.events.iter_mut() { + if entry.event_type == event_type { + entry.event.trigger(ctx); + } + } +} diff --git a/os/StarryOS/kernel/src/syscall/mod.rs b/os/StarryOS/kernel/src/syscall/mod.rs index 5d173fa906..e0de308173 100644 --- a/os/StarryOS/kernel/src/syscall/mod.rs +++ b/os/StarryOS/kernel/src/syscall/mod.rs @@ -768,14 +768,25 @@ pub fn handle_syscall(uctx: &mut UserContext) { // dummy fds Sysno::userfaultfd - | Sysno::perf_event_open | Sysno::io_uring_setup - | Sysno::bpf | Sysno::fsopen | Sysno::fspick | Sysno::open_tree | Sysno::memfd_secret => sys_dummy_fd(sysno), + #[cfg(feature = "ebpf")] + Sysno::bpf => crate::ebpf::sys_bpf(uctx.arg0() as _, uctx.arg1(), uctx.arg2() as _), + #[cfg(feature = "ebpf")] + Sysno::perf_event_open => crate::ebpf::sys_perf_event_open( + uctx.arg0(), + uctx.arg1() as _, + uctx.arg2() as _, + uctx.arg3() as _, + uctx.arg4() as _, + ), + #[cfg(not(feature = "ebpf"))] + Sysno::bpf | Sysno::perf_event_open => sys_dummy_fd(sysno), + Sysno::fanotify_init => Err(AxError::Unsupported), Sysno::timer_create => { diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/CMakeLists.txt new file mode 100644 index 0000000000..61bd3360cf --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ebpf-basics C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-ebpf-basics src/main.c) +target_include_directories(test-ebpf-basics PRIVATE src) +target_compile_options(test-ebpf-basics PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-ebpf-basics RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/src/main.c new file mode 100644 index 0000000000..0205492a29 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-basics/c/src/main.c @@ -0,0 +1,405 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define MODULE_START(name) \ + printf("\n--- MODULE: %s ---\n", name) + +#define SUMMARY() \ + printf("\n=== SUMMARY: %d passed, %d failed ===\n", __pass, __fail); \ + return __fail > 0 ? 1 : 0 + +static long raw_bpf(uint64_t cmd, void *attr, uint32_t size) { +#ifdef SYS_bpf + return syscall(SYS_bpf, cmd, attr, size); +#else + errno = ENOSYS; + return -1; +#endif +} + +struct bpf_map_create_attr { + uint32_t map_type; + uint32_t key_size; + uint32_t value_size; + uint32_t max_entries; + uint32_t map_flags; +}; + +struct bpf_map_elem_attr { + uint64_t map_fd; + uint64_t key; + uint64_t value; + uint64_t flags; +}; + +struct bpf_map_next_key_attr { + uint64_t map_fd; + uint64_t key; + uint64_t next_key; +}; + +struct bpf_prog_load_attr { + uint32_t prog_type; + uint32_t insn_cnt; + uint64_t insns; + uint64_t license; + uint32_t log_level; + uint32_t log_size; + uint64_t log_buf; + uint32_t kern_version; + uint32_t prog_flags; +}; + +struct bpf_insn { + uint8_t code; + uint8_t dst_src_reg; + int16_t off; + int32_t imm; +}; + +#define BPF_MAP_TYPE_HASH 1 +#define BPF_MAP_TYPE_ARRAY 2 + +#define BPF_PROG_TYPE_KPROBE 2 +#define BPF_PROG_TYPE_RAW_TRACEPOINT 17 + +#define BPF_MAP_CREATE 0 +#define BPF_MAP_LOOKUP_ELEM 1 +#define BPF_MAP_UPDATE_ELEM 2 +#define BPF_MAP_DELETE_ELEM 3 +#define BPF_MAP_GET_NEXT_KEY 4 +#define BPF_PROG_LOAD 5 +#define BPF_PROG_ATTACH 8 +#define BPF_PROG_DETACH 9 +#define BPF_RAW_TRACEPOINT_OPEN 17 + +#define BPF_ANY 0 +#define BPF_EXISTS 2 + +#define BPF_ALU64 0x07 +#define BPF_MOV 0xb0 +#define BPF_K 0x00 +#define BPF_JMP 0x05 +#define BPF_JA 0x00 +#define BPF_EXIT 0x90 +#define BPF_ST 0x02 +#define BPF_MEM 0x60 +#define BPF_DW 0x18 +#define BPF_JEQ 0x10 +#define BPF_X 0x08 +#define BPF_ADD 0x00 +#define BPF_ALU 0x04 +#define BPF_STX 0x03 +#define BPF_W 0x00 +#define BPF_LD 0x00 +#define BPF_IMM 0x00 + +static struct bpf_insn make_insn(uint8_t code, uint8_t dst, uint8_t src, int16_t off, int32_t imm) { + struct bpf_insn i; + i.code = code; + i.dst_src_reg = (dst & 0xf) | ((src & 0xf) << 4); + i.off = off; + i.imm = imm; + return i; +} + +static void test_map_create_array(void) { + MODULE_START("map_create_array"); + struct bpf_map_create_attr attr = { + .map_type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = 8, + .max_entries = 16, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + CHECK(fd >= 0, "BPF_MAP_CREATE array returns valid fd"); + if (fd >= 0) close(fd); +} + +static void test_map_create_hash(void) { + MODULE_START("map_create_hash"); + struct bpf_map_create_attr attr = { + .map_type = BPF_MAP_TYPE_HASH, + .key_size = 4, + .value_size = 8, + .max_entries = 64, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + CHECK(fd >= 0, "BPF_MAP_CREATE hash returns valid fd"); + if (fd >= 0) close(fd); +} + +static void test_map_update_lookup_array(void) { + MODULE_START("map_update_lookup_array"); + struct bpf_map_create_attr create = { + .map_type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = 8, + .max_entries = 16, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &create, sizeof(create)); + CHECK(fd >= 0, "create array map"); + if (fd < 0) return; + + uint32_t key = 3; + uint64_t value = 0xDEADBEEFCAFE1234ULL; + struct bpf_map_elem_attr upd = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = (uint64_t)&value, + .flags = BPF_ANY, + }; + long r = raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + CHECK(r == 0, "update array[3] = 0xDEADBEEF..."); + + uint64_t got = 0; + struct bpf_map_elem_attr lookup = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = (uint64_t)&got, + .flags = 0, + }; + r = raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); + CHECK(r == 0, "lookup array[3] succeeds"); + CHECK(got == 0xDEADBEEFCAFE1234ULL, "lookup returns correct value"); + + key = 3; + value = 42; + upd.value = (uint64_t)&value; + raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + + got = 0; + raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); + CHECK(got == 42, "update-then-lookup returns 42"); + + close(fd); +} + +static void test_map_update_lookup_hash(void) { + MODULE_START("map_update_lookup_hash"); + struct bpf_map_create_attr create = { + .map_type = BPF_MAP_TYPE_HASH, + .key_size = 4, + .value_size = 8, + .max_entries = 64, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &create, sizeof(create)); + CHECK(fd >= 0, "create hash map"); + if (fd < 0) return; + + uint32_t key = 100; + uint64_t value = 9999; + struct bpf_map_elem_attr upd = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = (uint64_t)&value, + .flags = BPF_ANY, + }; + long r = raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + CHECK(r == 0, "hash update key=100 val=9999"); + + uint64_t got = 0; + struct bpf_map_elem_attr lookup = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = (uint64_t)&got, + .flags = 0, + }; + r = raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); + CHECK(r == 0, "hash lookup key=100 succeeds"); + CHECK(got == 9999, "hash lookup returns 9999"); + + struct bpf_map_elem_attr del = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = 0, + .flags = 0, + }; + r = raw_bpf(BPF_MAP_DELETE_ELEM, &del, sizeof(del)); + CHECK(r == 0, "hash delete key=100 succeeds"); + + got = 0; + r = raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); + CHECK(r < 0, "hash lookup after delete fails"); + + close(fd); +} + +static void test_map_get_next_key(void) { + MODULE_START("map_get_next_key"); + struct bpf_map_create_attr create = { + .map_type = BPF_MAP_TYPE_HASH, + .key_size = 4, + .value_size = 4, + .max_entries = 64, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &create, sizeof(create)); + CHECK(fd >= 0, "create hash map for iteration"); + if (fd < 0) return; + + for (uint32_t i = 10; i <= 30; i += 10) { + uint32_t val = i * 100; + struct bpf_map_elem_attr upd = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&i, + .value = (uint64_t)&val, + .flags = BPF_ANY, + }; + raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + } + + uint32_t first_key = 0; + struct bpf_map_next_key_attr nk = { + .map_fd = (uint64_t)fd, + .key = 0, + .next_key = (uint64_t)&first_key, + }; + long r = raw_bpf(BPF_MAP_GET_NEXT_KEY, &nk, sizeof(nk)); + CHECK(r == 0, "get_first_key (NULL key) succeeds"); + int count = 0; + if (r == 0) count = 1; + + uint32_t cur = first_key; + for (int iter = 0; iter < 10; iter++) { + nk.key = (uint64_t)&cur; + nk.next_key = (uint64_t)&first_key; + r = raw_bpf(BPF_MAP_GET_NEXT_KEY, &nk, sizeof(nk)); + if (r != 0) break; + count++; + cur = first_key; + } + CHECK(count == 3, "iterating hash map yields 3 keys"); + + close(fd); +} + +static void test_prog_load(void) { + MODULE_START("prog_load"); + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 42), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + struct bpf_prog_load_attr attr = { + .prog_type = BPF_PROG_TYPE_KPROBE, + .insn_cnt = sizeof(prog) / sizeof(prog[0]), + .insns = (uint64_t)prog, + .license = 0, + .log_level = 0, + .log_size = 0, + .log_buf = 0, + .kern_version = 0, + .prog_flags = 0, + }; + long fd = raw_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); + CHECK(fd >= 0, "BPF_PROG_LOAD simple return-42 program"); + if (fd >= 0) close(fd); +} + +static void test_prog_load_with_alu(void) { + MODULE_START("prog_load_with_alu"); + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 10), + make_insn(BPF_ALU64 | BPF_ADD | BPF_K, 0, 0, 0, 20), + make_insn(BPF_ALU64 | BPF_ADD | BPF_K, 0, 0, 0, 12), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + struct bpf_prog_load_attr attr = { + .prog_type = BPF_PROG_TYPE_KPROBE, + .insn_cnt = sizeof(prog) / sizeof(prog[0]), + .insns = (uint64_t)prog, + .license = 0, + .log_level = 0, + .log_size = 0, + .log_buf = 0, + .kern_version = 0, + .prog_flags = 0, + }; + long fd = raw_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); + CHECK(fd >= 0, "BPF_PROG_LOAD alu program (10+20+12)"); + if (fd >= 0) close(fd); +} + +static void test_map_create_invalid(void) { + MODULE_START("map_create_invalid"); + struct bpf_map_create_attr attr = { + .map_type = 99, + .key_size = 4, + .value_size = 4, + .max_entries = 10, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + CHECK(fd < 0, "BPF_MAP_CREATE with invalid type returns error"); + + attr.map_type = BPF_MAP_TYPE_HASH; + attr.key_size = 0; + fd = raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + CHECK(fd < 0, "BPF_MAP_CREATE with key_size=0 returns error"); +} + +static void test_map_operations_invalid(void) { + MODULE_START("map_operations_invalid"); + uint32_t key = 0; + uint64_t val = 0; + struct bpf_map_elem_attr lookup = { + .map_fd = 9999, + .key = (uint64_t)&key, + .value = (uint64_t)&val, + .flags = 0, + }; + long r = raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); + CHECK(r < 0, "lookup on invalid fd returns error"); + + struct bpf_map_elem_attr upd = { + .map_fd = 9999, + .key = (uint64_t)&key, + .value = (uint64_t)&val, + .flags = 0, + }; + r = raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + CHECK(r < 0, "update on invalid fd returns error"); +} + +int main(void) { + printf("=== eBPF Basics Test Suite ===\n"); + + test_map_create_array(); + test_map_create_hash(); + test_map_update_lookup_array(); + test_map_update_lookup_hash(); + test_map_get_next_key(); + test_prog_load(); + test_prog_load_with_alu(); + test_map_create_invalid(); + test_map_operations_invalid(); + + SUMMARY(); +} From 6027c1248f859143679c2c92043cd896c1f63c90 Mon Sep 17 00:00:00 2001 From: date727 <2530901983@qq.com> Date: Wed, 27 May 2026 10:50:26 +0800 Subject: [PATCH 50/71] fix(starry-kernel): validate sync_file_range flags and offsets (#823) * fsync first commit * fsync second commit * Parameter Validation * fsync third commit * fsync fourth commit * harden shm-deadlock watchdog * Resolve the conflict * Fix Memfd fd * Avoid holding SHM_MANAGER during shmat mapping * Avoid false shm deadlock timeout * chore: rerun CI * chore: rerun CI --- os/StarryOS/kernel/src/syscall/fs/io.rs | 21 ++++++-- os/StarryOS/kernel/src/syscall/ipc/shm.rs | 33 +++++++++--- .../syscall/test-fsync-dir/c/src/main.c | 53 +++++++++++++++++-- .../qemu-smp4/test-shm-deadlock/c/src/main.c | 10 +++- 4 files changed, 100 insertions(+), 17 deletions(-) diff --git a/os/StarryOS/kernel/src/syscall/fs/io.rs b/os/StarryOS/kernel/src/syscall/fs/io.rs index a07ab189cb..550e476ccd 100644 --- a/os/StarryOS/kernel/src/syscall/fs/io.rs +++ b/os/StarryOS/kernel/src/syscall/fs/io.rs @@ -317,8 +317,19 @@ pub fn sys_fdatasync(fd: c_int) -> AxResult { Err(AxError::from(LinuxError::EINVAL)) } -pub fn sys_sync_file_range(fd: c_int, _offset: i64, _nbytes: i64, flags: u32) -> AxResult { +pub fn sys_sync_file_range(fd: c_int, offset: i64, nbytes: i64, flags: u32) -> AxResult { debug!("sys_sync_file_range <= fd: {fd}, flags: {flags:#x}"); + const SYNC_FILE_RANGE_WAIT_BEFORE: u32 = 1; + const SYNC_FILE_RANGE_WRITE: u32 = 2; + const SYNC_FILE_RANGE_WAIT_AFTER: u32 = 4; + const SYNC_FILE_RANGE_ALL: u32 = + SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER; + if offset < 0 || nbytes < 0 { + return Err(AxError::from(LinuxError::EINVAL)); + } + if (flags & !SYNC_FILE_RANGE_ALL) != 0 { + return Err(AxError::from(LinuxError::EINVAL)); + } // sync_file_range(2) is an advisory hint to initiate writeback for a // byte range. Until range-based writeback is implemented, keep this as // a no-op after basic fd validation rather than turning it into a @@ -326,10 +337,10 @@ pub fn sys_sync_file_range(fd: c_int, _offset: i64, _nbytes: i64, flags: u32) -> // nature documented in the man page). Invalid fds still surface the // underlying error (EBADF). Directory fds are accepted to match fsync. let any = get_file_like(fd)?; - if flags & !0x7 != 0 { - return Err(AxError::from(LinuxError::EINVAL)); - } - if any.downcast_ref::().is_none() && any.downcast_ref::().is_none() { + if any.downcast_ref::().is_none() + && any.downcast_ref::().is_none() + && any.downcast_ref::().is_none() + { return Err(AxError::from(LinuxError::ESPIPE)); } Ok(0) diff --git a/os/StarryOS/kernel/src/syscall/ipc/shm.rs b/os/StarryOS/kernel/src/syscall/ipc/shm.rs index 2e746e1793..138e070b51 100644 --- a/os/StarryOS/kernel/src/syscall/ipc/shm.rs +++ b/os/StarryOS/kernel/src/syscall/ipc/shm.rs @@ -497,15 +497,20 @@ pub fn sys_shmat(shmid: i32, addr: usize, shmflg: u32) -> AxResult { let proc_data = &curr.as_thread().proc_data; let pid = proc_data.proc.pid(); - // Lock SHM_MANAGER first, then shm_inner, then aspace. This matches - // the ordering used by sys_shmget and avoids the AB/BA deadlock that - // the old code caused by locking shm_inner before SHM_MANAGER. - let mut shm_manager = SHM_MANAGER.lock(); - let shm_inner_arc = shm_manager - .get_inner_by_shmid(shmid) - .ok_or(AxError::InvalidInput)?; + info!("shmat pid={pid} shmid={shmid} enter"); + + // Grab the shm_inner Arc under SHM_MANAGER, then drop it before + // mapping work to avoid holding the global lock across aspace ops. + let shm_inner_arc = { + let shm_manager = SHM_MANAGER.lock(); + shm_manager + .get_inner_by_shmid(shmid) + .ok_or(AxError::InvalidInput)? + }; + info!("shmat pid={pid} shmid={shmid} lock shm_inner"); let mut shm_inner = shm_inner_arc.lock(); let aspace_arc = proc_data.aspace(); + info!("shmat pid={pid} shmid={shmid} lock aspace"); let mut aspace = aspace_arc.lock(); let mut mapping_flags = shm_inner.mapping_flags; @@ -541,7 +546,6 @@ pub fn sys_shmat(shmid: i32, addr: usize, shmflg: u32) -> AxResult { let end_addr = VirtAddr::from(start_addr.as_usize() + length); let va_range = VirtAddrRange::new(start_addr, end_addr); - shm_manager.insert_shmid_vaddr(pid, shm_inner.shmid, start_addr); info!( "Process {} alloc shm virt addr start: {:#x}, size: {}, mapping_flags: {:#x?}", pid, @@ -565,7 +569,15 @@ pub fn sys_shmat(shmid: i32, addr: usize, shmflg: u32) -> AxResult { shm_inner.map_to_phys(pages); } + info!("shmat pid={pid} shmid={shmid} mapped; attach_process"); shm_inner.attach_process(pid, va_range)?; + drop(aspace); + drop(shm_inner); + + info!("shmat pid={pid} shmid={shmid} lock shm_manager for vaddr"); + let mut shm_manager = SHM_MANAGER.lock(); + shm_manager.insert_shmid_vaddr(pid, shmid, start_addr); + info!("shmat pid={pid} shmid={shmid} done"); Ok(start_addr.as_usize() as isize) } @@ -641,6 +653,8 @@ pub fn sys_shmdt(shmaddr: usize) -> AxResult { let proc_data = &curr.as_thread().proc_data; let pid = proc_data.proc.pid(); + info!("shmdt pid={pid} addr={shmaddr:?} enter"); + // Look up shmid and grab the inner Arc while holding SHM_MANAGER. let (shmid, shm_inner_arc) = { let shm_manager = SHM_MANAGER.lock(); @@ -655,12 +669,14 @@ pub fn sys_shmdt(shmaddr: usize) -> AxResult { // Snapshot the mapped range for this process. let va_range = { + info!("shmdt pid={pid} lock shm_inner for range"); let shm_inner = shm_inner_arc.lock(); shm_inner.get_addr_range(pid).ok_or(AxError::InvalidInput)? }; // Unmap while only holding the aspace lock. { + info!("shmdt pid={pid} lock aspace for unmap"); let aspace_arc = proc_data.aspace(); let mut aspace = aspace_arc.lock(); aspace.unmap(va_range.start, va_range.size())?; @@ -668,6 +684,7 @@ pub fn sys_shmdt(shmaddr: usize) -> AxResult { // Reacquire SHM_MANAGER then shm_inner for bookkeeping, matching // the global lock ordering. + info!("shmdt pid={pid} lock shm_manager for bookkeeping"); let mut shm_manager = SHM_MANAGER.lock(); shm_manager.remove_shmaddr(pid, shmaddr); let mut shm_inner = shm_inner_arc.lock(); diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync-dir/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync-dir/c/src/main.c index 285054bebb..c3059c1113 100644 --- a/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync-dir/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-fsync-dir/c/src/main.c @@ -4,14 +4,19 @@ * 测试文件系统修复: * 1. fsync 对目录 fd 应返回成功 (Linux 允许) * 2. fdatasync 对目录 fd 应返回成功 - * 3. sync_file_range 应返回成功 (建议性优化) + * 3. 非法 fd 应返回 EBADF + * 4. pipe fd 应返回 EINVAL + * 5. socket fd 应返回 EINVAL + * 6. sync_file_range 应返回成功 (建议性优化) */ #include "test_framework.h" #include #include -#include +#include #include +#include +#include int main(void) { @@ -40,7 +45,41 @@ int main(void) unlink("/tmp/starry_fsync_test/file"); } - /* Test 3: sync_file_range */ + /* Test 3: invalid fd handling */ + { + CHECK_ERR(fsync(-1), EBADF, "fsync on invalid fd -> EBADF"); + CHECK_ERR(fdatasync(-1), EBADF, "fdatasync on invalid fd -> EBADF"); + } + + /* Test 4: fsync on pipe -> EINVAL (Linux behavior) */ + { + int pfd[2]; + CHECK(pipe(pfd) == 0, "create pipe for fsync EINVAL"); + if (pfd[0] >= 0) { + CHECK_ERR(fsync(pfd[0]), EINVAL, "fsync on pipe -> EINVAL"); + CHECK_ERR(fdatasync(pfd[0]), EINVAL, "fdatasync on pipe -> EINVAL"); + close(pfd[0]); + } + if (pfd[1] >= 0) { + close(pfd[1]); + } + } + + /* Test 5: fsync on socket -> EINVAL (Linux behavior) */ + { + int sfd[2]; + CHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == 0, "create socketpair for fsync EINVAL"); + if (sfd[0] >= 0) { + CHECK_ERR(fsync(sfd[0]), EINVAL, "fsync on socket -> EINVAL"); + CHECK_ERR(fdatasync(sfd[0]), EINVAL, "fdatasync on socket -> EINVAL"); + close(sfd[0]); + } + if (sfd[1] >= 0) { + close(sfd[1]); + } + } + + /* Test 6: sync_file_range */ { int fd = open("/tmp/starry_fsync_test/sfrfile", O_RDWR | O_CREAT | O_TRUNC, 0644); CHECK(fd >= 0, "create file for sync_file_range"); @@ -50,6 +89,14 @@ int main(void) * SYNC_FILE_RANGE_WRITE = 2 */ long rc = syscall(SYS_sync_file_range, fd, 0, 29, 2); CHECK(rc == 0, "sync_file_range returns 0"); + CHECK_RET(syscall(SYS_sync_file_range, fd, 0, 29, 0), 0, + "sync_file_range flags==0 -> success"); + CHECK_ERR(syscall(SYS_sync_file_range, fd, 0, 29, 0x8000), EINVAL, + "sync_file_range invalid flags -> EINVAL"); + CHECK_ERR(syscall(SYS_sync_file_range, fd, (off_t)-1, 29, 2), EINVAL, + "sync_file_range negative offset -> EINVAL"); + CHECK_ERR(syscall(SYS_sync_file_range, fd, 0, (off_t)-1, 2), EINVAL, + "sync_file_range negative nbytes -> EINVAL"); close(fd); unlink("/tmp/starry_fsync_test/sfrfile"); } diff --git a/test-suit/starryos/normal/qemu-smp4/test-shm-deadlock/c/src/main.c b/test-suit/starryos/normal/qemu-smp4/test-shm-deadlock/c/src/main.c index 7d09c7e084..d29d8d681c 100644 --- a/test-suit/starryos/normal/qemu-smp4/test-shm-deadlock/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp4/test-shm-deadlock/c/src/main.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ static volatile int g_running = 1; static volatile int g_shmid = -1; static volatile int g_deadlock_detected = 0; static volatile int g_shmat_started = 0; +static volatile int g_threads_done = 0; /* Thread stack size */ #define STACK_SIZE (64 * 1024) @@ -90,7 +92,7 @@ static int watchdog_thread(void *arg) { for (int i = 0; i < SHM_ALARM_SEC * 10; i++) { usleep(100000); - if (!g_running) { + if (g_threads_done) { return 0; } } @@ -159,6 +161,12 @@ int main(void) int status; waitpid(tid1, &status, __WALL); waitpid(tid2, &status, __WALL); + g_threads_done = 1; + /* + * Ensure watchdog exits even if CLONE_VM isn't honored + * on this platform; avoids false timeouts. + */ + kill(tid3, SIGKILL); waitpid(tid3, &status, __WALL); CHECK(!g_deadlock_detected, "no deadlock detected"); From ea862a2afadfcd874de22555d09ab88e12424f09 Mon Sep 17 00:00:00 2001 From: Hong Song <93065880+SongShiQ@users.noreply.github.com> Date: Wed, 27 May 2026 10:59:49 +0800 Subject: [PATCH 51/71] =?UTF-8?q?test(starry):=20=E6=B7=BB=E5=8A=A0=20sqli?= =?UTF-8?q?te3=20CLI=20=E5=A4=9A=E6=9E=B6=E6=9E=84=E5=8E=8B=E5=8A=9B?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=85=8D=E7=BD=AE=20(#895)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(starry): add sqlite3 smoke test config * test(starry): add sqlite3 CLI stress tests (all 4 architectures) Add sqlite3-smoke (S0-S4) and sqlite3-deep (S5-S8) test configs for: - aarch64 (baseline) - x86_64 - riscv64 - loongarch64 Test results (QEMU 10.2.1): - aarch64: smoke PASS, deep PASS - x86_64: smoke PASS, deep PASS - riscv64: smoke PASS, deep PASS - loongarch64: smoke PASS, deep PASS (timeout=600s) sqlite3-deep tests cover: - S5: DELETE journal + COMMIT + ROLLBACK - S6: WAL mode + reopen query - S7: 500-row bulk insert + integrity_check - S8: 64KiB BLOB + reopen query * fix: address review feedback - Fix loongarch64 timeout 300→600 for smoke and deep tests - Change apk add sqlite to apk add --no-cache sqlite - Scripts already consistent across architectures (verified) * ci: increase loongarch64 apk-curl qemu memory to 2G * fix: revert loongarch64 memory to 512M per reviewer feedback Reviewer ZR233 noted 2G is unnecessary. Verified 512M passes on all 4 architectures: - aarch64: PASS (32.41s) - x86_64: PASS (32.80s) - riscv64: PASS (35.38s) - loongarch64: PASS (30.14s) * fix(starry): align sqlite3 stress build features with stress-ng-0 baseline sqlite3-smoke/sqlite3-deep build configs had only features=["qemu"], which enables defplat but not virtio-blk/virtio-net drivers. The guest kernel could not detect the rootfs block device, causing panic: "failed to determine root device from available block devices". This patch aligns all 8 build configs (smoke+deep, 4 architectures) with the passing stress-ng-0 baseline: explicit arch-specific platform feature + ax-driver/virtio-blk/virtio-net/pci/etc, plat_dyn=false. --------- Co-authored-by: root --- .../build-aarch64-unknown-none-softfloat.toml | 14 ++++ ...ld-loongarch64-unknown-none-softfloat.toml | 14 ++++ .../build-riscv64gc-unknown-none-elf.toml | 14 ++++ .../build-x86_64-unknown-none.toml | 14 ++++ .../sqlite3-deep/qemu-aarch64.toml | 79 +++++++++++++++++++ .../sqlite3-deep/qemu-loongarch64.toml | 69 ++++++++++++++++ .../sqlite3-deep/qemu-riscv64.toml | 67 ++++++++++++++++ .../sqlite3-deep/qemu-x86_64.toml | 67 ++++++++++++++++ .../build-aarch64-unknown-none-softfloat.toml | 14 ++++ ...ld-loongarch64-unknown-none-softfloat.toml | 14 ++++ .../build-riscv64gc-unknown-none-elf.toml | 14 ++++ .../build-x86_64-unknown-none.toml | 14 ++++ .../sqlite3-smoke/qemu-aarch64.toml | 49 ++++++++++++ .../sqlite3-smoke/qemu-loongarch64.toml | 51 ++++++++++++ .../sqlite3-smoke/qemu-riscv64.toml | 49 ++++++++++++ .../sqlite3-smoke/qemu-x86_64.toml | 49 ++++++++++++ 16 files changed, 592 insertions(+) create mode 100644 test-suit/starryos/stress/sqlite3-deep/build-aarch64-unknown-none-softfloat.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/build-loongarch64-unknown-none-softfloat.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/build-riscv64gc-unknown-none-elf.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/build-x86_64-unknown-none.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-aarch64.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-loongarch64.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-riscv64.toml create mode 100644 test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-x86_64.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/build-aarch64-unknown-none-softfloat.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/build-loongarch64-unknown-none-softfloat.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/build-riscv64gc-unknown-none-elf.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/build-x86_64-unknown-none.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-aarch64.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-loongarch64.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-riscv64.toml create mode 100644 test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-x86_64.toml diff --git a/test-suit/starryos/stress/sqlite3-deep/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/sqlite3-deep/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..a0d4152dfd --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,14 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/aarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +log = "Warn" +plat_dyn = false +target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/stress/sqlite3-deep/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/sqlite3-deep/build-loongarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..dc2c5242d5 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/build-loongarch64-unknown-none-softfloat.toml @@ -0,0 +1,14 @@ +target = "loongarch64-unknown-none-softfloat" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +plat_dyn = false diff --git a/test-suit/starryos/stress/sqlite3-deep/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/stress/sqlite3-deep/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..917e6b0c7b --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,14 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +log = "Warn" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/stress/sqlite3-deep/build-x86_64-unknown-none.toml b/test-suit/starryos/stress/sqlite3-deep/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..60beb495f3 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/build-x86_64-unknown-none.toml @@ -0,0 +1,14 @@ +target = "x86_64-unknown-none" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +plat_dyn = false diff --git a/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-aarch64.toml b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-aarch64.toml new file mode 100644 index 0000000000..cfa84dc7ca --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-aarch64.toml @@ -0,0 +1,79 @@ +args = [ + "-nographic", "-machine", "virt,gic-version=2", + "-cpu", "cortex-a53", "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "SQLITE_DEEP_ENV_BEGIN" +apk update +apk add --no-cache sqlite +sqlite3 --version +ls -ld /tmp +echo "SQLITE_DEEP_ENV_OK" + +echo "S5_TRANSACTION_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "PRAGMA journal_mode=DELETE;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);" +sqlite3 /tmp/test.db "BEGIN;INSERT INTO t(name) VALUES('alice');INSERT INTO t(name) VALUES('bob');COMMIT;" +out="$(sqlite3 /tmp/test.db "SELECT COUNT(*) FROM t;")" +echo "after_commit: $out" +test "$out" = "2" +sqlite3 /tmp/test.db "BEGIN;INSERT INTO t(name) VALUES('rollback_me');ROLLBACK;" +out="$(sqlite3 /tmp/test.db "SELECT COUNT(*) FROM t;")" +echo "after_rollback: $out" +test "$out" = "2" +echo "S5_TRANSACTION_OK" + +echo "S6_WAL_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=WAL;")" +echo "journal_mode: $out" +echo "$out" | grep -qx "wal" +sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);" +sqlite3 /tmp/test.db "INSERT INTO t(name) VALUES('alice');" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +echo "query_result: $out" +test "$out" = "1|alice" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +echo "reopen_result: $out" +test "$out" = "1|alice" +ls -l /tmp/test.db /tmp/test.db-wal /tmp/test.db-shm 2>/dev/null || true +echo "S6_WAL_OK" + +echo "S7_BULK_INSERT_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, value TEXT);" +sqlite3 /tmp/test.db "WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x < 500) INSERT INTO t(value) SELECT 'row-' || x FROM cnt;" +out="$(sqlite3 /tmp/test.db "SELECT COUNT(*) FROM t;")" +echo "count: $out" +test "$out" = "500" +out="$(sqlite3 /tmp/test.db "PRAGMA integrity_check;")" +echo "integrity: $out" +test "$out" = "ok" +echo "S7_BULK_INSERT_OK" + +echo "S8_BLOB_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "CREATE TABLE blob_test(id INTEGER PRIMARY KEY, data BLOB);" +sqlite3 /tmp/test.db "INSERT INTO blob_test(data) VALUES(randomblob(65536));" +out="$(sqlite3 /tmp/test.db "SELECT length(data) FROM blob_test;")" +echo "blob_length: $out" +test "$out" = "65536" +out="$(sqlite3 /tmp/test.db "SELECT length(data) FROM blob_test;")" +echo "reopen_blob_length: $out" +test "$out" = "65536" +echo "S8_BLOB_OK" + +echo "SQLITE3_DEEP_TEST_PASSED" +''' +success_regex = ["(?m)^SQLITE3_DEEP_TEST_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 300 diff --git a/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-loongarch64.toml b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-loongarch64.toml new file mode 100644 index 0000000000..72d5c38969 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-loongarch64.toml @@ -0,0 +1,69 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "SQLITE_DEEP_ENV_BEGIN" +apk update +apk add --no-cache sqlite +sqlite3 --version +ls -ld /tmp +echo "SQLITE_DEEP_ENV_OK" + +echo "S5_TRANSACTION_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=DELETE;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT); +BEGIN;INSERT INTO t(name) VALUES('alice');INSERT INTO t(name) VALUES('bob');COMMIT;SELECT COUNT(*) FROM t; +BEGIN;INSERT INTO t(name) VALUES('rollback_me');ROLLBACK;SELECT COUNT(*) FROM t;")" +echo "$out" +echo "$out" | grep -q "delete" +count2="$(echo "$out" | grep -c "^2$")" +test "$count2" -eq 2 +echo "S5_TRANSACTION_OK" + +echo "S6_WAL_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=WAL;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);INSERT INTO t(name) VALUES('alice');SELECT id, name FROM t;")" +echo "$out" +echo "$out" | grep -qx "wal" +echo "$out" | grep -qx "1|alice" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +echo "$out" +test "$out" = "1|alice" +ls -l /tmp/test.db /tmp/test.db-wal /tmp/test.db-shm 2>/dev/null || true +echo "S6_WAL_OK" + +echo "S7_BULK_INSERT_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, value TEXT);WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x < 500) INSERT INTO t(value) SELECT 'row-' || x FROM cnt;SELECT COUNT(*) FROM t;PRAGMA integrity_check;")" +echo "$out" +echo "$out" | grep -qx "500" +echo "$out" | grep -qx "ok" +echo "S7_BULK_INSERT_OK" + +echo "S8_BLOB_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "CREATE TABLE blob_test(id INTEGER PRIMARY KEY, data BLOB);INSERT INTO blob_test(data) VALUES(randomblob(65536));SELECT length(data) FROM blob_test;")" +echo "$out" +test "$out" = "65536" +out="$(sqlite3 /tmp/test.db "SELECT length(data) FROM blob_test;")" +echo "$out" +test "$out" = "65536" +echo "S8_BLOB_OK" + +echo "SQLITE3_DEEP_TEST_PASSED" +''' +success_regex = ["(?m)^SQLITE3_DEEP_TEST_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 600 diff --git a/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-riscv64.toml b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-riscv64.toml new file mode 100644 index 0000000000..162da21c87 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-riscv64.toml @@ -0,0 +1,67 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "SQLITE_DEEP_ENV_BEGIN" +apk update +apk add --no-cache sqlite +sqlite3 --version +ls -ld /tmp +echo "SQLITE_DEEP_ENV_OK" + +echo "S5_TRANSACTION_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=DELETE;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT); +BEGIN;INSERT INTO t(name) VALUES('alice');INSERT INTO t(name) VALUES('bob');COMMIT;SELECT COUNT(*) FROM t; +BEGIN;INSERT INTO t(name) VALUES('rollback_me');ROLLBACK;SELECT COUNT(*) FROM t;")" +echo "$out" +echo "$out" | grep -q "delete" +count2="$(echo "$out" | grep -c "^2$")" +test "$count2" -eq 2 +echo "S5_TRANSACTION_OK" + +echo "S6_WAL_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=WAL;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);INSERT INTO t(name) VALUES('alice');SELECT id, name FROM t;")" +echo "$out" +echo "$out" | grep -qx "wal" +echo "$out" | grep -qx "1|alice" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +echo "$out" +test "$out" = "1|alice" +ls -l /tmp/test.db /tmp/test.db-wal /tmp/test.db-shm 2>/dev/null || true +echo "S6_WAL_OK" + +echo "S7_BULK_INSERT_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, value TEXT);WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x < 500) INSERT INTO t(value) SELECT 'row-' || x FROM cnt;SELECT COUNT(*) FROM t;PRAGMA integrity_check;")" +echo "$out" +echo "$out" | grep -qx "500" +echo "$out" | grep -qx "ok" +echo "S7_BULK_INSERT_OK" + +echo "S8_BLOB_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "CREATE TABLE blob_test(id INTEGER PRIMARY KEY, data BLOB);INSERT INTO blob_test(data) VALUES(randomblob(65536));SELECT length(data) FROM blob_test;")" +echo "$out" +test "$out" = "65536" +out="$(sqlite3 /tmp/test.db "SELECT length(data) FROM blob_test;")" +echo "$out" +test "$out" = "65536" +echo "S8_BLOB_OK" + +echo "SQLITE3_DEEP_TEST_PASSED" +''' +success_regex = ["(?m)^SQLITE3_DEEP_TEST_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 300 diff --git a/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-x86_64.toml b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-x86_64.toml new file mode 100644 index 0000000000..0cdc42948a --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-deep/sqlite3-deep/qemu-x86_64.toml @@ -0,0 +1,67 @@ +args = [ + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "SQLITE_DEEP_ENV_BEGIN" +apk update +apk add --no-cache sqlite +sqlite3 --version +ls -ld /tmp +echo "SQLITE_DEEP_ENV_OK" + +echo "S5_TRANSACTION_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=DELETE;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT); +BEGIN;INSERT INTO t(name) VALUES('alice');INSERT INTO t(name) VALUES('bob');COMMIT;SELECT COUNT(*) FROM t; +BEGIN;INSERT INTO t(name) VALUES('rollback_me');ROLLBACK;SELECT COUNT(*) FROM t;")" +echo "$out" +echo "$out" | grep -q "delete" +count2="$(echo "$out" | grep -c "^2$")" +test "$count2" -eq 2 +echo "S5_TRANSACTION_OK" + +echo "S6_WAL_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "PRAGMA journal_mode=WAL;CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);INSERT INTO t(name) VALUES('alice');SELECT id, name FROM t;")" +echo "$out" +echo "$out" | grep -qx "wal" +echo "$out" | grep -qx "1|alice" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +echo "$out" +test "$out" = "1|alice" +ls -l /tmp/test.db /tmp/test.db-wal /tmp/test.db-shm 2>/dev/null || true +echo "S6_WAL_OK" + +echo "S7_BULK_INSERT_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, value TEXT);WITH RECURSIVE cnt(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cnt WHERE x < 500) INSERT INTO t(value) SELECT 'row-' || x FROM cnt;SELECT COUNT(*) FROM t;PRAGMA integrity_check;")" +echo "$out" +echo "$out" | grep -qx "500" +echo "$out" | grep -qx "ok" +echo "S7_BULK_INSERT_OK" + +echo "S8_BLOB_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +out="$(sqlite3 /tmp/test.db "CREATE TABLE blob_test(id INTEGER PRIMARY KEY, data BLOB);INSERT INTO blob_test(data) VALUES(randomblob(65536));SELECT length(data) FROM blob_test;")" +echo "$out" +test "$out" = "65536" +out="$(sqlite3 /tmp/test.db "SELECT length(data) FROM blob_test;")" +echo "$out" +test "$out" = "65536" +echo "S8_BLOB_OK" + +echo "SQLITE3_DEEP_TEST_PASSED" +''' +success_regex = ["(?m)^SQLITE3_DEEP_TEST_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 300 diff --git a/test-suit/starryos/stress/sqlite3-smoke/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/sqlite3-smoke/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..a0d4152dfd --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,14 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/aarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +log = "Warn" +plat_dyn = false +target = "aarch64-unknown-none-softfloat" diff --git a/test-suit/starryos/stress/sqlite3-smoke/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/sqlite3-smoke/build-loongarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..dc2c5242d5 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/build-loongarch64-unknown-none-softfloat.toml @@ -0,0 +1,14 @@ +target = "loongarch64-unknown-none-softfloat" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = [ + "ax-hal/loongarch64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +plat_dyn = false diff --git a/test-suit/starryos/stress/sqlite3-smoke/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/stress/sqlite3-smoke/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..917e6b0c7b --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,14 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +log = "Warn" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/stress/sqlite3-smoke/build-x86_64-unknown-none.toml b/test-suit/starryos/stress/sqlite3-smoke/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..60beb495f3 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/build-x86_64-unknown-none.toml @@ -0,0 +1,14 @@ +target = "x86_64-unknown-none" +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +log = "Warn" +features = [ + "ax-hal/x86-pc", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket" +] +plat_dyn = false diff --git a/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-aarch64.toml b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-aarch64.toml new file mode 100644 index 0000000000..0efb01a5b3 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-aarch64.toml @@ -0,0 +1,49 @@ +args = [ + "-nographic", "-machine", "virt,gic-version=2", + "-cpu", "cortex-a53", "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "STAGE_APK_BEGIN" +apk update +apk add --no-cache sqlite +echo "STAGE_APK_OK" + +echo "STAGE_S0_BEGIN" +sqlite3 --version +echo "S0_OK" + +echo "STAGE_S1_BEGIN" +out="$(sqlite3 :memory: 'SELECT 1;')" +test "$out" = "1" +echo "S1_OK" + +echo "STAGE_S2_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);" +test -f /tmp/test.db +echo "S2_OK" + +echo "STAGE_S3_BEGIN" +out="$(sqlite3 /tmp/test.db "INSERT INTO t(name) VALUES('alice'); SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S3_OK" + +echo "STAGE_S4_BEGIN" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S4_OK" + +echo "ALL_STAGES_PASSED" +''' +success_regex = ["(?m)^ALL_STAGES_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 300 diff --git a/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-loongarch64.toml b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-loongarch64.toml new file mode 100644 index 0000000000..c549175a76 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-loongarch64.toml @@ -0,0 +1,51 @@ +args = [ + "-machine", "virt", + "-cpu", "la464", + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "STAGE_APK_BEGIN" +apk update +apk add --no-cache sqlite +echo "STAGE_APK_OK" + +echo "STAGE_S0_BEGIN" +sqlite3 --version +echo "S0_OK" + +echo "STAGE_S1_BEGIN" +out="$(sqlite3 :memory: 'SELECT 1;')" +test "$out" = "1" +echo "S1_OK" + +echo "STAGE_S2_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);" +test -f /tmp/test.db +echo "S2_OK" + +echo "STAGE_S3_BEGIN" +out="$(sqlite3 /tmp/test.db "INSERT INTO t(name) VALUES('alice'); SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S3_OK" + +echo "STAGE_S4_BEGIN" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S4_OK" + +echo "ALL_STAGES_PASSED" +''' +success_regex = ["(?m)^ALL_STAGES_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 600 diff --git a/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-riscv64.toml b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-riscv64.toml new file mode 100644 index 0000000000..f2e5f5dac2 --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-riscv64.toml @@ -0,0 +1,49 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "STAGE_APK_BEGIN" +apk update +apk add --no-cache sqlite +echo "STAGE_APK_OK" + +echo "STAGE_S0_BEGIN" +sqlite3 --version +echo "S0_OK" + +echo "STAGE_S1_BEGIN" +out="$(sqlite3 :memory: 'SELECT 1;')" +test "$out" = "1" +echo "S1_OK" + +echo "STAGE_S2_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);" +test -f /tmp/test.db +echo "S2_OK" + +echo "STAGE_S3_BEGIN" +out="$(sqlite3 /tmp/test.db "INSERT INTO t(name) VALUES('alice'); SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S3_OK" + +echo "STAGE_S4_BEGIN" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S4_OK" + +echo "ALL_STAGES_PASSED" +''' +success_regex = ["(?m)^ALL_STAGES_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 300 diff --git a/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-x86_64.toml b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-x86_64.toml new file mode 100644 index 0000000000..0f4bc65b6a --- /dev/null +++ b/test-suit/starryos/stress/sqlite3-smoke/sqlite3-smoke/qemu-x86_64.toml @@ -0,0 +1,49 @@ +args = [ + "-nographic", + "-m", "512M", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = ''' +set -e + +echo "STAGE_APK_BEGIN" +apk update +apk add --no-cache sqlite +echo "STAGE_APK_OK" + +echo "STAGE_S0_BEGIN" +sqlite3 --version +echo "S0_OK" + +echo "STAGE_S1_BEGIN" +out="$(sqlite3 :memory: 'SELECT 1;')" +test "$out" = "1" +echo "S1_OK" + +echo "STAGE_S2_BEGIN" +rm -f /tmp/test.db /tmp/test.db-journal /tmp/test.db-wal /tmp/test.db-shm +sqlite3 /tmp/test.db "CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT);" +test -f /tmp/test.db +echo "S2_OK" + +echo "STAGE_S3_BEGIN" +out="$(sqlite3 /tmp/test.db "INSERT INTO t(name) VALUES('alice'); SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S3_OK" + +echo "STAGE_S4_BEGIN" +out="$(sqlite3 /tmp/test.db "SELECT id, name FROM t;")" +test "$out" = "1|alice" +echo "S4_OK" + +echo "ALL_STAGES_PASSED" +''' +success_regex = ["(?m)^ALL_STAGES_PASSED\\s*$"] +fail_regex = ['(?m)^(?:\x1b\[[0-9;]*[A-Za-z])*panicked at(?:\s|$)', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +timeout = 300 From 28fe7f2a1f1998d82b6c88c5f62de594d6c0e2ed Mon Sep 17 00:00:00 2001 From: linfeng Date: Wed, 27 May 2026 13:36:51 +0800 Subject: [PATCH 52/71] Adds support for kernel symbol dumping via kallsyms (#837) * Adds support for kernel symbol dumping via kallsyms Introduces infrastructure to extract, store, and expose kernel symbol information, enabling access via a dedicated procfs file. Integrates kallsyms section in the linker script, a build script for symbol extraction, and updates the build process to automate symbol generation. Signed-off-by: Godones * fix(starry): avoid qemu panic matcher for kallsyms output Upgrade ostool to 0.19 so empty QEMU fail_regex no longer gets default panic patterns injected, then clear the default Starry QEMU fail_regex entries to avoid terminating interactive QEMU sessions when /proc/kallsyms prints symbols containing "panic". Adjust axbuild for the new ostool Cargo fields, and refine pseudofs helpers so static string content can be exposed without overlapping trait impls while keeping read-only write failures as BadFileDescriptor. Signed-off-by: Godones * fix: Updates kallsyms handling and bumps ksym dependency. Signed-off-by: Godones * Improves kallsyms reading and initialization logic. Signed-off-by: Godones * fix: add memory config for util-linux test Signed-off-by: Godones --------- Signed-off-by: Godones --- Cargo.lock | 7 ++ os/StarryOS/kernel/Cargo.toml | 1 + os/StarryOS/kernel/src/pseudofs/dev/mod.rs | 2 +- os/StarryOS/kernel/src/pseudofs/file.rs | 47 ++++++-- os/StarryOS/kernel/src/pseudofs/proc.rs | 49 +++++++++ os/StarryOS/starryos/ext_linker.ld | 8 +- scripts/axbuild/scripts/starry-kallsyms.sh | 104 ++++++++++++++++++ scripts/axbuild/src/arceos/test.rs | 2 +- scripts/axbuild/src/build.rs | 2 +- scripts/axbuild/src/starry/build.rs | 47 ++++++++ .../qemu-smp1/util-linux/qemu-aarch64.toml | 2 + .../util-linux/qemu-loongarch64.toml | 2 +- .../qemu-smp1/util-linux/qemu-riscv64.toml | 2 + .../qemu-smp1/util-linux/qemu-x86_64.toml | 2 + 14 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 scripts/axbuild/scripts/starry-kallsyms.sh diff --git a/Cargo.lock b/Cargo.lock index c2b7f2d6bf..e8bc582b63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4978,6 +4978,12 @@ dependencies = [ "yaxpeax-x86", ] +[[package]] +name = "ksym" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b543b77d8cfa33302b6b51b6a255f9e85b06997b7c4a0a7354fcc8304036eb09" + [[package]] name = "ktest-helper" version = "0.1.1" @@ -7955,6 +7961,7 @@ dependencies = [ "inherit-methods-macro", "kernel-elf-parser", "kprobe", + "ksym", "ktracepoint", "linux-raw-sys 0.12.1", "lock_api", diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 9efe2b1e5a..ef124c89aa 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -139,6 +139,7 @@ sg200x-bsp = { workspace = true, optional = true } # because Writeable trait must come from the same version as sg200x-bsp's types. tock-registers = { version = "0.9", optional = true } ktracepoint = "0.6" +ksym = "0.6" [target.'cfg(target_arch = "x86_64")'.dependencies] x86 = "0.52" diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index 459af3989a..fa482e1c1c 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -269,7 +269,7 @@ fn builder(fs: Arc) -> DirMaker { #[cfg(feature = "dev-log")] root.add( "log", - crate::pseudofs::SimpleFile::new(fs.clone(), NodeType::Socket, || Ok(b"")), + crate::pseudofs::SimpleFile::new(fs.clone(), NodeType::Socket, || Ok("")), ); #[cfg(feature = "memtrack")] diff --git a/os/StarryOS/kernel/src/pseudofs/file.rs b/os/StarryOS/kernel/src/pseudofs/file.rs index 73476f1bb4..a8341c998c 100644 --- a/os/StarryOS/kernel/src/pseudofs/file.rs +++ b/os/StarryOS/kernel/src/pseudofs/file.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, sync::Arc, vec::Vec}; +use alloc::{borrow::Cow, string::String, sync::Arc, vec::Vec}; use core::{any::Any, cmp::Ordering, task::Context}; use ax_sync::Mutex; @@ -16,7 +16,9 @@ pub trait SimpleFileOps: Send + Sync + 'static { /// Reads all content in the file. fn read_all(&self) -> VfsResult>; /// Replaces the file's content with `data`. - fn write_all(&self, data: &[u8]) -> VfsResult<()>; + fn write_all(&self, _data: &[u8]) -> VfsResult<()> { + Err(VfsError::BadFileDescriptor) + } } /// Type representing operation applied to a simple file. @@ -56,17 +58,42 @@ where } } +pub trait SimpleFileContent { + /// Converts the content into bytes. + fn into_content(self) -> Cow<'static, [u8]>; +} + +impl SimpleFileContent for Vec { + fn into_content(self) -> Cow<'static, [u8]> { + Cow::Owned(self) + } +} + +impl SimpleFileContent for String { + fn into_content(self) -> Cow<'static, [u8]> { + Cow::Owned(self.into_bytes()) + } +} + +impl SimpleFileContent for &'static str { + fn into_content(self) -> Cow<'static, [u8]> { + Cow::Borrowed(self.as_bytes()) + } +} + +impl SimpleFileContent for &'static [u8] { + fn into_content(self) -> Cow<'static, [u8]> { + Cow::Borrowed(self) + } +} + impl SimpleFileOps for F where F: Fn() -> VfsResult + Send + Sync + 'static, - R: Into>, + R: SimpleFileContent, { fn read_all(&self) -> VfsResult> { - (self)().map(|it| Cow::Owned(it.into())) - } - - fn write_all(&self, _data: &[u8]) -> VfsResult<()> { - Err(VfsError::BadFileDescriptor) + Ok((self)()?.into_content()) } } @@ -262,8 +289,8 @@ impl FileNodeOps for SpecialFsFile { } fn append(&self, buf: &[u8]) -> VfsResult<(usize, u64)> { - self.ops.write_at(buf, 0)?; - Ok((buf.len(), 0)) + let w = self.ops.write_at(buf, 0)?; + Ok((w, 0)) } fn set_len(&self, len: u64) -> VfsResult<()> { diff --git a/os/StarryOS/kernel/src/pseudofs/proc.rs b/os/StarryOS/kernel/src/pseudofs/proc.rs index 4dd7d4cc8e..b16c7dce0b 100644 --- a/os/StarryOS/kernel/src/pseudofs/proc.rs +++ b/os/StarryOS/kernel/src/pseudofs/proc.rs @@ -14,6 +14,7 @@ use core::{ sync::atomic::{AtomicUsize, Ordering}, }; +use ax_lazyinit::LazyInit; use ax_memory_addr::PAGE_SIZE_4K; use ax_runtime::hal::{ paging::MappingFlags, @@ -21,6 +22,7 @@ use ax_runtime::hal::{ }; use ax_task::{AxCpuMask, AxTaskRef, TaskState, WeakAxTaskRef, current}; use axfs_ng_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult}; +use ksym::KallsymsMapped; use starry_process::{Pid, Process}; use crate::{ @@ -41,6 +43,36 @@ use crate::{ static IRQ_CNT: AtomicUsize = AtomicUsize::new(0); const PROCFS_INIT_PID: Pid = 1; +pub static KALLSYMS: LazyInit> = LazyInit::new(); + +fn read_kallsyms() -> KallsymsMapped<'static> { + unsafe extern "C" { + fn _stext(); + fn _etext(); + fn __kallsyms_start(); + fn __kallsyms_end(); + } + + let kallsyms_start = __kallsyms_start as *const () as usize; + let kallsyms_end = __kallsyms_end as *const () as usize; + let kallsyms_sec_size = kallsyms_end - kallsyms_start; + let kallsyms_sec = + unsafe { core::slice::from_raw_parts(__kallsyms_start as *const u8, kallsyms_sec_size) }; + + let total_size = + KallsymsMapped::check_total_bytes(kallsyms_sec).expect("Invalid kallsyms format"); + + let kallsyms = &kallsyms_sec[..total_size as usize]; + // TODO: recycle unused space in .kallsyms section + info!("Read kallsyms, size: {}KB", kallsyms.len() / 1024); + KallsymsMapped::from_blob( + kallsyms, + _stext as *const () as u64, + _etext as *const () as u64, + ) + .expect("Failed to create KallsymsMapped") +} + fn procfs_visible_pid(proc: &Arc) -> Pid { if proc.is_init() { PROCFS_INIT_PID @@ -961,6 +993,23 @@ fn builder(fs: Arc) -> DirMaker { SimpleDir::new_maker(fs.clone(), Arc::new(dynamic_debug)) }); + static ALL_SYMS: LazyInit = LazyInit::new(); + + let ksym = read_kallsyms(); + KALLSYMS.init_once(ksym); + + root.add("kallsyms", { + if !ALL_SYMS.is_inited() { + ALL_SYMS.init_once(KALLSYMS.dump_all_symbols()); + } + let seq_obj = SeqObject::new(|| Ok(ALL_SYMS.as_str())); + SpecialFsFile::new_regular_with_perm( + fs.clone(), + seq_obj, + NodePermission::from_bits_truncate(0o444), + ) + }); + let proc_dir = ProcFsHandler(fs.clone()); SimpleDir::new_maker(fs, Arc::new(proc_dir.chain(root))) } diff --git a/os/StarryOS/starryos/ext_linker.ld b/os/StarryOS/starryos/ext_linker.ld index 8e8253d8ed..b8db0952ab 100644 --- a/os/StarryOS/starryos/ext_linker.ld +++ b/os/StarryOS/starryos/ext_linker.ld @@ -16,6 +16,12 @@ SECTIONS { KEEP(*(.tracepoint .tracepoint.*)) __stop_tracepoint = .; } + + .kallsyms : ALIGN(4K) { + __kallsyms_start = .; + . += 8M; /* reserve space for kallsyms, can be recycled */ + __kallsyms_end = .; + } } -INSERT AFTER .data; \ No newline at end of file +INSERT AFTER .data; diff --git a/scripts/axbuild/scripts/starry-kallsyms.sh b/scripts/axbuild/scripts/starry-kallsyms.sh new file mode 100644 index 0000000000..372a0a29eb --- /dev/null +++ b/scripts/axbuild/scripts/starry-kallsyms.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env sh +set -eu + +auto_install_enabled() { + case "${AXBUILD_STARRY_KALLSYMS_AUTO_INSTALL:-1}" in + 0 | n | no | false | off) return 1 ;; + *) return 0 ;; + esac +} + +install_rust_binutils() { + if ! auto_install_enabled; then + echo "rust-nm and rust-objcopy are required for Starry kallsyms generation" >&2 + echo "install them with: rustup component add llvm-tools-preview && cargo install cargo-binutils" >&2 + exit 1 + fi + + echo "Installing rust-nm and rust-objcopy (cargo-binutils)..." >&2 + if command -v rustup >/dev/null 2>&1; then + rustup component add llvm-tools-preview + fi + cargo install cargo-binutils +} + +ensure_llvm_tools() { + if command -v rust-nm >/dev/null 2>&1 \ + && command -v rust-objcopy >/dev/null 2>&1 \ + && rust-nm --version >/dev/null 2>&1 \ + && rust-objcopy --version >/dev/null 2>&1; then + return + fi + + if ! command -v rustup >/dev/null 2>&1; then + return + fi + if rustup component list --installed | grep -q '^llvm-tools'; then + return + fi + + if ! auto_install_enabled; then + echo "llvm-tools-preview is required for rust-nm and rust-objcopy" >&2 + echo "install it with: rustup component add llvm-tools-preview" >&2 + exit 1 + fi + + echo "Installing llvm-tools-preview via rustup..." >&2 + rustup component add llvm-tools-preview +} + +install_ksym() { + if ! auto_install_enabled; then + echo "gen_ksym is required for Starry kallsyms generation" >&2 + echo "install it with: cargo install ksym" >&2 + exit 1 + fi + + echo "Installing ksym (gen_ksym) via cargo..." >&2 + cargo install ksym +} + +ensure_tools() { + ensure_llvm_tools + + if ! command -v rust-nm >/dev/null 2>&1 || ! command -v rust-objcopy >/dev/null 2>&1; then + install_rust_binutils + fi + if ! command -v gen_ksym >/dev/null 2>&1; then + install_ksym + fi + + command -v rust-nm >/dev/null 2>&1 + command -v rust-objcopy >/dev/null 2>&1 + command -v gen_ksym >/dev/null 2>&1 +} + +generate_kallsyms() { + symbols=$(mktemp "${KERNEL_ELF}.symbols.XXXXXX") + kallsyms=$(mktemp "${KERNEL_ELF}.kallsyms.XXXXXX") + trap 'rm -f "$symbols" "$kallsyms"' EXIT + + rust-nm -n "$KERNEL_ELF" > "$symbols" + grep ' [TtDBR] ' "$symbols" \ + | awk '$3 !~ /^\.L/' \ + | awk '$3 != "$x"' \ + | gen_ksym > "$kallsyms" + + rust-objcopy --update-section .kallsyms="$kallsyms" "$KERNEL_ELF" +} + +refresh_bin_if_present() { + bin="${KERNEL_ELF%.elf}.bin" + if [ -f "$bin" ]; then + rust-objcopy --strip-all -O binary "$KERNEL_ELF" "$bin" + fi +} + +if [ -z "${KERNEL_ELF:-}" ]; then + echo "KERNEL_ELF is required for Starry kallsyms generation" >&2 + exit 1 +fi + +ensure_tools +generate_kallsyms +refresh_bin_if_present diff --git a/scripts/axbuild/src/arceos/test.rs b/scripts/axbuild/src/arceos/test.rs index 34971c8467..59628343f0 100644 --- a/scripts/axbuild/src/arceos/test.rs +++ b/scripts/axbuild/src/arceos/test.rs @@ -1634,7 +1634,6 @@ mod tests { env: Default::default(), target: "x86_64-unknown-none".to_string(), package: package.to_string(), - bin: None, features: Vec::new(), log: None, extra_config: None, @@ -1644,6 +1643,7 @@ mod tests { pre_build_cmds: Vec::new(), post_build_cmds: Vec::new(), to_bin: false, + bin: None, }, qemu: QemuConfig::default(), } diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index e0bab7f43c..3caf0bbfc7 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -159,7 +159,6 @@ impl BuildInfo { env: self.env, target, package, - bin: None, features: self.features, log: Some(self.log), extra_config: None, @@ -169,6 +168,7 @@ impl BuildInfo { pre_build_cmds: vec![], post_build_cmds: vec![], to_bin, + bin: None, } } diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index 7664f5bc3b..e304ffbd3e 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -145,6 +145,8 @@ fn patch_starry_cargo_config( .or_insert_with(|| platform.to_string()); } + inject_kallsyms_post_build_cmd(cargo)?; + if cargo.env.get("UIMAGE").map(|v| v.as_str()) == Some("y") { inject_uimage_post_build_cmd(cargo, &request.arch)?; } @@ -152,6 +154,22 @@ fn patch_starry_cargo_config( Ok(()) } +fn inject_kallsyms_post_build_cmd(cargo: &mut Cargo) -> anyhow::Result<()> { + let script = crate::context::workspace_root_path()? + .join("scripts") + .join("axbuild") + .join("scripts") + .join("starry-kallsyms.sh"); + cargo + .post_build_cmds + .push(format!("sh {}", shell_quote(&script.display().to_string()))); + Ok(()) +} + +fn shell_quote(value: &str) -> String { + format!("'{}'", value.replace('\'', "'\\''")) +} + fn uimg_arch_for(arch: &str) -> String { match arch { "aarch64" => "arm64".to_string(), @@ -461,6 +479,8 @@ HELLO = "world" assert_eq!(cargo.env.get("AX_LOG").map(String::as_str), Some("info")); assert_eq!(cargo.env.get("CUSTOM").map(String::as_str), Some("1")); assert!(cargo.to_bin); + assert_eq!(cargo.post_build_cmds.len(), 1); + assert!(cargo.post_build_cmds[0].contains("scripts/axbuild/scripts/starry-kallsyms.sh")); } #[test] @@ -691,4 +711,31 @@ HELLO = "world" assert_eq!(args, vec!["--bin".to_string(), "starryos".to_string()]); } + + #[test] + fn patch_starry_cargo_config_runs_kallsyms_before_uimage_generation() { + let request = request( + PathBuf::from("/tmp/.build.toml"), + "aarch64", + "aarch64-unknown-none-softfloat", + ); + let mut build_info = default_starry_build_info_for_target(&request.target); + build_info.env.insert("UIMAGE".to_string(), "y".to_string()); + build_info.env.insert( + "AX_CONFIG_PATH".to_string(), + "/tmp/.axconfig.toml".to_string(), + ); + let mut cargo = build_info.into_base_cargo_config_with_log( + request.package.clone(), + request.target.clone(), + StarryBuildInfo::build_cargo_args(&request.target, false, &[]), + ); + + let metadata = crate::build::workspace_metadata().unwrap(); + patch_starry_cargo_config(&mut cargo, &request, &metadata).unwrap(); + + assert_eq!(cargo.post_build_cmds.len(), 2); + assert!(cargo.post_build_cmds[0].contains("scripts/axbuild/scripts/starry-kallsyms.sh")); + assert!(cargo.post_build_cmds[1].contains("mkimage")); + } } diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-aarch64.toml index 2dad49652b..6ebfe58dd9 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-aarch64.toml @@ -2,6 +2,8 @@ args = [ "-nographic", "-cpu", "cortex-a53", + "-m", + "512M", "-device", "virtio-blk-pci,drive=disk0", "-drive", diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-loongarch64.toml index ca2e59d304..de5ff7cbd2 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-loongarch64.toml @@ -5,7 +5,7 @@ args = [ "la464", "-nographic", "-m", - "128M", + "512M", "-device", "virtio-blk-pci,drive=disk0", "-drive", diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml index 9aa6085026..a5c178dd36 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-riscv64.toml @@ -1,5 +1,7 @@ args = [ "-nographic", + "-m", + "512M", "-cpu", "rv64", "-device", diff --git a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml index 8cb956e76a..6b51b6735b 100644 --- a/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/util-linux/qemu-x86_64.toml @@ -1,5 +1,7 @@ args = [ "-nographic", + "-m", + "512M", "-device", "virtio-blk-pci,drive=disk0", "-drive", From af597b9d03742971be10e79c1f11581d719cdc3a Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Wed, 27 May 2026 13:49:17 +0800 Subject: [PATCH 53/71] Implement platform-specific IRQ handling and architecture setup (#979) * Implement platform-specific interrupt handling and setup for various architectures - Added `PlatOp` trait in `common.rs` to define platform operations for IRQ handling. - Implemented `PlatOp` for `loongarch64`, `riscv64`, and `x86_64` architectures in their respective modules. - Introduced `rdrive_setup` function in `driver.rs` to initialize the rdrive with FDT. - Created `irq.rs` to manage IRQ setup and handling, including inter-processor interrupts (IPI). - Developed `setup.rs` to define kernel operations and manage MMIO mappings. - Updated `lib.rs` to include new modules and initialize the platform. - Refactored platform package handling in `build.rs` to accommodate new metadata structure. - Adjusted paths in various reports and scripts to reflect the new directory structure for platforms. * feat(build): add workspace fallback for platform package resolution * Remove hello-kernel, irq-kernel, and smp-kernel examples This commit deletes the hello-kernel, irq-kernel, and smp-kernel example projects from the repository. The following files and directories were removed: - platforms/examples/hello-kernel/ - platforms/examples/irq-kernel/ - platforms/examples/smp-kernel/ These examples included build scripts, linker scripts, source code, and documentation files. The removal is part of a restructuring effort to streamline the project and focus on core components. --- Cargo.lock | 183 +++++------ Cargo.toml | 40 +-- .../axplat_crates/.github/workflows/check.yml | 66 ---- .../axplat_crates/.github/workflows/docs.yml | 35 --- .../axplat_crates/.github/workflows/test.yml | 44 --- components/axplat_crates/.gitignore | 4 - components/axplat_crates/LICENSE | 201 ------------ components/axplat_crates/README.md | 54 ---- components/axplat_crates/README_CN.md | 54 ---- .../examples/hello-kernel/.gitignore | 1 - .../examples/hello-kernel/Cargo.toml | 21 -- .../examples/hello-kernel/Makefile | 59 ---- .../examples/hello-kernel/README.md | 81 ----- .../examples/hello-kernel/README_CN.md | 81 ----- .../examples/hello-kernel/build.rs | 27 -- .../examples/hello-kernel/linker.lds.S | 52 ---- .../examples/hello-kernel/src/main.rs | 49 --- .../examples/irq-kernel/.gitignore | 1 - .../examples/irq-kernel/Cargo.toml | 23 -- .../examples/irq-kernel/Makefile | 59 ---- .../examples/irq-kernel/README.md | 81 ----- .../examples/irq-kernel/README_CN.md | 81 ----- .../examples/irq-kernel/build.rs | 27 -- .../examples/irq-kernel/linker.lds.S | 52 ---- .../examples/irq-kernel/src/irq.rs | 75 ----- .../examples/irq-kernel/src/main.rs | 49 --- .../examples/smp-kernel/.gitignore | 1 - .../examples/smp-kernel/Cargo.toml | 26 -- .../examples/smp-kernel/Makefile | 63 ---- .../examples/smp-kernel/README.md | 81 ----- .../examples/smp-kernel/README_CN.md | 81 ----- .../examples/smp-kernel/build.rs | 32 -- .../examples/smp-kernel/linker.lds.S | 52 ---- .../examples/smp-kernel/src/init.rs | 29 -- .../examples/smp-kernel/src/irq.rs | 40 --- .../examples/smp-kernel/src/main.rs | 64 ---- .../examples/smp-kernel/src/mp.rs | 50 --- components/axplat_crates/publish.sh | 133 -------- docs/docs/architecture/arceos.md | 6 +- docs/docs/architecture/overview.md | 10 +- docs/docs/architecture/rdrive-rdif.md | 12 +- docs/docs/architecture/starryos.md | 4 +- docs/docs/build/build.md | 16 +- docs/docs/build/ci.md | 2 +- docs/docs/build/configuration.md | 4 +- docs/docs/components/crates/ax-alloc.md | 2 +- .../components/crates/ax-config-macros.md | 2 +- docs/docs/components/crates/ax-cpu.md | 2 +- docs/docs/components/crates/ax-dma.md | 4 +- docs/docs/components/crates/ax-driver.md | 2 +- .../components/crates/ax-handler-table.md | 2 +- docs/docs/components/crates/ax-int-ratio.md | 6 +- docs/docs/components/crates/ax-lazyinit.md | 2 +- .../crates/ax-plat-aarch64-bsta1000b.md | 2 +- .../crates/ax-plat-aarch64-peripherals.md | 4 +- .../crates/ax-plat-aarch64-phytium-pi.md | 2 +- .../crates/ax-plat-aarch64-qemu-virt.md | 9 +- .../crates/ax-plat-aarch64-raspi.md | 2 +- .../crates/ax-plat-loongarch64-qemu-virt.md | 11 +- docs/docs/components/crates/ax-plat-macros.md | 16 +- .../crates/ax-plat-riscv64-qemu-virt.md | 22 +- docs/docs/components/crates/ax-plat-x86-pc.md | 26 +- docs/docs/components/crates/ax-plat.md | 5 +- docs/docs/components/crates/axklib.md | 10 +- docs/docs/components/crates/axplat-dyn.md | 36 +-- .../components/crates/axplat-x86-qemu-q35.md | 24 +- docs/docs/components/crates/hello-kernel.md | 146 --------- docs/docs/components/crates/irq-kernel.md | 153 --------- docs/docs/components/crates/smp-kernel.md | 174 ----------- docs/docs/components/crates/starryos-test.md | 4 +- docs/docs/components/crates/starryos.md | 6 +- docs/docs/components/layers.md | 81 +++-- docs/docs/components/overview.md | 31 +- docs/docs/contributing/demo.md | 4 +- docs/docs/contributing/repo.md | 2 +- docs/docs/debug/check-mechanisms-summary.md | 2 +- docs/docs/debug/platform.md | 2 +- docs/docs/development/arceos.md | 10 +- docs/docs/development/axvisor.md | 2 +- docs/docs/development/components.md | 14 +- docs/docs/introduction/overview.md | 8 +- docs/src/pages/index.js | 4 +- os/StarryOS/Makefile | 2 +- os/StarryOS/kernel/Cargo.toml | 2 +- os/StarryOS/starryos/Cargo.toml | 4 +- os/arceos/doc/platform_raspi4.md | 2 +- os/arceos/modules/axhal/Cargo.toml | 40 +-- os/arceos/modules/axhal/build.rs | 8 +- os/arceos/modules/axhal/src/mem.rs | 2 +- os/arceos/modules/axhal/src/time.rs | 2 +- os/arceos/scripts/make/cargo.mk | 8 +- os/axvisor/Cargo.toml | 6 +- os/axvisor/doc/task.py-usage.md | 4 +- .../ax-plat-aarch64-bsta1000b}/CHANGELOG.md | 0 .../ax-plat-aarch64-bsta1000b}/Cargo.toml | 6 + .../ax-plat-aarch64-bsta1000b}/README.md | 2 +- .../ax-plat-aarch64-bsta1000b}/README_CN.md | 2 +- .../ax-plat-aarch64-bsta1000b}/axconfig.toml | 0 .../ax-plat-aarch64-bsta1000b}/build.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/boot.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/drivers.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/init.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/lib.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/mem.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/misc.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/mp.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/power.rs | 0 .../ax-plat-aarch64-bsta1000b}/src/serial.rs | 0 .../ax-plat-aarch64-peripherals}/CHANGELOG.md | 0 .../ax-plat-aarch64-peripherals}/Cargo.toml | 0 .../ax-plat-aarch64-peripherals}/README.md | 2 +- .../ax-plat-aarch64-peripherals}/README_CN.md | 2 +- .../src/generic_timer.rs | 0 .../ax-plat-aarch64-peripherals}/src/gic.rs | 0 .../ax-plat-aarch64-peripherals}/src/lib.rs | 0 .../ax-plat-aarch64-peripherals}/src/pl011.rs | 0 .../ax-plat-aarch64-peripherals}/src/pl031.rs | 0 .../ax-plat-aarch64-peripherals}/src/psci.rs | 0 .../ax-plat-aarch64-phytium-pi}/CHANGELOG.md | 0 .../ax-plat-aarch64-phytium-pi}/Cargo.toml | 6 + .../ax-plat-aarch64-phytium-pi}/README.md | 2 +- .../ax-plat-aarch64-phytium-pi}/README_CN.md | 2 +- .../ax-plat-aarch64-phytium-pi}/axconfig.toml | 0 .../ax-plat-aarch64-phytium-pi}/build.rs | 0 .../ax-plat-aarch64-phytium-pi}/src/boot.rs | 0 .../src/drivers.rs | 0 .../ax-plat-aarch64-phytium-pi}/src/init.rs | 0 .../ax-plat-aarch64-phytium-pi}/src/lib.rs | 0 .../ax-plat-aarch64-phytium-pi}/src/mem.rs | 0 .../ax-plat-aarch64-phytium-pi}/src/power.rs | 0 .../ax-plat-aarch64-qemu-virt}/CHANGELOG.md | 0 .../ax-plat-aarch64-qemu-virt}/Cargo.toml | 7 + .../ax-plat-aarch64-qemu-virt}/README.md | 2 +- .../ax-plat-aarch64-qemu-virt}/README_CN.md | 2 +- .../ax-plat-aarch64-qemu-virt}/axconfig.toml | 0 .../ax-plat-aarch64-qemu-virt}/build.rs | 0 .../ax-plat-aarch64-qemu-virt}/src/boot.rs | 0 .../ax-plat-aarch64-qemu-virt}/src/drivers.rs | 0 .../ax-plat-aarch64-qemu-virt}/src/init.rs | 0 .../ax-plat-aarch64-qemu-virt}/src/lib.rs | 0 .../ax-plat-aarch64-qemu-virt}/src/mem.rs | 0 .../ax-plat-aarch64-qemu-virt}/src/power.rs | 0 .../ax-plat-aarch64-raspi}/CHANGELOG.md | 0 .../ax-plat-aarch64-raspi}/Cargo.toml | 6 + .../ax-plat-aarch64-raspi}/README.md | 2 +- .../ax-plat-aarch64-raspi}/README_CN.md | 2 +- .../ax-plat-aarch64-raspi}/axconfig.toml | 0 .../ax-plat-aarch64-raspi}/build.rs | 0 .../ax-plat-aarch64-raspi}/src/boot.rs | 0 .../ax-plat-aarch64-raspi}/src/drivers.rs | 0 .../ax-plat-aarch64-raspi}/src/init.rs | 0 .../ax-plat-aarch64-raspi}/src/lib.rs | 0 .../ax-plat-aarch64-raspi}/src/mem.rs | 0 .../ax-plat-aarch64-raspi}/src/mp.rs | 0 .../ax-plat-aarch64-raspi}/src/power.rs | 0 .../ax-plat-dyn}/CHANGELOG.md | 0 .../ax-plat-dyn}/Cargo.toml | 8 +- .../ax-plat-dyn}/build.rs | 0 .../ax-plat-dyn}/link.ld | 0 .../ax-plat-dyn}/src/boot.rs | 0 .../ax-plat-dyn}/src/console.rs | 0 .../ax-plat-dyn}/src/drivers/mod.rs | 0 .../ax-plat-dyn}/src/generic_timer.rs | 0 .../ax-plat-dyn}/src/init.rs | 4 +- .../ax-plat-dyn}/src/irq.rs | 0 .../ax-plat-dyn}/src/lib.rs | 0 .../ax-plat-dyn}/src/mem.rs | 0 .../ax-plat-dyn}/src/power.rs | 0 .../CHANGELOG.md | 0 .../ax-plat-loongarch64-qemu-virt}/Cargo.toml | 7 + .../ax-plat-loongarch64-qemu-virt}/README.md | 2 +- .../README_CN.md | 2 +- .../axconfig.toml | 0 .../ax-plat-loongarch64-qemu-virt}/build.rs | 0 .../linker.lds.S | 0 .../src/boot.rs | 0 .../src/console.rs | 0 .../src/drivers.rs | 0 .../src/init.rs | 0 .../ax-plat-loongarch64-qemu-virt}/src/irq.rs | 0 .../src/irq/eiointc.rs | 0 .../src/irq/pch_pic.rs | 0 .../ax-plat-loongarch64-qemu-virt}/src/lib.rs | 0 .../ax-plat-loongarch64-qemu-virt}/src/mem.rs | 0 .../ax-plat-loongarch64-qemu-virt}/src/mp.rs | 0 .../src/power.rs | 0 .../src/time.rs | 0 .../ax-plat-macros}/Cargo.toml | 0 .../ax-plat-macros}/README.md | 2 +- .../ax-plat-macros}/README_CN.md | 2 +- .../ax-plat-macros}/src/lib.rs | 0 .../ax-plat-riscv64-qemu-virt}/CHANGELOG.md | 0 .../ax-plat-riscv64-qemu-virt}/Cargo.toml | 7 + .../ax-plat-riscv64-qemu-virt}/README.md | 2 +- .../ax-plat-riscv64-qemu-virt}/README_CN.md | 2 +- .../ax-plat-riscv64-qemu-virt}/axconfig.toml | 0 .../ax-plat-riscv64-qemu-virt}/build.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/boot.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/console.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/drivers.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/init.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/irq.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/lib.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/mem.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/power.rs | 0 .../ax-plat-riscv64-qemu-virt}/src/time.rs | 0 .../ax-plat-riscv64-sg2002}/CHANGELOG.md | 0 .../ax-plat-riscv64-sg2002}/Cargo.toml | 6 + .../ax-plat-riscv64-sg2002}/README.md | 2 +- .../ax-plat-riscv64-sg2002}/axconfig.toml | 0 .../ax-plat-riscv64-sg2002}/build.rs | 0 .../ax-plat-riscv64-sg2002}/src/boot.rs | 0 .../ax-plat-riscv64-sg2002}/src/console.rs | 0 .../src/drivers/cvsd.rs | 0 .../src/drivers/mod.rs | 0 .../ax-plat-riscv64-sg2002}/src/init.rs | 0 .../ax-plat-riscv64-sg2002}/src/irq.rs | 0 .../ax-plat-riscv64-sg2002}/src/lib.rs | 0 .../ax-plat-riscv64-sg2002}/src/mem.rs | 0 .../ax-plat-riscv64-sg2002}/src/power.rs | 0 .../ax-plat-riscv64-sg2002}/src/time.rs | 0 .../ax-plat-riscv64-visionfive2}/CHANGELOG.md | 0 .../ax-plat-riscv64-visionfive2}/Cargo.toml | 8 +- .../ax-plat-riscv64-visionfive2}/README.md | 0 .../axconfig.toml | 2 +- .../ax-plat-riscv64-visionfive2}/build.rs | 0 .../ax-plat-riscv64-visionfive2}/src/boot.rs | 0 .../src/console.rs | 0 .../src/drivers.rs | 0 .../ax-plat-riscv64-visionfive2}/src/init.rs | 0 .../ax-plat-riscv64-visionfive2}/src/irq.rs | 0 .../ax-plat-riscv64-visionfive2}/src/lib.rs | 0 .../ax-plat-riscv64-visionfive2}/src/mem.rs | 0 .../ax-plat-riscv64-visionfive2}/src/power.rs | 0 .../ax-plat-riscv64-visionfive2}/src/time.rs | 0 .../ax-plat-x86-pc}/CHANGELOG.md | 0 .../ax-plat-x86-pc}/Cargo.toml | 7 + .../ax-plat-x86-pc}/README.md | 2 +- .../ax-plat-x86-pc}/README_CN.md | 2 +- .../ax-plat-x86-pc}/axconfig.toml | 0 .../ax-plat-x86-pc}/build.rs | 0 .../ax-plat-x86-pc}/src/ap_start.S | 0 .../ax-plat-x86-pc}/src/apic.rs | 0 .../ax-plat-x86-pc}/src/boot.rs | 0 .../ax-plat-x86-pc}/src/console.rs | 0 .../ax-plat-x86-pc}/src/drivers.rs | 0 .../ax-plat-x86-pc}/src/init.rs | 0 .../ax-plat-x86-pc}/src/lib.rs | 0 .../ax-plat-x86-pc}/src/mem.rs | 0 .../ax-plat-x86-pc}/src/mp.rs | 0 .../ax-plat-x86-pc}/src/multiboot.S | 0 .../ax-plat-x86-pc}/src/power.rs | 0 .../ax-plat-x86-pc}/src/time.rs | 0 .../ax-plat-x86-qemu-q35}/.gitignore | 0 .../ax-plat-x86-qemu-q35}/CHANGELOG.md | 0 .../ax-plat-x86-qemu-q35}/Cargo.toml | 8 +- .../ax-plat-x86-qemu-q35}/LICENSE | 0 .../ax-plat-x86-qemu-q35}/README.md | 4 +- .../ax-plat-x86-qemu-q35}/axconfig.toml | 2 +- .../ax-plat-x86-qemu-q35}/build.rs | 0 .../ax-plat-x86-qemu-q35}/linker.lds.S | 0 .../ax-plat-x86-qemu-q35}/src/ap_start.S | 0 .../ax-plat-x86-qemu-q35}/src/apic.rs | 0 .../ax-plat-x86-qemu-q35}/src/boot.rs | 0 .../ax-plat-x86-qemu-q35}/src/console.rs | 0 .../ax-plat-x86-qemu-q35}/src/drivers.rs | 0 .../ax-plat-x86-qemu-q35}/src/init.rs | 0 .../ax-plat-x86-qemu-q35}/src/lib.rs | 0 .../ax-plat-x86-qemu-q35}/src/mem.rs | 0 .../ax-plat-x86-qemu-q35}/src/mp.rs | 0 .../ax-plat-x86-qemu-q35}/src/multiboot.S | 0 .../ax-plat-x86-qemu-q35}/src/power.rs | 0 .../ax-plat-x86-qemu-q35}/src/time.rs | 0 .../axplat => platforms/ax-plat}/CHANGELOG.md | 0 .../axplat => platforms/ax-plat}/Cargo.toml | 0 .../axplat => platforms/ax-plat}/README.md | 2 +- .../axplat => platforms/ax-plat}/README_CN.md | 2 +- .../ax-plat}/src/console.rs | 0 .../axplat => platforms/ax-plat}/src/init.rs | 0 .../axplat => platforms/ax-plat}/src/irq.rs | 0 .../axplat => platforms/ax-plat}/src/lib.rs | 0 .../axplat => platforms/ax-plat}/src/mem.rs | 0 .../ax-plat}/src/percpu.rs | 0 .../axplat => platforms/ax-plat}/src/power.rs | 0 .../axplat => platforms/ax-plat}/src/time.rs | 0 {platform => platforms}/somehal/CHANGELOG.md | 0 {platform => platforms}/somehal/Cargo.toml | 0 {platform => platforms}/somehal/README.md | 2 +- {platform => platforms}/somehal/build.rs | 0 {platform => platforms}/somehal/link.ld | 0 .../somehal/src/arch/aarch64/gic/mod.rs | 0 .../somehal/src/arch/aarch64/gic/v2.rs | 0 .../somehal/src/arch/aarch64/gic/v3.rs | 0 .../somehal/src/arch/aarch64/mod.rs | 0 .../somehal/src/arch/aarch64/systick.rs | 0 .../somehal/src/arch/loongarch64/mod.rs | 0 .../somehal/src/arch/riscv64/mod.rs | 0 .../somehal/src/arch/x86_64/mod.rs | 0 {platform => platforms}/somehal/src/common.rs | 0 {platform => platforms}/somehal/src/driver.rs | 0 {platform => platforms}/somehal/src/irq.rs | 0 {platform => platforms}/somehal/src/lib.rs | 0 {platform => platforms}/somehal/src/setup.rs | 0 reports/external-spin-audit.md | 30 +- reports/external-spin-migration-plan.md | 2 +- scripts/axbuild/src/build.rs | 291 +++++++++++++----- scripts/repo/repos.csv | 1 - 307 files changed, 646 insertions(+), 2930 deletions(-) delete mode 100644 components/axplat_crates/.github/workflows/check.yml delete mode 100644 components/axplat_crates/.github/workflows/docs.yml delete mode 100644 components/axplat_crates/.github/workflows/test.yml delete mode 100644 components/axplat_crates/.gitignore delete mode 100644 components/axplat_crates/LICENSE delete mode 100644 components/axplat_crates/README.md delete mode 100644 components/axplat_crates/README_CN.md delete mode 100644 components/axplat_crates/examples/hello-kernel/.gitignore delete mode 100644 components/axplat_crates/examples/hello-kernel/Cargo.toml delete mode 100644 components/axplat_crates/examples/hello-kernel/Makefile delete mode 100644 components/axplat_crates/examples/hello-kernel/README.md delete mode 100644 components/axplat_crates/examples/hello-kernel/README_CN.md delete mode 100644 components/axplat_crates/examples/hello-kernel/build.rs delete mode 100644 components/axplat_crates/examples/hello-kernel/linker.lds.S delete mode 100644 components/axplat_crates/examples/hello-kernel/src/main.rs delete mode 100644 components/axplat_crates/examples/irq-kernel/.gitignore delete mode 100644 components/axplat_crates/examples/irq-kernel/Cargo.toml delete mode 100644 components/axplat_crates/examples/irq-kernel/Makefile delete mode 100644 components/axplat_crates/examples/irq-kernel/README.md delete mode 100644 components/axplat_crates/examples/irq-kernel/README_CN.md delete mode 100644 components/axplat_crates/examples/irq-kernel/build.rs delete mode 100644 components/axplat_crates/examples/irq-kernel/linker.lds.S delete mode 100644 components/axplat_crates/examples/irq-kernel/src/irq.rs delete mode 100644 components/axplat_crates/examples/irq-kernel/src/main.rs delete mode 100644 components/axplat_crates/examples/smp-kernel/.gitignore delete mode 100644 components/axplat_crates/examples/smp-kernel/Cargo.toml delete mode 100644 components/axplat_crates/examples/smp-kernel/Makefile delete mode 100644 components/axplat_crates/examples/smp-kernel/README.md delete mode 100644 components/axplat_crates/examples/smp-kernel/README_CN.md delete mode 100644 components/axplat_crates/examples/smp-kernel/build.rs delete mode 100644 components/axplat_crates/examples/smp-kernel/linker.lds.S delete mode 100644 components/axplat_crates/examples/smp-kernel/src/init.rs delete mode 100644 components/axplat_crates/examples/smp-kernel/src/irq.rs delete mode 100644 components/axplat_crates/examples/smp-kernel/src/main.rs delete mode 100644 components/axplat_crates/examples/smp-kernel/src/mp.rs delete mode 100755 components/axplat_crates/publish.sh delete mode 100644 docs/docs/components/crates/hello-kernel.md delete mode 100644 docs/docs/components/crates/irq-kernel.md delete mode 100644 docs/docs/components/crates/smp-kernel.md rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/Cargo.toml (89%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/README.md (97%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/misc.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/mp.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-bsta1000b => platforms/ax-plat-aarch64-bsta1000b}/src/serial.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/Cargo.toml (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/README.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/src/generic_timer.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/src/gic.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/src/pl011.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/src/pl031.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-peripherals => platforms/ax-plat-aarch64-peripherals}/src/psci.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/Cargo.toml (87%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/README.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-phytium-pi => platforms/ax-plat-aarch64-phytium-pi}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/Cargo.toml (91%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/README.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-qemu-virt => platforms/ax-plat-aarch64-qemu-virt}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/Cargo.toml (89%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/README.md (97%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/mp.rs (100%) rename {components/axplat_crates/platforms/axplat-aarch64-raspi => platforms/ax-plat-aarch64-raspi}/src/power.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/CHANGELOG.md (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/Cargo.toml (89%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/build.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/link.ld (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/boot.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/console.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/drivers/mod.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/generic_timer.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/init.rs (93%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/irq.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/lib.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/mem.rs (100%) rename {platform/axplat-dyn => platforms/ax-plat-dyn}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/Cargo.toml (88%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/README.md (96%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/linker.lds.S (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/console.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/irq.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/irq/eiointc.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/irq/pch_pic.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/mp.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-loongarch64-qemu-virt => platforms/ax-plat-loongarch64-qemu-virt}/src/time.rs (100%) rename {components/axplat_crates/axplat-macros => platforms/ax-plat-macros}/Cargo.toml (100%) rename {components/axplat_crates/axplat-macros => platforms/ax-plat-macros}/README.md (97%) rename {components/axplat_crates/axplat-macros => platforms/ax-plat-macros}/README_CN.md (97%) rename {components/axplat_crates/axplat-macros => platforms/ax-plat-macros}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/Cargo.toml (89%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/README.md (96%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/README_CN.md (96%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/console.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/irq.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-qemu-virt => platforms/ax-plat-riscv64-qemu-virt}/src/time.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/Cargo.toml (91%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/README.md (97%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/axconfig.toml (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/console.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/drivers/cvsd.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/drivers/mod.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/irq.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-riscv64-sg2002 => platforms/ax-plat-riscv64-sg2002}/src/time.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/CHANGELOG.md (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/Cargo.toml (85%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/README.md (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/axconfig.toml (98%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-riscv64-visionfive2}/build.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/boot.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/console.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/drivers.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/init.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/irq.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/lib.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/mem.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/power.rs (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-riscv64-visionfive2}/src/time.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/CHANGELOG.md (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/Cargo.toml (90%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/README.md (97%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/README_CN.md (97%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/axconfig.toml (100%) rename {platform/riscv64-visionfive2 => platforms/ax-plat-x86-pc}/build.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/ap_start.S (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/apic.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/boot.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/console.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/drivers.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/init.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/lib.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/mem.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/mp.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/multiboot.S (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/power.rs (100%) rename {components/axplat_crates/platforms/axplat-x86-pc => platforms/ax-plat-x86-pc}/src/time.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/.gitignore (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/CHANGELOG.md (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/Cargo.toml (89%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/LICENSE (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/README.md (98%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/axconfig.toml (98%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/build.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/linker.lds.S (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/ap_start.S (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/apic.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/boot.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/console.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/drivers.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/init.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/lib.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/mem.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/mp.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/multiboot.S (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/power.rs (100%) rename {platform/x86-qemu-q35 => platforms/ax-plat-x86-qemu-q35}/src/time.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/CHANGELOG.md (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/Cargo.toml (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/README.md (98%) rename {components/axplat_crates/axplat => platforms/ax-plat}/README_CN.md (97%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/console.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/init.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/irq.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/lib.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/mem.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/percpu.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/power.rs (100%) rename {components/axplat_crates/axplat => platforms/ax-plat}/src/time.rs (100%) rename {platform => platforms}/somehal/CHANGELOG.md (100%) rename {platform => platforms}/somehal/Cargo.toml (100%) rename {platform => platforms}/somehal/README.md (99%) rename {platform => platforms}/somehal/build.rs (100%) rename {platform => platforms}/somehal/link.ld (100%) rename {platform => platforms}/somehal/src/arch/aarch64/gic/mod.rs (100%) rename {platform => platforms}/somehal/src/arch/aarch64/gic/v2.rs (100%) rename {platform => platforms}/somehal/src/arch/aarch64/gic/v3.rs (100%) rename {platform => platforms}/somehal/src/arch/aarch64/mod.rs (100%) rename {platform => platforms}/somehal/src/arch/aarch64/systick.rs (100%) rename {platform => platforms}/somehal/src/arch/loongarch64/mod.rs (100%) rename {platform => platforms}/somehal/src/arch/riscv64/mod.rs (100%) rename {platform => platforms}/somehal/src/arch/x86_64/mod.rs (100%) rename {platform => platforms}/somehal/src/common.rs (100%) rename {platform => platforms}/somehal/src/driver.rs (100%) rename {platform => platforms}/somehal/src/irq.rs (100%) rename {platform => platforms}/somehal/src/lib.rs (100%) rename {platform => platforms}/somehal/src/setup.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index e8bc582b63..d8cdadcb4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1033,13 +1033,13 @@ dependencies = [ "ax-plat-aarch64-phytium-pi", "ax-plat-aarch64-qemu-virt", "ax-plat-aarch64-raspi", + "ax-plat-dyn", "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", "ax-plat-riscv64-sg2002", + "ax-plat-riscv64-visionfive2", "ax-plat-x86-pc", - "axplat-dyn", - "axplat-riscv64-visionfive2", - "axplat-x86-qemu-q35", + "ax-plat-x86-qemu-q35", "cfg-if", "fdt-parser", "heapless 0.9.3", @@ -1395,6 +1395,26 @@ dependencies = [ "log", ] +[[package]] +name = "ax-plat-dyn" +version = "0.6.2" +dependencies = [ + "anyhow", + "ax-config-macros", + "ax-cpu", + "ax-driver", + "ax-errno", + "ax-memory-addr", + "ax-percpu", + "ax-plat", + "axklib", + "heapless 0.9.3", + "log", + "rdrive", + "somehal", + "spin", +] + [[package]] name = "ax-plat-loongarch64-qemu-virt" version = "0.5.10" @@ -1465,6 +1485,24 @@ dependencies = [ "spin", ] +[[package]] +name = "ax-plat-riscv64-visionfive2" +version = "0.1.4" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-kspin", + "ax-lazyinit", + "ax-plat", + "ax-riscv-plic", + "log", + "rdrive", + "riscv 0.14.0", + "riscv_goldfish", + "sbi-rt 0.0.3", + "uart_16550 0.4.0", +] + [[package]] name = "ax-plat-x86-pc" version = "0.5.10" @@ -1490,6 +1528,31 @@ dependencies = [ "x86_rtc", ] +[[package]] +name = "ax-plat-x86-qemu-q35" +version = "0.4.8" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-driver", + "ax-int-ratio", + "ax-kspin", + "ax-lazyinit", + "ax-percpu", + "ax-plat", + "axklib", + "bitflags 2.11.1", + "heapless 0.9.3", + "log", + "multiboot", + "raw-cpuid 11.6.0", + "uart_16550 0.4.0", + "x2apic", + "x86", + "x86_64", + "x86_rtc", +] + [[package]] name = "ax-posix-api" version = "0.5.16" @@ -1803,69 +1866,6 @@ dependencies = [ name = "axpanic" version = "0.1.0" -[[package]] -name = "axplat-dyn" -version = "0.6.2" -dependencies = [ - "anyhow", - "ax-config-macros", - "ax-cpu", - "ax-driver", - "ax-errno", - "ax-memory-addr", - "ax-percpu", - "ax-plat", - "axklib", - "heapless 0.9.3", - "log", - "rdrive", - "somehal", - "spin", -] - -[[package]] -name = "axplat-riscv64-visionfive2" -version = "0.1.4" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-kspin", - "ax-lazyinit", - "ax-plat", - "ax-riscv-plic", - "log", - "rdrive", - "riscv 0.14.0", - "riscv_goldfish", - "sbi-rt 0.0.3", - "uart_16550 0.4.0", -] - -[[package]] -name = "axplat-x86-qemu-q35" -version = "0.4.8" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-driver", - "ax-int-ratio", - "ax-kspin", - "ax-lazyinit", - "ax-percpu", - "ax-plat", - "axklib", - "bitflags 2.11.1", - "heapless 0.9.3", - "log", - "multiboot", - "raw-cpuid 11.6.0", - "uart_16550 0.4.0", - "x2apic", - "x86", - "x86_64", - "x86_rtc", -] - [[package]] name = "axpoll" version = "0.3.9" @@ -1961,8 +1961,10 @@ dependencies = [ "ax-page-table-entry", "ax-page-table-multiarch", "ax-percpu", + "ax-plat-dyn", "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", + "ax-plat-x86-qemu-q35", "ax-std", "ax-timer-list", "axaddrspace", @@ -1971,8 +1973,6 @@ dependencies = [ "axdevice_base", "axhvc", "axklib", - "axplat-dyn", - "axplat-x86-qemu-q35", "axvcpu", "axvisor_api", "axvm", @@ -4280,18 +4280,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hello-kernel" -version = "0.3.1" -dependencies = [ - "ax-plat", - "ax-plat-aarch64-qemu-virt", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-pc", - "cfg-if", -] - [[package]] name = "hermit-abi" version = "0.5.2" @@ -4691,20 +4679,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "irq-kernel" -version = "0.3.1" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-plat", - "ax-plat-aarch64-qemu-virt", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-pc", - "cfg-if", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -7740,23 +7714,6 @@ dependencies = [ "managed", ] -[[package]] -name = "smp-kernel" -version = "0.3.1" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-memory-addr", - "ax-percpu", - "ax-plat", - "ax-plat-aarch64-qemu-virt", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-pc", - "cfg-if", - "const-str", -] - [[package]] name = "socket2" version = "0.6.3" @@ -7936,12 +7893,12 @@ dependencies = [ "ax-net-ng", "ax-page-table-multiarch", "ax-percpu", + "ax-plat-dyn", "ax-runtime", "ax-sync", "ax-task", "axbacktrace", "axfs-ng-vfs", - "axplat-dyn", "axpoll", "bitflags 2.11.1", "bitmaps", @@ -8036,8 +7993,8 @@ dependencies = [ "ax-driver", "ax-feat", "ax-hal", + "ax-plat-dyn", "axbuild", - "axplat-dyn", "clap", "starry-kernel", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 44f241b771..3d507b75f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,10 +76,7 @@ members = [ "components/axfs_crates/axfs_vfs", "components/axmm_crates/memory_addr", "components/axmm_crates/memory_set", - "components/axplat_crates/axplat", - "components/axplat_crates/axplat-macros", - "components/axplat_crates/examples/*", - "components/axplat_crates/platforms/*", + "platforms/*", "components/crate_interface/crate_interface_lite", "components/crate_interface/test_crates/*", "components/ctor_bare/ctor_bare", @@ -147,8 +144,6 @@ members = [ "os/arceos/ulib/*", "os/arceos/ulib/arceos-rust/lib", "os/axvisor", - - "platform/*", "scripts/axbuild", "test-suit/arceos/rust/backtrace*", "test-suit/arceos/rust/display", @@ -245,17 +240,17 @@ ax-page-table-entry = { version = "0.8.9", path = "components/page_table_multiar ax-page-table-multiarch = { version = "0.8.10", path = "components/page_table_multiarch/page_table_multiarch" } ax-percpu = { version = "0.4.11", path = "components/percpu/percpu" } ax-percpu-macros = { version = "0.4.10", path = "components/percpu/percpu_macros" } -ax-plat = { version = "0.5.8", path = "components/axplat_crates/axplat" } -ax-plat-aarch64-bsta1000b = { version = "0.5.11", path = "components/axplat_crates/platforms/axplat-aarch64-bsta1000b" } -ax-plat-aarch64-peripherals = { version = "0.5.10", path = "components/axplat_crates/platforms/axplat-aarch64-peripherals" } -ax-plat-aarch64-phytium-pi = { version = "0.5.11", path = "components/axplat_crates/platforms/axplat-aarch64-phytium-pi" } -ax-plat-aarch64-qemu-virt = { version = "0.5.10", path = "components/axplat_crates/platforms/axplat-aarch64-qemu-virt" } -ax-plat-aarch64-raspi = { version = "0.5.11", path = "components/axplat_crates/platforms/axplat-aarch64-raspi" } -ax-plat-loongarch64-qemu-virt = { version = "0.5.10", path = "components/axplat_crates/platforms/axplat-loongarch64-qemu-virt", default-features = false } -ax-plat-macros = { version = "0.3.6", path = "components/axplat_crates/axplat-macros" } -ax-plat-riscv64-qemu-virt = { version = "0.5.9", path = "components/axplat_crates/platforms/axplat-riscv64-qemu-virt" } -ax-plat-riscv64-sg2002 = { version = "0.3.6", path = "components/axplat_crates/platforms/axplat-riscv64-sg2002" } -ax-plat-x86-pc = { version = "0.5.10", path = "components/axplat_crates/platforms/axplat-x86-pc" } +ax-plat = { version = "0.5.8", path = "platforms/ax-plat" } +ax-plat-aarch64-bsta1000b = { version = "0.5.11", path = "platforms/ax-plat-aarch64-bsta1000b" } +ax-plat-aarch64-peripherals = { version = "0.5.10", path = "platforms/ax-plat-aarch64-peripherals" } +ax-plat-aarch64-phytium-pi = { version = "0.5.11", path = "platforms/ax-plat-aarch64-phytium-pi" } +ax-plat-aarch64-qemu-virt = { version = "0.5.10", path = "platforms/ax-plat-aarch64-qemu-virt" } +ax-plat-aarch64-raspi = { version = "0.5.11", path = "platforms/ax-plat-aarch64-raspi" } +ax-plat-loongarch64-qemu-virt = { version = "0.5.10", path = "platforms/ax-plat-loongarch64-qemu-virt", default-features = false } +ax-plat-macros = { version = "0.3.6", path = "platforms/ax-plat-macros" } +ax-plat-riscv64-qemu-virt = { version = "0.5.9", path = "platforms/ax-plat-riscv64-qemu-virt" } +ax-plat-riscv64-sg2002 = { version = "0.3.6", path = "platforms/ax-plat-riscv64-sg2002" } +ax-plat-x86-pc = { version = "0.5.10", path = "platforms/ax-plat-x86-pc" } ax-posix-api = { version = "0.5.16", path = "os/arceos/api/arceos_posix_api" } ax-riscv-plic = { version = "0.4.6", path = "drivers/intc/riscv_plic" } ax-runtime = { version = "0.5.16", path = "os/arceos/modules/axruntime" } @@ -273,9 +268,9 @@ axdevice_base = { version = "0.4.11", path = "components/axdevice_base" } axfs-ng-vfs = { version = "0.4.2", path = "components/axfs-ng-vfs" } axhvc = { version = "0.4.8", path = "components/axhvc" } axklib = { version = "0.5.9", path = "components/axklib" } -axplat-dyn = { version = "0.6.2", path = "platform/axplat-dyn", default-features = false } -axplat-riscv64-visionfive2 = { version = "0.1.4", path = "platform/riscv64-visionfive2" } -axplat-x86-qemu-q35 = { version = "0.4.8", path = "platform/x86-qemu-q35", default-features = false } +ax-plat-dyn = { version = "0.6.2", path = "platforms/ax-plat-dyn", default-features = false } +ax-plat-riscv64-visionfive2 = { version = "0.1.4", path = "platforms/ax-plat-riscv64-visionfive2" } +ax-plat-x86-qemu-q35 = { version = "0.4.8", path = "platforms/ax-plat-x86-qemu-q35", default-features = false } axpoll = { version = "0.3.9", path = "components/axpoll" } axvcpu = { version = "0.5.9", path = "components/axvcpu" } axvisor = { version = "0.5.8", path = "os/axvisor" } @@ -289,14 +284,12 @@ define-simple-traits = { version = "0.3.4", path = "components/crate_interface/t define-weak-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/define-weak-traits" } deptool = { version = "0.3.1", path = "os/arceos/tools/deptool" } fxmac_rs = { version = "0.5.1", path = "drivers/net/fxmac_rs" } -hello-kernel = { version = "0.3.1", path = "components/axplat_crates/examples/hello-kernel" } impl-simple-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/impl-simple-traits" } impl-weak-partial = { version = "0.3.4", path = "components/crate_interface/test_crates/impl-weak-partial" } impl-weak-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/impl-weak-traits" } arm-gic-driver = { version = "0.17.1", path = "drivers/intc/arm-gic-driver" } enumerate = { version = "0.1.1", path = "drivers/examples/enumerate" } eth-intel = { version = "0.1.2", path = "drivers/net/eth-intel" } -irq-kernel = { version = "0.3.1", path = "components/axplat_crates/examples/irq-kernel" } loongarch_vcpu = { version = "0.5.3", path = "components/loongarch_vcpu" } mingo = { version = "0.8.3", path = "os/arceos/tools/raspi4/chainloader" } range-alloc-arceos = { version = "0.3.9", path = "components/range-alloc-arceos" } @@ -342,7 +335,6 @@ phytium-mci-host = { version = "0.1.1", path = "drivers/blk/phytium-mci-host", d sdhci-host = { version = "0.1.1", path = "drivers/blk/sdhci-host", default-features = false } sdmmc-protocol = { version = "0.1.1", path = "drivers/blk/sdmmc-protocol", default-features = false } smoltcp = { version = "0.13.1", default-features = false } -smp-kernel = { version = "0.3.1", path = "components/axplat_crates/examples/smp-kernel" } starry-kernel = { version = "0.5.12", path = "os/StarryOS/kernel" } starry-process = { version = "0.4.7", path = "components/starry-process" } starry-signal = { version = "0.7.0", path = "components/starry-signal" } @@ -361,7 +353,7 @@ kernutil = { path = "components/kernutil", version = "0.2.2" } ranges-ext = { version = "0.6.4", path = "components/ranges-ext" } somehal-macros = { path = "components/somehal-macros", version = "0.1.3" } kasm-aarch64 = { path = "components/kasm-aarch64", version = "0.2.1" } -somehal = { version = "0.6.7", path = "platform/somehal" } +somehal = { version = "0.6.7", path = "platforms/somehal" } # External crates anyhow = { version = "1.0", default-features = false } diff --git a/components/axplat_crates/.github/workflows/check.yml b/components/axplat_crates/.github/workflows/check.yml deleted file mode 100644 index 135b9874b3..0000000000 --- a/components/axplat_crates/.github/workflows/check.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Check - -on: [push, pull_request] - -jobs: - axplat: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - target: - - x86_64-unknown-linux-gnu - - x86_64-unknown-none - - riscv64gc-unknown-none-elf - - aarch64-unknown-none-softfloat - - loongarch64-unknown-none-softfloat - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-src, clippy, rustfmt - targets: ${{ matrix.target }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy -p axplat --target ${{ matrix.target }} --all-features - - name: Build - run: cargo build -p axplat --target ${{ matrix.target }} --all-features - - name: Unit test - if: ${{ matrix.target == 'x86_64-unknown-linux-gnu' }} - run: cargo test -p axplat --all-features -- --nocapture - - platforms: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - platform: - - name: axplat-x86-pc - target: x86_64-unknown-none - - name: axplat-aarch64-peripherals - target: aarch64-unknown-none - - name: axplat-aarch64-qemu-virt - target: aarch64-unknown-none - - name: axplat-aarch64-raspi - target: aarch64-unknown-none - - name: axplat-aarch64-bsta1000b - target: aarch64-unknown-none - - name: axplat-aarch64-phytium-pi - target: aarch64-unknown-none - - name: axplat-riscv64-qemu-virt - target: riscv64gc-unknown-none-elf - - name: axplat-loongarch64-qemu-virt - target: loongarch64-unknown-none - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-src, clippy - targets: ${{ matrix.platform.target }} - - name: Clippy - run: cargo clippy --target ${{ matrix.platform.target }} -p ${{ matrix.platform.name }} --all-features - - name: Build - run: cargo build --target ${{ matrix.platform.target }} -p ${{ matrix.platform.name }} --all-features diff --git a/components/axplat_crates/.github/workflows/docs.yml b/components/axplat_crates/.github/workflows/docs.yml deleted file mode 100644 index b262f23350..0000000000 --- a/components/axplat_crates/.github/workflows/docs.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Docs - -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - env: - RUSTDOCFLAGS: --cfg docsrs -Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - run: cargo doc --all-features --no-deps -p axplat -p axplat-macros -p axplat-aarch64-peripherals - - - uses: actions/configure-pages@v5 - - uses: actions/upload-pages-artifact@v4 - with: - path: target/doc - - deploy: - runs-on: ubuntu-latest - needs: build - if: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@v4 diff --git a/components/axplat_crates/.github/workflows/test.yml b/components/axplat_crates/.github/workflows/test.yml deleted file mode 100644 index 1be1ae19f9..0000000000 --- a/components/axplat_crates/.github/workflows/test.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Test - -on: [push, pull_request] - -jobs: - cli: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-src, clippy - - name: Clippy - run: cargo clippy -p cargo-axplat - - name: Build - run: cargo build -p cargo-axplat - - name: Create and test new axplat project from template - run: | - cargo run -p cargo-axplat -- axplat new /tmp/axplat-test --axplat-path "${{ github.workspace }}/axplat" - cd /tmp/axplat-test - cargo check - - examples: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - arch: [x86_64, aarch64, riscv64, loongarch64] - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-src, clippy - targets: x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat, loongarch64-unknown-none-softfloat - - name: Clippy - run: | - make -C examples/hello-kernel ARCH=${{ matrix.arch }} clippy - make -C examples/irq-kernel ARCH=${{ matrix.arch }} clippy - make -C examples/smp-kernel ARCH=${{ matrix.arch }} clippy - - name: Build - run: | - make -C examples/hello-kernel ARCH=${{ matrix.arch }} - make -C examples/irq-kernel ARCH=${{ matrix.arch }} - make -C examples/smp-kernel ARCH=${{ matrix.arch }} SMP=4 diff --git a/components/axplat_crates/.gitignore b/components/axplat_crates/.gitignore deleted file mode 100644 index 6e2ba627a1..0000000000 --- a/components/axplat_crates/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/.vscode -.DS_Store -/Cargo.lock diff --git a/components/axplat_crates/LICENSE b/components/axplat_crates/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/components/axplat_crates/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/components/axplat_crates/README.md b/components/axplat_crates/README.md deleted file mode 100644 index 2d274a8c06..0000000000 --- a/components/axplat_crates/README.md +++ /dev/null @@ -1,54 +0,0 @@ -

axplat_crates

- -

Workspace for hardware platform abstraction crates built on axplat

- -
- -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`axplat_crates` is a workspace that groups related TGOSKits components under a unified layout. It helps organize closely related crates that are typically developed, versioned, and used together. - -> axplat_crates was derived from https://github.com/arceos-org/axplat_crates - -## Workspace Members - -- `axplat` -- `axplat-macros` -- `platforms/axplat-x86-pc` -- `platforms/axplat-aarch64-peripherals` -- `platforms/axplat-aarch64-qemu-virt` -- `platforms/axplat-aarch64-raspi` -- `platforms/axplat-aarch64-bsta1000b` -- `platforms/axplat-aarch64-phytium-pi` -- `platforms/axplat-riscv64-qemu-virt` -- `platforms/axplat-loongarch64-qemu-virt` -- `examples/hello-kernel` -- `examples/irq-kernel` -- `examples/smp-kernel` - -## Quick Start - -```bash -# Enter the workspace directory -cd components/axplat_crates - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --workspace --all-targets --all-features - -# Run tests -cargo test --workspace --all-features -``` - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axplat_crates/README_CN.md b/components/axplat_crates/README_CN.md deleted file mode 100644 index c0c3b6f66d..0000000000 --- a/components/axplat_crates/README_CN.md +++ /dev/null @@ -1,54 +0,0 @@ -

axplat_crates

- -

基于 axplat 的硬件平台抽象 crate 工作区

- -
- -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`axplat_crates` 是一个工作区,用于将相关的 TGOSKits 组件放在统一的目录结构下,便于协同开发、版本管理与组合使用。 - -> axplat_crates 派生自 https://github.com/arceos-org/axplat_crates - -## 工作区成员 - -- `axplat` -- `axplat-macros` -- `platforms/axplat-x86-pc` -- `platforms/axplat-aarch64-peripherals` -- `platforms/axplat-aarch64-qemu-virt` -- `platforms/axplat-aarch64-raspi` -- `platforms/axplat-aarch64-bsta1000b` -- `platforms/axplat-aarch64-phytium-pi` -- `platforms/axplat-riscv64-qemu-virt` -- `platforms/axplat-loongarch64-qemu-virt` -- `examples/hello-kernel` -- `examples/irq-kernel` -- `examples/smp-kernel` - -## 快速开始 - -```bash -# 进入工作区目录 -cd components/axplat_crates - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --workspace --all-targets --all-features - -# 运行测试 -cargo test --workspace --all-features -``` - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axplat_crates/examples/hello-kernel/.gitignore b/components/axplat_crates/examples/hello-kernel/.gitignore deleted file mode 100644 index 6b8069a1de..0000000000 --- a/components/axplat_crates/examples/hello-kernel/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.lds diff --git a/components/axplat_crates/examples/hello-kernel/Cargo.toml b/components/axplat_crates/examples/hello-kernel/Cargo.toml deleted file mode 100644 index 1d9e3a3caa..0000000000 --- a/components/axplat_crates/examples/hello-kernel/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "hello-kernel" -version = "0.3.1" -edition.workspace = true -authors = ["Yuekai Jia ", "Youjie Zheng ", "yanjuguang ", "Su Mingxian ", "RobertYuan <634954435@qq.com>", "hky1999 "] -repository = "https://github.com/rcore-os/tgoskits" -license = "Apache-2.0" -publish = false - -[dependencies] -cfg-if = "1.0" -ax-plat = { workspace = true } - -[target.'cfg(target_arch = "x86_64")'.dependencies] -ax-plat-x86-pc = { workspace = true } -[target.'cfg(target_arch = "aarch64")'.dependencies] -ax-plat-aarch64-qemu-virt = { workspace = true } -[target.'cfg(target_arch = "riscv64")'.dependencies] -ax-plat-riscv64-qemu-virt = { workspace = true } -[target.'cfg(target_arch = "loongarch64")'.dependencies] -ax-plat-loongarch64-qemu-virt = { workspace = true } diff --git a/components/axplat_crates/examples/hello-kernel/Makefile b/components/axplat_crates/examples/hello-kernel/Makefile deleted file mode 100644 index e2587d00cf..0000000000 --- a/components/axplat_crates/examples/hello-kernel/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -ARCH ?= x86_64 -APP := hello-kernel - -OBJDUMP ?= rust-objdump -d --print-imm-hex --x86-asm-syntax=intel -OBJCOPY ?= rust-objcopy --binary-architecture=$(ARCH) - -ifeq ($(ARCH), x86_64) - TARGET := x86_64-unknown-none -else ifeq ($(ARCH), aarch64) - TARGET := aarch64-unknown-none-softfloat -else ifeq ($(ARCH), riscv64) - TARGET := riscv64gc-unknown-none-elf -else ifeq ($(ARCH), loongarch64) - TARGET := loongarch64-unknown-none-softfloat -else - $(error "ARCH" must be one of "x86_64", "riscv64", "aarch64" or "loongarch64") -endif - -OUT_ELF := $(CURDIR)/../../target/$(TARGET)/release/$(APP) -OUT_BIN := $(OUT_ELF).bin - -qemu_args-x86_64 := \ - -machine q35 \ - -kernel $(OUT_ELF) - -qemu_args-riscv64 := \ - -machine virt \ - -bios default \ - -kernel $(OUT_BIN) - -qemu_args-aarch64 := \ - -cpu cortex-a72 \ - -machine virt \ - -kernel $(OUT_BIN) - -qemu_args-loongarch64 := \ - -machine virt \ - -m 1G \ - -kernel $(OUT_BIN) - -all: build - -build: - cargo build -p $(APP) --target $(TARGET) --release - -$(OUT_BIN): build - $(OBJCOPY) --strip-all -O binary $(OUT_ELF) $(OUT_BIN) - -run: $(OUT_BIN) - qemu-system-$(ARCH) $(qemu_args-$(ARCH)) -nographic - -disasm: - $(OBJDUMP) $(OUT_ELF) | less - -clippy: - cargo clippy -p $(APP) --target $(TARGET) - -clean: - cargo clean diff --git a/components/axplat_crates/examples/hello-kernel/README.md b/components/axplat_crates/examples/hello-kernel/README.md deleted file mode 100644 index 0d52c03b72..0000000000 --- a/components/axplat_crates/examples/hello-kernel/README.md +++ /dev/null @@ -1,81 +0,0 @@ -

hello-kernel

- -

hello-kernel component for TGOSKits

- -
- -[![Crates.io](https://img.shields.io/crates/v/hello-kernel.svg)](https://crates.io/crates/hello-kernel) -[![Docs.rs](https://docs.rs/hello-kernel/badge.svg)](https://docs.rs/hello-kernel) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`hello-kernel` provides hello-kernel component for TGOSKits. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -hello-kernel = "0.3.0" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axplat_crates/examples/hello-kernel - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use hello_kernel as _; - -fn main() { - // Integrate `hello-kernel` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/hello-kernel](https://docs.rs/hello-kernel) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axplat_crates/examples/hello-kernel/README_CN.md b/components/axplat_crates/examples/hello-kernel/README_CN.md deleted file mode 100644 index 542d79754a..0000000000 --- a/components/axplat_crates/examples/hello-kernel/README_CN.md +++ /dev/null @@ -1,81 +0,0 @@ -

hello-kernel

- -

hello-kernel component for TGOSKits

- -
- -[![Crates.io](https://img.shields.io/crates/v/hello-kernel.svg)](https://crates.io/crates/hello-kernel) -[![Docs.rs](https://docs.rs/hello-kernel/badge.svg)](https://docs.rs/hello-kernel) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`hello-kernel` 提供了 hello-kernel component for TGOSKits。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -hello-kernel = "0.3.0" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axplat_crates/examples/hello-kernel - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use hello_kernel as _; - -fn main() { - // 在这里将 `hello-kernel` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/hello-kernel](https://docs.rs/hello-kernel) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axplat_crates/examples/hello-kernel/build.rs b/components/axplat_crates/examples/hello-kernel/build.rs deleted file mode 100644 index 5be47b5cf0..0000000000 --- a/components/axplat_crates/examples/hello-kernel/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::io::Result; - -fn kernel_base(arch: &str) -> usize { - match arch { - "x86_64" => 0xffff_8000_0020_0000, - "aarch64" => 0xffff_0000_4020_0000, - "riscv64" => 0xffff_ffc0_8020_0000, - "loongarch64" => 0xffff_8000_0020_0000, - _ => panic!("Unsupported target architecture"), - } -} - -fn gen_linker_script(arch: &str) -> Result<()> { - let ld_content = std::fs::read_to_string("linker.lds.S")?; - let ld_content = ld_content.replace("%KERNEL_BASE%", &format!("{:#x}", kernel_base(arch))); - let root = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let out_fname = format!("linker_{arch}.lds"); - std::fs::write(&out_fname, ld_content)?; - println!("cargo:rustc-link-arg=-T{root}/{out_fname}"); - Ok(()) -} - -fn main() { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - gen_linker_script(&arch).unwrap(); - println!("cargo:rustc-link-arg=-no-pie"); -} diff --git a/components/axplat_crates/examples/hello-kernel/linker.lds.S b/components/axplat_crates/examples/hello-kernel/linker.lds.S deleted file mode 100644 index 1cbf39531c..0000000000 --- a/components/axplat_crates/examples/hello-kernel/linker.lds.S +++ /dev/null @@ -1,52 +0,0 @@ -ENTRY(_start) -SECTIONS -{ - . = %KERNEL_BASE%; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data .data.*) - *(.got .got.*) - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * 1; - } - . = _percpu_end; - _edata = .; - - .bss : AT(.) ALIGN(4K) { - *(.bss.stack) - . = ALIGN(4K); - _sbss = .; - *(.bss .bss.*) - *(COMMON) - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) - } -} diff --git a/components/axplat_crates/examples/hello-kernel/src/main.rs b/components/axplat_crates/examples/hello-kernel/src/main.rs deleted file mode 100644 index 4add2aa2c3..0000000000 --- a/components/axplat_crates/examples/hello-kernel/src/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![no_std] -#![no_main] - -cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - extern crate ax_plat_x86_pc; - } else if #[cfg(target_arch = "aarch64")] { - extern crate ax_plat_aarch64_qemu_virt; - } else if #[cfg(target_arch = "riscv64")] { - extern crate ax_plat_riscv64_qemu_virt; - } else if #[cfg(target_arch = "loongarch64")] { - extern crate ax_plat_loongarch64_qemu_virt; - } else { - compile_error!("Unsupported target architecture"); - } -} - -fn init_kernel(cpu_id: usize, arg: usize) { - ax_plat::percpu::init_primary(cpu_id); - - // Initialize trap, console, time. - ax_plat::init::init_early(cpu_id, arg); - - // Initialize platform peripherals (not used in this example). - ax_plat::init::init_later(cpu_id, arg); -} - -#[ax_plat::main] -fn main(cpu_id: usize, arg: usize) -> ! { - init_kernel(cpu_id, arg); - - ax_plat::console_println!("Hello, ArceOS!"); - ax_plat::console_println!("cpu_id = {cpu_id}, arg = {arg:#x}"); - - for _ in 0..5 { - ax_plat::time::busy_wait(ax_plat::time::TimeValue::from_secs(1)); - ax_plat::console_println!("{:?} elapsed.", ax_plat::time::monotonic_time()); - } - - ax_plat::console_println!("All done, shutting down!"); - ax_plat::power::system_off(); -} - -#[cfg(all(target_os = "none", not(test)))] -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - ax_plat::console_println!("{info}"); - ax_plat::power::system_off() -} diff --git a/components/axplat_crates/examples/irq-kernel/.gitignore b/components/axplat_crates/examples/irq-kernel/.gitignore deleted file mode 100644 index 6b8069a1de..0000000000 --- a/components/axplat_crates/examples/irq-kernel/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.lds diff --git a/components/axplat_crates/examples/irq-kernel/Cargo.toml b/components/axplat_crates/examples/irq-kernel/Cargo.toml deleted file mode 100644 index da158b52d1..0000000000 --- a/components/axplat_crates/examples/irq-kernel/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "irq-kernel" -version = "0.3.1" -edition.workspace = true -authors = ["Yuekai Jia ", "Youjie Zheng ", "yanjuguang ", "Su Mingxian ", "RobertYuan <634954435@qq.com>", "hky1999 "] -repository = "https://github.com/rcore-os/tgoskits" -license = "Apache-2.0" -publish = false - -[dependencies] -ax-config-macros = { workspace = true } -cfg-if = "1.0" -ax-cpu = { workspace = true } -ax-plat = { workspace = true } - -[target.'cfg(target_arch = "x86_64")'.dependencies] -ax-plat-x86-pc = { workspace = true, features = ["irq"] } -[target.'cfg(target_arch = "aarch64")'.dependencies] -ax-plat-aarch64-qemu-virt = { workspace = true, features = ["irq"] } -[target.'cfg(target_arch = "riscv64")'.dependencies] -ax-plat-riscv64-qemu-virt = { workspace = true, features = ["irq"] } -[target.'cfg(target_arch = "loongarch64")'.dependencies] -ax-plat-loongarch64-qemu-virt = { workspace = true, features = ["irq"] } diff --git a/components/axplat_crates/examples/irq-kernel/Makefile b/components/axplat_crates/examples/irq-kernel/Makefile deleted file mode 100644 index 493cc6015a..0000000000 --- a/components/axplat_crates/examples/irq-kernel/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -ARCH ?= x86_64 -APP := irq-kernel - -OBJDUMP ?= rust-objdump -d --print-imm-hex --x86-asm-syntax=intel -OBJCOPY ?= rust-objcopy --binary-architecture=$(ARCH) - -ifeq ($(ARCH), x86_64) - TARGET := x86_64-unknown-none -else ifeq ($(ARCH), aarch64) - TARGET := aarch64-unknown-none-softfloat -else ifeq ($(ARCH), riscv64) - TARGET := riscv64gc-unknown-none-elf -else ifeq ($(ARCH), loongarch64) - TARGET := loongarch64-unknown-none-softfloat -else - $(error "ARCH" must be one of "x86_64", "riscv64", "aarch64" or "loongarch64") -endif - -OUT_ELF := $(CURDIR)/../../target/$(TARGET)/release/$(APP) -OUT_BIN := $(OUT_ELF).bin - -qemu_args-x86_64 := \ - -machine q35 \ - -kernel $(OUT_ELF) - -qemu_args-riscv64 := \ - -machine virt \ - -bios default \ - -kernel $(OUT_BIN) - -qemu_args-aarch64 := \ - -cpu cortex-a72 \ - -machine virt \ - -kernel $(OUT_BIN) - -qemu_args-loongarch64 := \ - -machine virt \ - -m 1G \ - -kernel $(OUT_BIN) - -all: build - -build: - cargo build -p $(APP) --target $(TARGET) --release - -$(OUT_BIN): build - $(OBJCOPY) --strip-all -O binary $(OUT_ELF) $(OUT_BIN) - -run: $(OUT_BIN) - qemu-system-$(ARCH) $(qemu_args-$(ARCH)) -nographic - -disasm: - $(OBJDUMP) $(OUT_ELF) | less - -clippy: - cargo clippy -p $(APP) --target $(TARGET) - -clean: - cargo clean diff --git a/components/axplat_crates/examples/irq-kernel/README.md b/components/axplat_crates/examples/irq-kernel/README.md deleted file mode 100644 index c6d0a0cf35..0000000000 --- a/components/axplat_crates/examples/irq-kernel/README.md +++ /dev/null @@ -1,81 +0,0 @@ -

irq-kernel

- -

irq-kernel component for TGOSKits

- -
- -[![Crates.io](https://img.shields.io/crates/v/irq-kernel.svg)](https://crates.io/crates/irq-kernel) -[![Docs.rs](https://docs.rs/irq-kernel/badge.svg)](https://docs.rs/irq-kernel) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`irq-kernel` provides irq-kernel component for TGOSKits. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -irq-kernel = "0.3.0" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axplat_crates/examples/irq-kernel - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use irq_kernel as _; - -fn main() { - // Integrate `irq-kernel` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/irq-kernel](https://docs.rs/irq-kernel) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axplat_crates/examples/irq-kernel/README_CN.md b/components/axplat_crates/examples/irq-kernel/README_CN.md deleted file mode 100644 index 1467ec686b..0000000000 --- a/components/axplat_crates/examples/irq-kernel/README_CN.md +++ /dev/null @@ -1,81 +0,0 @@ -

irq-kernel

- -

irq-kernel component for TGOSKits

- -
- -[![Crates.io](https://img.shields.io/crates/v/irq-kernel.svg)](https://crates.io/crates/irq-kernel) -[![Docs.rs](https://docs.rs/irq-kernel/badge.svg)](https://docs.rs/irq-kernel) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`irq-kernel` 提供了 irq-kernel component for TGOSKits。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -irq-kernel = "0.3.0" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axplat_crates/examples/irq-kernel - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use irq_kernel as _; - -fn main() { - // 在这里将 `irq-kernel` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/irq-kernel](https://docs.rs/irq-kernel) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axplat_crates/examples/irq-kernel/build.rs b/components/axplat_crates/examples/irq-kernel/build.rs deleted file mode 100644 index 5be47b5cf0..0000000000 --- a/components/axplat_crates/examples/irq-kernel/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::io::Result; - -fn kernel_base(arch: &str) -> usize { - match arch { - "x86_64" => 0xffff_8000_0020_0000, - "aarch64" => 0xffff_0000_4020_0000, - "riscv64" => 0xffff_ffc0_8020_0000, - "loongarch64" => 0xffff_8000_0020_0000, - _ => panic!("Unsupported target architecture"), - } -} - -fn gen_linker_script(arch: &str) -> Result<()> { - let ld_content = std::fs::read_to_string("linker.lds.S")?; - let ld_content = ld_content.replace("%KERNEL_BASE%", &format!("{:#x}", kernel_base(arch))); - let root = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let out_fname = format!("linker_{arch}.lds"); - std::fs::write(&out_fname, ld_content)?; - println!("cargo:rustc-link-arg=-T{root}/{out_fname}"); - Ok(()) -} - -fn main() { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - gen_linker_script(&arch).unwrap(); - println!("cargo:rustc-link-arg=-no-pie"); -} diff --git a/components/axplat_crates/examples/irq-kernel/linker.lds.S b/components/axplat_crates/examples/irq-kernel/linker.lds.S deleted file mode 100644 index 1cbf39531c..0000000000 --- a/components/axplat_crates/examples/irq-kernel/linker.lds.S +++ /dev/null @@ -1,52 +0,0 @@ -ENTRY(_start) -SECTIONS -{ - . = %KERNEL_BASE%; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data .data.*) - *(.got .got.*) - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * 1; - } - . = _percpu_end; - _edata = .; - - .bss : AT(.) ALIGN(4K) { - *(.bss.stack) - . = ALIGN(4K); - _sbss = .; - *(.bss .bss.*) - *(COMMON) - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) - } -} diff --git a/components/axplat_crates/examples/irq-kernel/src/irq.rs b/components/axplat_crates/examples/irq-kernel/src/irq.rs deleted file mode 100644 index e91c1b2898..0000000000 --- a/components/axplat_crates/examples/irq-kernel/src/irq.rs +++ /dev/null @@ -1,75 +0,0 @@ -use core::sync::atomic::{ - AtomicU64, - Ordering::{Acquire, Release}, -}; - -use ax_cpu::trap::irq_handler; - -const TICKS_PER_SEC: u64 = 100; - -static IRQ_COUNTER: AtomicU64 = AtomicU64::new(0); - -pub fn irq_count() -> u64 { - IRQ_COUNTER.load(Acquire) -} - -#[irq_handler] -fn handle_irq(vector: usize) -> bool { - ax_plat::irq::handle(vector); - true -} - -pub fn init_irq() { - fn update_timer(_irq_num: usize) { - static PERIODIC_INTERVAL_NANOS: u64 = ax_plat::time::NANOS_PER_SEC / TICKS_PER_SEC; - - IRQ_COUNTER.fetch_add(1, Release); - // Reset the timer for the next interrupt. - static NEXT_DEADLINE: AtomicU64 = AtomicU64::new(0); - - let now_ns = ax_plat::time::monotonic_time_nanos(); - let mut deadline = NEXT_DEADLINE.load(Acquire); - if now_ns >= deadline { - deadline = now_ns + PERIODIC_INTERVAL_NANOS; - } - - NEXT_DEADLINE.store(deadline + PERIODIC_INTERVAL_NANOS, Release); - ax_plat::time::set_oneshot_timer(deadline); - } - - // Register the timer IRQ handler. - ax_plat::irq::register(axplat_crate::config::devices::TIMER_IRQ, update_timer); - ax_plat::console_println!("Timer IRQ handler registered."); - - // Enable the timer IRQ. - ax_cpu::asm::enable_irqs(); -} - -pub fn test_irq() { - let interval = 5; - ax_plat::console_println!("Waiting for timer IRQs for {} seconds...", interval); - - for _ in 0..interval { - ax_plat::time::busy_wait(ax_plat::time::TimeValue::from_secs(1)); - ax_plat::console_println!( - "{:?} elapsed. {} Timer IRQ processed.", - ax_plat::time::monotonic_time(), - irq_count() - ); - } - - let irq_count = irq_count(); - ax_plat::console_println!("Timer IRQ count: {irq_count}"); - - // A lower bound for the number of IRQs expected in the given interval. - let irq_min_count = TICKS_PER_SEC * interval; - - if irq_count < irq_min_count { - panic!( - "Timer IRQ was not triggered enough times within the expected time frame, expected at \ - least {irq_min_count}, got {irq_count}" - ); - } - - ax_plat::console_println!("Timer IRQ test passed."); -} diff --git a/components/axplat_crates/examples/irq-kernel/src/main.rs b/components/axplat_crates/examples/irq-kernel/src/main.rs deleted file mode 100644 index 13f554d68f..0000000000 --- a/components/axplat_crates/examples/irq-kernel/src/main.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![no_std] -#![no_main] - -cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - extern crate ax_plat_x86_pc as axplat_crate; - } else if #[cfg(target_arch = "aarch64")] { - extern crate ax_plat_aarch64_qemu_virt as axplat_crate; - } else if #[cfg(target_arch = "riscv64")] { - extern crate ax_plat_riscv64_qemu_virt as axplat_crate; - } else if #[cfg(target_arch = "loongarch64")] { - extern crate ax_plat_loongarch64_qemu_virt as axplat_crate; - } else { - compile_error!("Unsupported target architecture"); - } -} - -mod irq; -use irq::*; - -fn init_kernel(cpu_id: usize, arg: usize) { - ax_plat::percpu::init_primary(cpu_id); - - // Initialize trap, console, time. - ax_plat::init::init_early(cpu_id, arg); - - // Initialize platform peripherals, such as IRQ handlers. - ax_plat::init::init_later(cpu_id, arg); -} - -#[ax_plat::main] -fn main(cpu_id: usize, arg: usize) -> ! { - init_kernel(cpu_id, arg); - - ax_plat::console_println!("Hello, ArceOS!"); - ax_plat::console_println!("cpu_id = {cpu_id}, arg = {arg:#x}"); - - init_irq(); - test_irq(); - - ax_plat::power::system_off(); -} - -#[cfg(all(target_os = "none", not(test)))] -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - ax_plat::console_println!("{info}"); - ax_plat::power::system_off() -} diff --git a/components/axplat_crates/examples/smp-kernel/.gitignore b/components/axplat_crates/examples/smp-kernel/.gitignore deleted file mode 100644 index 6b8069a1de..0000000000 --- a/components/axplat_crates/examples/smp-kernel/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.lds diff --git a/components/axplat_crates/examples/smp-kernel/Cargo.toml b/components/axplat_crates/examples/smp-kernel/Cargo.toml deleted file mode 100644 index 0f9f58da9b..0000000000 --- a/components/axplat_crates/examples/smp-kernel/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "smp-kernel" -version = "0.3.1" -edition.workspace = true -authors = ["Yuekai Jia ", "Youjie Zheng ", "yanjuguang ", "Su Mingxian ", "RobertYuan <634954435@qq.com>", "hky1999 "] -repository = "https://github.com/rcore-os/tgoskits" -license = "Apache-2.0" -publish = false - -[dependencies] -ax-config-macros = { workspace = true } -cfg-if = "1.0" -ax-percpu.workspace = true -const-str = "1.0" -ax-memory-addr = { workspace = true } -ax-cpu = { workspace = true } -ax-plat = { workspace = true } - -[target.'cfg(target_arch = "x86_64")'.dependencies] -ax-plat-x86-pc = { workspace = true, features = ["irq", "smp"] } -[target.'cfg(target_arch = "aarch64")'.dependencies] -ax-plat-aarch64-qemu-virt = { workspace = true, features = ["irq", "smp"] } -[target.'cfg(target_arch = "riscv64")'.dependencies] -ax-plat-riscv64-qemu-virt = { workspace = true, features = ["irq", "smp"] } -[target.'cfg(target_arch = "loongarch64")'.dependencies] -ax-plat-loongarch64-qemu-virt = { workspace = true, features = ["irq", "smp"] } diff --git a/components/axplat_crates/examples/smp-kernel/Makefile b/components/axplat_crates/examples/smp-kernel/Makefile deleted file mode 100644 index a00c30fc7d..0000000000 --- a/components/axplat_crates/examples/smp-kernel/Makefile +++ /dev/null @@ -1,63 +0,0 @@ -ARCH ?= x86_64 -APP := smp-kernel -SMP ?= 1 -OBJDUMP ?= rust-objdump -d --print-imm-hex --x86-asm-syntax=intel -OBJCOPY ?= rust-objcopy --binary-architecture=$(ARCH) - -ifeq ($(ARCH), x86_64) - TARGET := x86_64-unknown-none -else ifeq ($(ARCH), aarch64) - TARGET := aarch64-unknown-none-softfloat -else ifeq ($(ARCH), riscv64) - TARGET := riscv64gc-unknown-none-elf -else ifeq ($(ARCH), loongarch64) - TARGET := loongarch64-unknown-none-softfloat -else - $(error "ARCH" must be one of "x86_64", "riscv64", "aarch64" or "loongarch64") -endif - -export AX_CPU_NUM=$(SMP) - -OUT_ELF := $(CURDIR)/../../target/$(TARGET)/release/$(APP) -OUT_BIN := $(OUT_ELF).bin - -qemu_args-x86_64 := \ - -machine q35 \ - -kernel $(OUT_ELF) - -qemu_args-riscv64 := \ - -machine virt \ - -bios default \ - -kernel $(OUT_BIN) - -qemu_args-aarch64 := \ - -cpu cortex-a72 \ - -machine virt \ - -kernel $(OUT_BIN) - -qemu_args-loongarch64 := \ - -machine virt \ - -m 1G \ - -kernel $(OUT_BIN) - -qemu_args-y := -smp $(SMP) $(qemu_args-$(ARCH)) - -all: build - -build: - cargo build -p $(APP) --target $(TARGET) --release - -$(OUT_BIN): build - $(OBJCOPY) --strip-all -O binary $(OUT_ELF) $(OUT_BIN) - -run: $(OUT_BIN) - qemu-system-$(ARCH) $(qemu_args-y) -nographic - -disasm: - $(OBJDUMP) $(OUT_ELF) | less - -clippy: - cargo clippy -p $(APP) --target $(TARGET) - -clean: - cargo clean diff --git a/components/axplat_crates/examples/smp-kernel/README.md b/components/axplat_crates/examples/smp-kernel/README.md deleted file mode 100644 index 4bf6e4cff5..0000000000 --- a/components/axplat_crates/examples/smp-kernel/README.md +++ /dev/null @@ -1,81 +0,0 @@ -

smp-kernel

- -

smp-kernel component for TGOSKits

- -
- -[![Crates.io](https://img.shields.io/crates/v/smp-kernel.svg)](https://crates.io/crates/smp-kernel) -[![Docs.rs](https://docs.rs/smp-kernel/badge.svg)](https://docs.rs/smp-kernel) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`smp-kernel` provides smp-kernel component for TGOSKits. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -smp-kernel = "0.3.0" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axplat_crates/examples/smp-kernel - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use smp_kernel as _; - -fn main() { - // Integrate `smp-kernel` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/smp-kernel](https://docs.rs/smp-kernel) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axplat_crates/examples/smp-kernel/README_CN.md b/components/axplat_crates/examples/smp-kernel/README_CN.md deleted file mode 100644 index ec2d3d218c..0000000000 --- a/components/axplat_crates/examples/smp-kernel/README_CN.md +++ /dev/null @@ -1,81 +0,0 @@ -

smp-kernel

- -

smp-kernel component for TGOSKits

- -
- -[![Crates.io](https://img.shields.io/crates/v/smp-kernel.svg)](https://crates.io/crates/smp-kernel) -[![Docs.rs](https://docs.rs/smp-kernel/badge.svg)](https://docs.rs/smp-kernel) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`smp-kernel` 提供了 smp-kernel component for TGOSKits。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -smp-kernel = "0.3.0" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axplat_crates/examples/smp-kernel - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use smp_kernel as _; - -fn main() { - // 在这里将 `smp-kernel` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/smp-kernel](https://docs.rs/smp-kernel) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axplat_crates/examples/smp-kernel/build.rs b/components/axplat_crates/examples/smp-kernel/build.rs deleted file mode 100644 index 69992e0310..0000000000 --- a/components/axplat_crates/examples/smp-kernel/build.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::io::Result; - -fn kernel_base(arch: &str) -> usize { - match arch { - "x86_64" => 0xffff_8000_0020_0000, - "aarch64" => 0xffff_0000_4020_0000, - "riscv64" => 0xffff_ffc0_8020_0000, - "loongarch64" => 0xffff_8000_0020_0000, - _ => panic!("Unsupported target architecture"), - } -} - -fn gen_linker_script(arch: &str) -> Result<()> { - let ld_content = std::fs::read_to_string("linker.lds.S")?; - let ld_content = ld_content.replace("%KERNEL_BASE%", &format!("{:#x}", kernel_base(arch))); - let cpu_num = std::env::var("AX_CPU_NUM").unwrap_or(String::from("1")); - let ld_content = ld_content.replace("%CPU_NUM%", &cpu_num); - - let root = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let out_fname = format!("linker_{arch}.lds"); - std::fs::write(&out_fname, ld_content)?; - println!("cargo:rustc-link-arg=-T{root}/{out_fname}"); - Ok(()) -} - -fn main() { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - gen_linker_script(&arch).unwrap(); - println!("cargo:rustc-link-arg=-no-pie"); - - println!("cargo:rerun-if-env-changed=AX_CPU_NUM"); -} diff --git a/components/axplat_crates/examples/smp-kernel/linker.lds.S b/components/axplat_crates/examples/smp-kernel/linker.lds.S deleted file mode 100644 index ce88ce8cd4..0000000000 --- a/components/axplat_crates/examples/smp-kernel/linker.lds.S +++ /dev/null @@ -1,52 +0,0 @@ -ENTRY(_start) -SECTIONS -{ - . = %KERNEL_BASE%; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data .data.*) - *(.got .got.*) - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * %CPU_NUM%; - } - . = _percpu_end; - _edata = .; - - .bss : AT(.) ALIGN(4K) { - *(.bss.stack) - . = ALIGN(4K); - _sbss = .; - *(.bss .bss.*) - *(COMMON) - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) - } -} diff --git a/components/axplat_crates/examples/smp-kernel/src/init.rs b/components/axplat_crates/examples/smp-kernel/src/init.rs deleted file mode 100644 index 6af1e3d20a..0000000000 --- a/components/axplat_crates/examples/smp-kernel/src/init.rs +++ /dev/null @@ -1,29 +0,0 @@ -use core::sync::atomic::{AtomicUsize, Ordering::Acquire}; - -use crate::CPU_NUM; - -/// Number of CPUs finished initialization. -pub static INITED_CPUS: AtomicUsize = AtomicUsize::new(0); -pub fn init_smp_ok() -> bool { - INITED_CPUS.load(Acquire) == CPU_NUM -} - -pub fn init_kernel(cpu_id: usize, arg: usize) { - ax_plat::percpu::init_primary(cpu_id); - - // Initialize trap, console, time. - ax_plat::init::init_early(cpu_id, arg); - - // Initialize platform peripherals, such as IRQ handlers. - ax_plat::init::init_later(cpu_id, arg); -} - -pub fn init_kernel_secondary(cpu_id: usize) { - ax_plat::percpu::init_secondary(cpu_id); - - // Initialize trap, console, time. - ax_plat::init::init_early_secondary(cpu_id); - - // Initialize platform peripherals, such as IRQ handlers. - ax_plat::init::init_later_secondary(cpu_id); -} diff --git a/components/axplat_crates/examples/smp-kernel/src/irq.rs b/components/axplat_crates/examples/smp-kernel/src/irq.rs deleted file mode 100644 index 442a138adb..0000000000 --- a/components/axplat_crates/examples/smp-kernel/src/irq.rs +++ /dev/null @@ -1,40 +0,0 @@ -use ax_cpu::trap::irq_handler; - -#[irq_handler] -fn handle_irq(vector: usize) -> bool { - ax_plat::irq::handle(vector); - true -} - -pub fn init_irq() { - fn update_timer(_irq_num: usize) { - // One timer interrupt per second. - static PERIODIC_INTERVAL_NANOS: u64 = ax_plat::time::NANOS_PER_SEC; - // Reset the timer for the next interrupt. - #[ax_percpu::def_percpu] - static NEXT_DEADLINE: u64 = 0; - - ax_plat::console_println!( - "{:?} elapsed. Timer IRQ processed on CPU {}.", - ax_plat::time::monotonic_time(), - ax_plat::percpu::this_cpu_id() - ); - - let now_ns = ax_plat::time::monotonic_time_nanos(); - let mut deadline = unsafe { NEXT_DEADLINE.read_current_raw() }; - if now_ns >= deadline { - deadline = now_ns + PERIODIC_INTERVAL_NANOS; - } - unsafe { - NEXT_DEADLINE.write_current_raw(deadline + PERIODIC_INTERVAL_NANOS); - } - ax_plat::time::set_oneshot_timer(deadline); - } - - // Register the timer IRQ handler. - ax_plat::irq::register(axplat_crate::config::devices::TIMER_IRQ, update_timer); - ax_plat::console_println!("Timer IRQ handler registered."); - - // Enable the timer IRQ. - ax_cpu::asm::enable_irqs(); -} diff --git a/components/axplat_crates/examples/smp-kernel/src/main.rs b/components/axplat_crates/examples/smp-kernel/src/main.rs deleted file mode 100644 index cebeb33603..0000000000 --- a/components/axplat_crates/examples/smp-kernel/src/main.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![no_std] -#![no_main] - -cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - extern crate ax_plat_x86_pc as axplat_crate; - } else if #[cfg(target_arch = "aarch64")] { - extern crate ax_plat_aarch64_qemu_virt as axplat_crate; - } else if #[cfg(target_arch = "riscv64")] { - extern crate ax_plat_riscv64_qemu_virt as axplat_crate; - } else if #[cfg(target_arch = "loongarch64")] { - extern crate ax_plat_loongarch64_qemu_virt as axplat_crate; - } else { - compile_error!("Unsupported target architecture"); - } -} - -mod init; -mod irq; -mod mp; - -use core::sync::atomic::Ordering::Release; - -use init::*; -use irq::*; -use mp::start_secondary_cpus; - -const CPU_NUM: usize = match option_env!("AX_CPU_NUM") { - Some(val) => const_str::parse!(val, usize), - None => axplat_crate::config::plat::MAX_CPU_NUM, -}; - -#[ax_plat::main] -fn main(cpu_id: usize, arg: usize) -> ! { - init_kernel(cpu_id, arg); - - ax_plat::console_println!("Hello, ArceOS!"); - ax_plat::console_println!("Primary CPU {cpu_id} started."); - - start_secondary_cpus(cpu_id); - - init_irq(); - - INITED_CPUS.fetch_add(1, Release); - - ax_plat::console_println!("Primary CPU {cpu_id} init OK."); - - while !init_smp_ok() { - core::hint::spin_loop(); - } - - ax_plat::time::busy_wait(ax_plat::time::TimeValue::from_secs(5)); - - ax_plat::console_println!("Primary CPU {cpu_id} finished. Shutting down..."); - - ax_plat::power::system_off(); -} - -#[cfg(all(target_os = "none", not(test)))] -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - ax_plat::console_println!("{info}"); - ax_plat::power::system_off() -} diff --git a/components/axplat_crates/examples/smp-kernel/src/mp.rs b/components/axplat_crates/examples/smp-kernel/src/mp.rs deleted file mode 100644 index 11bb4ad1c8..0000000000 --- a/components/axplat_crates/examples/smp-kernel/src/mp.rs +++ /dev/null @@ -1,50 +0,0 @@ -use core::sync::atomic::Ordering::{Acquire, Release}; - -use ax_memory_addr::VirtAddr; -use axplat_crate::config::plat::BOOT_STACK_SIZE; - -use crate::{CPU_NUM, INITED_CPUS, init_kernel_secondary}; - -#[unsafe(link_section = ".bss.stack")] -static mut SECONDARY_BOOT_STACK: [[u8; BOOT_STACK_SIZE]; CPU_NUM - 1] = - [[0; BOOT_STACK_SIZE]; CPU_NUM - 1]; - -#[allow(clippy::absurd_extreme_comparisons)] -pub fn start_secondary_cpus(primary_cpu_id: usize) { - let mut logic_cpu_id = 0; - for i in 0..CPU_NUM { - if i != primary_cpu_id && logic_cpu_id < CPU_NUM - 1 { - let stack_top = ax_plat::mem::virt_to_phys(VirtAddr::from(unsafe { - SECONDARY_BOOT_STACK[logic_cpu_id].as_ptr_range().end as usize - })); - - ax_plat::power::cpu_boot(i, stack_top.as_usize()); - - logic_cpu_id += 1; - - while INITED_CPUS.load(Acquire) < logic_cpu_id { - core::hint::spin_loop(); - } - } - } -} - -#[ax_plat::secondary_main] -fn secondary_main(cpu_id: usize) -> ! { - init_kernel_secondary(cpu_id); - - INITED_CPUS.fetch_add(1, Release); - - ax_plat::console_println!("Secondary CPU {cpu_id} init OK."); - - while !crate::init_smp_ok() { - core::hint::spin_loop(); - } - - ax_cpu::asm::enable_irqs(); - - // Infinite loop to receive and handle timer interrupts - loop { - core::hint::spin_loop(); - } -} diff --git a/components/axplat_crates/publish.sh b/components/axplat_crates/publish.sh deleted file mode 100755 index 74dec553b6..0000000000 --- a/components/axplat_crates/publish.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/bin/bash - -# Publish script for axplat crates -# This script publishes crates in the correct dependency order - -set -e # Exit on error - -echo "==========================================" -echo "Publishing axplat crates..." -echo "==========================================" - -# Define targets for different architectures -AARCH64_TARGET="aarch64-unknown-none-softfloat" -X86_64_TARGET="x86_64-unknown-none" -RISCV64_TARGET="riscv64gc-unknown-none-elf" -LOONGARCH64_TARGET="loongarch64-unknown-none" - -# Colors for output -GREEN='\033[0;32m' -BLUE='\033[0;34m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Function to get local crate version from Cargo.toml -# Handles both direct version and workspace = true -get_local_version() { - local crate_path=$1 - local version_line - # Only match direct version assignment (version = "..."), not version.workspace = true - version_line=$(grep -E '^version\s*=\s*"' "${crate_path}/Cargo.toml" | head -1) - - if [ -n "$version_line" ]; then - # Direct version: version = "0.1.0" - echo "$version_line" | sed -E 's/.*=\s*"([^"]+)".*/\1/' - else - # Workspace version: version.workspace = true - # Get from workspace Cargo.toml (always in project root) - grep -E '^version\s*=' "Cargo.toml" | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/' - fi -} - -# Function to check if a crate version already exists on crates.io -crate_version_exists() { - local crate_name=$1 - local version=$2 - - # Search for the crate on crates.io and check if the version exists - local search_result - search_result=$(cargo search "${crate_name}" --limit 1 2>/dev/null | grep "^${crate_name} = \"${version}\"") || true - - if [ -n "${search_result}" ]; then - return 0 # Version exists - else - return 1 # Version does not exist - fi -} - -# Function to publish a crate -publish_crate() { - local crate_name=$1 - local crate_path=$2 - local target=$3 - local version - - # Get the local version from Cargo.toml - version=$(get_local_version "${crate_path}") - - echo "" - echo -e "${BLUE}Checking ${crate_name} (version ${version})...${NC}" - - # Check if this version already exists on crates.io - if crate_version_exists "${crate_name}" "${version}"; then - echo -e "${YELLOW}Skipping ${crate_name} ${version} - already published on crates.io${NC}" - return 0 - fi - - echo -e "${BLUE}Publishing ${crate_name} ${version}...${NC}" - - if [ -n "$target" ]; then - cargo publish -p "${crate_name}" --target "${target}" - else - cargo publish -p "${crate_name}" - fi - - if [ $? -eq 0 ]; then - echo -e "${GREEN}Successfully published ${crate_name} ${version}${NC}" - else - echo -e "${RED}Failed to publish ${crate_name}${NC}" - exit 1 - fi - - # Wait a bit for the crate to be available on crates.io - echo "Waiting for ${crate_name} to be available on crates.io..." - sleep 10 -} - -echo "" -echo "Step 1: Publishing base crates (no special target required)..." -echo "------------------------------------------------------------" - -# 1. Publish axplat-macros first (no dependencies) -publish_crate "axplat-macros" "axplat-macros" "" - -# 2. Publish axplat (depends on axplat-macros) -publish_crate "axplat" "axplat" "" - -echo "" -echo "Step 2: Publishing platform-specific crates with appropriate targets..." -echo "-----------------------------------------------------------------------" - -# 3. Publish ax-plat-aarch64-peripherals (depends on axplat, needs aarch64 target) -publish_crate "ax-plat-aarch64-peripherals" "platforms/axplat-aarch64-peripherals" "${AARCH64_TARGET}" - -# 4. Publish aarch64 platform crates (all depend on ax-plat-aarch64-peripherals) -publish_crate "axplat-aarch64-qemu-virt" "platforms/axplat-aarch64-qemu-virt" "${AARCH64_TARGET}" -publish_crate "axplat-aarch64-raspi" "platforms/axplat-aarch64-raspi" "${AARCH64_TARGET}" -publish_crate "axplat-aarch64-bsta1000b" "platforms/axplat-aarch64-bsta1000b" "${AARCH64_TARGET}" -publish_crate "axplat-aarch64-phytium-pi" "platforms/axplat-aarch64-phytium-pi" "${AARCH64_TARGET}" - -# 5. Publish x86_64 platform crate -publish_crate "ax-plat-x86-pc" "platforms/axplat-x86-pc" "${X86_64_TARGET}" - -# 6. Publish riscv64 platform crate -publish_crate "ax-plat-riscv64-qemu-virt" "platforms/axplat-riscv64-qemu-virt" "${RISCV64_TARGET}" - -# 7. Publish loongarch64 platform crate -publish_crate "ax-plat-loongarch64-qemu-virt" "platforms/axplat-loongarch64-qemu-virt" "${LOONGARCH64_TARGET}" - -echo "" -echo "==========================================" -echo -e "${GREEN}All crates published successfully!${NC}" -echo "==========================================" diff --git a/docs/docs/architecture/arceos.md b/docs/docs/architecture/arceos.md index a89e3741e9..dfaf988548 100644 --- a/docs/docs/architecture/arceos.md +++ b/docs/docs/architecture/arceos.md @@ -28,7 +28,7 @@ ArceOS 的 crate 按职责组织成一条从底层平台到上层应用的能力 ```mermaid flowchart LR reusableCrates["ReusableCrates: components/*"] - platformLayer["PlatformLayer: axplat-* + platform/*"] + platformLayer["PlatformLayer: axplat-* + platforms/*"] requiredModules["RequiredModules: ax-runtime ax-hal axconfig ax-log"] optionalModules["OptionalModules: ax-alloc ax-mm ax-task ax-sync ax-driver ax-fs ax-net ..."] publicApi["PublicApi: ax-feat ax-api ax-posix-api"] @@ -61,7 +61,7 @@ ArceOS 从底部硬件平台到顶部应用,依次经过六层。每一层只 | 层次 | 主要目录 | 关注点 | | --- | --- | --- | | 可复用 crate 层 | `components/*` | 算法、同步、容器、地址空间、设备抽象等可被多个系统复用的基础构件 | -| 平台与 HAL 层 | `platform/*`、`components/axplat_crates/platforms/*`、`os/arceos/modules/axhal` | 架构相关启动、时钟、中断、内存映射、设备访问 | +| 平台与 HAL 层 | `platforms/*`、`os/arceos/modules/axhal` | 架构相关启动、时钟、中断、内存映射、设备访问 | | 内核服务模块层 | `os/arceos/modules/*` | 内存分配、页表、任务调度、驱动、文件系统、网络、图形等 OS 能力 | | API 聚合层 | `os/arceos/api/*` | feature 选择、稳定 API 封装、POSIX 兼容接口 | | 用户库层 | `os/arceos/ulib/*` | `ax-std`、`ax-libc` 等高层开发接口 | @@ -268,5 +268,5 @@ ArceOS 的模块间交互可归纳为四条主线: 1. **启动主线**:`ax-runtime → ax-hal → ax-alloc/ax-mm → ax-task → ax-driver → ax-fs/ax-net` 2. **API 主线**:`ax-std/arceos_api → ax-task/ax-fs/ax-net/... → ax-hal` -3. **平台主线**:`axplat-* / platform/* → ax-hal → ax-runtime` +3. **平台主线**:`axplat-* / platforms/* → ax-hal → ax-runtime` 4. **测试主线**:`examples/* / test-suit/* → ax-std or ax-api → modules/*` diff --git a/docs/docs/architecture/overview.md b/docs/docs/architecture/overview.md index b4dc6e6fa0..4281ab98a9 100644 --- a/docs/docs/architecture/overview.md +++ b/docs/docs/architecture/overview.md @@ -46,7 +46,7 @@ flowchart TD direction LR P1["riscv64-qemu-virt"] P2["x86-qemu-q35"] - P3["axplat-dyn"] + P3["ax-plat-dyn"] end Components --> ArceOS @@ -117,7 +117,7 @@ TGOSKits 按职责将 crate 组织为六个核心层次和一个辅助层,每 辅助层: -- `platform/` 与 `components/axplat_crates/` — 平台实现,当前包含 `riscv64-qemu-virt`、`x86-qemu-q35`、`axplat-dyn` +- `platforms/` — 平台实现,当前包含 `riscv64-qemu-virt`、`x86-qemu-q35`、`ax-plat-dyn` - `drivers/` — SoC 专用驱动(blk、net、npu、pci、soc) - `test-suit/` — 系统级测试入口(ArceOS、StarryOS、Axvisor) @@ -128,7 +128,7 @@ TGOSKits 按职责将 crate 组织为六个核心层次和一个辅助层,每 ```mermaid flowchart LR subgraph 底层 - P["platform/"] + P["platforms/"] C["components/"] end subgraph ArceOS @@ -156,7 +156,7 @@ flowchart LR | `components/` → Axvisor runtime | 虚拟化组件(axvm、axvcpu、axvisor_api)被 Axvisor 直接使用 | | ArceOS modules → StarryOS kernel | StarryOS 复用 ArceOS 的 HAL、调度、内存、驱动等基础能力 | | ArceOS modules → Axvisor runtime | Axvisor 复用 ArceOS 的 std、HAL、alloc、task、sync | -| `platform/` → 三个系统 | 平台实现通过 ax-hal 注入各系统 | +| `platforms/` → 三个系统 | 平台实现通过 ax-hal 注入各系统 | ## 改动影响评估 @@ -168,4 +168,4 @@ flowchart LR | `os/arceos/modules/` | ArceOS 本身 + StarryOS/Axvisor 的复用路径 | 不要只测 ArceOS,补跑上游系统最小用例 | | `os/StarryOS/kernel/` | 仅 StarryOS | 重点关注 rootfs 和 Linux 兼容行为 | | `os/axvisor/` | 仅 Axvisor | 代码、配置和 Guest 镜像要一起验证 | -| `platform/` | 所有使用该平台的系统 | 至少验证对应架构的 ArceOS 基础启动 | +| `platforms/` | 所有使用该平台的系统 | 至少验证对应架构的 ArceOS 基础启动 | diff --git a/docs/docs/architecture/rdrive-rdif.md b/docs/docs/architecture/rdrive-rdif.md index eeda79b3f2..45a71463db 100644 --- a/docs/docs/architecture/rdrive-rdif.md +++ b/docs/docs/architecture/rdrive-rdif.md @@ -226,9 +226,9 @@ IRQ 路径只返回稳定事件和唤醒等待方;不能在 IRQ handler 中执 | 层 | 位置 | 允许依赖 | 不允许 | | --- | --- | --- | --- | -| Driver Core | `drivers//` | `no_std`、寄存器/队列/描述符、`mmio-api`、`dma-api` 小边界 | `ax-driver`、`ax-hal`、`axplat-dyn`、`rdrive::PlatformDevice` | +| Driver Core | `drivers//` | `no_std`、寄存器/队列/描述符、`mmio-api`、`dma-api` 小边界 | `ax-driver`、`ax-hal`、`ax-plat-dyn`、`rdrive::PlatformDevice` | | Capability Boundary | `drivers/interface/rdif-*` | `rdif-base`、小型错误和事件类型 | 平台、runtime、任务调度 | -| OS Glue | `platform/axplat-dyn/src/drivers/*` 或平台 crate | `rdrive::module_driver!`、FDT/PCI/Static probe、iomap、IRQ 注册、DMA op | 上层 FS/NET 策略 | +| OS Glue | `platforms/ax-plat-dyn/src/drivers/*` 或平台 crate | `rdrive::module_driver!`、FDT/PCI/Static probe、iomap、IRQ 注册、DMA op | 上层 FS/NET 策略 | | Runtime | `drivers/*/rd-*` | `rdif-*`、waker、poll/blocking wrapper、buffer pool | probe、设备树、ACPI、平台选择 | Driver Core 只推进硬件状态机。OS Glue 将硬件实例包装成 `rdif-*::Interface` 后通过 `PlatformDevice::register(...)` 注册。Runtime wrapper 从 `rdif-*::Interface` 构建领域运行时对象,供服务层和上层模块使用。 @@ -315,10 +315,10 @@ src/ | 文件 | 当前问题 | 拆分方向 | | --- | --- | --- | -| `platform/axplat-dyn/src/drivers/pci/rk3588.rs` | 单文件超过 600 行 | RC init、ATU/window、MSI/IRQ、config space、FDT glue | -| `platform/axplat-dyn/src/drivers/blk/rockchip_sd.rs` | 单文件超过 600 行 | probe/FDT、clock/tuning、card init、rd-block adapter | -| `platform/axplat-dyn/src/drivers/blk/mod.rs` | 容器、adapter、IRQ、FDT decode 混杂 | registry、adapter、irq、probe | -| `platform/axplat-dyn/src/drivers/mod.rs` | 设备收集、iomap、DMA 混杂 | device collection、iomap、dma | +| `platforms/ax-plat-dyn/src/drivers/pci/rk3588.rs` | 单文件超过 600 行 | RC init、ATU/window、MSI/IRQ、config space、FDT glue | +| `platforms/ax-plat-dyn/src/drivers/blk/rockchip_sd.rs` | 单文件超过 600 行 | probe/FDT、clock/tuning、card init、rd-block adapter | +| `platforms/ax-plat-dyn/src/drivers/blk/mod.rs` | 容器、adapter、IRQ、FDT decode 混杂 | registry、adapter、irq、probe | +| `platforms/ax-plat-dyn/src/drivers/mod.rs` | 设备收集、iomap、DMA 混杂 | device collection、iomap、dma | `lib.rs` 只做模块声明和 re-export,不承载核心实现。 diff --git a/docs/docs/architecture/starryos.md b/docs/docs/architecture/starryos.md index ae84ff2cf5..6421ac321e 100644 --- a/docs/docs/architecture/starryos.md +++ b/docs/docs/architecture/starryos.md @@ -32,7 +32,7 @@ flowchart LR starryComponents["StarryComponents: starry-process starry-signal starry-vm"] arceosModules["ArceosModules: ax-hal ax-task ax-mm ax-fs ax-net ax-sync"] sharedCrates["SharedCrates: components/*"] - platformLayer["PlatformLayer: axplat-* + platform/*"] + platformLayer["PlatformLayer: axplat-* + platforms/*"] sharedCrates --> arceosModules sharedCrates --> starryComponents @@ -60,7 +60,7 @@ flowchart LR | 内核核心层 | `os/StarryOS/kernel` | `entry`、`syscall`、`task`、`mm`、`file`、`pseudofs`、`time`、`trap` | | Starry 专用组件层 | `components/starry-*` | 进程、信号、虚拟内存等抽象 | | ArceOS 基础模块层 | `os/arceos/modules/*` | HAL、任务调度、同步、基础 I/O、文件与网络能力 | -| 平台层 | `platform/*`、`axplat-*` | 架构与板级支持 | +| 平台层 | `platforms/*`、`axplat-*` | 架构与板级支持 | ## 内核子系统 diff --git a/docs/docs/build/build.md b/docs/docs/build/build.md index 24bd7deadc..4f0eb07131 100644 --- a/docs/docs/build/build.md +++ b/docs/docs/build/build.md @@ -182,12 +182,12 @@ ArceOS 的平台配置(如内存布局、中断控制器地址、串口基地 ### 6a. 平台包解析 -平台配置生成的关键前提是**确定使用哪个平台包**。仓库中 `axplat` 平台实现主要位于 `components/axplat_crates/platforms/`,少量非 `ax-plat-*` 平台仍位于独立 `platform/` 目录;同一平台应只保留一个包入口: +平台配置生成的关键前提是**确定使用哪个平台包**。仓库中 `axplat` 平台实现主要位于 `platforms/`,少量非 `ax-plat-*` 平台仍位于独立 `platforms/` 目录;同一平台应只保留一个包入口: | 目录 | 命名示例 | 包名格式 | 定位方式 | |------|---------|---------|---------| -| `components/axplat_crates/platforms/` | `axplat-riscv64-qemu-virt/` | `ax-plat-riscv64-qemu-virt` | Workspace member,通过 cargo metadata 直接定位 | -| `components/axplat_crates/platforms/` | `axplat-aarch64-qemu-virt/` | `ax-plat-aarch64-qemu-virt` | Workspace/deps metadata;必要时可按目录约定回退 | +| `platforms/` | `axplat-riscv64-qemu-virt/` | `ax-plat-riscv64-qemu-virt` | Workspace member,通过 cargo metadata 直接定位 | +| `platforms/` | `axplat-aarch64-qemu-virt/` | `ax-plat-aarch64-qemu-virt` | Workspace/deps metadata;必要时可按目录约定回退 | `axbuild` 通过 `resolve_platform_package()` 按以下优先级确定平台包: @@ -198,7 +198,7 @@ flowchart TD C --> D["返回匹配的依赖包名"] B -->|否| E{feature 包含 myplat?} E -->|是| F{是 Axvisor?} - F -->|x86_64| G["→ axplat-x86-qemu-q35"] + F -->|x86_64| G["→ ax-plat-x86-qemu-q35"] F -->|riscv64| H["→ ax-plat-riscv64-qemu-virt"] F -->|否| I["在依赖中查找
架构前缀匹配的平台包"] E -->|否| J["回退到默认平台"] @@ -231,21 +231,21 @@ flowchart TD C -->|否| E["2. 在 deps metadata 中查找
(同样逻辑)"] E --> F{找到?} F -->|是| D - F -->|否| G["3. 包名映射回退
ax-plat-* → axplat-* 前缀转换
→ components/axplat_crates/platforms/{dir}/axconfig.toml"] + F -->|否| G["3. 包名映射回退
ax-plat-* → axplat-* 前缀转换
→ platforms/{dir}/axconfig.toml"] G --> H{存在?} H -->|是| D H -->|否| I["错误:无法解析平台配置"] ``` -**两级 metadata 查找**:第1步 `workspace metadata` 查找的是 workspace `Cargo.toml` 的 `[workspace.members]` 中声明的包。对 `components/axplat_crates/platforms/` 下的平台包(如 `ax-plat-riscv64-qemu-virt`),其 `Cargo.toml`(如 `components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml`)旁即为 `axconfig.toml`。第2步 `deps metadata` 查找的是传递依赖中的包,覆盖平台包位于 workspace 外部或被间接依赖的场景。只有在两步都找不到时,才进入第3步的目录约定回退。 +**两级 metadata 查找**:第1步 `workspace metadata` 查找的是 workspace `Cargo.toml` 的 `[workspace.members]` 中声明的包。对 `platforms/` 下的平台包(如 `ax-plat-riscv64-qemu-virt`),其 `Cargo.toml`(如 `platforms/ax-plat-riscv64-qemu-virt/Cargo.toml`)旁即为 `axconfig.toml`。第2步 `deps metadata` 查找的是传递依赖中的包,覆盖平台包位于 workspace 外部或被间接依赖的场景。只有在两步都找不到时,才进入第3步的目录约定回退。 **回退路径的包名 ↔ 目录名映射**: 当通过 workspace/debug metadata 均找不到平台包的 `axconfig.toml` 时,`find_local_platform_config_path()` 执行包名到目录名的转换: - `ax-plat-aarch64-qemu-virt` → 去掉前缀 `ax-plat-` → `aarch64-qemu-virt` → 重新拼为 `axplat-aarch64-qemu-virt` -- 最终路径:`components/axplat_crates/platforms/axplat-aarch64-qemu-virt/axconfig.toml` +- 最终路径:`platforms/ax-plat-aarch64-qemu-virt/axconfig.toml` -这一映射确保无论平台包位于 `platform/`(workspace member)还是 `components/axplat_crates/platforms/`(按约定组织结构),`axbuild` 都能正确找到配置文件。平台名(`platform` 字段)优先从 `axconfig.toml` 中的 `platform` 键读取,读取失败时回退到 `linker_platform_name()` 从包名中提取。 +这一映射确保平台包位于 `platforms/` 时,`axbuild` 能正确找到配置文件。平台名(`platform` 字段)优先从 `axconfig.toml` 中的 `platform` 键读取,读取失败时回退到 `linker_platform_name()` 从包名中提取。 ### 6c. 配置合并与生成 diff --git a/docs/docs/build/ci.md b/docs/docs/build/ci.md index de35f96c3d..a8c3c462e4 100644 --- a/docs/docs/build/ci.md +++ b/docs/docs/build/ci.md @@ -63,7 +63,7 @@ detect_changes | 检测路径 | 触发任务 | |----------|----------| -| `.cargo/`、`Cargo.toml`、`Cargo.lock`、`components/`、`drivers/`、`examples/`、`os/`、`platform/`、`scripts/`、`test-suit/`、`xtask/` 等 | CI 检查 | +| `.cargo/`、`Cargo.toml`、`Cargo.lock`、`components/`、`drivers/`、`examples/`、`os/`、`platforms/`、`scripts/`、`test-suit/`、`xtask/` 等 | CI 检查 | | `container/Dockerfile`、`rust-toolchain.toml` | 发布基础容器镜像 | | `container/Dockerfile.axvisor-lvz`、`rust-toolchain.toml` | 发布 LVZ 扩展镜像 | diff --git a/docs/docs/build/configuration.md b/docs/docs/build/configuration.md index 33cf8ed041..69d5c27e51 100644 --- a/docs/docs/build/configuration.md +++ b/docs/docs/build/configuration.md @@ -322,7 +322,7 @@ flowchart TD | 字段 | 值 | |------|-----| | `arch` | 从 target triple 提取的架构名 | -| `platform` | 平台包名(如 `riscv64-qemu-virt`) | +| `platforms` | 平台包名(如 `riscv64-qemu-virt`) | | `plat.max-cpu-num` | `--smp` 参数值(仅 `max_cpu_num > 1` 时注入) | ### 配置内容示例 @@ -348,7 +348,7 @@ virtio-mmio-ranges = [[0x1000_1000, 0x1000], ...] `resolve_platform_config_path()` 按以下顺序查找平台配置: 1. 包在 workspace `Cargo.toml` 中的 manifest 路径旁查找 `axconfig.toml` -2. `components/axplat_crates/platforms//axconfig.toml`(组件目录约定) +2. `platforms//axconfig.toml`(组件目录约定) 3. 在完整依赖元数据中重新查找(支持平台包位于 workspace 外部的场景) 如果所有路径都未找到配置文件,构建会报错终止,提示用户确保平台包包含 `axconfig.toml`。 diff --git a/docs/docs/components/crates/ax-alloc.md b/docs/docs/components/crates/ax-alloc.md index ae3796d641..ae8610335b 100644 --- a/docs/docs/components/crates/ax-alloc.md +++ b/docs/docs/components/crates/ax-alloc.md @@ -94,7 +94,7 @@ graph LR ax-alloc --> axfsng["ax-fs-ng"] ax-alloc --> ax-api["ax-api"] ax-alloc --> starry_kernel["starry-kernel"] - ax-alloc --> axplat_dyn["axplat-dyn"] + ax-alloc --> ax_plat_dyn["ax-plat-dyn"] ``` ### 直接依赖 diff --git a/docs/docs/components/crates/ax-config-macros.md b/docs/docs/components/crates/ax-config-macros.md index eae0b9e081..6226a6d4dc 100644 --- a/docs/docs/components/crates/ax-config-macros.md +++ b/docs/docs/components/crates/ax-config-macros.md @@ -4,7 +4,7 @@ > 类型:过程宏库 > 分层:组件层 / 编译期配置展开层 > 版本:`0.2.1` -> 文档依据:`Cargo.toml`、`src/lib.rs`、`tests/example_config.rs`、`README.md`、`os/arceos/modules/axconfig/src/lib.rs`、`components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs` +> 文档依据:`Cargo.toml`、`src/lib.rs`、`tests/example_config.rs`、`README.md`、`os/arceos/modules/axconfig/src/lib.rs`、`platforms/ax-plat-aarch64-qemu-virt/src/lib.rs` `ax-config-macros` 把 TOML 配置文本直接变成 Rust 常量定义,是 `axconfig*` 链路中的“编译期翻译器”。它不生成配置文件,也不保存任何运行时状态;它的职责是在宏展开阶段读取配置文本,调用 `ax-config-gen` 解析后输出等价的 Rust 代码。 diff --git a/docs/docs/components/crates/ax-cpu.md b/docs/docs/components/crates/ax-cpu.md index 5a2ff12213..a9e5cb4f9d 100644 --- a/docs/docs/components/crates/ax-cpu.md +++ b/docs/docs/components/crates/ax-cpu.md @@ -177,7 +177,7 @@ - `ax-hal` - 各类 `axplat-*` 平台包 -- `platform/*` 下的平台实现 +- `platforms/*` 下的平台实现 - `starry-signal` `axvm` 并不直接依赖它;两者关系更多体现在 hypervisor 宿主 CPU 模式与 trap 行为的间接配合上。 diff --git a/docs/docs/components/crates/ax-dma.md b/docs/docs/components/crates/ax-dma.md index 331a31337c..9bc908cbea 100644 --- a/docs/docs/components/crates/ax-dma.md +++ b/docs/docs/components/crates/ax-dma.md @@ -4,7 +4,7 @@ > 类型:库 crate > 分层:ArceOS 层 / DMA 内存服务层 > 版本:`0.3.0-preview.3` -> 文档依据:`Cargo.toml`、`src/lib.rs`、`src/dma.rs`、`drivers/ax-driver/src/ixgbe.rs`、`drivers/ax-driver/src/drivers.rs`、`os/arceos/api/ax-api/src/imp/mem.rs`、`platform/axplat-dyn/src/drivers/mod.rs`、`os/axvisor/src/driver/blk/mod.rs` +> 文档依据:`Cargo.toml`、`src/lib.rs`、`src/dma.rs`、`drivers/ax-driver/src/ixgbe.rs`、`drivers/ax-driver/src/drivers.rs`、`os/arceos/api/ax-api/src/imp/mem.rs`、`platforms/ax-plat-dyn/src/drivers/mod.rs`、`os/axvisor/src/driver/blk/mod.rs` `ax-dma` 不是驱动聚合层,也不是某类设备驱动。它的真实职责是为 ArceOS 内核提供一套全局一致的 DMA 一致性内存分配服务:从页分配器拿到内存、把页表属性改成 `UNCACHED`、给设备返回可用的总线地址,并在释放时尽可能恢复映射属性。它位于 `ax-alloc` / `ax-mm` / `ax-hal` 等内存基础设施之上,位于需要软件管理 DMA 缓冲的驱动之下。 @@ -81,7 +81,7 @@ ### 1.7 与其它 DMA 实现的边界 仓库里至少还有两条独立 DMA 路径: -- `platform/axplat-dyn/src/drivers/mod.rs`:提供 `dma_api::DmaOp` 实现,服务动态平台块设备探测。 +- `platforms/ax-plat-dyn/src/drivers/mod.rs`:提供 `dma_api::DmaOp` 实现,服务动态平台块设备探测。 - `os/axvisor/src/driver/blk/mod.rs`:提供 Axvisor 自己的 `rdif_block::dma_api::DmaOp` 实现。 因此不能把 `ax-dma` 写成“整个仓库唯一的 DMA 抽象层”;它是 ArceOS 主线中的一个内核 DMA 内存服务模块。 diff --git a/docs/docs/components/crates/ax-driver.md b/docs/docs/components/crates/ax-driver.md index ad31157a6a..b7bc207d85 100644 --- a/docs/docs/components/crates/ax-driver.md +++ b/docs/docs/components/crates/ax-driver.md @@ -52,7 +52,7 @@ graph LR ```bash cargo xtask clippy --package ax-driver -cargo xtask clippy --package axplat-dyn +cargo xtask clippy --package ax-plat-dyn ``` 涉及 ArceOS 运行路径时,继续跑对应 `cargo xtask arceos test qemu ...` 用例。 diff --git a/docs/docs/components/crates/ax-handler-table.md b/docs/docs/components/crates/ax-handler-table.md index 9a2af51f84..18418b5cbd 100644 --- a/docs/docs/components/crates/ax-handler-table.md +++ b/docs/docs/components/crates/ax-handler-table.md @@ -54,7 +54,7 @@ flowchart TD ### 使用场景 - `HandlerTable::new()`:各平台 IRQ 子系统用它声明静态处理器表。 -- `register_handler()` / `unregister_handler()`:被 `components/axplat_crates/ax-plat/src/irq.rs` 的平台实现间接消费。 +- `register_handler()` / `unregister_handler()`:被 `platforms/ax-plat/src/irq.rs` 的平台实现间接消费。 - `handle()`:由平台 IRQ 处理路径在拿到实际 IRQ 号后调用。 ### 边界说明 diff --git a/docs/docs/components/crates/ax-int-ratio.md b/docs/docs/components/crates/ax-int-ratio.md index db423cbfd4..fef4282098 100644 --- a/docs/docs/components/crates/ax-int-ratio.md +++ b/docs/docs/components/crates/ax-int-ratio.md @@ -50,7 +50,7 @@ numerator / denominator ~= mult / (1 << shift) - 支持快速取倒数 `inverse()`。 ### 使用场景 -- `Ratio::new()`:在 `ax-plat-x86-pc`、`axplat-x86-qemu-q35`、`ax-plat-aarch64-peripherals` 的时间初始化代码中使用。 +- `Ratio::new()`:在 `ax-plat-x86-pc`、`ax-plat-x86-qemu-q35`、`ax-plat-aarch64-peripherals` 的时间初始化代码中使用。 - `mul_trunc()`:平台时间路径用来把 deadline 或 tick 值做快速转换。 - `inverse()`:AArch64 generic timer 初始化时直接通过现有比例求反比率。 - `Ratio::zero()`:常作为静态变量的初始化哨兵值。 @@ -64,7 +64,7 @@ numerator / denominator ~= mult / (1 << shift) ```mermaid graph LR ax_int_ratio["ax-int-ratio"] --> x86pc["ax-plat-x86-pc"] - ax_int_ratio --> x86q35["axplat-x86-qemu-q35"] + ax_int_ratio --> x86q35["ax-plat-x86-qemu-q35"] ax_int_ratio --> aarch64["ax-plat-aarch64-peripherals"] ``` @@ -72,7 +72,7 @@ graph LR `ax-int-ratio` 没有本地 crate 依赖,体量非常小。 ### 主要消费者 -- `ax-plat-x86-pc` / `axplat-x86-qemu-q35`:LAPIC 计时换算。 +- `ax-plat-x86-pc` / `ax-plat-x86-qemu-q35`:LAPIC 计时换算。 - `ax-plat-aarch64-peripherals`:ARM generic timer 的 ticks / nanos 双向换算。 ## 开发指南 diff --git a/docs/docs/components/crates/ax-lazyinit.md b/docs/docs/components/crates/ax-lazyinit.md index d6cc421e3d..92210453af 100644 --- a/docs/docs/components/crates/ax-lazyinit.md +++ b/docs/docs/components/crates/ax-lazyinit.md @@ -60,7 +60,7 @@ stateDiagram-v2 ### 使用场景 - `LazyInit::new()`:大量平台和模块静态对象都以它声明,如 `ax-task` 的运行队列、`ax-mm` 的内核地址空间、`ax-ipi` 的 IPI 队列。 - `init_once()`:平台 UART、IO APIC、GIC、显示/输入设备等对象初始化时广泛使用。 -- `call_once()`:用于“按需首次构造”的场景,如 `axplat-dyn` 内存区域表、`ax-std` 标准 IO 包装器、`axbacktrace` 的地址范围缓存。 +- `call_once()`:用于“按需首次构造”的场景,如 `ax-plat-dyn` 内存区域表、`ax-std` 标准 IO 包装器、`axbacktrace` 的地址范围缓存。 - `get()` / `Deref`:初始化完成后作为普通全局对象读取。 ### 边界说明 diff --git a/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md b/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md index 278b618f65..e976a3005d 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md +++ b/docs/docs/components/crates/ax-plat-aarch64-bsta1000b.md @@ -1,6 +1,6 @@ # `ax-plat-aarch64-bsta1000b` -> 路径:`components/axplat_crates/platforms/axplat-aarch64-bsta1000b` +> 路径:`platforms/ax-plat-aarch64-bsta1000b` > 类型:库 crate > 分层:组件层 / AArch64 板级平台包 > 版本:`0.3.1-pre.6` diff --git a/docs/docs/components/crates/ax-plat-aarch64-peripherals.md b/docs/docs/components/crates/ax-plat-aarch64-peripherals.md index 924532b702..b4055d8f42 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-peripherals.md +++ b/docs/docs/components/crates/ax-plat-aarch64-peripherals.md @@ -1,6 +1,6 @@ # `ax-plat-aarch64-peripherals` -> 路径:`components/axplat_crates/platforms/axplat-aarch64-peripherals` +> 路径:`platforms/ax-plat-aarch64-peripherals` > 类型:库 crate > 分层:组件层 / AArch64 通用外设 glue 层 > 版本:`0.3.1-pre.6` @@ -222,7 +222,7 @@ graph TD cargo build -p ax-plat-aarch64-peripherals --target aarch64-unknown-none --all-features ``` -但真正有意义的验证应放到板级平台包上进行,例如通过 `ax-plat-aarch64-qemu-virt` 运行 `hello-kernel`、`irq-kernel` 或多核示例,确认串口输出、时钟推进和中断分发都贯通。 +但真正有意义的验证应放到板级平台包上进行,例如通过 `ax-plat-aarch64-qemu-virt` 运行 ArceOS/StarryOS/Axvisor 的系统级 smoke test,确认串口输出、时钟推进和中断分发都贯通。 ### 4.4 维护时的注意事项 diff --git a/docs/docs/components/crates/ax-plat-aarch64-phytium-pi.md b/docs/docs/components/crates/ax-plat-aarch64-phytium-pi.md index 29baf4389f..07ae01340d 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-phytium-pi.md +++ b/docs/docs/components/crates/ax-plat-aarch64-phytium-pi.md @@ -1,6 +1,6 @@ # `ax-plat-aarch64-phytium-pi` -> 路径:`components/axplat_crates/platforms/axplat-aarch64-phytium-pi` +> 路径:`platforms/ax-plat-aarch64-phytium-pi` > 类型:库 crate > 分层:组件层 / AArch64 板级平台包 > 版本:`0.3.1-pre.6` diff --git a/docs/docs/components/crates/ax-plat-aarch64-qemu-virt.md b/docs/docs/components/crates/ax-plat-aarch64-qemu-virt.md index 7e01aa9b33..a51896ec22 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-qemu-virt.md +++ b/docs/docs/components/crates/ax-plat-aarch64-qemu-virt.md @@ -1,6 +1,6 @@ # `ax-plat-aarch64-qemu-virt` -> 路径:`components/axplat_crates/platforms/axplat-aarch64-qemu-virt` +> 路径:`platforms/ax-plat-aarch64-qemu-virt` > 类型:库 crate > 分层:组件层 / AArch64 板级平台包 > 版本:`0.3.1-pre.6` @@ -206,9 +206,6 @@ flowchart TD ### 主要消费者 - `os/arceos/modules/axhal`:AArch64 默认平台之一。 -- `components/axplat_crates/examples/hello-kernel` -- `components/axplat_crates/examples/irq-kernel` -- `components/axplat_crates/examples/smp-kernel` - Axvisor 宿主侧经 `ax-hal` 间接引入的 AArch64 QEMU 平台栈 ### 3.3 关系示意 @@ -219,7 +216,7 @@ graph TD C[ax-plat-aarch64-peripherals] --> B D[axplat] --> B B --> E[ax-hal] - B --> F[hello-kernel / irq-kernel / smp-kernel] + B --> F[ax-helloworld-myplat/系统级 smoke test] E --> G[ArceOS] E --> H[StarryOS] E --> I[Axvisor 宿主环境] @@ -282,7 +279,7 @@ cargo build -p ax-plat-aarch64-qemu-virt --target aarch64-unknown-none --feature ### 5.2 现有有效验证面 -- 示例内核 `hello-kernel`、`irq-kernel`、`smp-kernel` 可以覆盖主启动链、IRQ 和多核路径。 +- `ax-helloworld-myplat` 和系统级 smoke test 可以覆盖主启动链、IRQ 和多核路径。 - 该平台是 ArceOS AArch64/QEMU 的主力默认平台之一,因此实际回归频率通常高于专用板卡平台。 ### 5.3 风险点 diff --git a/docs/docs/components/crates/ax-plat-aarch64-raspi.md b/docs/docs/components/crates/ax-plat-aarch64-raspi.md index 0192a053b3..76211289f5 100644 --- a/docs/docs/components/crates/ax-plat-aarch64-raspi.md +++ b/docs/docs/components/crates/ax-plat-aarch64-raspi.md @@ -1,6 +1,6 @@ # `ax-plat-aarch64-raspi` -> 路径:`components/axplat_crates/platforms/axplat-aarch64-raspi` +> 路径:`platforms/ax-plat-aarch64-raspi` > 类型:库 crate > 分层:组件层 / AArch64 板级平台包 > 版本:`0.3.1-pre.6` diff --git a/docs/docs/components/crates/ax-plat-loongarch64-qemu-virt.md b/docs/docs/components/crates/ax-plat-loongarch64-qemu-virt.md index 7e5b1a33c3..60bf976d82 100644 --- a/docs/docs/components/crates/ax-plat-loongarch64-qemu-virt.md +++ b/docs/docs/components/crates/ax-plat-loongarch64-qemu-virt.md @@ -1,6 +1,6 @@ # `ax-plat-loongarch64-qemu-virt` -> 路径:`components/axplat_crates/platforms/axplat-loongarch64-qemu-virt` +> 路径:`platforms/ax-plat-loongarch64-qemu-virt` > 类型:库 crate > 分层:组件层 / LoongArch64 板级平台包 > 版本:`0.3.1-pre.6` @@ -25,7 +25,7 @@ - 向下依赖 `ax-cpu`、`loongArch64`、`uart_16550` 等架构/设备库。 - 向上直接把 LoongArch QEMU virt 的全部最小平台能力暴露给 `axplat`。 -- 在仓库里既是 `ax-hal` 的 LoongArch 默认平台之一,也是 `hello-kernel`、`irq-kernel`、`smp-kernel` 的示例平台。 +- 在仓库里既是 `ax-hal` 的 LoongArch 默认平台之一。 这意味着它不是“仅供样例演示”的平台包,而是当前 LoongArch 路径里的主力参考实现。 @@ -179,9 +179,6 @@ LoongArch QEMU virt 的中断模型在这个 crate 里被明确分层了: ### 主要消费者 - `os/arceos/modules/axhal`:当前 LoongArch 默认平台路径之一。 -- `components/axplat_crates/examples/hello-kernel` -- `components/axplat_crates/examples/irq-kernel` -- `components/axplat_crates/examples/smp-kernel` - `os/arceos/examples/helloworld-myplat` ### 3.3 依赖关系示意 @@ -191,7 +188,7 @@ graph TD A[ax-cpu / loongArch64 / ax-page-table-entry / uart_16550] --> B[ax-plat-loongarch64-qemu-virt] C[axplat / ax-config-macros / ax-lazyinit / ax-kspin] --> B B --> D[ax-hal] - B --> E[hello-kernel / irq-kernel / smp-kernel] + B --> E[ax-helloworld-myplat/系统级 smoke test] B --> F[ax-helloworld-myplat] D --> G[ArceOS] ``` @@ -231,7 +228,7 @@ ax-plat-loongarch64-qemu-virt = { workspace = true, features = ["irq", "smp", "r ### 测试覆盖 - `ax-hal` 默认平台链路会持续编译和运行它。 -- `hello-kernel`、`irq-kernel`、`smp-kernel` 分别覆盖最小启动、中断和多核路径。 +- `ax-helloworld-myplat` 和系统级 smoke test 覆盖最小启动、中断和多核路径。 - `ax-helloworld-myplat` 提供额外的直接平台验证入口。 ### 5.2 推荐测试矩阵 diff --git a/docs/docs/components/crates/ax-plat-macros.md b/docs/docs/components/crates/ax-plat-macros.md index fbc3e008d7..24f4f7cab6 100644 --- a/docs/docs/components/crates/ax-plat-macros.md +++ b/docs/docs/components/crates/ax-plat-macros.md @@ -1,6 +1,6 @@ # `ax-plat-macros` -> 路径:`components/axplat_crates/axplat-macros` +> 路径:`platforms/ax-plat-macros` > 类型:过程宏库 > 分层:组件层 / 可复用基础组件 > 版本:`0.1.0` @@ -87,8 +87,7 @@ graph LR current --> axplat["ax-plat"] axplat --> ax-runtime["ax-runtime"] - axplat --> hello["hello-kernel / smp-kernel / irq-kernel"] - axplat --> oses["ArceOS / StarryOS / Axvisor 平台入口链"] + axplat --> oses["ArceOS / StarryOS / Axvisor 平台入口链"] ``` ### 直接依赖 @@ -100,7 +99,6 @@ graph LR ### 3.3 间接消费者 - `ax-runtime`:通过 `#[ax_plat::main]` / `#[ax_plat::secondary_main]` 接入平台入口。 -- `components/axplat_crates/examples/*`:最小平台样例。 - 通过 `axplat` 体系间接复用入口契约的 ArceOS、StarryOS 和 Axvisor 路径。 ## 开发指南 @@ -131,7 +129,7 @@ graph LR - 展开后导出符号与 `call_interface` 调用路径是否符合预期。 ### 集成测试 -- 用 `hello-kernel` / `smp-kernel` 验证 `_start -> ax_plat::call_main -> __axplat_main` 链条。 +- 用 `ax-helloworld-myplat` 和系统级 smoke test 验证 `_start -> ax_plat::call_main -> __axplat_main` 链条。 - 用 `axplat` 内部 trait 接口验证 `def_plat_interface` 与 `impl_plat_interface` 的配合。 ### 覆盖率 @@ -150,18 +148,18 @@ StarryOS 并不直接面向 `ax-plat-macros` 编程,但只要复用同一套 ` Axvisor 同样不是直接依赖 `ax-plat-macros` 的业务代码,但在共享 `axplat` / `ax-hal` 体系时,会间接依赖这层宏生成的链接和接口约定。因此它在 Axvisor 中仍然是基础设施层,而不是业务层。 # `ax-plat-macros` 技术文档 -> 路径:`components/axplat_crates/ax-plat-macros` +> 路径:`platforms/ax-plat-macros` > 类型:过程宏库 > 分层:组件层 / 可复用基础组件 > 版本:`0.1.0` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/axplat_crates/axplat-macros/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `platforms/ax-plat-macros/README.md` `ax-plat-macros` 的核心定位是:Procedural macros for the `axplat` crate ## 架构设计 - 目录角色:可复用基础组件 - crate 形态:过程宏库 -- 工作区位置:子工作区 `components/axplat_crates` +- 工作区位置:子工作区 `platforms` - feature 视角:该 crate 没有显式声明额外 Cargo feature,功能边界主要由模块本身决定。 - 关键数据结构:关键“结构”更多体现在编译期语法树节点、宏输入 token 流和展开规则上。 - 设计重心:该 crate 应从宏入口、语法树解析和展开产物理解,运行时模块树通常不长,但编译期接口契约很关键。 @@ -222,7 +220,7 @@ graph LR ax-plat-macros = { workspace = true } # 如果在仓库外独立验证,也可以显式绑定本地路径: -# ax-plat-macros = { path = "components/axplat_crates/ax-plat-macros" } +# ax-plat-macros = { path = "platforms/ax-plat-macros" } ``` ### 初始化 diff --git a/docs/docs/components/crates/ax-plat-riscv64-qemu-virt.md b/docs/docs/components/crates/ax-plat-riscv64-qemu-virt.md index f94b778923..580cbb831d 100644 --- a/docs/docs/components/crates/ax-plat-riscv64-qemu-virt.md +++ b/docs/docs/components/crates/ax-plat-riscv64-qemu-virt.md @@ -1,6 +1,6 @@ # `ax-plat-riscv64-qemu-virt` -> 路径:`components/axplat_crates/platforms/axplat-riscv64-qemu-virt` +> 路径:`platforms/ax-plat-riscv64-qemu-virt` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.3.1-pre.6` @@ -113,7 +113,7 @@ flowchart TD ax-plat-riscv64-qemu-virt = { workspace = true, features = ["irq", "smp", "rtc"] } ``` -在真正的 ArceOS 系统里,它通常进一步经 `ax-hal` 的平台选择或 `components/axplat_crates/examples/*` 的样例工程间接生效。 +在真正的 ArceOS 系统里,它通常进一步经 `ax-hal` 的平台选择间接生效。 ## 依赖关系 ```mermaid @@ -126,7 +126,6 @@ graph LR plic["riscv_plic"] --> current current --> ax-hal["ax-hal"] - current --> hello["hello-kernel / irq-kernel / smp-kernel"] current --> arceos["ArceOS RISC-V virt 平台路径"] ``` @@ -139,7 +138,6 @@ graph LR ### 主要消费者 - `ax-hal`:在 RISC-V `virt` 平台选择中复用本 crate 的所有 `axplat` 实现。 -- `components/axplat_crates/examples/*`:作为最小平台 bring-up 示例的底层平台包。 - ArceOS 栈中的 RISC-V 默认平台路径与 `helloworld-myplat` 等示例。 ### 3.3 间接消费者 @@ -174,7 +172,7 @@ ax-plat-riscv64-qemu-virt = { workspace = true, features = ["irq", "smp"] } - 对 `config` 生成结果和 `PACKAGE` 一致性应保留编译期校验。 ### 集成测试 -- 使用 `hello-kernel`、`irq-kernel`、`smp-kernel` 验证主核 bring-up、中断和多 hart 启动。 +- 使用 `ax-helloworld-myplat` 和系统级 smoke test 验证主核 bring-up、中断和多 hart 启动。 - 在启用 `rtc` 时验证 `wall_time` 路径是否正确。 - 在 `irq` + `smp` 组合下验证 IPI、timer 和 PLIC 外部中断是否都能工作。 @@ -194,18 +192,18 @@ StarryOS 在 RISC-V 默认平台配置里会复用这条 `axplat` 路径。因 Axvisor 在 RISC-V 上也复用该平台包,并通过 `hypervisor` feature 启用虚拟中断注入等差异化逻辑。 # `ax-plat-riscv64-qemu-virt` 技术文档 -> 路径:`components/axplat_crates/platforms/axplat-riscv64-qemu-virt` +> 路径:`platforms/ax-plat-riscv64-qemu-virt` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.3.1-pre.6` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `platforms/ax-plat-riscv64-qemu-virt/README.md` `ax-plat-riscv64-qemu-virt` 的核心定位是:Implementation of `axplat` hardware abstraction layer for QEMU RISC-V virt board. ## 架构设计 - 目录角色:可复用基础组件 - crate 形态:库 crate -- 工作区位置:子工作区 `components/axplat_crates` +- 工作区位置:子工作区 `platforms` - feature 视角:主要通过 `fp-simd`、`irq`、`rtc`、`smp` 控制编译期能力装配。 - 关键数据结构:可直接观察到的关键数据结构/对象包括 `ConsoleIfImpl`、`InitIfImpl`、`IrqIfImpl`、`MemIfImpl`、`PowerImpl`、`TimeIfImpl`、`UART`、`TIMER_HANDLER`、`IPI_HANDLER`。 - 设计重心:该 crate 的重心通常是板级假设、条件编译矩阵和启动时序,阅读时应优先关注架构/平台绑定点。 @@ -242,9 +240,6 @@ graph LR current --> riscv_plic["riscv_plic"] arceos_helloworld_myplat["ax-helloworld-myplat"] --> current ax-hal["ax-hal"] --> current - hello_kernel["hello-kernel"] --> current - irq_kernel["irq-kernel"] --> current - smp_kernel["smp-kernel"] --> current ``` ### 直接依赖 @@ -272,9 +267,6 @@ graph LR ### 3.3 被依赖情况 - `ax-helloworld-myplat` - `ax-hal` -- `hello-kernel` -- `irq-kernel` -- `smp-kernel` ### 被依赖情况 - `arceos-affinity` @@ -305,7 +297,7 @@ graph LR ax-plat-riscv64-qemu-virt = { workspace = true } # 如果在仓库外独立验证,也可以显式绑定本地路径: -# ax-plat-riscv64-qemu-virt = { path = "components/axplat_crates/platforms/axplat-riscv64-qemu-virt" } +# ax-plat-riscv64-qemu-virt = { path = "platforms/ax-plat-riscv64-qemu-virt" } ``` ### 初始化 diff --git a/docs/docs/components/crates/ax-plat-x86-pc.md b/docs/docs/components/crates/ax-plat-x86-pc.md index e47edcd149..6d498082b9 100644 --- a/docs/docs/components/crates/ax-plat-x86-pc.md +++ b/docs/docs/components/crates/ax-plat-x86-pc.md @@ -1,6 +1,6 @@ # `ax-plat-x86-pc` -> 路径:`components/axplat_crates/platforms/axplat-x86-pc` +> 路径:`platforms/ax-plat-x86-pc` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.3.1-pre.6` @@ -129,7 +129,6 @@ graph LR cpuid["raw-cpuid"] --> current current --> ax-hal["ax-hal"] - current --> hello["hello-kernel / irq-kernel / smp-kernel"] current --> arceos["ArceOS x86 默认平台路径"] ``` @@ -142,13 +141,12 @@ graph LR ### 主要消费者 - `ax-hal`:在 x86 PC 场景下复用本 crate 的 `axplat` 实现。 -- `components/axplat_crates/examples/*`:最小平台样例。 - ArceOS 和 StarryOS 的 x86 默认平台路径。 ### 3.3 间接消费者 - 通过 `ax-hal` 运行在 Standard PC 环境上的 ArceOS 样例与测试。 - StarryOS 的 x86 平台 bring-up。 -- Axvisor 的依赖图中可能出现该包,但主 x86 平台更偏向 `axplat-x86-qemu-q35`。 +- Axvisor 的依赖图中可能出现该包,但主 x86 平台更偏向 `ax-plat-x86-qemu-q35`。 ## 开发指南 ### 接入方式 @@ -177,9 +175,7 @@ ax-plat-x86-pc = { workspace = true, features = ["irq", "smp"] } - 对启动路径中的结构布局和符号契约,优先考虑编译期断言和最小样例验证。 ### 集成测试 -- `hello-kernel`:验证 Multiboot 启动、串口和最小 bring-up。 -- `irq-kernel`:验证 IOAPIC/LAPIC 中断路径。 -- `smp-kernel`:验证 AP 启动与 secondary path。 +- `ax-helloworld-myplat` 和系统级 smoke test:验证 Multiboot 启动、串口、IOAPIC/LAPIC 和 secondary path。 - 启用 `rtc` 时验证 wall time 路径。 ### 覆盖率 @@ -195,21 +191,21 @@ ax-plat-x86-pc = { workspace = true, features = ["irq", "smp"] } StarryOS 在 x86 默认平台路径中也会复用这条 `axplat` 实现。因此它在 StarryOS 中承担的是板级 bring-up 与平台设施角色,而不是 Linux 兼容语义层。 ### Axvisor -Axvisor 的主 x86 manifest 更明确地偏向 `axplat-x86-qemu-q35`,因此 `ax-plat-x86-pc` 在 Axvisor 中更适合作为“通用 PC / Multiboot 参考实现”来理解,而不是其当前主平台实现。 +Axvisor 的主 x86 manifest 更明确地偏向 `ax-plat-x86-qemu-q35`,因此 `ax-plat-x86-pc` 在 Axvisor 中更适合作为“通用 PC / Multiboot 参考实现”来理解,而不是其当前主平台实现。 # `ax-plat-x86-pc` 技术文档 -> 路径:`components/axplat_crates/platforms/axplat-x86-pc` +> 路径:`platforms/ax-plat-x86-pc` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.3.1-pre.6` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/axplat_crates/platforms/axplat-x86-pc/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `platforms/ax-plat-x86-pc/README.md` `ax-plat-x86-pc` 的核心定位是:Implementation of `axplat` hardware abstraction layer for x86 Standard PC machine. ## 架构设计 - 目录角色:可复用基础组件 - crate 形态:库 crate -- 工作区位置:子工作区 `components/axplat_crates` +- 工作区位置:子工作区 `platforms` - feature 视角:主要通过 `fp-simd`、`irq`、`reboot-on-system-off`、`rtc`、`smp` 控制编译期能力装配。 - 关键数据结构:可直接观察到的关键数据结构/对象包括 `IrqIfImpl`、`ConsoleIfImpl`、`InitIfImpl`、`MemIfImpl`、`PowerImpl`、`TimeIfImpl`、`APIC_TIMER_VECTOR`、`APIC_SPURIOUS_VECTOR`、`APIC_ERROR_VECTOR`、`IO_APIC_BASE`。 - 设计重心:该 crate 的重心通常是板级假设、条件编译矩阵和启动时序,阅读时应优先关注架构/平台绑定点。 @@ -247,9 +243,6 @@ graph LR current --> ax-percpu["ax-percpu"] arceos_helloworld_myplat["ax-helloworld-myplat"] --> current ax-hal["ax-hal"] --> current - hello_kernel["hello-kernel"] --> current - irq_kernel["irq-kernel"] --> current - smp_kernel["smp-kernel"] --> current ``` ### 直接依赖 @@ -277,9 +270,6 @@ graph LR ### 3.3 被依赖情况 - `ax-helloworld-myplat` - `ax-hal` -- `hello-kernel` -- `irq-kernel` -- `smp-kernel` ### 被依赖情况 - `arceos-affinity` @@ -315,7 +305,7 @@ graph LR ax-plat-x86-pc = { workspace = true } # 如果在仓库外独立验证,也可以显式绑定本地路径: -# ax-plat-x86-pc = { path = "components/axplat_crates/platforms/axplat-x86-pc" } +# ax-plat-x86-pc = { path = "platforms/ax-plat-x86-pc" } ``` ### 初始化 diff --git a/docs/docs/components/crates/ax-plat.md b/docs/docs/components/crates/ax-plat.md index 2c40dc8113..8787189618 100644 --- a/docs/docs/components/crates/ax-plat.md +++ b/docs/docs/components/crates/ax-plat.md @@ -1,10 +1,10 @@ # `ax-plat` -> 路径:`components/axplat_crates/axplat` +> 路径:`platforms/ax-plat` > 类型:库 crate > 分层:组件层 / 平台抽象 > 版本:`0.3.1-pre.6` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/axplat_crates/axplat/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `platforms/ax-plat/README.md` `ax-plat` 不是某个具体板级平台的驱动集合,而是 ArceOS 平台层对上层内核暴露的统一契约包。它把“平台初始化、物理内存描述、控制台、时间、中断、电源、每核上下文”等能力拆成稳定接口;具体 `ax-plat-*` 平台包通过 `ax-crate-interface` 机制填充实现,内核只面向 `ax-plat` 编程,而不直接依赖某个 UART、GIC、APIC 或 PSCI/SBI 实现。 @@ -198,7 +198,6 @@ impl ax_plat::init::InitIf for InitIfImpl { - 所有具体平台包:如 `ax-plat-aarch64-qemu-virt`、`ax-plat-x86-pc`、`ax-plat-riscv64-qemu-virt`。 - `ax-hal`:通过选择某个 `ax-plat-*` 平台包把平台实现纳入构建。 -- `components/axplat_crates/examples/*`:示例内核直接使用 `ax-plat` 接口与平台包。 - 上层 ArceOS/StarryOS/Axvisor 宿主侧内核:通常通过 `ax-hal` 间接消费,而不是直接依赖 `ax-plat`。 ### 3.3 依赖关系示意 diff --git a/docs/docs/components/crates/axklib.md b/docs/docs/components/crates/axklib.md index e99c90ccf8..bf24b8ddd4 100644 --- a/docs/docs/components/crates/axklib.md +++ b/docs/docs/components/crates/axklib.md @@ -16,7 +16,7 @@ - 调用点必须足够直白,便于平台代码或驱动代码直接复用。 - ABI 必须尽量稳定,避免每个项目都重新发明一套 helper trait。 -在当前仓库里,`ax-runtime/src/klib.rs` 就是 `axklib::Klib` 的一个真实实现方;`platform/axplat-dyn` 和 `os/axvisor` 的驱动代码则是典型使用方。 +在当前仓库里,`ax-runtime/src/klib.rs` 就是 `axklib::Klib` 的一个真实实现方;`platforms/ax-plat-dyn` 和 `os/axvisor` 的驱动代码则是典型使用方。 ### 1.2 核心接口 `axklib` 只有一个核心 trait: @@ -68,7 +68,7 @@ flowchart TD - 让 ArceOS 与 Axvisor 等项目共享一套极小的 helper 抽象。 ### 使用场景 -- `mem::iomap()`:被 `platform/axplat-dyn` 和 `os/axvisor/src/driver/*` 的驱动代码直接使用。 +- `mem::iomap()`:被 `platforms/ax-plat-dyn` 和 `os/axvisor/src/driver/*` 的驱动代码直接使用。 - `time::busy_wait()`:被 Axvisor 驱动中的短延时场景直接使用。 - `irq::register()` / `irq::set_enable()`:由具体运行时实现映射到 HAL。 - `Klib` trait:由 `os/arceos/modules/axruntime/src/klib.rs` 真实实现。 @@ -86,7 +86,7 @@ graph LR trait_ffi["trait-ffi"] --> axklib axklib --> ax-runtime["ax-runtime 实现方"] - axklib --> axplat_dyn["axplat-dyn 使用方"] + axklib --> ax_plat_dyn["ax-plat-dyn 使用方"] axklib --> axvisor["axvisor 使用方"] ``` @@ -97,7 +97,7 @@ graph LR ### 主要消费者 - `ax-runtime`:当前 ArceOS 侧的主要实现方。 -- `axplat-dyn`:动态平台与设备接入路径的调用方。 +- `ax-plat-dyn`:动态平台与设备接入路径的调用方。 - `axvisor`:若干驱动直接通过 `axklib` 访问 iomap 与 busy-wait。 ## 开发指南 @@ -123,7 +123,7 @@ axklib = { workspace = true } `axklib` 本体没有独立测试;当前验证主要依赖实现方和调用方: - `ax-runtime` 对 `Klib` 的实现是否与 `ax-mm` / `ax-hal` 对齐; -- `axplat-dyn` 和 Axvisor 驱动是否能通过 `iomap`、`busy_wait` 正常工作。 +- `ax-plat-dyn` 和 Axvisor 驱动是否能通过 `iomap`、`busy_wait` 正常工作。 ### 单元测试 - trait 桥接代码的签名稳定性。 diff --git a/docs/docs/components/crates/axplat-dyn.md b/docs/docs/components/crates/axplat-dyn.md index ac00cdd89c..fc50868030 100644 --- a/docs/docs/components/crates/axplat-dyn.md +++ b/docs/docs/components/crates/axplat-dyn.md @@ -1,18 +1,18 @@ -# `axplat-dyn` +# `ax-plat-dyn` -> 路径:`platform/axplat-dyn` +> 路径:`platforms/ax-plat-dyn` > 类型:库 crate > 分层:平台层 / 动态平台桥接层 > 版本:`0.3.0-preview.3` > 文档依据:当前仓库源码、`Cargo.toml`、`build.rs`、`link.ld` 及 `os/arceos/modules/axhal`/`ax-driver` 的接入路径 -`axplat-dyn` 不是那类“用 `axconfig.toml` 固化板级常量”的常规 `axplat-*` 平台包。它更像一层桥接适配器:把 `somehal` 已经建立好的启动入口、FDT 地址、内存图、时钟、IRQ、电源与 SMP 元数据转译成 `axplat` 的统一契约;同时再补上一条 `ax-driver` 动态设备模型所需的设备探测与 DMA glue。这里的 `dyn` 真正表示“平台事实来自运行时抽象层和探测结果”,而不是“把平台包当作运行时可装卸模块加载”。 +`ax-plat-dyn` 不是那类“用 `axconfig.toml` 固化板级常量”的常规 `axplat-*` 平台包。它更像一层桥接适配器:把 `somehal` 已经建立好的启动入口、FDT 地址、内存图、时钟、IRQ、电源与 SMP 元数据转译成 `axplat` 的统一契约;同时再补上一条 `ax-driver` 动态设备模型所需的设备探测与 DMA glue。这里的 `dyn` 真正表示“平台事实来自运行时抽象层和探测结果”,而不是“把平台包当作运行时可装卸模块加载”。 ## 架构设计 ### 设计定位 -`axplat-dyn` 在当前仓库里的位置可以概括为: +`ax-plat-dyn` 在当前仓库里的位置可以概括为: - 向下:依赖 `somehal` 提供的入口宏、内存映射、控制台、时钟、中断、电源和 CPU 元数据。 - 向上:实现 `InitIf`、`ConsoleIf`、`MemIf`、`TimeIf`、`PowerIf` 以及可选 `IrqIf`,并通过 `ax_plat::call_main()` / `call_secondary_main()` 把控制权交给内核入口。 @@ -22,7 +22,7 @@ 这决定了它与普通板级包的一个根本差异: - 普通 `axplat-*` 平台包主要把编译期 `axconfig.toml` 变成板级常量,再围绕这些常量实现 `axplat` 接口。 -- `axplat-dyn` 则把 `somehal` 暴露的运行时事实直接转成 `axplat` 接口,不以 `axconfig.toml` 为主线。 +- `ax-plat-dyn` 则把 `somehal` 暴露的运行时事实直接转成 `axplat` 接口,不以 `axconfig.toml` 为主线。 ### 模块结构 @@ -40,7 +40,7 @@ ### 1.3 平台实现装配方式 -`axplat-dyn` 的实现不是单点完成的,而是由四层 glue 组合出来: +`ax-plat-dyn` 的实现不是单点完成的,而是由四层 glue 组合出来: | 装配层 | 依赖来源 | 本 crate 的落点 | 作用 | | --- | --- | --- | --- | @@ -78,7 +78,7 @@ flowchart TD - `init_later()`:在分页建立更完整后调用 `somehal::post_paging()`,随后使能定时器 IRQ。 - `init_later_secondary()`:次核路径上只补做定时器 IRQ 使能。 -这里有一个重要前提:`axplat-dyn` 默认假设“更早的架构级 bring-up 已经由 `somehal` 完成”,它不自己构造早期页表,也不自己处理最底层的 CPU 模式切换。 +这里有一个重要前提:`ax-plat-dyn` 默认假设“更早的架构级 bring-up 已经由 `somehal` 完成”,它不自己构造早期页表,也不自己处理最底层的 CPU 模式切换。 ### 1.5 内存、时间、中断与电源 glue @@ -122,11 +122,11 @@ flowchart TD - `system_off()` 调用 `somehal::power::shutdown()`。 - `cpu_num()` 通过 `somehal::smp::cpu_meta_list()` 统计 CPU 数。 -这里还暴露出一个细节:`cpu_boot()` 当前忽略了 `stack_top_paddr` 参数,说明次核启动栈安排不是由 `axplat-dyn` 独立决定,而是纳入了 `somehal` 的启动协议。 +这里还暴露出一个细节:`cpu_boot()` 当前忽略了 `stack_top_paddr` 参数,说明次核启动栈安排不是由 `ax-plat-dyn` 独立决定,而是纳入了 `somehal` 的启动协议。 ### 1.6 动态设备探测路径 -`drivers` 模块是 `axplat-dyn` 与普通平台包最不一样的部分之一。它不是简单地“列出 MMIO 区间”,而是主动承担一部分动态设备发现职责: +`drivers` 模块是 `ax-plat-dyn` 与普通平台包最不一样的部分之一。它不是简单地“列出 MMIO 区间”,而是主动承担一部分动态设备发现职责: 1. `probe_all_devices()` 先清空本地块设备注册表。 2. 调用 `rdrive::probe_all(true)` 触发探测。 @@ -146,17 +146,17 @@ flowchart TD 这一层的关键价值在于: -- `axplat-dyn` 不只是“平台初始化 glue”,还是 `ax-driver` 动态设备模型的探测前端。 +- `ax-plat-dyn` 不只是“平台初始化 glue”,还是 `ax-driver` 动态设备模型的探测前端。 - 当前有效覆盖面主要是块设备;网络、显示等类别并没有在本 crate 中提供同等级的动态探测路径。 - `VirtIO` block 路径里的 `enable_irq()` / `disable_irq()` 仍是 `todo!()`,说明它更偏向当前可用的基础探测和阻塞 I/O 路径,而非完整中断驱动栈。 ### 1.7 与 `axplat`、`ax-plat-macros` 和工具链的边界 -`axplat-dyn` 的边界必须明确区分: +`ax-plat-dyn` 的边界必须明确区分: -- 与 `axplat` 的边界:`axplat` 定义的是稳定平台契约和入口调用面;`axplat-dyn` 只是其中一个实现者,并不改变接口定义。 +- 与 `axplat` 的边界:`axplat` 定义的是稳定平台契约和入口调用面;`ax-plat-dyn` 只是其中一个实现者,并不改变接口定义。 - 与 `ax-plat-macros` 的边界:本 crate 不直接依赖 `ax-plat-macros`,只通过 `axplat` 重新导出的 `#[impl_plat_interface]` 和入口宏参与体系。 -- 与 `somehal` 的边界:真正的“平台事实来源”在 `somehal`,包括入口、内存图、时钟、IRQ、电源与 CPU 元数据;`axplat-dyn` 负责转译,而不是重新探测 CPU 模式或自己管理整套启动环境。 +- 与 `somehal` 的边界:真正的“平台事实来源”在 `somehal`,包括入口、内存图、时钟、IRQ、电源与 CPU 元数据;`ax-plat-dyn` 负责转译,而不是重新探测 CPU 模式或自己管理整套启动环境。 - 与 `ax-config-gen` 的边界:当前源码中保留了一段被注释掉的 `config` 模块草稿,但现行实现并没有启用 `axconfig.toml -> AX_CONFIG_PATH -> include_configs!` 这条常规平台包主线,因此它不属于典型 `axplat-*` 配置化平台生态。 ## 核心功能 @@ -211,7 +211,7 @@ flowchart TD ```mermaid graph TD - A[somehal / ax-cpu / axklib] --> B[axplat-dyn] + A[somehal / ax-cpu / axklib] --> B[ax-plat-dyn] C[axplat] --> B D[rdrive / rd-block / dma-api / ax-driver] --> B @@ -227,7 +227,7 @@ graph TD ### 4.1 何时应使用这条路径 -适合使用 `axplat-dyn` 的情况是: +适合使用 `ax-plat-dyn` 的情况是: - 你已经有 `somehal` 这层更底部的平台抽象,希望把它接进 `axplat`/`ax-hal`。 - 你需要的是“运行时探测 + 动态设备模型”,而不是“固定板级参数 + 静态平台包”。 @@ -243,7 +243,7 @@ graph TD 2. 确保目标是裸机环境,而不是 `unix`/`windows` 宿主机构建路径。 3. 让 `somehal` 提供入口、FDT、内存图、控制台、时钟、中断和电源实现。 4. 由 `boot.rs` 把控制流统一转到 `ax_plat::call_main()`,随后上层只通过 `axplat` 接口使用平台能力。 -5. 若需要动态块设备,在适当阶段调用 `ax-driver::init_drivers()`,其内部会落到 `axplat_dyn::drivers::probe_all_devices()`。 +5. 若需要动态块设备,在适当阶段调用 `ax-driver::init_drivers()`,其内部会落到 `ax_plat_dyn::drivers::probe_all_devices()`。 ### 4.3 维护注意事项 @@ -271,7 +271,7 @@ graph TD ### 5.3 重点风险 -- 一旦 `somehal` 的内存图语义变化,`axplat-dyn` 的 RAM/保留区/MMIO 划分会整体漂移。 +- 一旦 `somehal` 的内存图语义变化,`ax-plat-dyn` 的 RAM/保留区/MMIO 划分会整体漂移。 - 该 crate 同时承担“平台契约 glue”和“设备探测 glue”两类职责,回归面比普通平台包更宽。 - 当前动态设备路径主要覆盖块设备,若上层以为 `dyn` 模式天然涵盖所有设备类型,容易产生错误预期。 @@ -285,4 +285,4 @@ graph TD ## 总结 -`axplat-dyn` 的价值不在“又实现了一套新的板级常量配置”,而在它把 `somehal` 的运行时平台事实和 `rdrive` 的设备探测能力拼成了 `axplat`/`ax-driver` 能消费的标准形态。它既不是常规 `axplat-*` 平台包,也不是运行时装卸模块,而是一条面向动态平台事实和动态驱动模型的桥接路径。理解这一点,是读懂它与 `axplat`、`ax-plat-macros` 及上层构建系统边界的关键。 +`ax-plat-dyn` 的价值不在“又实现了一套新的板级常量配置”,而在它把 `somehal` 的运行时平台事实和 `rdrive` 的设备探测能力拼成了 `axplat`/`ax-driver` 能消费的标准形态。它既不是常规 `axplat-*` 平台包,也不是运行时装卸模块,而是一条面向动态平台事实和动态驱动模型的桥接路径。理解这一点,是读懂它与 `axplat`、`ax-plat-macros` 及上层构建系统边界的关键。 diff --git a/docs/docs/components/crates/axplat-x86-qemu-q35.md b/docs/docs/components/crates/axplat-x86-qemu-q35.md index 442c41244f..67ecd86490 100644 --- a/docs/docs/components/crates/axplat-x86-qemu-q35.md +++ b/docs/docs/components/crates/axplat-x86-qemu-q35.md @@ -1,12 +1,12 @@ -# `axplat-x86-qemu-q35` +# `ax-plat-x86-qemu-q35` -> 路径:`platform/x86-qemu-q35` +> 路径:`platforms/ax-plat-x86-qemu-q35` > 类型:库 crate > 分层:平台层 / x86_64 Q35 板级平台包 > 版本:`0.2.0` > 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`build.rs`、`linker.lds.S`、`src/lib.rs`、`src/boot.rs`、`src/multiboot.S`、`src/init.rs`、`src/mem.rs`、`src/console.rs`、`src/time.rs`、`src/apic.rs`、`src/mp.rs`、`src/power.rs` -`axplat-x86-qemu-q35` 是面向 QEMU Q35 机型的 x86_64 `axplat` 平台实现,当前主要服务于 Axvisor 的宿主侧启动路径。它把 Multiboot 引导、临时 GDT/页表、COM1 控制台、TSC/LAPIC/IOAPIC、AP 启动页、Q35 MMIO 窗口和关机/重启策略收敛成 `axplat` 契约。它不是通用 x86 PC 平台抽象,也不是虚拟化核心本身;它解决的是“Axvisor 在 Q35 这台机器上怎样把宿主环境带起来”的问题。 +`ax-plat-x86-qemu-q35` 是面向 QEMU Q35 机型的 x86_64 `axplat` 平台实现,当前主要服务于 Axvisor 的宿主侧启动路径。它把 Multiboot 引导、临时 GDT/页表、COM1 控制台、TSC/LAPIC/IOAPIC、AP 启动页、Q35 MMIO 窗口和关机/重启策略收敛成 `axplat` 契约。它不是通用 x86 PC 平台抽象,也不是虚拟化核心本身;它解决的是“Axvisor 在 Q35 这台机器上怎样把宿主环境带起来”的问题。 ## 架构设计 @@ -14,8 +14,8 @@ 这个 crate 和仓库里另一个 `ax-plat-x86-pc` 很容易被混淆,但它们的定位并不相同: -- `ax-plat-x86-pc` 位于 `components/axplat_crates`,更偏 ArceOS 侧的标准 PC 参考平台。 -- `axplat-x86-qemu-q35` 位于根目录 `platform/`,是 Axvisor 当前 x86_64 宿主平台依赖。 +- `ax-plat-x86-pc` 位于 `platforms`,更偏 ArceOS 侧的标准 PC 参考平台。 +- `ax-plat-x86-qemu-q35` 位于根目录 `platforms/`,是 Axvisor 当前 x86_64 宿主平台依赖。 - 前者走 `axplat_crates` 的常规平台组织方式;后者把 Q35 和 Axvisor 的构建约束直接内嵌进自己的 `build.rs` 与链接脚本。 它在平台栈中的职责可以概括为: @@ -98,7 +98,7 @@ flowchart TD | 层 | 负责内容 | 不负责内容 | | --- | --- | --- | | `ax-cpu` | trap 初始化、停机等 CPU 原语 | Q35 MMIO 窗口、Multiboot 内存图解析、APIC/TSC/串口接线 | -| `axplat-x86-qemu-q35` | Multiboot 启动、COM1、TSC/LAPIC/IOAPIC、RAM/MMIO 描述、AP 启动 | VMX/EPT、虚拟设备、客户机管理、通用 HAL 聚合 | +| `ax-plat-x86-qemu-q35` | Multiboot 启动、COM1、TSC/LAPIC/IOAPIC、RAM/MMIO 描述、AP 启动 | VMX/EPT、虚拟设备、客户机管理、通用 HAL 聚合 | | `axplat` | 统一平台契约与 `call_main()` / `call_secondary_main()` | Q35 板级实现细节 | | `ax-hal` | 当前仓库里不是它的主要消费者 | 板级启动与 Q35 宿主环境 bring-up | | Axvisor 虚拟化核心 | VCPU、VM exit、EPT、设备虚拟化 | 宿主侧 COM1/APIC/TSC/Multiboot 平台初始化 | @@ -107,9 +107,9 @@ flowchart TD ### 1.6 与 `ax-plat-x86-pc` 的差异 -| 维度 | `ax-plat-x86-pc` | `axplat-x86-qemu-q35` | +| 维度 | `ax-plat-x86-pc` | `ax-plat-x86-qemu-q35` | | --- | --- | --- | -| 代码位置 | `components/axplat_crates/platforms` | 根目录 `platform/` | +| 代码位置 | `platforms` | 根目录 `platforms/` | | 主要消费者 | ArceOS `ax-hal` 默认 x86 平台 | Axvisor x86_64 宿主平台 | | 配置来源 | `axconfig` 体系 | `build.rs` + `linker.lds.S` + `AXVISOR_SMP` | | 目标机型 | 标准 PC / Multiboot 参考实现 | QEMU Q35 明确机型 | @@ -176,7 +176,7 @@ flowchart TD ```mermaid graph TD - A[multiboot / x2apic / x86 / x86_64 / uart_16550] --> B[axplat-x86-qemu-q35] + A[multiboot / x2apic / x86 / x86_64 / uart_16550] --> B[ax-plat-x86-qemu-q35] C[axplat / ax-cpu / ax-percpu / int_ratio / ax-lazyinit / ax-kspin] --> B B --> D[Axvisor] D --> E[x86_64 Q35 宿主环境] @@ -190,7 +190,7 @@ graph TD ```toml [target.'cfg(target_arch = "x86_64")'.dependencies] -axplat-x86-qemu-q35 = { version = "0.2.0", default-features = false, features = [ +ax-plat-x86-qemu-q35 = { version = "0.2.0", default-features = false, features = [ "reboot-on-system-off", "irq", "smp", @@ -200,7 +200,7 @@ axplat-x86-qemu-q35 = { version = "0.2.0", default-features = false, features = 若单独验证平台包,可在构建前设置: ```bash -AXVISOR_SMP=4 cargo build -p axplat-x86-qemu-q35 --target x86_64-unknown-none +AXVISOR_SMP=4 cargo build -p ax-plat-x86-qemu-q35 --target x86_64-unknown-none ``` ### 4.2 修改时需要成组验证的点 @@ -250,4 +250,4 @@ AXVISOR_SMP=4 cargo build -p axplat-x86-qemu-q35 --target x86_64-unknown-none ## 总结 -`axplat-x86-qemu-q35` 的真正价值,在于它把 Axvisor 在 x86_64 Q35 上的宿主 bring-up 路径压缩成了一份独立而清晰的 `axplat` 实现:配置从哪里来、CPU 数如何注入、Multiboot 怎样切到长模式、APIC 和 TSC 怎样接、Q35 的关键 MMIO 窗口有哪些。它不是通用 PC 平台的重复实现,更不是虚拟化核心,而是 Axvisor 宿主环境最底层的板级基座。 +`ax-plat-x86-qemu-q35` 的真正价值,在于它把 Axvisor 在 x86_64 Q35 上的宿主 bring-up 路径压缩成了一份独立而清晰的 `axplat` 实现:配置从哪里来、CPU 数如何注入、Multiboot 怎样切到长模式、APIC 和 TSC 怎样接、Q35 的关键 MMIO 窗口有哪些。它不是通用 PC 平台的重复实现,更不是虚拟化核心,而是 Axvisor 宿主环境最底层的板级基座。 diff --git a/docs/docs/components/crates/hello-kernel.md b/docs/docs/components/crates/hello-kernel.md deleted file mode 100644 index d0ae43c286..0000000000 --- a/docs/docs/components/crates/hello-kernel.md +++ /dev/null @@ -1,146 +0,0 @@ -# `hello-kernel` - -> 路径:`components/axplat_crates/examples/hello-kernel` -> 类型:平台样例内核 crate -> 分层:组件层 / `axplat` 最小 bring-up 示例 -> 版本:`0.1.0` -> 文档依据:`Cargo.toml`、`src/main.rs`、`Makefile`、`README.md` - -`hello-kernel` 是 `axplat` 工作区里最短的“能真正跑起来”的内核样例。它显式链接一个目标平台包,调用 `ax_plat::percpu::init_primary()`、`ax_plat::init::init_early()` 和 `init_later()`,然后打印启动信息、忙等 5 秒并关机。 - -因此它最重要的边界是:**它不是可复用内核框架,也不是上层系统应该直接继承的骨架;它只是验证 `axplat` 最小启动链是否成立的样例入口。** - -## 架构设计 -### 1.1 最小内核主线 -源码只有一个文件,但结构非常清晰: - -1. 通过 `cfg_if!` 选择当前架构对应的平台 crate。 -2. `init_kernel(cpu_id, arg)` 完成 per-CPU 与平台初始化。 -3. `#[ax_plat::main] fn main(...) -> !` 打印信息、忙等、关机。 -4. `panic_handler` 在 panic 时走控制台输出并关机。 - -它几乎把“基于 `axplat` 写最小内核”压缩到了最短。 - -### 1.2 真实调用链 -```mermaid -flowchart LR - A["平台 boot 代码"] --> B["ax_plat::call_main"] - B --> C["#[ax_plat::main] main(cpu_id, arg)"] - C --> D["init_kernel()"] - D --> E["percpu::init_primary"] - E --> F["init_early / init_later"] - F --> G["console_println! / busy_wait / system_off"] -``` - -这个顺序非常重要,因为它展示了 `axplat` 使用者最基本的职责分工: - -- 平台 crate 提供启动入口与板级实现 -- 示例内核负责在进入主逻辑前按顺序完成平台抽象要求的初始化 - -### 1.3 为什么要显式 `extern crate` 平台包 -这里没有默认平台自动选择逻辑,而是按架构显式链接: - -- `ax-plat-x86-pc` -- `ax-plat-aarch64-qemu-virt` -- `ax-plat-riscv64-qemu-virt` -- `ax-plat-loongarch64-qemu-virt` - -这说明它本质上是 `axplat` 平台包的最小消费者,用来证明平台包已经具备最小 bring-up 能力。 - -## 核心功能 -### 2.1 实际演示的能力链 -这个样例实际覆盖的是: - -- 启动入口成功跳到 `#[ax_plat::main]` -- BSP 的 per-CPU 状态已建立 -- 控制台与时间源可用 -- `busy_wait` 可以推进时间 -- 关机路径 `system_off()` 可用 - -### 2.2 为什么只做 `busy_wait` -这里不用调度器、不用文件系统、不用中断,是刻意缩短故障面。只要这份样例都跑不通,就说明问题还在更底层: - -- 平台启动入口 -- 串口 -- 时间源 -- 电源关机路径 - -### 2.3 边界澄清 -这份样例不是: - -- ArceOS 的应用入口 -- `ax-runtime` 的替代品 -- 可直接扩展成完整内核的通用骨架 - -它只是 `axplat` 平台包的最小消费者。 - -## 依赖关系 -```mermaid -graph LR - sample["hello-kernel"] --> axplat["ax-plat"] - sample --> x86["ax-plat-x86-pc"] - sample --> a64["ax-plat-aarch64-qemu-virt"] - sample --> rv["ax-plat-riscv64-qemu-virt"] - sample --> loong["ax-plat-loongarch64-qemu-virt"] -``` - -### 直接依赖 -- `axplat`:对外暴露统一的平台抽象接口。 -- `cfg-if`:按架构切换平台 crate。 -- 各平台包:提供真正的板级实现。 - -### 间接依赖 -- `ax-plat-macros`:支撑 `#[ax_plat::main]`。 -- `ax-cpu`、串口/时钟相关底层组件:由平台包继续下接。 - -### 主要消费者 -- `axplat` 平台包的 bring-up 验证。 -- 新平台接入时的第一条最短运行路径。 -- `irq-kernel`、`smp-kernel` 之前的基础烟雾测试。 - -## 开发指南 -### 接入方式 -```bash -cd components/axplat_crates/examples/hello-kernel -make ARCH= run -``` - -`Makefile` 会根据架构选择 target triple,并在必要时把 ELF 转成 bin 后交给 QEMU。 - -### 注意事项 -1. 保持这个样例足够小,不要把更高层能力堆进来。 -2. 如果新增某个平台支持,要同时更新 `Cargo.toml`、`cfg_if!` 分支和 `Makefile` 运行路径。 -3. `panic_handler` 和正常退出都应走最短控制台/关机链,便于定位问题。 - -### 4.3 什么时候该换别的样例 -- 要测中断:换 `irq-kernel` -- 要测多核:换 `smp-kernel` -- 要测上层 OS bring-up:换 ArceOS 的 `ax-helloworld` - -## 测试 -### 测试覆盖 -这是手工和联调型样例,不是 `test-suit` 自动回归包。README 给出了预期输出,重点观察: - -- `Hello, ArceOS!` -- `cpu_id = ...` -- 每秒一次的 `elapsed` -- 最终 `All done, shutting down!` - -### 5.2 成功标准 -- 能成功进入 `main()` -- 控制台与时间输出正常 -- 5 次忙等后顺利关机 - -### 5.3 风险点 -- 如果时间源没有初始化,`elapsed` 输出会异常。 -- 如果平台关机路径有问题,最后一步会卡住。 - -## 跨项目定位 -### ArceOS -ArceOS 不直接依赖这个样例,但会复用同一批平台包。它对 ArceOS 的价值在于:平台层有问题时,先用这条最小路径定位,再进入 `ax-runtime` 和更高层。 - -### StarryOS -StarryOS 同样不会直接运行它。这个样例只是帮助验证其下方共享的 `axplat` 平台栈是否最起码可启动。 - -### Axvisor -Axvisor 也不直接消费它。不过对平台 bring-up 来说,先跑 `hello-kernel` 往往比直接调 Hypervisor 更容易分辨问题在平台层还是上层逻辑。 diff --git a/docs/docs/components/crates/irq-kernel.md b/docs/docs/components/crates/irq-kernel.md deleted file mode 100644 index 96c47d5e0f..0000000000 --- a/docs/docs/components/crates/irq-kernel.md +++ /dev/null @@ -1,153 +0,0 @@ -# `irq-kernel` - -> 路径:`components/axplat_crates/examples/irq-kernel` -> 类型:平台样例内核 crate -> 分层:组件层 / `axplat` 中断链路示例 -> 版本:`0.1.0` -> 文档依据:`Cargo.toml`、`src/main.rs`、`src/irq.rs`、`Makefile`、`README.md` - -`irq-kernel` 在 `hello-kernel` 的最小 bring-up 基础上,再往前走一步:它注册 trap handler、挂接平台定时器 IRQ、按 one-shot 方式重装下一次中断,并在 5 秒内统计中断触发次数。这条路径可以非常直接地证明“平台中断控制器 + trap 分发 + 定时器重装”是否工作。 - -因此必须明确:**它不是通用 IRQ 框架,也不是驱动层抽象库;它只是 `axplat` 平台中断能力的最小验证入口。** - -## 架构设计 -### 1.1 与 `hello-kernel` 的差异 -`main.rs` 的总体结构与 `hello-kernel` 很像,但多了两步: - -- `init_irq()` -- `test_irq()` - -真正的核心逻辑集中在 `src/irq.rs`。 - -### 1.2 中断处理主线 -`src/irq.rs` 里有三块关键内容: - -1. `#[register_trap_handler(IRQ)] fn irq_handler(vector)` - 把平台收到的 IRQ 交给 `ax_plat::irq::handle(vector)` 分发。 -2. `init_irq()` - 注册定时器处理函数并开中断。 -3. `test_irq()` - 每秒观察一次累计中断次数,并在 5 秒后检查数量下限。 - -可以把它的真实链路概括为: - -```mermaid -flowchart LR - A["定时器硬件触发 IRQ"] --> B["irq_handler"] - B --> C["ax_plat::irq::handle(vector)"] - C --> D["update_timer()"] - D --> E["IRQ_COUNTER += 1"] - E --> F["set_oneshot_timer(deadline)"] -``` - -### 1.3 为什么使用 one-shot timer -`update_timer()` 并不是简单假设“平台会自动周期触发”。它自己维护 `NEXT_DEADLINE`,每次中断后重新设置下一次到期时间。这样可以同时验证: - -- 中断真的被分发到了处理函数 -- 定时器重新装载接口可用 -- 时间推进与中断数量大致匹配 - -## 核心功能 -### 2.1 `init_irq()` -这个函数做了三件事: - -1. 注册平台定时器 IRQ 对应的回调。 -2. 打印 `Timer IRQ handler registered.` 作为观测点。 -3. 调用 `ax-cpu::asm::enable_irqs()` 真正打开中断。 - -也就是说,它验证的不只是 handler 注册,还有 IRQ 使能本身。 - -### 2.2 `test_irq()` -该测试在 5 秒内每秒打印一次: - -- 已过时间 -- 已处理 IRQ 数 - -最后要求: - -- `irq_count >= TICKS_PER_SEC * interval` - -这里的断言不是追求绝对精确,而是要求“至少达到了预期的最低触发次数”,这更适合 QEMU 和多架构平台环境。 - -### 2.3 边界澄清 -它不负责: - -- 多设备 IRQ 共存验证 -- 中断嵌套复杂场景 -- OS 级抢占调度 - -它只聚焦“最小内核 + 定时器 IRQ”这条链。 - -## 依赖关系 -```mermaid -graph LR - sample["irq-kernel"] --> axplat["ax-plat"] - sample --> ax-cpu["ax-cpu"] - sample --> linkme["linkme / trap 注册"] - sample --> x86["ax-plat-x86-pc(irq)"] - sample --> a64["ax-plat-aarch64-qemu-virt(irq)"] - sample --> rv["ax-plat-riscv64-qemu-virt(irq)"] - sample --> loong["ax-plat-loongarch64-qemu-virt(irq)"] -``` - -### 直接依赖 -- `axplat`:平台抽象与 `irq`/`time` 接口。 -- `ax-cpu`:trap handler 注册与开中断辅助。 -- `linkme`:支撑 `register_trap_handler` 这类分布式注册。 -- 各平台包的 `irq` feature:真正接入板级中断控制器与定时器。 - -### 间接依赖 -- `ax_plat::irq::register` -- `ax_plat::irq::handle` -- `ax_plat::time::set_oneshot_timer` -- 平台配置中的 `config::devices::TIMER_IRQ` - -### 主要消费者 -- 平台中断能力 bring-up 的第一条最小路径。 -- `smp-kernel` 之前的单核 IRQ smoke test。 - -## 开发指南 -### 接入方式 -```bash -cd components/axplat_crates/examples/irq-kernel -make ARCH= run -``` - -### 注意事项 -1. 处理函数应尽量短,只做计数和重装定时器。 -2. 若修改 `TICKS_PER_SEC`,要同步检查 README 和期望计数下限。 -3. 不要在这里引入复杂调度逻辑,否则会把“IRQ 问题”与“上层任务问题”混在一起。 - -### 4.3 适合补充的方向 -- 平台特定 IRQ 源验证 -- 更长时间窗口下的统计稳定性 -- 与 SMP 结合的多核中断路径,但那通常应放到 `smp-kernel` - -## 测试 -### 测试覆盖 -这是 README 驱动的运行样例,典型输出会显示: - -- handler 注册成功 -- 每秒累计中断数上升 -- 最终 `Timer IRQ test passed.` - -### 5.2 成功标准 -- IRQ handler 能被触发 -- 中断计数持续增长 -- one-shot timer 能不断被重装 -- 最后计数达到最低阈值并通过断言 - -### 5.3 风险点 -- 如果平台 timer IRQ 号配置错了,计数会停在 0。 -- 如果 trap 分发没接通,`irq_handler` 根本不会执行。 -- 如果 one-shot 重装有 bug,计数通常只增长一次或几次。 - -## 跨项目定位 -### ArceOS -ArceOS 不直接依赖这个样例,但会复用相同的 `axplat` 平台包和 IRQ 底座。因此它对 ArceOS 的意义是“先确认平台 IRQ 最小链路成立,再进入 `ax-runtime` 和 `ax-task` 的更复杂场景”。 - -### StarryOS -StarryOS 也不会直接运行它。这个样例只是帮助确认共享平台包的 trap/IRQ 基础能力仍然健全。 - -### Axvisor -Axvisor 同样不直接消费它,不过对任何依赖平台中断的系统来说,先用这条最小路径验证 timer IRQ,通常比直接调完整系统更高效。 diff --git a/docs/docs/components/crates/smp-kernel.md b/docs/docs/components/crates/smp-kernel.md deleted file mode 100644 index 34fd926c6f..0000000000 --- a/docs/docs/components/crates/smp-kernel.md +++ /dev/null @@ -1,174 +0,0 @@ -# `smp-kernel` - -> 路径:`components/axplat_crates/examples/smp-kernel` -> 类型:平台样例内核 crate -> 分层:组件层 / `axplat` 多核与中断示例 -> 版本:`0.1.0` -> 文档依据:`Cargo.toml`、`src/main.rs`、`src/init.rs`、`src/mp.rs`、`src/irq.rs`、`Makefile`、`README.md` - -`smp-kernel` 是 `axplat` 示例里最完整的一条 bring-up 样例。它不只验证主核启动,还验证次核 boot、`#[ax_plat::secondary_main]` 入口、per-CPU 数据、中断在多核上的工作方式,以及所有 CPU 完成初始化后的同步收敛。 - -因此必须先把边界说透:**它不是可复用 SMP 框架,也不是 ArceOS `ax-runtime` 的替代实现;它只是把 `axplat` 的多核启动链用最小内核样例形式跑通。** - -## 架构设计 -### 模块结构 -与前两个样例不同,这个 crate 已经拆成了三个明确模块: - -- `init.rs`:主核/次核的初始化共性与 `INITED_CPUS` 同步计数 -- `mp.rs`:次核栈、`cpu_boot()` 和 `#[ax_plat::secondary_main]` -- `irq.rs`:多核场景下的定时器中断注册与 per-CPU 下一次到期时间 - -这说明它的关注点已经从“最小启动”扩展到“多核 bring-up 协同”。 - -### 1.2 主核与次核主线 -主核路径大致是: - -```mermaid -flowchart TD - A["#[ax_plat::main] main"] --> B["init_kernel(primary)"] - B --> C["start_secondary_cpus()"] - C --> D["init_irq()"] - D --> E["INITED_CPUS += 1"] - E --> F["等待所有 CPU 完成初始化"] - F --> G["busy_wait 5s"] - G --> H["system_off()"] -``` - -次核路径则是: - -```mermaid -flowchart TD - A["cpu_boot() 唤起次核"] --> B["#[ax_plat::secondary_main]"] - B --> C["init_kernel_secondary()"] - C --> D["INITED_CPUS += 1"] - D --> E["等待全部 CPU 就绪"] - E --> F["enable_irqs()"] - F --> G["自旋等待并处理中断"] -``` - -### 1.3 关键同步点 -这个样例里有两个非常关键的同步设计: - -1. `start_secondary_cpus()` 在逐个启动 AP 后,等待 `INITED_CPUS` 增长,确保主核知道 AP 至少已进入初始化完成点。 -2. 主核和次核都调用 `init_smp_ok()`,等到 `INITED_CPUS == CPU_NUM` 后才进入下一阶段。 - -因此它验证的不是“能 boot 第二个核”这么简单,而是“多核初始化能否有序收敛”。 - -## 核心功能 -### 2.1 `CPU_NUM` 与运行规模 -`CPU_NUM` 不是写死在源码里,而是: - -- 优先取环境变量 `AX_CPU_NUM` -- 否则退回平台配置里的 `MAX_CPU_NUM` - -而 `Makefile` 又会把 `SMP=` 导出为 `AX_CPU_NUM`。这让样例可以直接通过: - -```bash -make ARCH=riscv64 run SMP=4 -``` - -在不同核数下复用同一份代码。 - -### 2.2 `mp.rs` 的启动责任 -`start_secondary_cpus()` 里做了三件真实而关键的事情: - -- 为每个 AP 分配独立 boot stack -- 把该栈顶虚拟地址转成物理地址 -- 调用 `ax_plat::power::cpu_boot(i, stack_top_paddr)` - -这条链是 `axplat` 平台支持多核的核心契约之一。 - -### 2.3 `irq.rs` 的 per-CPU 定时器逻辑 -与 `irq-kernel` 不同,这里用的是: - -- `#[ax_percpu::def_percpu] static NEXT_DEADLINE: u64 = 0;` - -这说明在多核场景下,每个 CPU 都维护自己下一次 one-shot timer 的到期时间。它不是全局共享一个 deadline,而是显式验证 per-CPU 数据和多核 timer IRQ 是否能共存。 - -### 2.4 边界澄清 -这个样例不负责: - -- 提供通用的 SMP runtime -- 管理任务调度 -- 作为上层 OS 多核框架直接复用 - -它只是最小内核级的多核 bring-up 演示。 - -## 依赖关系 -```mermaid -graph LR - sample["smp-kernel"] --> axplat["ax-plat"] - sample --> ax-cpu["ax-cpu"] - sample --> ax-percpu["ax-percpu"] - sample --> memaddr["memory_addr"] - sample --> conststr["const-str"] - sample --> x86["ax-plat-x86-pc(irq,smp)"] - sample --> a64["ax-plat-aarch64-qemu-virt(irq,smp)"] - sample --> rv["ax-plat-riscv64-qemu-virt(irq,smp)"] - sample --> loong["ax-plat-loongarch64-qemu-virt(irq,smp)"] -``` - -### 直接依赖 -- `axplat`:统一平台抽象入口。 -- `ax-cpu`:IRQ/trap 与 CPU 辅助。 -- `ax-percpu`:多核定时器中的 per-CPU 状态。 -- `memory_addr`:次核启动栈地址转换。 -- `const-str`:解析 `AX_CPU_NUM`。 -- 各平台包的 `irq` + `smp` feature:真正提供 AP boot 与多核 IRQ 能力。 - -### 间接依赖 -- `ax_plat::power::cpu_boot` -- `ax_plat::call_secondary_main` -- `ax_plat::init::{init_early_secondary, init_later_secondary}` -- `ax_plat::time::set_oneshot_timer` - -### 主要消费者 -- 平台包 SMP 能力 bring-up。 -- 在接入更高层 `ax-runtime` 前验证多核基础链路。 - -## 开发指南 -### 接入方式 -```bash -cd components/axplat_crates/examples/smp-kernel -make ARCH= run SMP=4 -``` - -不带 `SMP` 时默认为 1 核,此时它仍能验证主核路径,但多核价值会明显降低。 - -### 注意事项 -1. 所有主核初始化改动,都要同时考虑次核对应路径。 -2. 任何与启动栈、物理地址转换相关的改动都属于高风险。 -3. 不要把任务调度或复杂内存管理逻辑堆进这里;那已经超出 `axplat` 样例的边界。 - -### 4.3 适合新增的方向 -- 更多 CPU 数组合测试 -- 平台特定 AP boot 差异验证 -- 多核 IPI 或更复杂 IRQ 但仍保持“最小内核”定位 - -## 测试 -### 测试覆盖 -README 已给出两类典型输出: - -- 单核:验证主核启动与 timer IRQ -- 多核:验证次核初始化消息与多 CPU 的 timer IRQ 输出 - -### 5.2 成功标准 -- 主核打印 `Primary CPU 0 started.` -- 次核在多核场景下打印 `Secondary CPU init OK.` -- 各 CPU 都能处理中断并打印时间推进 -- 主核最终正常关机 - -### 5.3 风险点 -- `cpu_boot()` 不通时,次核永远起不来。 -- `INITED_CPUS` 同步不正确时,主核和次核会卡在等待屏障。 -- per-CPU `NEXT_DEADLINE` 有问题时,多核定时器行为会异常。 - -## 跨项目定位 -### ArceOS -ArceOS 不直接依赖这个样例,但 `ax-runtime` 的 SMP bring-up 与它验证的是同一类底层平台契约。先跑通它,通常能更快判断问题在平台层还是运行时层。 - -### StarryOS -StarryOS 也不会直接运行它。这个样例的价值在于提前验证共享平台包的多核 boot 与中断基础能力。 - -### Axvisor -Axvisor 同样不直接消费它;不过 Hypervisor 对多核启动路径更敏感,因此在平台层改动后,先用 `smp-kernel` 做最小多核 smoke test 往往非常有用。 diff --git a/docs/docs/components/crates/starryos-test.md b/docs/docs/components/crates/starryos-test.md index 28b565a2f6..10549d5008 100644 --- a/docs/docs/components/crates/starryos-test.md +++ b/docs/docs/components/crates/starryos-test.md @@ -105,13 +105,13 @@ cargo xtask starry run --arch riscv64 --package starryos-test graph LR ax-feat["ax-feat"] --> test["starryos-test"] kernel["starry-kernel"] --> test - vf2["axplat-riscv64-visionfive2 (optional)"] --> test + vf2["ax-plat-riscv64-visionfive2 (optional)"] --> test ``` ### 直接依赖 - `ax-feat`:提供底层平台、驱动和运行时装配。 - `starry-kernel`:真正执行系统 bring-up。 -- `axplat-riscv64-visionfive2`:在 `vf2` feature 下可选引入。 +- `ax-plat-riscv64-visionfive2`:在 `vf2` feature 下可选引入。 ### 3.2 关键外部驱动者 - `tg-xtask`:不是 Cargo 依赖,但是真正让这个包进入测试流水线的上层驱动者。 diff --git a/docs/docs/components/crates/starryos.md b/docs/docs/components/crates/starryos.md index afd0317756..e48348a623 100644 --- a/docs/docs/components/crates/starryos.md +++ b/docs/docs/components/crates/starryos.md @@ -26,7 +26,7 @@ - `qemu`:二进制 `[[bin]]` 的必需 feature,同时启用 `ax-feat/defplat`、`ax-feat/bus-pci`、`ax-feat/display`、`ax-feat/fs-ng-times`,以及 `starry-kernel` 的 `input`、`vsock`、`dev-log`。 - `smp`:启用 `ax-feat/smp`,并在 VisionFive2 平台上联动开启 SMP。 -- `vf2`:引入可选依赖 `axplat-riscv64-visionfive2`,并额外打开 `ax-feat/driver-sdmmc`。 +- `vf2`:引入可选依赖 `ax-plat-riscv64-visionfive2`,并额外打开 `ax-feat/driver-sdmmc`。 这意味着 `starryos` 的主要复杂度不在运行时逻辑,而在于“生成哪一类镜像”。 @@ -104,13 +104,13 @@ cargo xtask starry run --arch riscv64 --package starryos graph LR ax-feat["ax-feat"] --> starryos["starryos"] kernel["starry-kernel"] --> starryos - vf2["axplat-riscv64-visionfive2 (optional)"] --> starryos + vf2["ax-plat-riscv64-visionfive2 (optional)"] --> starryos ``` ### 直接依赖 - `ax-feat`:把底层 ArceOS 运行时、驱动和平台 feature 接到镜像入口包上。 - `starry-kernel`:真正的内核实现,`starryos` 只在 `main()` 里调用其入口。 -- `axplat-riscv64-visionfive2`:仅在 `vf2` feature 下引入,用于板级适配。 +- `ax-plat-riscv64-visionfive2`:仅在 `vf2` feature 下引入,用于板级适配。 ### 3.2 关键运行时外部条件 - rootfs / `rootfs-.img`:由 `cargo xtask starry rootfs` 或 `run` 路径自动准备。 diff --git a/docs/docs/components/layers.md b/docs/docs/components/layers.md index 765ed4c00f..6fdc75e10c 100644 --- a/docs/docs/components/layers.md +++ b/docs/docs/components/layers.md @@ -42,13 +42,13 @@ flowchart TB L7["层级 7
堆叠层(依赖更底层 crate)
`ax-ipi`、`ax-mm`、`ax-task`、`axvm`"] classDef ls7 fill:#f8bbd0,stroke:#c2185b,stroke-width:2px,color:#000 class L7 ls7 - L6["层级 6
堆叠层(依赖更底层 crate)
`arm_vcpu`、`ax-hal`、`axdevice`、`hello-kernel`、`irq-kernel`、`riscv_vcpu`、`smp-kernel`、`x86_vcpu`"] + L6["层级 6
堆叠层(依赖更底层 crate)
`arm_vcpu`、`ax-hal`、`axdevice`、`riscv_vcpu`、`x86_vcpu`"] classDef ls6 fill:#b2ebf2,stroke:#00838f,stroke-width:2px,color:#000 class L6 ls6 L5["层级 5
堆叠层(依赖更底层 crate)
`arm_vgic`、`ax-plat-aarch64-bsta1000b`、`ax-plat-aarch64-phytium-pi`、`ax-plat-aarch64-qemu-virt`、`ax-plat-aarch64-raspi`、`ax-plat-riscv64-qemu-virt`、`ax-plat-riscv64-qemu-virt`、`axvcpu`、`riscv_vplic`、`x86_vlapic`"] classDef ls5 fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000 class L5 ls5 - L4["层级 4
堆叠层(依赖更底层 crate)
`ax-plat-aarch64-peripherals`、`ax-plat-loongarch64-qemu-virt`、`ax-plat-x86-pc`、`axdevice_base`、`axplat-dyn`、`axplat-x86-qemu-q35`、`axvisor_api`、`starry-signal`"] + L4["层级 4
堆叠层(依赖更底层 crate)
`ax-plat-aarch64-peripherals`、`ax-plat-loongarch64-qemu-virt`、`ax-plat-x86-pc`、`axdevice_base`、`ax-plat-dyn`、`ax-plat-x86-qemu-q35`、`axvisor_api`、`starry-signal`"] classDef ls4 fill:#e1bee7,stroke:#6a1b9a,stroke-width:2px,color:#000 class L4 ls4 L3["层级 3
堆叠层(依赖更底层 crate)
`ax-alloc`、`ax-cpu`、`ax-log`、`ax-plat`、`axaddrspace`、`scope-local`、`starry-process`、`test-simple`、`test-weak`、`test-weak-partial`、`tg-xtask`"] @@ -122,7 +122,7 @@ flowchart TB | 1 | 堆叠层 | 组件层 | `ax-kernel-guard` | `0.3.3` | `components/kernel_guard` | | 1 | 堆叠层 | 组件层 | `ax-memory-set` | `0.6.1` | `components/axmm_crates/memory_set` | | 1 | 堆叠层 | 组件层 | `ax-page-table-entry` | `0.8.1` | `components/page_table_multiarch/page_table_entry` | -| 1 | 堆叠层 | 组件层 | `ax-plat-macros` | `0.3.0` | `components/axplat_crates/axplat-macros` | +| 1 | 堆叠层 | 组件层 | `ax-plat-macros` | `0.3.0` | `platforms/ax-plat-macros` | | 1 | 堆叠层 | 组件层 | `ax-sched` | `0.5.1` | `components/axsched` | | 1 | 堆叠层 | 组件层 | `axfs-ng-vfs` | `0.3.1` | `components/axfs-ng-vfs` | | 1 | 堆叠层 | 组件层 | `axhvc` | `0.4.0` | `components/axhvc` | @@ -147,37 +147,34 @@ flowchart TB | 3 | 堆叠层 | ArceOS 层 | `ax-log` | `0.5.0` | `os/arceos/modules/axlog` | | 3 | 堆叠层 | 工具层 | `tg-xtask` | `0.5.0` | `xtask` | | 3 | 堆叠层 | 组件层 | `ax-cpu` | `0.5.0` | `components/axcpu` | -| 3 | 堆叠层 | 组件层 | `ax-plat` | `0.5.1` | `components/axplat_crates/axplat` | +| 3 | 堆叠层 | 组件层 | `ax-plat` | `0.5.1` | `platforms/ax-plat` | | 3 | 堆叠层 | 组件层 | `axaddrspace` | `0.5.0` | `components/axaddrspace` | | 3 | 堆叠层 | 组件层 | `scope-local` | `0.3.2` | `components/scope-local` | | 3 | 堆叠层 | 组件层 | `starry-process` | `0.4.0` | `components/starry-process` | | 3 | 堆叠层 | 组件层 | `test-simple` | `0.3.0` | `components/crate_interface/test_crates/test-simple` | | 3 | 堆叠层 | 组件层 | `test-weak` | `0.3.0` | `components/crate_interface/test_crates/test-weak` | | 3 | 堆叠层 | 组件层 | `test-weak-partial` | `0.3.0` | `components/crate_interface/test_crates/test-weak-partial` | -| 4 | 堆叠层 | 平台层 | `axplat-dyn` | `0.5.0` | `platform/axplat-dyn` | -| 4 | 堆叠层 | 平台层 | `axplat-x86-qemu-q35` | `0.4.0` | `platform/x86-qemu-q35` | -| 4 | 堆叠层 | 组件层 | `ax-plat-aarch64-peripherals` | `0.5.1` | `components/axplat_crates/platforms/axplat-aarch64-peripherals` | -| 4 | 堆叠层 | 组件层 | `ax-plat-loongarch64-qemu-virt` | `0.5.1` | `components/axplat_crates/platforms/axplat-loongarch64-qemu-virt` | -| 4 | 堆叠层 | 组件层 | `ax-plat-x86-pc` | `0.5.1` | `components/axplat_crates/platforms/axplat-x86-pc` | +| 4 | 堆叠层 | 平台层 | `ax-plat-dyn` | `0.5.0` | `platforms/ax-plat-dyn` | +| 4 | 堆叠层 | 平台层 | `ax-plat-x86-qemu-q35` | `0.4.0` | `platforms/ax-plat-x86-qemu-q35` | +| 4 | 堆叠层 | 组件层 | `ax-plat-aarch64-peripherals` | `0.5.1` | `platforms/ax-plat-aarch64-peripherals` | +| 4 | 堆叠层 | 组件层 | `ax-plat-loongarch64-qemu-virt` | `0.5.1` | `platforms/ax-plat-loongarch64-qemu-virt` | +| 4 | 堆叠层 | 组件层 | `ax-plat-x86-pc` | `0.5.1` | `platforms/ax-plat-x86-pc` | | 4 | 堆叠层 | 组件层 | `axdevice_base` | `0.4.2` | `components/axdevice_base` | | 4 | 堆叠层 | 组件层 | `axvisor_api` | `0.5.0` | `components/axvisor_api` | | 4 | 堆叠层 | 组件层 | `starry-signal` | `0.5.0` | `components/starry-signal` | | 5 | 堆叠层 | 组件层 | `arm_vgic` | `0.4.2` | `components/arm_vgic` | -| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-bsta1000b` | `0.5.1` | `components/axplat_crates/platforms/axplat-aarch64-bsta1000b` | -| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-phytium-pi` | `0.5.1` | `components/axplat_crates/platforms/axplat-aarch64-phytium-pi` | -| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-qemu-virt` | `0.5.1` | `components/axplat_crates/platforms/axplat-aarch64-qemu-virt` | -| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-raspi` | `0.5.1` | `components/axplat_crates/platforms/axplat-aarch64-raspi` | -| 5 | 堆叠层 | 组件层 | `ax-plat-riscv64-qemu-virt` | `0.5.1` | `components/axplat_crates/platforms/axplat-riscv64-qemu-virt` | +| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-bsta1000b` | `0.5.1` | `platforms/ax-plat-aarch64-bsta1000b` | +| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-phytium-pi` | `0.5.1` | `platforms/ax-plat-aarch64-phytium-pi` | +| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-qemu-virt` | `0.5.1` | `platforms/ax-plat-aarch64-qemu-virt` | +| 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-raspi` | `0.5.1` | `platforms/ax-plat-aarch64-raspi` | +| 5 | 堆叠层 | 组件层 | `ax-plat-riscv64-qemu-virt` | `0.5.1` | `platforms/ax-plat-riscv64-qemu-virt` | | 5 | 堆叠层 | 组件层 | `axvcpu` | `0.5.0` | `components/axvcpu` | | 5 | 堆叠层 | 组件层 | `riscv_vplic` | `0.4.2` | `components/riscv_vplic` | | 5 | 堆叠层 | 组件层 | `x86_vlapic` | `0.4.2` | `components/x86_vlapic` | | 6 | 堆叠层 | ArceOS 层 | `ax-hal` | `0.5.0` | `os/arceos/modules/axhal` | | 6 | 堆叠层 | 组件层 | `arm_vcpu` | `0.5.0` | `components/arm_vcpu` | | 6 | 堆叠层 | 组件层 | `axdevice` | `0.4.2` | `components/axdevice` | -| 6 | 堆叠层 | 组件层 | `hello-kernel` | `0.3.0` | `components/axplat_crates/examples/hello-kernel` | -| 6 | 堆叠层 | 组件层 | `irq-kernel` | `0.3.0` | `components/axplat_crates/examples/irq-kernel` | | 6 | 堆叠层 | 组件层 | `riscv_vcpu` | `0.5.0` | `components/riscv_vcpu` | -| 6 | 堆叠层 | 组件层 | `smp-kernel` | `0.3.0` | `components/axplat_crates/examples/smp-kernel` | | 6 | 堆叠层 | 组件层 | `x86_vcpu` | `0.5.0` | `components/x86_vcpu` | | 7 | 堆叠层 | ArceOS 层 | `ax-ipi` | `0.5.0` | `os/arceos/modules/axipi` | | 7 | 堆叠层 | ArceOS 层 | `ax-mm` | `0.5.0` | `os/arceos/modules/axmm` | @@ -232,9 +229,9 @@ flowchart TB | 1 | 19 | `ax-allocator` `ax-config-macros` `ax-ctor-bare` `ax-fs-vfs` `ax-io` `ax-kernel-guard` `ax-memory-set` `ax-page-table-entry` `ax-plat-macros` `ax-sched` `axfs-ng-vfs` `axhvc` `axklib` `axvmconfig` `define-simple-traits` `define-weak-traits` `fxmac_rs` `smoltcp-fuzz` `starry-vm` | | 2 | 10 | `ax-config` `ax-fs-devfs` `ax-fs-ramfs` `ax-kspin` `ax-page-table-multiarch` `ax-percpu` `axbuild` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` | | 3 | 11 | `ax-alloc` `ax-cpu` `ax-log` `ax-plat` `axaddrspace` `scope-local` `starry-process` `test-simple` `test-weak` `test-weak-partial` `tg-xtask` | -| 4 | 8 | `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-x86-pc` `axdevice_base` `axplat-dyn` `axplat-x86-qemu-q35` `axvisor_api` `starry-signal` | +| 4 | 8 | `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-x86-pc` `axdevice_base` `ax-plat-dyn` `ax-plat-x86-qemu-q35` `axvisor_api` `starry-signal` | | 5 | 10 | `arm_vgic` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-riscv64-qemu-virt` `ax-plat-riscv64-qemu-virt` `axvcpu` `riscv_vplic` `x86_vlapic` | -| 6 | 8 | `arm_vcpu` `ax-hal` `axdevice` `hello-kernel` `irq-kernel` `riscv_vcpu` `smp-kernel` `x86_vcpu` | +| 6 | 5 | `arm_vcpu` `ax-hal` `axdevice` `riscv_vcpu` `x86_vcpu` | | 7 | 4 | `ax-ipi` `ax-mm` `ax-task` `axvm` | | 8 | 2 | `ax-dma` `ax-sync` | | 9 | 1 | `ax-driver` | @@ -272,15 +269,15 @@ flowchart TB | `arceos-yield` | 16 | 系统级测试与回归入口 | `ax-std` | — | | `arm_vcpu` | 6 | Aarch64 VCPU implementation for Arceos Hypervisor | `ax-errno` `ax-percpu` `axaddrspace` `axdevice_base` `axvcpu` `axvisor_api` | `axvm` | | `arm_vgic` | 5 | ARM Virtual Generic Interrupt Controller (VGIC) i… | `aarch64_sysreg` `ax-errno` `ax-memory-addr` `axaddrspace` `axdevice_base` `axvisor_api` | `axdevice` `axvm` | -| `ax-alloc` | 3 | ArceOS global memory allocator | `ax-allocator` `ax-errno` `ax-kspin` `ax-memory-addr` `ax-percpu` `axbacktrace` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs-ng` `ax-hal` `ax-mm` `ax-posix-api` `ax-runtime` `axplat-dyn` `starry-kernel` | +| `ax-alloc` | 3 | ArceOS global memory allocator | `ax-allocator` `ax-errno` `ax-kspin` `ax-memory-addr` `ax-percpu` `axbacktrace` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs-ng` `ax-hal` `ax-mm` `ax-posix-api` `ax-runtime` `ax-plat-dyn` `starry-kernel` | | `ax-allocator` | 1 | Various allocator algorithms in a unified interfa… | `ax-errno` `bitmap-allocator` | `ax-alloc` `ax-dma` | | `ax-api` | 14 | Public APIs and types for ArceOS modules | `ax-alloc` `ax-config` `ax-display` `ax-dma` `ax-driver` `ax-errno` `ax-feat` `ax-fs` `ax-hal` `ax-io` `ax-ipi` `ax-log` `ax-mm` `ax-net` `ax-runtime` `ax-sync` `ax-task` | `ax-std` | | `ax-arm-pl031` | 0 | System Real Time Clock (RTC) Drivers for aarch64 … | — | `ax-plat-aarch64-peripherals` | | `ax-cap-access` | 0 | Provide basic capability-based access control to … | — | `ax-fs` | | `ax-config` | 2 | Platform-specific constants and parameters for Ar… | `ax-config-macros` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-hal` `ax-ipi` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | | `ax-config-gen` | 0 | A TOML-based configuration generation tool for Ar… | — | `ax-config-macros` | -| `ax-config-macros` | 1 | Procedural macros for converting TOML format conf… | `ax-config-gen` | `ax-config` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `axplat-dyn` `axplat-x86-qemu-q35` `irq-kernel` `smp-kernel` | -| `ax-cpu` | 3 | Privileged instruction and structure abstractions… | `ax-lazyinit` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `axbacktrace` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `axplat-dyn` `axplat-x86-qemu-q35` `irq-kernel` `smp-kernel` `starry-signal` | +| `ax-config-macros` | 1 | Procedural macros for converting TOML format conf… | `ax-config-gen` | `ax-config` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-plat-dyn` `ax-plat-x86-qemu-q35` | +| `ax-cpu` | 3 | Privileged instruction and structure abstractions… | `ax-lazyinit` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `axbacktrace` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-plat-dyn` `ax-plat-x86-qemu-q35` `starry-signal` | | `ax-cpumask` | 0 | CPU mask library in Rust | — | `ax-task` `axvisor` `axvisor_api` `axvm` | | `ax-crate-interface` | 0 | Provides a way to define an interface (trait) in … | — | `arceos-fs-shell` `ax-driver` `ax-kernel-guard` `ax-log` `ax-plat` `ax-plat-macros` `ax-plat-riscv64-qemu-virt` `ax-runtime` `ax-task` `axvisor` `axvisor_api` `define-simple-traits` `define-weak-traits` `fxmac_rs` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` `riscv_vcpu` `test-simple` `test-weak` `test-weak-partial` `x86_vcpu` | | `ax-crate-interface-lite` | 0 | Provides a way to define an interface (trait) in … | — | — | @@ -289,49 +286,49 @@ flowchart TB | `ax-display` | 10 | ArceOS graphics module | `ax-driver` `ax-lazyinit` `ax-sync` | `ax-api` `ax-feat` `ax-runtime` `starry-kernel` | | `ax-dma` | 8 | ArceOS global DMA allocator | `ax-alloc` `ax-allocator` `ax-config` `ax-hal` `ax-kspin` `ax-memory-addr` `ax-mm` | `ax-api` `ax-driver` | | `ax-driver` | 9 | ArceOS device drivers | `anyhow` `ax-alloc` `ax-crate-interface` `ax-dma` `ax-errno` `ax-kernel-guard` `ax-kspin` `axklib` `dma-api` `mmio-api` `rdrive` `rd-block` `rd-net` `rdif-*` `virtio-drivers` | `ax-api` `ax-display` `ax-feat` `ax-fs` `ax-fs-ng` `ax-input` `ax-net` `ax-net-ng` `ax-runtime` `starry-kernel` | -| `ax-errno` | 0 | Generic error code representation. | — | `arm_vcpu` `arm_vgic` `ax-alloc` `ax-allocator` `ax-api` `ax-driver` `ax-fs` `ax-fs-ng` `ax-fs-vfs` `ax-io` `ax-libc` `ax-memory-set` `ax-mm` `ax-net` `ax-net-ng` `ax-page-table-multiarch` `ax-posix-api` `ax-std` `ax-task` `axaddrspace` `axdevice` `axdevice_base` `axfs-ng-vfs` `axhvc` `axklib` `axplat-dyn` `axvcpu` `axvisor` `axvm` `axvmconfig` `riscv_vcpu` `riscv_vplic` `starry-kernel` `starry-vm` `x86_vcpu` `x86_vlapic` | +| `ax-errno` | 0 | Generic error code representation. | — | `arm_vcpu` `arm_vgic` `ax-alloc` `ax-allocator` `ax-api` `ax-driver` `ax-fs` `ax-fs-ng` `ax-fs-vfs` `ax-io` `ax-libc` `ax-memory-set` `ax-mm` `ax-net` `ax-net-ng` `ax-page-table-multiarch` `ax-posix-api` `ax-std` `ax-task` `axaddrspace` `axdevice` `axdevice_base` `axfs-ng-vfs` `axhvc` `axklib` `ax-plat-dyn` `axvcpu` `axvisor` `axvm` `axvmconfig` `riscv_vcpu` `riscv_vplic` `starry-kernel` `starry-vm` `x86_vcpu` `x86_vlapic` | | `ax-feat` | 13 | Top-level feature selection for ArceOS | `ax-alloc` `ax-config` `ax-display` `ax-driver` `ax-fs` `ax-fs-ng` `ax-hal` `ax-input` `ax-ipi` `ax-kspin` `ax-log` `ax-net` `ax-runtime` `ax-sync` `ax-task` `axbacktrace` | `ax-api` `ax-libc` `ax-posix-api` `ax-std` `starry-kernel` `starryos` `starryos-test` | | `ax-fs` | 10 | ArceOS filesystem module | `ax-cap-access` `ax-driver` `ax-errno` `ax-fs-devfs` `ax-fs-ramfs` `ax-fs-vfs` `ax-hal` `ax-io` `ax-lazyinit` `rsext4` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` | | `ax-fs-devfs` | 2 | Device filesystem used by ArceOS | `ax-fs-vfs` | `ax-fs` | | `ax-fs-ng` | 10 | ArceOS filesystem module | `ax-alloc` `ax-driver` `ax-errno` `ax-hal` `ax-io` `ax-kspin` `ax-sync` `axfs-ng-vfs` `axpoll` `scope-local` | `ax-feat` `ax-net-ng` `ax-runtime` `starry-kernel` | | `ax-fs-ramfs` | 2 | RAM filesystem used by ArceOS | `ax-fs-vfs` | `arceos-fs-shell` `ax-fs` | | `ax-fs-vfs` | 1 | Virtual filesystem interfaces used by ArceOS | `ax-errno` | `arceos-fs-shell` `ax-fs` `ax-fs-devfs` `ax-fs-ramfs` | -| `ax-hal` | 6 | ArceOS hardware abstraction layer, provides unifi… | `ax-alloc` `ax-config` `ax-cpu` `ax-kernel-guard` `ax-memory-addr` `ax-page-table-multiarch` `ax-percpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `axplat-dyn` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs` `ax-fs-ng` `ax-ipi` `ax-mm` `ax-net` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | +| `ax-hal` | 6 | ArceOS hardware abstraction layer, provides unifi… | `ax-alloc` `ax-config` `ax-cpu` `ax-kernel-guard` `ax-memory-addr` `ax-page-table-multiarch` `ax-percpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-plat-dyn` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs` `ax-fs-ng` `ax-ipi` `ax-mm` `ax-net` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | | `ax-handler-table` | 0 | A lock-free table of event handlers | — | `ax-plat` | | `ax-helloworld` | 16 | ArceOS 示例程序 | `ax-std` | — | | `ax-helloworld-myplat` | 16 | ArceOS 示例程序 | `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-std` | — | | `ax-httpclient` | 16 | ArceOS 示例程序 | `ax-std` | — | | `ax-httpserver` | 16 | Simple HTTP server. Benchmark with Apache HTTP se… | `ax-std` | — | | `ax-input` | 10 | Input device management for ArceOS | `ax-driver` `ax-lazyinit` `ax-sync` | `ax-feat` `ax-runtime` `starry-kernel` | -| `ax-int-ratio` | 0 | The type of ratios represented by two integers. | — | `ax-plat-aarch64-peripherals` `ax-plat-x86-pc` `axplat-x86-qemu-q35` | +| `ax-int-ratio` | 0 | The type of ratios represented by two integers. | — | `ax-plat-aarch64-peripherals` `ax-plat-x86-pc` `ax-plat-x86-qemu-q35` | | `ax-io` | 1 | `std::io` for `no_std` environment | `ax-errno` | `ax-api` `ax-fs` `ax-fs-ng` `ax-libc` `ax-net` `ax-net-ng` `ax-posix-api` `ax-std` `starry-kernel` | | `ax-ipi` | 7 | ArceOS IPI management module | `ax-config` `ax-hal` `ax-kspin` `ax-lazyinit` `ax-percpu` | `ax-api` `ax-feat` `ax-runtime` | | `ax-kernel-guard` | 1 | RAII wrappers to create a critical section with l… | `ax-crate-interface` | `ax-hal` `ax-kspin` `ax-percpu` `ax-task` `axvisor` `starry-kernel` | -| `ax-kspin` | 2 | Spinlocks used for kernel space that can disable … | `ax-kernel-guard` | `ax-alloc` `ax-dma` `ax-feat` `ax-fs-ng` `ax-ipi` `ax-log` `ax-mm` `ax-plat` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-std` `ax-sync` `ax-task` `axplat-x86-qemu-q35` `axvisor` `starry-kernel` `starry-process` `starry-signal` | -| `ax-lazyinit` | 0 | Initialize a static value lazily. | — | `ax-cpu` `ax-display` `ax-fs` `ax-input` `ax-ipi` `ax-mm` `ax-net` `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-std` `ax-task` `axaddrspace` `axplat-x86-qemu-q35` `axvisor` `starry-process` | +| `ax-kspin` | 2 | Spinlocks used for kernel space that can disable … | `ax-kernel-guard` | `ax-alloc` `ax-dma` `ax-feat` `ax-fs-ng` `ax-ipi` `ax-log` `ax-mm` `ax-plat` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-std` `ax-sync` `ax-task` `ax-plat-x86-qemu-q35` `axvisor` `starry-kernel` `starry-process` `starry-signal` | +| `ax-lazyinit` | 0 | Initialize a static value lazily. | — | `ax-cpu` `ax-display` `ax-fs` `ax-input` `ax-ipi` `ax-mm` `ax-net` `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-std` `ax-task` `axaddrspace` `ax-plat-x86-qemu-q35` `axvisor` `starry-process` | | `ax-libc` | 15 | ArceOS user program library for C apps | `ax-errno` `ax-feat` `ax-io` `ax-posix-api` | — | | `ax-linked-list-r4l` | 0 | Linked lists that supports arbitrary removal in c… | — | `ax-sched` | | `ax-log` | 3 | Macros for multi-level formatted logging used by … | `ax-crate-interface` `ax-kspin` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` `starry-kernel` | -| `ax-memory-addr` | 0 | Wrappers and helper functions for physical and vi… | — | `arm_vgic` `ax-alloc` `ax-cpu` `ax-dma` `ax-hal` `ax-memory-set` `ax-mm` `ax-page-table-entry` `ax-page-table-multiarch` `ax-plat` `ax-task` `axaddrspace` `axdevice` `axklib` `axplat-dyn` `axvcpu` `axvisor` `axvisor_api` `axvm` `riscv_vcpu` `smp-kernel` `starry-kernel` `x86_vcpu` `x86_vlapic` | +| `ax-memory-addr` | 0 | Wrappers and helper functions for physical and vi… | — | `arm_vgic` `ax-alloc` `ax-cpu` `ax-dma` `ax-hal` `ax-memory-set` `ax-mm` `ax-page-table-entry` `ax-page-table-multiarch` `ax-plat` `ax-task` `axaddrspace` `axdevice` `axklib` `ax-plat-dyn` `axvcpu` `axvisor` `axvisor_api` `axvm` `riscv_vcpu` `starry-kernel` `x86_vcpu` `x86_vlapic` | | `ax-memory-set` | 1 | Data structures and operations for managing memor… | `ax-errno` `ax-memory-addr` | `ax-mm` `axaddrspace` `starry-kernel` | | `ax-mm` | 7 | ArceOS virtual memory management module | `ax-alloc` `ax-errno` `ax-hal` `ax-kspin` `ax-lazyinit` `ax-memory-addr` `ax-memory-set` `ax-page-table-multiarch` | `ax-api` `ax-dma` `ax-runtime` `starry-kernel` | | `ax-net` | 10 | ArceOS network module | `ax-driver` `ax-errno` `ax-hal` `ax-io` `ax-lazyinit` `ax-sync` `ax-task` `smoltcp` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` | | `ax-net-ng` | 11 | ArceOS network module | `ax-config` `ax-driver` `ax-errno` `ax-fs-ng` `ax-hal` `ax-io` `ax-sync` `ax-task` `axfs-ng-vfs` `axpoll` `smoltcp` | `ax-runtime` `starry-kernel` | | `ax-page-table-entry` | 1 | Page table entry definition for various hardware … | `ax-memory-addr` | `ax-cpu` `ax-page-table-multiarch` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `axaddrspace` `axvisor` `axvm` `riscv_vcpu` `x86_vcpu` | | `ax-page-table-multiarch` | 2 | Generic page table structures for various hardwar… | `ax-errno` `ax-memory-addr` `ax-page-table-entry` | `ax-cpu` `ax-hal` `ax-mm` `axaddrspace` `axvisor` `axvm` `starry-kernel` | -| `ax-percpu` | 2 | Define and access per-CPU data structures | `ax-kernel-guard` `ax-percpu-macros` | `arm_vcpu` `ax-alloc` `ax-cpu` `ax-hal` `ax-ipi` `ax-plat` `ax-plat-x86-pc` `ax-runtime` `ax-task` `axplat-dyn` `axplat-x86-qemu-q35` `axvcpu` `axvisor` `axvm` `scope-local` `smp-kernel` `starry-kernel` | +| `ax-percpu` | 2 | Define and access per-CPU data structures | `ax-kernel-guard` `ax-percpu-macros` | `arm_vcpu` `ax-alloc` `ax-cpu` `ax-hal` `ax-ipi` `ax-plat` `ax-plat-x86-pc` `ax-runtime` `ax-task` `ax-plat-dyn` `ax-plat-x86-qemu-q35` `axvcpu` `axvisor` `axvm` `scope-local` `starry-kernel` | | `ax-percpu-macros` | 0 | Macros to define and access a per-CPU data struct… | — | `ax-percpu` | -| `ax-plat` | 3 | This crate provides a unified abstraction layer f… | `ax-crate-interface` `ax-handler-table` `ax-kspin` `ax-memory-addr` `ax-percpu` `ax-plat-macros` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-runtime` `axplat-dyn` `axplat-x86-qemu-q35` `hello-kernel` `irq-kernel` `smp-kernel` | +| `ax-plat` | 3 | This crate provides a unified abstraction layer f… | `ax-crate-interface` `ax-handler-table` `ax-kspin` `ax-memory-addr` `ax-percpu` `ax-plat-macros` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-runtime` `ax-plat-dyn` `ax-plat-x86-qemu-q35` | | `ax-plat-aarch64-bsta1000b` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-kspin` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | | `ax-plat-aarch64-peripherals` | 4 | ARM64 common peripheral drivers with `axplat` com… | `ax-arm-pl031` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-plat` `some-serial` | `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` | | `ax-plat-aarch64-phytium-pi` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | -| `ax-plat-aarch64-qemu-virt` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-hal` `ax-helloworld-myplat` `hello-kernel` `irq-kernel` `smp-kernel` | +| `ax-plat-aarch64-qemu-virt` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-hal` `ax-helloworld-myplat` | | `ax-plat-aarch64-raspi` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | -| `ax-plat-loongarch64-qemu-virt` | 4 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-kspin` `ax-lazyinit` `ax-page-table-entry` `ax-plat` | `ax-hal` `ax-helloworld-myplat` `hello-kernel` `irq-kernel` `smp-kernel` | +| `ax-plat-loongarch64-qemu-virt` | 4 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-kspin` `ax-lazyinit` `ax-page-table-entry` `ax-plat` | `ax-hal` `ax-helloworld-myplat` | | `ax-plat-macros` | 1 | Procedural macros for the `axplat` crate | `ax-crate-interface` | `ax-plat` | -| `ax-plat-riscv64-qemu-virt` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-crate-interface` `ax-kspin` `ax-lazyinit` `ax-plat` `ax-riscv-plic` `axvisor_api` | `ax-hal` `ax-helloworld-myplat` `axvisor` `hello-kernel` `irq-kernel` `smp-kernel` | -| `ax-plat-riscv64-qemu-virt` | 5 | Axvisor Hypervisor 运行时 | `ax-config-macros` `ax-cpu` `ax-crate-interface` `ax-kspin` `ax-lazyinit` `ax-plat` `ax-riscv-plic` `axvisor_api` | `ax-hal` `ax-helloworld-myplat` `axvisor` `hello-kernel` `irq-kernel` `smp-kernel` | -| `ax-plat-x86-pc` | 4 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-percpu` `ax-plat` | `ax-hal` `ax-helloworld-myplat` `hello-kernel` `irq-kernel` `smp-kernel` | +| `ax-plat-riscv64-qemu-virt` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-crate-interface` `ax-kspin` `ax-lazyinit` `ax-plat` `ax-riscv-plic` `axvisor_api` | `ax-hal` `ax-helloworld-myplat` `axvisor` | +| `ax-plat-riscv64-qemu-virt` | 5 | Axvisor Hypervisor 运行时 | `ax-config-macros` `ax-cpu` `ax-crate-interface` `ax-kspin` `ax-lazyinit` `ax-plat` `ax-riscv-plic` `axvisor_api` | `ax-hal` `ax-helloworld-myplat` `axvisor` | +| `ax-plat-x86-pc` | 4 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-percpu` `ax-plat` | `ax-hal` `ax-helloworld-myplat` | | `ax-posix-api` | 14 | POSIX-compatible APIs for ArceOS modules | `ax-alloc` `ax-config` `ax-errno` `ax-feat` `ax-fs` `ax-hal` `ax-io` `ax-log` `ax-net` `ax-runtime` `ax-sync` `ax-task` `scope-local` | `ax-libc` | | `ax-riscv-plic` | 0 | RISC-V platform-level interrupt controller (PLIC)… | — | `ax-plat-riscv64-qemu-virt` | | `ax-runtime` | 12 | Runtime library of ArceOS | `ax-alloc` `ax-config` `ax-crate-interface` `ax-ctor-bare` `ax-display` `ax-driver` `ax-fs` `ax-fs-ng` `ax-hal` `ax-input` `ax-ipi` `ax-log` `ax-mm` `ax-net` `ax-net-ng` `ax-percpu` `ax-plat` `ax-task` `axbacktrace` `axklib` | `ax-api` `ax-feat` `ax-posix-api` `starry-kernel` | @@ -348,12 +345,12 @@ flowchart TB | `axdevice_base` | 4 | Basic traits and structures for emulated devices … | `ax-errno` `axaddrspace` `axvmconfig` | `arm_vcpu` `arm_vgic` `axdevice` `axvisor` `axvm` `riscv_vplic` `x86_vcpu` `x86_vlapic` | | `axfs-ng-vfs` | 1 | Virtual filesystem layer for ArceOS | `ax-errno` `axpoll` | `ax-fs-ng` `ax-net-ng` `starry-kernel` | | `axhvc` | 1 | AxVisor HyperCall definitions for guest-hyperviso… | `ax-errno` | `axvisor` | -| `axklib` | 1 | Small kernel-helper abstractions used across the … | `ax-errno` `ax-memory-addr` | `ax-runtime` `axplat-dyn` `axvisor` | -| `axplat-dyn` | 4 | A dynamic platform module for ArceOS, providing r… | `ax-cpu` `ax-driver` `ax-errno` `ax-memory-addr` `ax-percpu` `ax-plat` `axklib` `rdrive` `somehal` | `ax-driver` `ax-hal` | -| `axplat-x86-qemu-q35` | 4 | Hardware platform implementation for x86_64 QEMU … | `ax-config-macros` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-percpu` `ax-plat` | `axvisor` | +| `axklib` | 1 | Small kernel-helper abstractions used across the … | `ax-errno` `ax-memory-addr` | `ax-runtime` `ax-plat-dyn` `axvisor` | +| `ax-plat-dyn` | 4 | A dynamic platform module for ArceOS, providing r… | `ax-cpu` `ax-driver` `ax-errno` `ax-memory-addr` `ax-percpu` `ax-plat` `axklib` `rdrive` `somehal` | `ax-driver` `ax-hal` | +| `ax-plat-x86-qemu-q35` | 4 | Hardware platform implementation for x86_64 QEMU … | `ax-config-macros` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-percpu` `ax-plat` | `axvisor` | | `axpoll` | 0 | A library for polling I/O events and waking up ta… | — | `ax-fs-ng` `ax-net-ng` `ax-task` `axfs-ng-vfs` `starry-kernel` | | `axvcpu` | 5 | Virtual CPU abstraction for ArceOS hypervisor | `ax-errno` `ax-memory-addr` `ax-percpu` `axaddrspace` `axvisor_api` | `arm_vcpu` `axvisor` `axvm` `riscv_vcpu` `x86_vcpu` | -| `axvisor` | 16 | A lightweight type-1 hypervisor based on ArceOS | `ax-config` `ax-cpumask` `ax-crate-interface` `ax-errno` `ax-hal` `ax-kernel-guard` `ax-kspin` `ax-lazyinit` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `ax-plat-riscv64-qemu-virt` `ax-std` `ax-timer-list` `axaddrspace` `axbuild` `axdevice` `axdevice_base` `axhvc` `axklib` `axplat-x86-qemu-q35` `axvcpu` `axvisor_api` `axvm` `riscv_vcpu` `riscv_vplic` | — | +| `axvisor` | 16 | A lightweight type-1 hypervisor based on ArceOS | `ax-config` `ax-cpumask` `ax-crate-interface` `ax-errno` `ax-hal` `ax-kernel-guard` `ax-kspin` `ax-lazyinit` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `ax-plat-riscv64-qemu-virt` `ax-std` `ax-timer-list` `axaddrspace` `axbuild` `axdevice` `axdevice_base` `axhvc` `axklib` `ax-plat-x86-qemu-q35` `axvcpu` `axvisor_api` `axvm` `riscv_vcpu` `riscv_vplic` | — | | `axvisor_api` | 4 | Basic API for components of the Hypervisor on Arc… | `ax-cpumask` `ax-crate-interface` `ax-memory-addr` `axaddrspace` `axvisor_api_proc` | `arm_vcpu` `arm_vgic` `ax-plat-riscv64-qemu-virt` `axvcpu` `axvisor` `axvm` `riscv_vcpu` `riscv_vplic` `x86_vcpu` `x86_vlapic` | | `axvisor_api_proc` | 0 | Procedural macros for the `axvisor_api` crate | — | `axvisor_api` | | `axvm` | 7 | Virtual Machine resource management crate for Arc… | `arm_vcpu` `arm_vgic` `ax-cpumask` `ax-errno` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `axaddrspace` `axdevice` `axdevice_base` `axvcpu` `axvisor_api` `axvmconfig` `riscv_vcpu` `x86_vcpu` | `axvisor` | @@ -364,11 +361,11 @@ flowchart TB | `define-weak-traits` | 1 | Define traits with default implementations using … | `ax-crate-interface` | `impl-weak-partial` `impl-weak-traits` `test-weak` `test-weak-partial` | | `deptool` | 0 | ArceOS 配套工具与辅助程序 | — | — | | `fxmac_rs` | 1 | FXMAC Ethernet driver in Rust for PhytiumPi (Phyt… | `ax-crate-interface` | `ax-driver` | -| `hello-kernel` | 6 | 可复用基础组件 | `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | +| | 6 | 可复用基础组件 | `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | | `impl-simple-traits` | 2 | Implement the simple traits defined in define-sim… | `ax-crate-interface` `define-simple-traits` | `test-simple` | | `impl-weak-partial` | 2 | Partial implementation of WeakDefaultIf trait. Th… | `ax-crate-interface` `define-weak-traits` | `test-weak-partial` | | `impl-weak-traits` | 2 | Full implementation of weak_default traits define… | `ax-crate-interface` `define-weak-traits` | `test-weak` | -| `irq-kernel` | 6 | 可复用基础组件 | `ax-config-macros` `ax-cpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | +| | 6 | 可复用基础组件 | `ax-config-macros` `ax-cpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | | `mingo` | 0 | ArceOS 配套工具与辅助程序 | — | — | | `range-alloc-arceos` | 0 | Generic range allocator | — | `axdevice` | | `riscv-h` | 0 | RISC-V virtualization-related registers | — | `riscv_vcpu` `riscv_vplic` | @@ -378,7 +375,7 @@ flowchart TB | `scope-local` | 3 | Scope local storage | `ax-percpu` | `ax-fs-ng` `ax-posix-api` `starry-kernel` | | `smoltcp` | 0 | A TCP/IP stack designed for bare-metal, real-time… | — | `ax-net` `ax-net-ng` `smoltcp-fuzz` | | `smoltcp-fuzz` | 1 | 可复用基础组件 | `smoltcp` | — | -| `smp-kernel` | 6 | 可复用基础组件 | `ax-config-macros` `ax-cpu` `ax-memory-addr` `ax-percpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | +| | 6 | 可复用基础组件 | `ax-config-macros` `ax-cpu` `ax-memory-addr` `ax-percpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | | `starry-kernel` | 14 | A Linux-compatible OS kernel built on ArceOS unik… | `ax-alloc` `ax-config` `ax-display` `ax-driver` `ax-errno` `ax-feat` `ax-fs-ng` `ax-hal` `ax-input` `ax-io` `ax-kernel-guard` `ax-kspin` `ax-log` `ax-memory-addr` `ax-memory-set` `ax-mm` `ax-net-ng` `ax-page-table-multiarch` `ax-percpu` `ax-runtime` `ax-sync` `ax-task` `axbacktrace` `axfs-ng-vfs` `axpoll` `scope-local` `starry-process` `starry-signal` `starry-vm` | `starryos` `starryos-test` | | `starry-process` | 3 | Process management for Starry OS | `ax-kspin` `ax-lazyinit` | `starry-kernel` | | `starry-signal` | 4 | Signal management library for Starry OS | `ax-cpu` `ax-kspin` `starry-vm` | `starry-kernel` | diff --git a/docs/docs/components/overview.md b/docs/docs/components/overview.md index 335ddc79e8..1e559caadb 100644 --- a/docs/docs/components/overview.md +++ b/docs/docs/components/overview.md @@ -43,8 +43,8 @@ flowchart TB end subgraph sg_plat["平台层"] direction LR - p1["axplat-dyn"] - p2["axplat-x86-qemu-q35"] + p1["ax-plat-dyn"] + p2["ax-plat-x86-qemu-q35"] end subgraph sg_comp["组件层 (93 crates)"] direction LR @@ -156,16 +156,16 @@ flowchart TB | `ax-page-table-multiarch` | 组件层 | `components/page_table_multiarch/page_table_multiarch` | 3 | 7 | [查看](crates/ax-page-table-multiarch) | | `ax-percpu` | 组件层 | `components/percpu/percpu` | 2 | 17 | [查看](crates/ax-percpu) | | `ax-percpu-macros` | 组件层 | `components/percpu/percpu_macros` | 0 | 1 | [查看](crates/ax-percpu-macros) | -| `ax-plat` | 组件层 | `components/axplat_crates/axplat` | 6 | 15 | [查看](crates/ax-plat) | -| `ax-plat-aarch64-bsta1000b` | 组件层 | `components/axplat_crates/platforms/axplat-aarch64-bsta1000b` | 6 | 1 | [查看](crates/ax-plat-aarch64-bsta1000b) | -| `ax-plat-aarch64-peripherals` | 组件层 | `components/axplat_crates/platforms/axplat-aarch64-peripherals` | 7 | 4 | [查看](crates/ax-plat-aarch64-peripherals) | -| `ax-plat-aarch64-phytium-pi` | 组件层 | `components/axplat_crates/platforms/axplat-aarch64-phytium-pi` | 5 | 1 | [查看](crates/ax-plat-aarch64-phytium-pi) | -| `ax-plat-aarch64-qemu-virt` | 组件层 | `components/axplat_crates/platforms/axplat-aarch64-qemu-virt` | 5 | 5 | [查看](crates/ax-plat-aarch64-qemu-virt) | -| `ax-plat-aarch64-raspi` | 组件层 | `components/axplat_crates/platforms/axplat-aarch64-raspi` | 5 | 1 | [查看](crates/ax-plat-aarch64-raspi) | -| `ax-plat-loongarch64-qemu-virt` | 组件层 | `components/axplat_crates/platforms/axplat-loongarch64-qemu-virt` | 6 | 5 | [查看](crates/ax-plat-loongarch64-qemu-virt) | -| `ax-plat-macros` | 组件层 | `components/axplat_crates/axplat-macros` | 1 | 1 | [查看](crates/ax-plat-macros) | -| `ax-plat-riscv64-qemu-virt` | 组件层 | `components/axplat_crates/platforms/axplat-riscv64-qemu-virt` | 6 | 6 | [查看](crates/ax-plat-riscv64-qemu-virt) | -| `ax-plat-x86-pc` | 组件层 | `components/axplat_crates/platforms/axplat-x86-pc` | 7 | 5 | [查看](crates/ax-plat-x86-pc) | +| `ax-plat` | 组件层 | `platforms/ax-plat` | 6 | 15 | [查看](crates/ax-plat) | +| `ax-plat-aarch64-bsta1000b` | 组件层 | `platforms/ax-plat-aarch64-bsta1000b` | 6 | 1 | [查看](crates/ax-plat-aarch64-bsta1000b) | +| `ax-plat-aarch64-peripherals` | 组件层 | `platforms/ax-plat-aarch64-peripherals` | 7 | 4 | [查看](crates/ax-plat-aarch64-peripherals) | +| `ax-plat-aarch64-phytium-pi` | 组件层 | `platforms/ax-plat-aarch64-phytium-pi` | 5 | 1 | [查看](crates/ax-plat-aarch64-phytium-pi) | +| `ax-plat-aarch64-qemu-virt` | 组件层 | `platforms/ax-plat-aarch64-qemu-virt` | 5 | 5 | [查看](crates/ax-plat-aarch64-qemu-virt) | +| `ax-plat-aarch64-raspi` | 组件层 | `platforms/ax-plat-aarch64-raspi` | 5 | 1 | [查看](crates/ax-plat-aarch64-raspi) | +| `ax-plat-loongarch64-qemu-virt` | 组件层 | `platforms/ax-plat-loongarch64-qemu-virt` | 6 | 5 | [查看](crates/ax-plat-loongarch64-qemu-virt) | +| `ax-plat-macros` | 组件层 | `platforms/ax-plat-macros` | 1 | 1 | [查看](crates/ax-plat-macros) | +| `ax-plat-riscv64-qemu-virt` | 组件层 | `platforms/ax-plat-riscv64-qemu-virt` | 6 | 6 | [查看](crates/ax-plat-riscv64-qemu-virt) | +| `ax-plat-x86-pc` | 组件层 | `platforms/ax-plat-x86-pc` | 7 | 5 | [查看](crates/ax-plat-x86-pc) | | `ax-posix-api` | ArceOS 层 | `os/arceos/api/arceos_posix_api` | 13 | 1 | [查看](crates/ax-posix-api) | | `ax-runtime` | ArceOS 层 | `os/arceos/modules/axruntime` | 20 | 4 | [查看](crates/ax-runtime) | | `ax-sched` | 组件层 | `components/axsched` | 1 | 1 | [查看](crates/ax-sched) | @@ -181,8 +181,8 @@ flowchart TB | `axfs-ng-vfs` | 组件层 | `components/axfs-ng-vfs` | 2 | 3 | [查看](crates/axfs-ng-vfs) | | `axhvc` | 组件层 | `components/axhvc` | 1 | 1 | [查看](crates/axhvc) | | `axklib` | 组件层 | `components/axklib` | 2 | 3 | [查看](crates/axklib) | -| `axplat-dyn` | 平台层 | `platform/axplat-dyn` | 11 | 2 | [查看](crates/axplat-dyn) | -| `axplat-x86-qemu-q35` | 平台层 | `platform/x86-qemu-q35` | 7 | 1 | [查看](crates/axplat-x86-qemu-q35) | +| `ax-plat-dyn` | 平台层 | `platforms/ax-plat-dyn` | 11 | 2 | [查看](crates/ax-plat-dyn) | +| `ax-plat-x86-qemu-q35` | 平台层 | `platforms/ax-plat-x86-qemu-q35` | 7 | 1 | [查看](crates/ax-plat-x86-qemu-q35) | | `axpoll` | 组件层 | `components/axpoll` | 0 | 5 | [查看](crates/axpoll) | | `axvcpu` | 组件层 | `components/axvcpu` | 5 | 5 | [查看](crates/axvcpu) | | `axvisor` | Axvisor 层 | `os/axvisor` | 27 | 0 | [查看](crates/axvisor) | @@ -196,11 +196,9 @@ flowchart TB | `define-weak-traits` | 组件层 | `components/crate_interface/test_crates/define-weak-traits` | 1 | 4 | [查看](crates/define-weak-traits) | | `deptool` | ArceOS 层 | `os/arceos/tools/deptool` | 0 | 0 | [查看](crates/deptool) | | `fxmac_rs` | 组件层 | `drivers/net/fxmac_rs` | 1 | 1 | [查看](crates/fxmac-rs) | -| `hello-kernel` | 组件层 | `components/axplat_crates/examples/hello-kernel` | 5 | 0 | [查看](crates/hello-kernel) | | `impl-simple-traits` | 组件层 | `components/crate_interface/test_crates/impl-simple-traits` | 2 | 1 | [查看](crates/impl-simple-traits) | | `impl-weak-partial` | 组件层 | `components/crate_interface/test_crates/impl-weak-partial` | 2 | 1 | [查看](crates/impl-weak-partial) | | `impl-weak-traits` | 组件层 | `components/crate_interface/test_crates/impl-weak-traits` | 2 | 1 | [查看](crates/impl-weak-traits) | -| `irq-kernel` | 组件层 | `components/axplat_crates/examples/irq-kernel` | 7 | 0 | [查看](crates/irq-kernel) | | `mingo` | ArceOS 层 | `os/arceos/tools/raspi4/chainloader` | 0 | 0 | [查看](crates/mingo) | | `range-alloc-arceos` | 组件层 | `components/range-alloc-arceos` | 0 | 1 | [查看](crates/range-alloc-arceos) | | `riscv-h` | 组件层 | `components/riscv-h` | 0 | 2 | [查看](crates/riscv-h) | @@ -211,7 +209,6 @@ flowchart TB | `scope-local` | 组件层 | `components/scope-local` | 1 | 3 | [查看](crates/scope-local) | | `smoltcp` | 组件层 | `components/starry-smoltcp` | 0 | 3 | [查看](crates/smoltcp) | | `smoltcp-fuzz` | 组件层 | `components/starry-smoltcp/fuzz` | 1 | 0 | [查看](crates/smoltcp-fuzz) | -| `smp-kernel` | 组件层 | `components/axplat_crates/examples/smp-kernel` | 9 | 0 | [查看](crates/smp-kernel) | | `starry-kernel` | StarryOS 层 | `os/StarryOS/kernel` | 29 | 2 | [查看](crates/starry-kernel) | | `starry-process` | 组件层 | `components/starry-process` | 2 | 1 | [查看](crates/starry-process) | | `starry-signal` | 组件层 | `components/starry-signal` | 3 | 1 | [查看](crates/starry-signal) | diff --git a/docs/docs/contributing/demo.md b/docs/docs/contributing/demo.md index f2074e91d8..2ca0203743 100644 --- a/docs/docs/contributing/demo.md +++ b/docs/docs/contributing/demo.md @@ -79,7 +79,7 @@ TGOSKits 的组件按职责分布在不同目录中,正式添加组件应该 | ArceOS API / 用户库 | `os/arceos/api/` 或 `os/arceos/ulib/` | | StarryOS 内核 | `os/StarryOS/kernel/` | | Axvisor 运行时 | `os/axvisor/src/` | -| 平台适配 | `platform/` 或 `components/axplat_crates/` | +| 平台适配 | `platforms/` | 为了同时演示新增和修改组件,`examples/tgmath` 本身已经存在了。对于新增组件的演示,请先删除 `examples/tgmath` 后执行后续步骤! @@ -576,7 +576,7 @@ workspace 自动构建。 ## 新增组件或示例 -- 新增通用可复用组件时,放到合适的 `components/`、`drivers/`、`platform/` 或 +- 新增通用可复用组件时,放到合适的 `components/`、`drivers/`、`platforms/` 或 `os/*/modules/` 子目录,并同步 workspace、文档和验证白名单。 - 新增 ArceOS 应用示例时,优先使用 `os/arceos/examples/`。 - 新增 StarryOS 板端场景时,使用 `apps/starry//`,并确保 case 可以通过 diff --git a/docs/docs/contributing/repo.md b/docs/docs/contributing/repo.md index c8de4792af..bf2b19171e 100644 --- a/docs/docs/contributing/repo.md +++ b/docs/docs/contributing/repo.md @@ -24,7 +24,7 @@ tgoskits/ │ ├── arceos/ │ ├── axvisor/ │ └── StarryOS/ -├── platform/ # 平台相关 crate +├── platforms/ # 平台相关 crate ├── scripts/ │ └── repo/ │ ├── repo.py # subtree 管理脚本 diff --git a/docs/docs/debug/check-mechanisms-summary.md b/docs/docs/debug/check-mechanisms-summary.md index 8406905e42..b4c1b95c11 100644 --- a/docs/docs/debug/check-mechanisms-summary.md +++ b/docs/docs/debug/check-mechanisms-summary.md @@ -117,7 +117,7 @@ boot/current 栈,也不覆盖未来可能引入的独立 IRQ stack、exception - `os/arceos/modules/axtask/src/task.rs` - `os/arceos/modules/axtask/src/run_queue.rs` - `os/arceos/modules/axruntime/src/mp.rs` -- `platform/axplat-dyn/src/boot.rs` +- `platforms/ax-plat-dyn/src/boot.rs` 后续改进方向: diff --git a/docs/docs/debug/platform.md b/docs/docs/debug/platform.md index cb46205027..e755891851 100644 --- a/docs/docs/debug/platform.md +++ b/docs/docs/debug/platform.md @@ -134,7 +134,7 @@ sidebar_label: "平台实现" | ArceOS Main | 单个软件断点 + `continue` | `os/arceos/examples/helloworld/src/main.rs:8` | | ArceOS Boot | 多个符号/行号断点(不自动 continue) | `ax_plat::call_main`、`axruntime/src/lib.rs:141`、`main.rs:8` | | Axvisor Main | 单个软件断点 + `continue` | `os/axvisor/src/main.rs:42` | -| Axvisor Boot | 多个行号断点(不自动 continue) | `platform/axplat-dyn/src/boot.rs:8`、`axvisor/src/main.rs:42` | +| Axvisor Boot | 多个行号断点(不自动 continue) | `platforms/ax-plat-dyn/src/boot.rs:8`、`axvisor/src/main.rs:42` | | StarryOS Main | **单个硬件断点** + `continue` | `os/StarryOS/starryos/src/main.rs:12` | | StarryOS Boot | 混合符号/行号断点(不自动 continue) | `ax_plat::call_main`、`axruntime/src/lib.rs:141`、`starry_kernel::entry::init`、`starryos/src/main.rs:12` | diff --git a/docs/docs/development/arceos.md b/docs/docs/development/arceos.md index 549f46e594..488fa73501 100644 --- a/docs/docs/development/arceos.md +++ b/docs/docs/development/arceos.md @@ -368,7 +368,7 @@ C 示例放在 `os/arceos/examples/-c/`。仓库中已有 `helloworld-c` 以 `axplat-aarch64-qemu-virt` 为例: ``` -components/axplat_crates/platforms/axplat-aarch64-qemu-virt/ +platforms/ax-plat-aarch64-qemu-virt/ ├── Cargo.toml ├── axconfig.toml # 平台配置(内存布局、SMP 数等) ├── build.rs # 构建脚本 @@ -386,9 +386,9 @@ components/axplat_crates/platforms/axplat-aarch64-qemu-virt/ | 目录 | 内容 | |------|------| -| `components/axplat_crates/platforms/` | 工作区内 `ax-plat-*` 平台 crate | -| `platform/axplat-dyn/` | 动态平台加载(设备树驱动) | -| `platform/x86-qemu-q35/` | x86_64 QEMU Q35 独立平台 | +| `platforms/` | 工作区内 `ax-plat-*` 平台 crate | +| `platforms/ax-plat-dyn/` | 动态平台加载(设备树驱动) | +| `platforms/ax-plat-x86-qemu-q35/` | x86_64 QEMU Q35 独立平台 | 已有平台: @@ -405,7 +405,7 @@ components/axplat_crates/platforms/axplat-aarch64-qemu-virt/ ### 5.3 添加新平台 -1. 在 `components/axplat_crates/platforms/` 下创建新 crate +1. 在 `platforms/` 下创建新 crate 2. 实现 `axhal` 要求的平台接口 3. 编写 `axconfig.toml` 配置内存布局和硬件参数 4. 在根 `Cargo.toml` 中注册为 workspace member diff --git a/docs/docs/development/axvisor.md b/docs/docs/development/axvisor.md index 1fcd58e172..db2f297a0b 100644 --- a/docs/docs/development/axvisor.md +++ b/docs/docs/development/axvisor.md @@ -328,7 +328,7 @@ vm_configs = [] # 注意:默认为空,需手动指定或通过 setup_qemu. ### 7.3 新增板级支持 1. 创建 `os/axvisor/configs/board/.toml` -2. 在 `components/axplat_crates/platforms/` 下添加对应平台 crate(如需要) +2. 在 `platforms/` 下添加对应平台 crate(如需要) 3. 创建对应的 VM 配置 `configs/vms/--.toml` 4. 验证: diff --git a/docs/docs/development/components.md b/docs/docs/development/components.md index 619a6b4225..a76bddc44e 100644 --- a/docs/docs/development/components.md +++ b/docs/docs/development/components.md @@ -25,7 +25,7 @@ TGOSKits 的核心价值不仅在于将各仓库整合到同一工作区,更 除上述六类核心组件层次外,以下两个目录在验证组件功能和系统集成时也需关注: -- `components/axplat_crates/platforms/*` 与 `platform/*`:平台实现 +- `platforms/*`:平台实现 - `test-suit/*`:系统级测试入口 ## 2. 组件依赖关系 @@ -40,7 +40,7 @@ flowchart TD ArceosApps["ArceOS examples + test-suit/arceos"] StarryKernel["os/StarryOS/kernel + components/starry-*"] AxvisorRuntime["os/axvisor + components/axvm/axvcpu/axdevice/*"] - PlatformCrates["components/axplat_crates/platforms/* + platform/*"] + PlatformCrates["platforms/*"] ReusableCrate --> ArceosModules ArceosModules --> ArceosApi @@ -63,7 +63,7 @@ flowchart TD 例如 `ax-hal`、`ax-task`、`ax-driver`、`ax-net` 3. 通过平台和配置接到最终系统 - 例如 `axplat-*`、`platform/x86-qemu-q35`、Axvisor 的 `configs/board/*.toml` + 例如 `axplat-*`、`platforms/ax-plat-x86-qemu-q35`、Axvisor 的 `configs/board/*.toml` ### 2.1 依赖统计 @@ -95,7 +95,7 @@ flowchart TD | ArceOS 的 feature 或应用接口 | `os/arceos/api/axfeat`、`os/arceos/ulib/axstd`、`os/arceos/ulib/axlibc` | ArceOS 应用与上层系统 | | StarryOS 的 Linux 兼容行为 | `components/starry-*`、`os/StarryOS/kernel/*` | StarryOS | | Hypervisor、vCPU、虚拟设备、VM 管理 | `components/axvm`、`components/axvcpu`、`components/axdevice`、`components/axvisor_api`、`os/axvisor/src/*` | Axvisor | -| 平台、板级适配或 VM 启动配置 | `components/axplat_crates/platforms/*`、`platform/*`、`os/axvisor/configs/*` | 一到多个系统 | +| 平台、板级适配或 VM 启动配置 | `platforms/*`、`os/axvisor/configs/*` | 一到多个系统 | 若不确定某个 crate 的维护者或来源仓库,可查看 `scripts/repo/repos.csv`,该文件记录了所有 subtree 组件的来源信息。 @@ -271,7 +271,7 @@ my_component = { path = "components/my_component" } ### 5.5 遇到嵌套 workspace 时不要照抄 -`components/axplat_crates`、`components/axmm_crates` 这类目录本身是独立 workspace。给这类目录加新 crate 时,需要特别注意处理方式: +`components/axmm_crates` 这类目录本身是独立 workspace。给这类目录加新 crate 时,需要特别注意处理方式: - 先在它自己的 workspace 里接好 - 再在根 `Cargo.toml` 里为具体 leaf crate 增加 patch 或 member @@ -346,8 +346,8 @@ cargo xtask qemu \ 若修改涉及板级能力,还需同时关注: -- `components/axplat_crates/platforms/*` -- `platform/x86-qemu-q35` +- `platforms/*` +- `platforms/ax-plat-x86-qemu-q35` - `os/axvisor/configs/board/*.toml` ## 9. 测试与质量检查 diff --git a/docs/docs/introduction/overview.md b/docs/docs/introduction/overview.md index 0af87d26db..7d9c60ba4f 100644 --- a/docs/docs/introduction/overview.md +++ b/docs/docs/introduction/overview.md @@ -43,7 +43,7 @@ flowchart TD starry["StarryOS
os/StarryOS/"] axvisor["Axvisor
os/axvisor/"] - platform["平台层
platform/"] + platform["平台层
platforms/"] tests["测试套件
test-suit/"] components --> arceos @@ -65,7 +65,7 @@ flowchart TD |------|------|------| | 组件层 | `components/` | 调度、内存、驱动、文件系统、网络、虚拟化等基础能力 | | 系统层 | `os/{arceos,starryos,axvisor}/` | 三套操作系统 / Hypervisor 的核心实现 | -| 平台层 | `platform/`、`components/axplat_crates/` | 架构与板级适配 | +| 平台层 | `platforms/` | 架构与板级适配 | | 测试层 | `test-suit/`、`scripts/test/` | 系统级 QEMU / 板级测试及主机端验证 | ## 目录结构 @@ -97,8 +97,8 @@ tgoskits/ │ ├── src/ # HAL, VMM, Shell, 任务管理 │ ├── configs/ # 板级 / VM / 测试配置 │ └── xtask/ # Axvisor 专用构建任务 -├── platform/ # 平台适配层 -│ ├── axplat-dyn/ # 动态平台支持 +├── platforms/ # 平台适配层 +│ ├── ax-plat-dyn/ # 动态平台支持 │ ├── riscv64-qemu-virt/ # RISC-V QEMU virt 平台 │ └── x86-qemu-q35/ # x86 Q35 平台 ├── drivers/ # SoC 专用驱动(RK3588 时钟 / NPU / 电源管理) diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index d630d73234..4495589b35 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -186,7 +186,7 @@ function SystemsDiagram({ systems }) {
共享组件基础层 - components/ · ax* crates · starry-* · drivers/ · platform/ + components/ · ax* crates · starry-* · drivers/ · platforms/
); @@ -659,7 +659,7 @@ function QualitySection() { const lanes = [ { title: 'Host 侧组件验证', desc: '在宿主机上直接执行标准库测试与 clippy 静态检查,秒级反馈,无需交叉编译。', items: ['cargo test -p ', 'cargo xtask clippy', 'cargo xtask test'] }, { title: 'QEMU 系统级验证', desc: '构建目标系统镜像后在 QEMU 中运行,验证 syscall、进程管理、设备驱动等系统级行为。', items: ['ArceOS example 运行检查', 'StarryOS rootfs + shell 启动', 'Axvisor Guest 引导与交互'] }, - { title: '板级场景回归', desc: '变更涉及平台适配或跨系统共享组件时,在物理板卡上执行端到端回归测试,确认硬件行为一致。', items: ['platform/* 编译与启动验证', 'VM / Guest 配置兼容性回归', '共享 crate 变更的多系统影响面检查'] }, + { title: '板级场景回归', desc: '变更涉及平台适配或跨系统共享组件时,在物理板卡上执行端到端回归测试,确认硬件行为一致。', items: ['platforms/* 编译与启动验证', 'VM / Guest 配置兼容性回归', '共享 crate 变更的多系统影响面检查'] }, ]; return ( diff --git a/os/StarryOS/Makefile b/os/StarryOS/Makefile index fb0929b178..bdc3affe43 100644 --- a/os/StarryOS/Makefile +++ b/os/StarryOS/Makefile @@ -56,7 +56,7 @@ la: $(MAKE) ARCH=loongarch64 run vf2: - $(MAKE) ARCH=riscv64 APP_FEATURES=vf2 MYPLAT=axplat-riscv64-visionfive2 BUS=mmio build + $(MAKE) ARCH=riscv64 APP_FEATURES=vf2 MYPLAT=ax-plat-riscv64-visionfive2 BUS=mmio build sg2002: $(MAKE) ARCH=riscv64 APP_FEATURES=sg2002 MYPLAT=ax-plat-riscv64-sg2002 BUS=mmio SMP=1 UIMAGE=y LOG=info build diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index ef124c89aa..b153f395a4 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -68,7 +68,7 @@ ax-log.workspace = true ax-mm.workspace = true axnet = { version = "0.6.1", path = "../../arceos/modules/axnet-ng", package = "ax-net-ng" } ax-driver = { workspace = true, optional = true } -axplat-dyn = { workspace = true, optional = true } +ax-plat-dyn = { workspace = true, optional = true } ax-runtime.workspace = true ax-sync.workspace = true ax-task.workspace = true diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index c9fb4538aa..4b91741f37 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -24,7 +24,7 @@ qemu = [ "starry-kernel/input", "starry-kernel/vsock", # auxilary features ] -smp = ["ax-feat/smp", "axplat-dyn?/smp", "ax-hal/smp"] +smp = ["ax-feat/smp", "ax-plat-dyn?/smp", "ax-hal/smp"] sg2002 = [ "ax-hal/riscv64-sg2002", @@ -64,7 +64,7 @@ path = "xtask/main.rs" ax-feat = { workspace = true, features = ["fs-ng-times", "irq"] } ax-driver.workspace = true ax-hal.workspace = true -axplat-dyn = { workspace = true, optional = true } +ax-plat-dyn = { workspace = true, optional = true } starry-kernel = { workspace = true, features = ["dev-log", "ext4"] } [target.'cfg(any(windows,unix))'.dependencies] diff --git a/os/arceos/doc/platform_raspi4.md b/os/arceos/doc/platform_raspi4.md index c4a8cf75ab..a332722963 100644 --- a/os/arceos/doc/platform_raspi4.md +++ b/os/arceos/doc/platform_raspi4.md @@ -19,7 +19,7 @@ And follow this tutorial to run the 08_hw_debug_JTAG chapter. It will help you to connect your raspi4 to the JTAG and connect your JTAG to your computer and it will also teach you how to use `make jtagboot` to debug your raspi4. Because the JTAG only support the first line of code be loaded at `0x80000` and only support single core, so you have to -1. Change the file: /modules/axconfig/src/platform/raspi4_aarch64, replace the "kernel-base-vaddr" with "0x8_0000" and replace the "phys-virt-offset" with "0x0" +1. Change the file: /modules/axconfig/src/platforms/raspi4_aarch64, replace the "kernel-base-vaddr" with "0x8_0000" and replace the "phys-virt-offset" with "0x0" 2. set the feature `SMP=1` Then run with features `ARCH=aarch64 MYPLAT=axplat-aarch64-raspi` and diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 3554652a16..c36324bb97 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -16,10 +16,10 @@ smp = [ "ax-plat-aarch64-phytium-pi?/smp", "ax-plat-riscv64-qemu-virt?/smp", "ax-plat-riscv64-sg2002?/smp", - "axplat-riscv64-visionfive2?/smp", + "ax-plat-riscv64-visionfive2?/smp", "ax-plat-loongarch64-qemu-virt?/smp", - "axplat-x86-qemu-q35?/smp", - "axplat-dyn?/smp", + "ax-plat-x86-qemu-q35?/smp", + "ax-plat-dyn?/smp", "ax-plat/smp", ] irq = [ @@ -30,11 +30,11 @@ irq = [ "ax-plat-aarch64-phytium-pi?/irq", "ax-plat-riscv64-qemu-virt?/irq", "ax-plat-riscv64-sg2002?/irq", - "axplat-riscv64-visionfive2?/irq", + "ax-plat-riscv64-visionfive2?/irq", "ax-plat-loongarch64-qemu-virt?/irq", - "axplat-x86-qemu-q35?/irq", + "ax-plat-x86-qemu-q35?/irq", "ax-plat/irq", - "axplat-dyn?/irq", + "ax-plat-dyn?/irq", ] fp-simd = [ "ax-cpu/fp-simd", @@ -45,10 +45,10 @@ fp-simd = [ "ax-plat-aarch64-phytium-pi?/fp-simd", "ax-plat-riscv64-qemu-virt?/fp-simd", "ax-plat-riscv64-sg2002?/fp-simd", - "axplat-riscv64-visionfive2?/fp-simd", + "ax-plat-riscv64-visionfive2?/fp-simd", "ax-plat-loongarch64-qemu-virt?/fp-simd", - "axplat-x86-qemu-q35?/fp-simd", - "axplat-dyn?/fp-simd", + "ax-plat-x86-qemu-q35?/fp-simd", + "ax-plat-dyn?/fp-simd", ] rtc = [ "ax-plat-x86-pc?/rtc", @@ -58,10 +58,10 @@ rtc = [ "ax-plat-aarch64-phytium-pi?/rtc", "ax-plat-riscv64-qemu-virt?/rtc", "ax-plat-riscv64-sg2002?/rtc", - "axplat-riscv64-visionfive2?/rtc", + "ax-plat-riscv64-visionfive2?/rtc", "ax-plat-loongarch64-qemu-virt?/rtc", - "axplat-x86-qemu-q35?/rtc", - "axplat-dyn?/rtc", + "ax-plat-x86-qemu-q35?/rtc", + "ax-plat-dyn?/rtc", ] # Forward the GICv3 toggle to the aarch64 qemu-virt platform. # `?` lets non-aarch64 builds pass the feature through without @@ -81,19 +81,19 @@ paging = [ "ax-plat-riscv64-qemu-virt?/paging", ] tls = ["ax-cpu/tls"] -uspace = ["paging", "ax-cpu/uspace", "axplat-dyn?/uspace"] +uspace = ["paging", "ax-cpu/uspace", "ax-plat-dyn?/uspace"] hv = [ "paging", "ax-cpu/arm-el2", "ax-cpu/exception-table", "ax-percpu/arm-el2", - "axplat-dyn?/hv", + "ax-plat-dyn?/hv", ] axvisor-linker = [] # Custom or default platforms myplat = [] -plat-dyn = ["axplat-dyn"] +plat-dyn = ["ax-plat-dyn"] x86-pc = ["dep:ax-plat-x86-pc"] aarch64-qemu-virt = ["dep:ax-plat-aarch64-qemu-virt"] aarch64-raspi = ["dep:ax-plat-aarch64-raspi"] @@ -101,13 +101,13 @@ aarch64-bsta1000b = ["dep:ax-plat-aarch64-bsta1000b"] aarch64-phytium-pi = ["dep:ax-plat-aarch64-phytium-pi"] riscv64-qemu-virt = ["dep:ax-plat-riscv64-qemu-virt"] riscv64-sg2002 = ["dep:ax-plat-riscv64-sg2002"] -riscv64-visionfive2 = ["dep:axplat-riscv64-visionfive2"] +riscv64-visionfive2 = ["dep:ax-plat-riscv64-visionfive2"] riscv64-qemu-virt-hv = [ "dep:ax-plat-riscv64-qemu-virt", "ax-plat-riscv64-qemu-virt/hypervisor", ] loongarch64-qemu-virt = ["dep:ax-plat-loongarch64-qemu-virt"] -x86-qemu-q35 = ["dep:axplat-x86-qemu-q35", "axplat-x86-qemu-q35/reboot-on-system-off"] +x86-qemu-q35 = ["dep:ax-plat-x86-qemu-q35", "ax-plat-x86-qemu-q35/reboot-on-system-off"] defplat = [ "dep:ax-plat-x86-pc", "dep:ax-plat-aarch64-qemu-virt", @@ -136,10 +136,10 @@ spin.workspace = true [target.'cfg(target_arch = "x86_64")'.dependencies] ax-plat-x86-pc = { workspace = true, optional = true } -axplat-x86-qemu-q35 = { workspace = true, optional = true } +ax-plat-x86-qemu-q35 = { workspace = true, optional = true } [target.'cfg(target_os = "none")'.dependencies] -axplat-dyn = { workspace = true, default-features = false, optional = true } +ax-plat-dyn = { workspace = true, default-features = false, optional = true } [target.'cfg(target_arch = "aarch64")'.dependencies] ax-plat-aarch64-qemu-virt = { workspace = true, optional = true } @@ -150,7 +150,7 @@ ax-plat-aarch64-phytium-pi = { workspace = true, optional = true } [target.'cfg(target_arch = "riscv64")'.dependencies] ax-plat-riscv64-qemu-virt = { workspace = true, optional = true } ax-plat-riscv64-sg2002 = { workspace = true, optional = true } -axplat-riscv64-visionfive2 = { workspace = true, optional = true } +ax-plat-riscv64-visionfive2 = { workspace = true, optional = true } [target.'cfg(target_arch = "loongarch64")'.dependencies] ax-plat-loongarch64-qemu-virt = { workspace = true, optional = true } diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index a86e7ba5c2..b2c5e393e8 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -18,7 +18,7 @@ const PLATFORM_FEATURES: &[PlatformFeature] = &[ PlatformFeature { feature: "plat-dyn", target_arch: None, - crate_name: "axplat_dyn", + crate_name: "ax_plat_dyn", }, PlatformFeature { feature: "x86-pc", @@ -28,7 +28,7 @@ const PLATFORM_FEATURES: &[PlatformFeature] = &[ PlatformFeature { feature: "x86-qemu-q35", target_arch: Some("x86_64"), - crate_name: "axplat_x86_qemu_q35", + crate_name: "ax_plat_x86_qemu_q35", }, PlatformFeature { feature: "aarch64-qemu-virt", @@ -63,7 +63,7 @@ const PLATFORM_FEATURES: &[PlatformFeature] = &[ PlatformFeature { feature: "riscv64-visionfive2", target_arch: Some("riscv64"), - crate_name: "axplat_riscv64_visionfive2", + crate_name: "ax_plat_riscv64_visionfive2", }, PlatformFeature { feature: "riscv64-qemu-virt-hv", @@ -188,7 +188,7 @@ fn gen_selected_platform( None }; - if crate_name == Some("axplat_dyn") { + if crate_name == Some("ax_plat_dyn") { println!("cargo:rustc-cfg=plat_dyn"); } diff --git a/os/arceos/modules/axhal/src/mem.rs b/os/arceos/modules/axhal/src/mem.rs index e52c1b868a..0a02a1b2fb 100644 --- a/os/arceos/modules/axhal/src/mem.rs +++ b/os/arceos/modules/axhal/src/mem.rs @@ -113,7 +113,7 @@ pub fn memory_regions() -> impl Iterator { pub fn boot_stack_bounds(cpu_id: usize) -> (VirtAddr, usize) { #[cfg(plat_dyn)] { - axplat_dyn::boot_stack_bounds(cpu_id) + ax_plat_dyn::boot_stack_bounds(cpu_id) } #[cfg(not(plat_dyn))] { diff --git a/os/arceos/modules/axhal/src/time.rs b/os/arceos/modules/axhal/src/time.rs index 76f305f7d1..97017619b5 100644 --- a/os/arceos/modules/axhal/src/time.rs +++ b/os/arceos/modules/axhal/src/time.rs @@ -11,7 +11,7 @@ pub use ax_plat::time::{irq_num, set_oneshot_timer}; pub fn try_init_epoch_offset(epoch_time_nanos: u64) -> bool { #[cfg(plat_dyn)] { - axplat_dyn::try_init_epoch_offset(epoch_time_nanos) + ax_plat_dyn::try_init_epoch_offset(epoch_time_nanos) } #[cfg(not(plat_dyn))] { diff --git a/os/arceos/scripts/make/cargo.mk b/os/arceos/scripts/make/cargo.mk index cab1882fb9..2c41632725 100644 --- a/os/arceos/scripts/make/cargo.mk +++ b/os/arceos/scripts/make/cargo.mk @@ -41,16 +41,16 @@ endef clippy_args := -A unsafe_op_in_unsafe_fn define cargo_clippy - $(call run_cmd,cargo clippy,--workspace --exclude ax-log --exclude axplat-dyn --exclude "arceos-*" $(1) $(verbose) -- $(clippy_args)) + $(call run_cmd,cargo clippy,--workspace --exclude ax-log --exclude ax-plat-dyn --exclude "arceos-*" $(1) $(verbose) -- $(clippy_args)) $(call run_cmd,cargo clippy,-p ax-log $(1) $(verbose) -- $(clippy_args)) endef all_packages := \ - $(filter-out axplat-dyn,$(shell ls $(CURDIR)/modules)) \ + $(filter-out ax-plat-dyn,$(shell ls $(CURDIR)/modules)) \ ax-feat ax-api ax-std ax-libc define cargo_doc - $(call run_cmd,cargo doc,--no-deps --all-features --workspace --exclude "arceos-*" --exclude axplat-dyn $(verbose)) + $(call run_cmd,cargo doc,--no-deps --all-features --workspace --exclude "arceos-*" --exclude ax-plat-dyn $(verbose)) @# run twice to fix broken hyperlinks $(foreach p,$(all_packages), \ $(call run_cmd,cargo rustdoc,--all-features -p $(p) $(verbose)) @@ -59,5 +59,5 @@ endef define unit_test $(call run_cmd,cargo test,-p ax-fs $(1) $(verbose) -- --nocapture) - $(call run_cmd,cargo test,--workspace --exclude ax-fs --exclude axplat-dyn $(1) $(verbose) -- --nocapture) + $(call run_cmd,cargo test,--workspace --exclude ax-fs --exclude ax-plat-dyn $(1) $(verbose) -- --nocapture) endef diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index e46bf955ff..18fb804ab3 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -39,7 +39,7 @@ fs = ["ax-std/fs"] vmx = ["axvm/vmx"] svm = ["axvm/svm"] sstc = ["riscv_vcpu/sstc"] -dyn-plat = ["ax-std/plat-dyn", "dep:axplat-dyn"] +dyn-plat = ["ax-std/plat-dyn", "dep:ax-plat-dyn"] rockchip-soc = ["ax-driver/rockchip-soc"] rockchip-sdhci = ["ax-driver/rockchip-sdhci", "ax-driver/rockchip-soc"] rockchip-dwmmc = ["ax-driver/rockchip-dwmmc", "ax-driver/rockchip-soc"] @@ -93,10 +93,10 @@ ax-percpu = { workspace = true, features = ["arm-el2", "non-zero-vma"] } rdif-intc.workspace = true rdrive.workspace = true ax-driver.workspace = true -axplat-dyn = { workspace = true, optional = true, default-features = false } +ax-plat-dyn = { workspace = true, optional = true, default-features = false } [target.'cfg(target_arch = "x86_64")'.dependencies] -axplat-x86-qemu-q35 = { workspace = true, default-features = false, features = ["reboot-on-system-off", "irq", "smp"] } +ax-plat-x86-qemu-q35 = { workspace = true, default-features = false, features = ["reboot-on-system-off", "irq", "smp"] } ax-config = { workspace = true, features = ["plat-dyn"] } [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu-ext = "0.1" diff --git a/os/axvisor/doc/task.py-usage.md b/os/axvisor/doc/task.py-usage.md index 5aab2b5fa6..66f0682d6a 100644 --- a/os/axvisor/doc/task.py-usage.md +++ b/os/axvisor/doc/task.py-usage.md @@ -99,7 +99,7 @@ cp .hvconfig.prod.toml .hvconfig.toml && ./scripts/task.py run #### --plat (平台) -指定目标平台,系统会自动从 `platform/{plat}/axconfig.toml` 读取对应的架构和包配置。 +指定目标平台,系统会自动从 `platforms/{plat}/axconfig.toml` 读取对应的架构和包配置。 ```bash --plat aarch64-generic @@ -212,7 +212,7 @@ cp .hvconfig.prod.toml .hvconfig.toml && ./task.py run 2. **平台配置找不到** ```text - 警告:平台配置文件 platform/xxx/axconfig.toml 不存在 + 警告:平台配置文件 platforms/xxx/axconfig.toml 不存在 ``` - 检查平台名称是否正确 diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/CHANGELOG.md b/platforms/ax-plat-aarch64-bsta1000b/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/CHANGELOG.md rename to platforms/ax-plat-aarch64-bsta1000b/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml b/platforms/ax-plat-aarch64-bsta1000b/Cargo.toml similarity index 89% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml rename to platforms/ax-plat-aarch64-bsta1000b/Cargo.toml index b4b760f0d4..91115c6650 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/Cargo.toml +++ b/platforms/ax-plat-aarch64-bsta1000b/Cargo.toml @@ -26,5 +26,11 @@ ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +[package.metadata.axplat] +platform = "aarch64-bsta1000b" +arch = "aarch64" +config = "axconfig.toml" +crate = "ax_plat_aarch64_bsta1000b" + [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/README.md b/platforms/ax-plat-aarch64-bsta1000b/README.md similarity index 97% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/README.md rename to platforms/ax-plat-aarch64-bsta1000b/README.md index e54e397992..e0a2d2dbcc 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/README.md +++ b/platforms/ax-plat-aarch64-bsta1000b/README.md @@ -35,7 +35,7 @@ ax-plat-aarch64-bsta1000b = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-aarch64-bsta1000b +cd platforms/ax-plat-aarch64-bsta1000b # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/README_CN.md b/platforms/ax-plat-aarch64-bsta1000b/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/README_CN.md rename to platforms/ax-plat-aarch64-bsta1000b/README_CN.md index b463b20e82..0e85bac764 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/README_CN.md +++ b/platforms/ax-plat-aarch64-bsta1000b/README_CN.md @@ -35,7 +35,7 @@ ax-plat-aarch64-bsta1000b = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-aarch64-bsta1000b +cd platforms/ax-plat-aarch64-bsta1000b # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/axconfig.toml b/platforms/ax-plat-aarch64-bsta1000b/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/axconfig.toml rename to platforms/ax-plat-aarch64-bsta1000b/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/build.rs b/platforms/ax-plat-aarch64-bsta1000b/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/build.rs rename to platforms/ax-plat-aarch64-bsta1000b/build.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/boot.rs b/platforms/ax-plat-aarch64-bsta1000b/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/boot.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs b/platforms/ax-plat-aarch64-bsta1000b/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/drivers.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/init.rs b/platforms/ax-plat-aarch64-bsta1000b/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/init.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs b/platforms/ax-plat-aarch64-bsta1000b/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/lib.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/mem.rs b/platforms/ax-plat-aarch64-bsta1000b/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/mem.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/misc.rs b/platforms/ax-plat-aarch64-bsta1000b/src/misc.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/misc.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/misc.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/mp.rs b/platforms/ax-plat-aarch64-bsta1000b/src/mp.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/mp.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/mp.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/power.rs b/platforms/ax-plat-aarch64-bsta1000b/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/power.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/serial.rs b/platforms/ax-plat-aarch64-bsta1000b/src/serial.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-bsta1000b/src/serial.rs rename to platforms/ax-plat-aarch64-bsta1000b/src/serial.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/CHANGELOG.md b/platforms/ax-plat-aarch64-peripherals/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/CHANGELOG.md rename to platforms/ax-plat-aarch64-peripherals/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml b/platforms/ax-plat-aarch64-peripherals/Cargo.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml rename to platforms/ax-plat-aarch64-peripherals/Cargo.toml diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/README.md b/platforms/ax-plat-aarch64-peripherals/README.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/README.md rename to platforms/ax-plat-aarch64-peripherals/README.md index b1702c7ad5..93989fc0ca 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-peripherals/README.md +++ b/platforms/ax-plat-aarch64-peripherals/README.md @@ -35,7 +35,7 @@ ax-plat-aarch64-peripherals = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-aarch64-peripherals +cd platforms/ax-plat-aarch64-peripherals # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/README_CN.md b/platforms/ax-plat-aarch64-peripherals/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/README_CN.md rename to platforms/ax-plat-aarch64-peripherals/README_CN.md index 3a7e884418..59aa3ac1df 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-peripherals/README_CN.md +++ b/platforms/ax-plat-aarch64-peripherals/README_CN.md @@ -35,7 +35,7 @@ ax-plat-aarch64-peripherals = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-aarch64-peripherals +cd platforms/ax-plat-aarch64-peripherals # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/generic_timer.rs b/platforms/ax-plat-aarch64-peripherals/src/generic_timer.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/src/generic_timer.rs rename to platforms/ax-plat-aarch64-peripherals/src/generic_timer.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/gic.rs b/platforms/ax-plat-aarch64-peripherals/src/gic.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/src/gic.rs rename to platforms/ax-plat-aarch64-peripherals/src/gic.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/lib.rs b/platforms/ax-plat-aarch64-peripherals/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/src/lib.rs rename to platforms/ax-plat-aarch64-peripherals/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs b/platforms/ax-plat-aarch64-peripherals/src/pl011.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl011.rs rename to platforms/ax-plat-aarch64-peripherals/src/pl011.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl031.rs b/platforms/ax-plat-aarch64-peripherals/src/pl031.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/src/pl031.rs rename to platforms/ax-plat-aarch64-peripherals/src/pl031.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-peripherals/src/psci.rs b/platforms/ax-plat-aarch64-peripherals/src/psci.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-peripherals/src/psci.rs rename to platforms/ax-plat-aarch64-peripherals/src/psci.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/CHANGELOG.md b/platforms/ax-plat-aarch64-phytium-pi/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/CHANGELOG.md rename to platforms/ax-plat-aarch64-phytium-pi/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml b/platforms/ax-plat-aarch64-phytium-pi/Cargo.toml similarity index 87% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml rename to platforms/ax-plat-aarch64-phytium-pi/Cargo.toml index ec22899bf5..4101728dbc 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/Cargo.toml +++ b/platforms/ax-plat-aarch64-phytium-pi/Cargo.toml @@ -23,5 +23,11 @@ ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +[package.metadata.axplat] +platform = "aarch64-phytium-pi" +arch = "aarch64" +config = "axconfig.toml" +crate = "ax_plat_aarch64_phytium_pi" + [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/README.md b/platforms/ax-plat-aarch64-phytium-pi/README.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/README.md rename to platforms/ax-plat-aarch64-phytium-pi/README.md index 9650df8d84..51cff05d65 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/README.md +++ b/platforms/ax-plat-aarch64-phytium-pi/README.md @@ -35,7 +35,7 @@ ax-plat-aarch64-phytium-pi = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-aarch64-phytium-pi +cd platforms/ax-plat-aarch64-phytium-pi # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/README_CN.md b/platforms/ax-plat-aarch64-phytium-pi/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/README_CN.md rename to platforms/ax-plat-aarch64-phytium-pi/README_CN.md index fb0c395404..8c760664b0 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/README_CN.md +++ b/platforms/ax-plat-aarch64-phytium-pi/README_CN.md @@ -35,7 +35,7 @@ ax-plat-aarch64-phytium-pi = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-aarch64-phytium-pi +cd platforms/ax-plat-aarch64-phytium-pi # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/axconfig.toml b/platforms/ax-plat-aarch64-phytium-pi/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/axconfig.toml rename to platforms/ax-plat-aarch64-phytium-pi/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/build.rs b/platforms/ax-plat-aarch64-phytium-pi/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/build.rs rename to platforms/ax-plat-aarch64-phytium-pi/build.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/boot.rs b/platforms/ax-plat-aarch64-phytium-pi/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/boot.rs rename to platforms/ax-plat-aarch64-phytium-pi/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs b/platforms/ax-plat-aarch64-phytium-pi/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/drivers.rs rename to platforms/ax-plat-aarch64-phytium-pi/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/init.rs b/platforms/ax-plat-aarch64-phytium-pi/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/init.rs rename to platforms/ax-plat-aarch64-phytium-pi/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/lib.rs b/platforms/ax-plat-aarch64-phytium-pi/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/lib.rs rename to platforms/ax-plat-aarch64-phytium-pi/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/mem.rs b/platforms/ax-plat-aarch64-phytium-pi/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/mem.rs rename to platforms/ax-plat-aarch64-phytium-pi/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/power.rs b/platforms/ax-plat-aarch64-phytium-pi/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-phytium-pi/src/power.rs rename to platforms/ax-plat-aarch64-phytium-pi/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/CHANGELOG.md b/platforms/ax-plat-aarch64-qemu-virt/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/CHANGELOG.md rename to platforms/ax-plat-aarch64-qemu-virt/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml b/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml similarity index 91% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml rename to platforms/ax-plat-aarch64-qemu-virt/Cargo.toml index 948e148731..677f394324 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml @@ -35,6 +35,13 @@ ax-plat = { workspace = true } axklib = { workspace = true, optional = true } mmio-api.workspace = true +[package.metadata.axplat] +platform = "aarch64-qemu-virt" +arch = "aarch64" +config = "axconfig.toml" +crate = "ax_plat_aarch64_qemu_virt" +default-for-arch = true + [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/README.md b/platforms/ax-plat-aarch64-qemu-virt/README.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/README.md rename to platforms/ax-plat-aarch64-qemu-virt/README.md index 243459cf22..971b4bd9ad 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/README.md +++ b/platforms/ax-plat-aarch64-qemu-virt/README.md @@ -35,7 +35,7 @@ ax-plat-aarch64-qemu-virt = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-aarch64-qemu-virt +cd platforms/ax-plat-aarch64-qemu-virt # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/README_CN.md b/platforms/ax-plat-aarch64-qemu-virt/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/README_CN.md rename to platforms/ax-plat-aarch64-qemu-virt/README_CN.md index 25107b1e5c..2a24e35e2d 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/README_CN.md +++ b/platforms/ax-plat-aarch64-qemu-virt/README_CN.md @@ -35,7 +35,7 @@ ax-plat-aarch64-qemu-virt = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-aarch64-qemu-virt +cd platforms/ax-plat-aarch64-qemu-virt # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/axconfig.toml b/platforms/ax-plat-aarch64-qemu-virt/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/axconfig.toml rename to platforms/ax-plat-aarch64-qemu-virt/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/build.rs b/platforms/ax-plat-aarch64-qemu-virt/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/build.rs rename to platforms/ax-plat-aarch64-qemu-virt/build.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/boot.rs b/platforms/ax-plat-aarch64-qemu-virt/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/boot.rs rename to platforms/ax-plat-aarch64-qemu-virt/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs b/platforms/ax-plat-aarch64-qemu-virt/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/drivers.rs rename to platforms/ax-plat-aarch64-qemu-virt/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/init.rs b/platforms/ax-plat-aarch64-qemu-virt/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/init.rs rename to platforms/ax-plat-aarch64-qemu-virt/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs b/platforms/ax-plat-aarch64-qemu-virt/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/lib.rs rename to platforms/ax-plat-aarch64-qemu-virt/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/mem.rs b/platforms/ax-plat-aarch64-qemu-virt/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/mem.rs rename to platforms/ax-plat-aarch64-qemu-virt/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/power.rs b/platforms/ax-plat-aarch64-qemu-virt/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-qemu-virt/src/power.rs rename to platforms/ax-plat-aarch64-qemu-virt/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/CHANGELOG.md b/platforms/ax-plat-aarch64-raspi/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/CHANGELOG.md rename to platforms/ax-plat-aarch64-raspi/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml b/platforms/ax-plat-aarch64-raspi/Cargo.toml similarity index 89% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml rename to platforms/ax-plat-aarch64-raspi/Cargo.toml index fd1626eefa..21ff757392 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/Cargo.toml +++ b/platforms/ax-plat-aarch64-raspi/Cargo.toml @@ -24,5 +24,11 @@ ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +[package.metadata.axplat] +platform = "aarch64-raspi" +arch = "aarch64" +config = "axconfig.toml" +crate = "ax_plat_aarch64_raspi" + [package.metadata.docs.rs] targets = ["aarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/README.md b/platforms/ax-plat-aarch64-raspi/README.md similarity index 97% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/README.md rename to platforms/ax-plat-aarch64-raspi/README.md index a63de23d32..6bf1eb5cd5 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/README.md +++ b/platforms/ax-plat-aarch64-raspi/README.md @@ -35,7 +35,7 @@ ax-plat-aarch64-raspi = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-aarch64-raspi +cd platforms/ax-plat-aarch64-raspi # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/README_CN.md b/platforms/ax-plat-aarch64-raspi/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/README_CN.md rename to platforms/ax-plat-aarch64-raspi/README_CN.md index 9b61d56cbe..1e85069ba9 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/README_CN.md +++ b/platforms/ax-plat-aarch64-raspi/README_CN.md @@ -35,7 +35,7 @@ ax-plat-aarch64-raspi = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-aarch64-raspi +cd platforms/ax-plat-aarch64-raspi # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/axconfig.toml b/platforms/ax-plat-aarch64-raspi/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/axconfig.toml rename to platforms/ax-plat-aarch64-raspi/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/build.rs b/platforms/ax-plat-aarch64-raspi/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/build.rs rename to platforms/ax-plat-aarch64-raspi/build.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/boot.rs b/platforms/ax-plat-aarch64-raspi/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/boot.rs rename to platforms/ax-plat-aarch64-raspi/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs b/platforms/ax-plat-aarch64-raspi/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/drivers.rs rename to platforms/ax-plat-aarch64-raspi/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/init.rs b/platforms/ax-plat-aarch64-raspi/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/init.rs rename to platforms/ax-plat-aarch64-raspi/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/lib.rs b/platforms/ax-plat-aarch64-raspi/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/lib.rs rename to platforms/ax-plat-aarch64-raspi/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/mem.rs b/platforms/ax-plat-aarch64-raspi/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/mem.rs rename to platforms/ax-plat-aarch64-raspi/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/mp.rs b/platforms/ax-plat-aarch64-raspi/src/mp.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/mp.rs rename to platforms/ax-plat-aarch64-raspi/src/mp.rs diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/src/power.rs b/platforms/ax-plat-aarch64-raspi/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-aarch64-raspi/src/power.rs rename to platforms/ax-plat-aarch64-raspi/src/power.rs diff --git a/platform/axplat-dyn/CHANGELOG.md b/platforms/ax-plat-dyn/CHANGELOG.md similarity index 100% rename from platform/axplat-dyn/CHANGELOG.md rename to platforms/ax-plat-dyn/CHANGELOG.md diff --git a/platform/axplat-dyn/Cargo.toml b/platforms/ax-plat-dyn/Cargo.toml similarity index 89% rename from platform/axplat-dyn/Cargo.toml rename to platforms/ax-plat-dyn/Cargo.toml index df69d8cfe2..ef216f627d 100644 --- a/platform/axplat-dyn/Cargo.toml +++ b/platforms/ax-plat-dyn/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "axplat-dyn" +name = "ax-plat-dyn" version = "0.6.2" authors = ["周睿 "] categories.workspace = true @@ -33,3 +33,9 @@ ax-percpu = { workspace = true, features = ["custom-base"] } rdrive.workspace = true somehal.workspace = true spin = "0.10" + +[package.metadata.axplat] +platform = "dyn" +arch = "aarch64" +crate = "ax_plat_dyn" +dynamic = true diff --git a/platform/axplat-dyn/build.rs b/platforms/ax-plat-dyn/build.rs similarity index 100% rename from platform/axplat-dyn/build.rs rename to platforms/ax-plat-dyn/build.rs diff --git a/platform/axplat-dyn/link.ld b/platforms/ax-plat-dyn/link.ld similarity index 100% rename from platform/axplat-dyn/link.ld rename to platforms/ax-plat-dyn/link.ld diff --git a/platform/axplat-dyn/src/boot.rs b/platforms/ax-plat-dyn/src/boot.rs similarity index 100% rename from platform/axplat-dyn/src/boot.rs rename to platforms/ax-plat-dyn/src/boot.rs diff --git a/platform/axplat-dyn/src/console.rs b/platforms/ax-plat-dyn/src/console.rs similarity index 100% rename from platform/axplat-dyn/src/console.rs rename to platforms/ax-plat-dyn/src/console.rs diff --git a/platform/axplat-dyn/src/drivers/mod.rs b/platforms/ax-plat-dyn/src/drivers/mod.rs similarity index 100% rename from platform/axplat-dyn/src/drivers/mod.rs rename to platforms/ax-plat-dyn/src/drivers/mod.rs diff --git a/platform/axplat-dyn/src/generic_timer.rs b/platforms/ax-plat-dyn/src/generic_timer.rs similarity index 100% rename from platform/axplat-dyn/src/generic_timer.rs rename to platforms/ax-plat-dyn/src/generic_timer.rs diff --git a/platform/axplat-dyn/src/init.rs b/platforms/ax-plat-dyn/src/init.rs similarity index 93% rename from platform/axplat-dyn/src/init.rs rename to platforms/ax-plat-dyn/src/init.rs index d68349a3e8..46d6be0532 100644 --- a/platform/axplat-dyn/src/init.rs +++ b/platforms/ax-plat-dyn/src/init.rs @@ -14,7 +14,7 @@ impl InitIf for InitIfImpl { #[cfg(all(target_arch = "aarch64", feature = "fp-simd"))] { ax_cpu::asm::enable_fp(); - debug!("axplat-dyn: fp/simd enabled"); + debug!("ax-plat-dyn: fp/simd enabled"); } somehal::timer::enable(); } @@ -26,7 +26,7 @@ impl InitIf for InitIfImpl { #[cfg(all(target_arch = "aarch64", feature = "fp-simd"))] { ax_cpu::asm::enable_fp(); - debug!("axplat-dyn: secondary fp/simd enabled"); + debug!("ax-plat-dyn: secondary fp/simd enabled"); } somehal::timer::enable(); } diff --git a/platform/axplat-dyn/src/irq.rs b/platforms/ax-plat-dyn/src/irq.rs similarity index 100% rename from platform/axplat-dyn/src/irq.rs rename to platforms/ax-plat-dyn/src/irq.rs diff --git a/platform/axplat-dyn/src/lib.rs b/platforms/ax-plat-dyn/src/lib.rs similarity index 100% rename from platform/axplat-dyn/src/lib.rs rename to platforms/ax-plat-dyn/src/lib.rs diff --git a/platform/axplat-dyn/src/mem.rs b/platforms/ax-plat-dyn/src/mem.rs similarity index 100% rename from platform/axplat-dyn/src/mem.rs rename to platforms/ax-plat-dyn/src/mem.rs diff --git a/platform/axplat-dyn/src/power.rs b/platforms/ax-plat-dyn/src/power.rs similarity index 100% rename from platform/axplat-dyn/src/power.rs rename to platforms/ax-plat-dyn/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/CHANGELOG.md b/platforms/ax-plat-loongarch64-qemu-virt/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/CHANGELOG.md rename to platforms/ax-plat-loongarch64-qemu-virt/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml b/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml similarity index 88% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml rename to platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml index 87fbda9b75..b0132ff62b 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml @@ -32,5 +32,12 @@ ax-plat = { workspace = true } axklib = { workspace = true, optional = true } mmio-api.workspace = true +[package.metadata.axplat] +platform = "loongarch64-qemu-virt" +arch = "loongarch64" +config = "axconfig.toml" +crate = "ax_plat_loongarch64_qemu_virt" +default-for-arch = true + [package.metadata.docs.rs] targets = ["loongarch64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/README.md b/platforms/ax-plat-loongarch64-qemu-virt/README.md similarity index 96% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/README.md rename to platforms/ax-plat-loongarch64-qemu-virt/README.md index d5697ce83b..c577e2b62c 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/README.md +++ b/platforms/ax-plat-loongarch64-qemu-virt/README.md @@ -35,7 +35,7 @@ ax-plat-loongarch64-qemu-virt = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-loongarch64-qemu-virt +cd platforms/ax-plat-loongarch64-qemu-virt # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/README_CN.md b/platforms/ax-plat-loongarch64-qemu-virt/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/README_CN.md rename to platforms/ax-plat-loongarch64-qemu-virt/README_CN.md index 80af42faa9..638bf2f129 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/README_CN.md +++ b/platforms/ax-plat-loongarch64-qemu-virt/README_CN.md @@ -35,7 +35,7 @@ ax-plat-loongarch64-qemu-virt = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-loongarch64-qemu-virt +cd platforms/ax-plat-loongarch64-qemu-virt # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/axconfig.toml b/platforms/ax-plat-loongarch64-qemu-virt/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/axconfig.toml rename to platforms/ax-plat-loongarch64-qemu-virt/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/build.rs b/platforms/ax-plat-loongarch64-qemu-virt/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/build.rs rename to platforms/ax-plat-loongarch64-qemu-virt/build.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/linker.lds.S b/platforms/ax-plat-loongarch64-qemu-virt/linker.lds.S similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/linker.lds.S rename to platforms/ax-plat-loongarch64-qemu-virt/linker.lds.S diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/boot.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/boot.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/console.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/console.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/console.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/console.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/drivers.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/init.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/init.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/irq.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/irq.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/irq.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/irq.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/irq/eiointc.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/irq/eiointc.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/irq/eiointc.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/irq/eiointc.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/irq/pch_pic.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/irq/pch_pic.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/irq/pch_pic.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/irq/pch_pic.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/lib.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/mem.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/mem.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/mp.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/mp.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/mp.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/mp.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/power.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/power.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/time.rs b/platforms/ax-plat-loongarch64-qemu-virt/src/time.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/src/time.rs rename to platforms/ax-plat-loongarch64-qemu-virt/src/time.rs diff --git a/components/axplat_crates/axplat-macros/Cargo.toml b/platforms/ax-plat-macros/Cargo.toml similarity index 100% rename from components/axplat_crates/axplat-macros/Cargo.toml rename to platforms/ax-plat-macros/Cargo.toml diff --git a/components/axplat_crates/axplat-macros/README.md b/platforms/ax-plat-macros/README.md similarity index 97% rename from components/axplat_crates/axplat-macros/README.md rename to platforms/ax-plat-macros/README.md index 1464cfcc04..ff13ec98be 100644 --- a/components/axplat_crates/axplat-macros/README.md +++ b/platforms/ax-plat-macros/README.md @@ -35,7 +35,7 @@ ax-plat-macros = "0.3.0" ```bash # Enter the crate directory -cd components/axplat_crates/axplat-macros +cd platforms/ax-plat-macros # Format code cargo fmt --all diff --git a/components/axplat_crates/axplat-macros/README_CN.md b/platforms/ax-plat-macros/README_CN.md similarity index 97% rename from components/axplat_crates/axplat-macros/README_CN.md rename to platforms/ax-plat-macros/README_CN.md index b71b650d03..066ab7d29f 100644 --- a/components/axplat_crates/axplat-macros/README_CN.md +++ b/platforms/ax-plat-macros/README_CN.md @@ -35,7 +35,7 @@ ax-plat-macros = "0.3.0" ```bash # 进入 crate 目录 -cd components/axplat_crates/axplat-macros +cd platforms/ax-plat-macros # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/axplat-macros/src/lib.rs b/platforms/ax-plat-macros/src/lib.rs similarity index 100% rename from components/axplat_crates/axplat-macros/src/lib.rs rename to platforms/ax-plat-macros/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/CHANGELOG.md b/platforms/ax-plat-riscv64-qemu-virt/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/CHANGELOG.md rename to platforms/ax-plat-riscv64-qemu-virt/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml b/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml similarity index 89% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml rename to platforms/ax-plat-riscv64-qemu-virt/Cargo.toml index 6cdc7a6442..12bdbd40fa 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml @@ -34,5 +34,12 @@ ax-plat = { workspace = true } axklib = { workspace = true, optional = true } mmio-api.workspace = true +[package.metadata.axplat] +platform = "riscv64-qemu-virt" +arch = "riscv64" +config = "axconfig.toml" +crate = "ax_plat_riscv64_qemu_virt" +default-for-arch = true + [package.metadata.docs.rs] targets = ["riscv64gc-unknown-none-elf"] diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README.md b/platforms/ax-plat-riscv64-qemu-virt/README.md similarity index 96% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README.md rename to platforms/ax-plat-riscv64-qemu-virt/README.md index 91f3eac92c..c2a38f75a9 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README.md +++ b/platforms/ax-plat-riscv64-qemu-virt/README.md @@ -35,7 +35,7 @@ ax-plat-riscv64-qemu-virt = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-riscv64-qemu-virt +cd platforms/ax-plat-riscv64-qemu-virt # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README_CN.md b/platforms/ax-plat-riscv64-qemu-virt/README_CN.md similarity index 96% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README_CN.md rename to platforms/ax-plat-riscv64-qemu-virt/README_CN.md index 3a43ab384a..84d37e81bb 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/README_CN.md +++ b/platforms/ax-plat-riscv64-qemu-virt/README_CN.md @@ -35,7 +35,7 @@ ax-plat-riscv64-qemu-virt = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-riscv64-qemu-virt +cd platforms/ax-plat-riscv64-qemu-virt # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/axconfig.toml b/platforms/ax-plat-riscv64-qemu-virt/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/axconfig.toml rename to platforms/ax-plat-riscv64-qemu-virt/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/build.rs b/platforms/ax-plat-riscv64-qemu-virt/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/build.rs rename to platforms/ax-plat-riscv64-qemu-virt/build.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/boot.rs b/platforms/ax-plat-riscv64-qemu-virt/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/boot.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/console.rs b/platforms/ax-plat-riscv64-qemu-virt/src/console.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/console.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/console.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs b/platforms/ax-plat-riscv64-qemu-virt/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/drivers.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/init.rs b/platforms/ax-plat-riscv64-qemu-virt/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/init.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/irq.rs b/platforms/ax-plat-riscv64-qemu-virt/src/irq.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/irq.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/irq.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs b/platforms/ax-plat-riscv64-qemu-virt/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/lib.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/mem.rs b/platforms/ax-plat-riscv64-qemu-virt/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/mem.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/power.rs b/platforms/ax-plat-riscv64-qemu-virt/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/power.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/time.rs b/platforms/ax-plat-riscv64-qemu-virt/src/time.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-qemu-virt/src/time.rs rename to platforms/ax-plat-riscv64-qemu-virt/src/time.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/CHANGELOG.md b/platforms/ax-plat-riscv64-sg2002/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/CHANGELOG.md rename to platforms/ax-plat-riscv64-sg2002/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml b/platforms/ax-plat-riscv64-sg2002/Cargo.toml similarity index 91% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml rename to platforms/ax-plat-riscv64-sg2002/Cargo.toml index f06e70966d..c09cd6fae9 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/Cargo.toml +++ b/platforms/ax-plat-riscv64-sg2002/Cargo.toml @@ -39,5 +39,11 @@ ax-riscv-plic = { workspace = true, optional = true } riscv = "0.16" sbi-rt = { version = "0.0.3", features = ["legacy"] } +[package.metadata.axplat] +platform = "riscv64-sg2002" +arch = "riscv64" +config = "axconfig.toml" +crate = "ax_plat_riscv64_sg2002" + [package.metadata.docs.rs] targets = ["riscv64gc-unknown-none-elf"] diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/README.md b/platforms/ax-plat-riscv64-sg2002/README.md similarity index 97% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/README.md rename to platforms/ax-plat-riscv64-sg2002/README.md index 37f737ea33..49316e8a5e 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/README.md +++ b/platforms/ax-plat-riscv64-sg2002/README.md @@ -45,7 +45,7 @@ fn kernel_main(cpu_id: usize, arg: usize) -> ! { ```rust // Can be located at any dependency crate. -extern crate axplat_riscv64_sg2002; +extern crate ax_plat_riscv64_sg2002; ``` #### 3. Use a linker script like the following diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/axconfig.toml b/platforms/ax-plat-riscv64-sg2002/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/axconfig.toml rename to platforms/ax-plat-riscv64-sg2002/axconfig.toml diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/build.rs b/platforms/ax-plat-riscv64-sg2002/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/build.rs rename to platforms/ax-plat-riscv64-sg2002/build.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/boot.rs b/platforms/ax-plat-riscv64-sg2002/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/boot.rs rename to platforms/ax-plat-riscv64-sg2002/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/console.rs b/platforms/ax-plat-riscv64-sg2002/src/console.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/console.rs rename to platforms/ax-plat-riscv64-sg2002/src/console.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/cvsd.rs b/platforms/ax-plat-riscv64-sg2002/src/drivers/cvsd.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/cvsd.rs rename to platforms/ax-plat-riscv64-sg2002/src/drivers/cvsd.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/mod.rs b/platforms/ax-plat-riscv64-sg2002/src/drivers/mod.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/drivers/mod.rs rename to platforms/ax-plat-riscv64-sg2002/src/drivers/mod.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/init.rs b/platforms/ax-plat-riscv64-sg2002/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/init.rs rename to platforms/ax-plat-riscv64-sg2002/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/irq.rs b/platforms/ax-plat-riscv64-sg2002/src/irq.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/irq.rs rename to platforms/ax-plat-riscv64-sg2002/src/irq.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs b/platforms/ax-plat-riscv64-sg2002/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/lib.rs rename to platforms/ax-plat-riscv64-sg2002/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/mem.rs b/platforms/ax-plat-riscv64-sg2002/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/mem.rs rename to platforms/ax-plat-riscv64-sg2002/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/power.rs b/platforms/ax-plat-riscv64-sg2002/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/power.rs rename to platforms/ax-plat-riscv64-sg2002/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/src/time.rs b/platforms/ax-plat-riscv64-sg2002/src/time.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-riscv64-sg2002/src/time.rs rename to platforms/ax-plat-riscv64-sg2002/src/time.rs diff --git a/platform/riscv64-visionfive2/CHANGELOG.md b/platforms/ax-plat-riscv64-visionfive2/CHANGELOG.md similarity index 100% rename from platform/riscv64-visionfive2/CHANGELOG.md rename to platforms/ax-plat-riscv64-visionfive2/CHANGELOG.md diff --git a/platform/riscv64-visionfive2/Cargo.toml b/platforms/ax-plat-riscv64-visionfive2/Cargo.toml similarity index 85% rename from platform/riscv64-visionfive2/Cargo.toml rename to platforms/ax-plat-riscv64-visionfive2/Cargo.toml index 7caf1b9d25..23b6dabb83 100644 --- a/platform/riscv64-visionfive2/Cargo.toml +++ b/platforms/ax-plat-riscv64-visionfive2/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "axplat-riscv64-visionfive2" +name = "ax-plat-riscv64-visionfive2" version = "0.1.4" edition.workspace = true authors = ["朝倉水希 "] @@ -29,5 +29,11 @@ riscv_goldfish = { version = "0.1", optional = true } sbi-rt = { version = "0.0.3", features = ["legacy"] } uart_16550 = "0.4.0" +[package.metadata.axplat] +platform = "riscv64-visionfive2" +arch = "riscv64" +config = "axconfig.toml" +crate = "ax_plat_riscv64_visionfive2" + [package.metadata.docs.rs] targets = ["riscv64gc-unknown-none-elf"] diff --git a/platform/riscv64-visionfive2/README.md b/platforms/ax-plat-riscv64-visionfive2/README.md similarity index 100% rename from platform/riscv64-visionfive2/README.md rename to platforms/ax-plat-riscv64-visionfive2/README.md diff --git a/platform/riscv64-visionfive2/axconfig.toml b/platforms/ax-plat-riscv64-visionfive2/axconfig.toml similarity index 98% rename from platform/riscv64-visionfive2/axconfig.toml rename to platforms/ax-plat-riscv64-visionfive2/axconfig.toml index b2fd947285..587941a310 100644 --- a/platform/riscv64-visionfive2/axconfig.toml +++ b/platforms/ax-plat-riscv64-visionfive2/axconfig.toml @@ -3,7 +3,7 @@ arch = "riscv64" # str # Platform identifier. platform = "visionfive2" # str # Platform package. -package = "axplat-riscv64-visionfive2" # str +package = "ax-plat-riscv64-visionfive2" # str # # Platform configs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/build.rs b/platforms/ax-plat-riscv64-visionfive2/build.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/build.rs rename to platforms/ax-plat-riscv64-visionfive2/build.rs diff --git a/platform/riscv64-visionfive2/src/boot.rs b/platforms/ax-plat-riscv64-visionfive2/src/boot.rs similarity index 100% rename from platform/riscv64-visionfive2/src/boot.rs rename to platforms/ax-plat-riscv64-visionfive2/src/boot.rs diff --git a/platform/riscv64-visionfive2/src/console.rs b/platforms/ax-plat-riscv64-visionfive2/src/console.rs similarity index 100% rename from platform/riscv64-visionfive2/src/console.rs rename to platforms/ax-plat-riscv64-visionfive2/src/console.rs diff --git a/platform/riscv64-visionfive2/src/drivers.rs b/platforms/ax-plat-riscv64-visionfive2/src/drivers.rs similarity index 100% rename from platform/riscv64-visionfive2/src/drivers.rs rename to platforms/ax-plat-riscv64-visionfive2/src/drivers.rs diff --git a/platform/riscv64-visionfive2/src/init.rs b/platforms/ax-plat-riscv64-visionfive2/src/init.rs similarity index 100% rename from platform/riscv64-visionfive2/src/init.rs rename to platforms/ax-plat-riscv64-visionfive2/src/init.rs diff --git a/platform/riscv64-visionfive2/src/irq.rs b/platforms/ax-plat-riscv64-visionfive2/src/irq.rs similarity index 100% rename from platform/riscv64-visionfive2/src/irq.rs rename to platforms/ax-plat-riscv64-visionfive2/src/irq.rs diff --git a/platform/riscv64-visionfive2/src/lib.rs b/platforms/ax-plat-riscv64-visionfive2/src/lib.rs similarity index 100% rename from platform/riscv64-visionfive2/src/lib.rs rename to platforms/ax-plat-riscv64-visionfive2/src/lib.rs diff --git a/platform/riscv64-visionfive2/src/mem.rs b/platforms/ax-plat-riscv64-visionfive2/src/mem.rs similarity index 100% rename from platform/riscv64-visionfive2/src/mem.rs rename to platforms/ax-plat-riscv64-visionfive2/src/mem.rs diff --git a/platform/riscv64-visionfive2/src/power.rs b/platforms/ax-plat-riscv64-visionfive2/src/power.rs similarity index 100% rename from platform/riscv64-visionfive2/src/power.rs rename to platforms/ax-plat-riscv64-visionfive2/src/power.rs diff --git a/platform/riscv64-visionfive2/src/time.rs b/platforms/ax-plat-riscv64-visionfive2/src/time.rs similarity index 100% rename from platform/riscv64-visionfive2/src/time.rs rename to platforms/ax-plat-riscv64-visionfive2/src/time.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/CHANGELOG.md b/platforms/ax-plat-x86-pc/CHANGELOG.md similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/CHANGELOG.md rename to platforms/ax-plat-x86-pc/CHANGELOG.md diff --git a/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml b/platforms/ax-plat-x86-pc/Cargo.toml similarity index 90% rename from components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml rename to platforms/ax-plat-x86-pc/Cargo.toml index 808ec12786..7c035e9652 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/Cargo.toml +++ b/platforms/ax-plat-x86-pc/Cargo.toml @@ -38,5 +38,12 @@ raw-cpuid = "11.5" uart_16550 = "0.5" x86_rtc = { version = "0.1", optional = true } +[package.metadata.axplat] +platform = "x86-pc" +arch = "x86_64" +config = "axconfig.toml" +crate = "ax_plat_x86_pc" +default-for-arch = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-none"] diff --git a/components/axplat_crates/platforms/axplat-x86-pc/README.md b/platforms/ax-plat-x86-pc/README.md similarity index 97% rename from components/axplat_crates/platforms/axplat-x86-pc/README.md rename to platforms/ax-plat-x86-pc/README.md index b2a7b5372b..3353fe3787 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/README.md +++ b/platforms/ax-plat-x86-pc/README.md @@ -35,7 +35,7 @@ ax-plat-x86-pc = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/platforms/axplat-x86-pc +cd platforms/ax-plat-x86-pc # Format code cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-x86-pc/README_CN.md b/platforms/ax-plat-x86-pc/README_CN.md similarity index 97% rename from components/axplat_crates/platforms/axplat-x86-pc/README_CN.md rename to platforms/ax-plat-x86-pc/README_CN.md index 04bf261365..63f4eadae8 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/README_CN.md +++ b/platforms/ax-plat-x86-pc/README_CN.md @@ -35,7 +35,7 @@ ax-plat-x86-pc = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/platforms/axplat-x86-pc +cd platforms/ax-plat-x86-pc # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/platforms/axplat-x86-pc/axconfig.toml b/platforms/ax-plat-x86-pc/axconfig.toml similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/axconfig.toml rename to platforms/ax-plat-x86-pc/axconfig.toml diff --git a/platform/riscv64-visionfive2/build.rs b/platforms/ax-plat-x86-pc/build.rs similarity index 100% rename from platform/riscv64-visionfive2/build.rs rename to platforms/ax-plat-x86-pc/build.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/ap_start.S b/platforms/ax-plat-x86-pc/src/ap_start.S similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/ap_start.S rename to platforms/ax-plat-x86-pc/src/ap_start.S diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/apic.rs b/platforms/ax-plat-x86-pc/src/apic.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/apic.rs rename to platforms/ax-plat-x86-pc/src/apic.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/boot.rs b/platforms/ax-plat-x86-pc/src/boot.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/boot.rs rename to platforms/ax-plat-x86-pc/src/boot.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/console.rs b/platforms/ax-plat-x86-pc/src/console.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/console.rs rename to platforms/ax-plat-x86-pc/src/console.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs b/platforms/ax-plat-x86-pc/src/drivers.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/drivers.rs rename to platforms/ax-plat-x86-pc/src/drivers.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/init.rs b/platforms/ax-plat-x86-pc/src/init.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/init.rs rename to platforms/ax-plat-x86-pc/src/init.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs b/platforms/ax-plat-x86-pc/src/lib.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/lib.rs rename to platforms/ax-plat-x86-pc/src/lib.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/mem.rs b/platforms/ax-plat-x86-pc/src/mem.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/mem.rs rename to platforms/ax-plat-x86-pc/src/mem.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/mp.rs b/platforms/ax-plat-x86-pc/src/mp.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/mp.rs rename to platforms/ax-plat-x86-pc/src/mp.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/multiboot.S b/platforms/ax-plat-x86-pc/src/multiboot.S similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/multiboot.S rename to platforms/ax-plat-x86-pc/src/multiboot.S diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/power.rs b/platforms/ax-plat-x86-pc/src/power.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/power.rs rename to platforms/ax-plat-x86-pc/src/power.rs diff --git a/components/axplat_crates/platforms/axplat-x86-pc/src/time.rs b/platforms/ax-plat-x86-pc/src/time.rs similarity index 100% rename from components/axplat_crates/platforms/axplat-x86-pc/src/time.rs rename to platforms/ax-plat-x86-pc/src/time.rs diff --git a/platform/x86-qemu-q35/.gitignore b/platforms/ax-plat-x86-qemu-q35/.gitignore similarity index 100% rename from platform/x86-qemu-q35/.gitignore rename to platforms/ax-plat-x86-qemu-q35/.gitignore diff --git a/platform/x86-qemu-q35/CHANGELOG.md b/platforms/ax-plat-x86-qemu-q35/CHANGELOG.md similarity index 100% rename from platform/x86-qemu-q35/CHANGELOG.md rename to platforms/ax-plat-x86-qemu-q35/CHANGELOG.md diff --git a/platform/x86-qemu-q35/Cargo.toml b/platforms/ax-plat-x86-qemu-q35/Cargo.toml similarity index 89% rename from platform/x86-qemu-q35/Cargo.toml rename to platforms/ax-plat-x86-qemu-q35/Cargo.toml index 9f840eadb3..f4cd07df20 100644 --- a/platform/x86-qemu-q35/Cargo.toml +++ b/platforms/ax-plat-x86-qemu-q35/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "axplat-x86-qemu-q35" +name = "ax-plat-x86-qemu-q35" version = "0.4.8" edition.workspace = true authors = ["The Axvisor Team"] @@ -45,5 +45,11 @@ x86 = "0.52" x86_64 = "0.15.2" x86_rtc = {version = "0.1", optional = true} +[package.metadata.axplat] +platform = "x86-qemu-q35" +arch = "x86_64" +config = "axconfig.toml" +crate = "ax_plat_x86_qemu_q35" + [package.metadata.docs.rs] targets = ["x86_64-unknown-none"] diff --git a/platform/x86-qemu-q35/LICENSE b/platforms/ax-plat-x86-qemu-q35/LICENSE similarity index 100% rename from platform/x86-qemu-q35/LICENSE rename to platforms/ax-plat-x86-qemu-q35/LICENSE diff --git a/platform/x86-qemu-q35/README.md b/platforms/ax-plat-x86-qemu-q35/README.md similarity index 98% rename from platform/x86-qemu-q35/README.md rename to platforms/ax-plat-x86-qemu-q35/README.md index ffb8e387c4..3de6737ade 100644 --- a/platform/x86-qemu-q35/README.md +++ b/platforms/ax-plat-x86-qemu-q35/README.md @@ -1,4 +1,4 @@ -# axplat-x86-qemu-q35 +# ax-plat-x86-qemu-q35 Hardware platform implementation for x86_64 QEMU Q35 chipset, designed for the Axvisor hypervisor framework. @@ -24,7 +24,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -axplat-x86-qemu-q35 = "0.1" +ax-plat-x86-qemu-q35 = "0.1" ``` ### Feature Flags diff --git a/platform/x86-qemu-q35/axconfig.toml b/platforms/ax-plat-x86-qemu-q35/axconfig.toml similarity index 98% rename from platform/x86-qemu-q35/axconfig.toml rename to platforms/ax-plat-x86-qemu-q35/axconfig.toml index 666a651067..0d71ffe00d 100644 --- a/platform/x86-qemu-q35/axconfig.toml +++ b/platforms/ax-plat-x86-qemu-q35/axconfig.toml @@ -3,7 +3,7 @@ arch = "x86_64" # str # Platform identifier. platform = "x86-qemu-q35" # str # Platform package. -package = "axplat-x86-qemu-q35" # str +package = "ax-plat-x86-qemu-q35" # str # # Platform configs diff --git a/platform/x86-qemu-q35/build.rs b/platforms/ax-plat-x86-qemu-q35/build.rs similarity index 100% rename from platform/x86-qemu-q35/build.rs rename to platforms/ax-plat-x86-qemu-q35/build.rs diff --git a/platform/x86-qemu-q35/linker.lds.S b/platforms/ax-plat-x86-qemu-q35/linker.lds.S similarity index 100% rename from platform/x86-qemu-q35/linker.lds.S rename to platforms/ax-plat-x86-qemu-q35/linker.lds.S diff --git a/platform/x86-qemu-q35/src/ap_start.S b/platforms/ax-plat-x86-qemu-q35/src/ap_start.S similarity index 100% rename from platform/x86-qemu-q35/src/ap_start.S rename to platforms/ax-plat-x86-qemu-q35/src/ap_start.S diff --git a/platform/x86-qemu-q35/src/apic.rs b/platforms/ax-plat-x86-qemu-q35/src/apic.rs similarity index 100% rename from platform/x86-qemu-q35/src/apic.rs rename to platforms/ax-plat-x86-qemu-q35/src/apic.rs diff --git a/platform/x86-qemu-q35/src/boot.rs b/platforms/ax-plat-x86-qemu-q35/src/boot.rs similarity index 100% rename from platform/x86-qemu-q35/src/boot.rs rename to platforms/ax-plat-x86-qemu-q35/src/boot.rs diff --git a/platform/x86-qemu-q35/src/console.rs b/platforms/ax-plat-x86-qemu-q35/src/console.rs similarity index 100% rename from platform/x86-qemu-q35/src/console.rs rename to platforms/ax-plat-x86-qemu-q35/src/console.rs diff --git a/platform/x86-qemu-q35/src/drivers.rs b/platforms/ax-plat-x86-qemu-q35/src/drivers.rs similarity index 100% rename from platform/x86-qemu-q35/src/drivers.rs rename to platforms/ax-plat-x86-qemu-q35/src/drivers.rs diff --git a/platform/x86-qemu-q35/src/init.rs b/platforms/ax-plat-x86-qemu-q35/src/init.rs similarity index 100% rename from platform/x86-qemu-q35/src/init.rs rename to platforms/ax-plat-x86-qemu-q35/src/init.rs diff --git a/platform/x86-qemu-q35/src/lib.rs b/platforms/ax-plat-x86-qemu-q35/src/lib.rs similarity index 100% rename from platform/x86-qemu-q35/src/lib.rs rename to platforms/ax-plat-x86-qemu-q35/src/lib.rs diff --git a/platform/x86-qemu-q35/src/mem.rs b/platforms/ax-plat-x86-qemu-q35/src/mem.rs similarity index 100% rename from platform/x86-qemu-q35/src/mem.rs rename to platforms/ax-plat-x86-qemu-q35/src/mem.rs diff --git a/platform/x86-qemu-q35/src/mp.rs b/platforms/ax-plat-x86-qemu-q35/src/mp.rs similarity index 100% rename from platform/x86-qemu-q35/src/mp.rs rename to platforms/ax-plat-x86-qemu-q35/src/mp.rs diff --git a/platform/x86-qemu-q35/src/multiboot.S b/platforms/ax-plat-x86-qemu-q35/src/multiboot.S similarity index 100% rename from platform/x86-qemu-q35/src/multiboot.S rename to platforms/ax-plat-x86-qemu-q35/src/multiboot.S diff --git a/platform/x86-qemu-q35/src/power.rs b/platforms/ax-plat-x86-qemu-q35/src/power.rs similarity index 100% rename from platform/x86-qemu-q35/src/power.rs rename to platforms/ax-plat-x86-qemu-q35/src/power.rs diff --git a/platform/x86-qemu-q35/src/time.rs b/platforms/ax-plat-x86-qemu-q35/src/time.rs similarity index 100% rename from platform/x86-qemu-q35/src/time.rs rename to platforms/ax-plat-x86-qemu-q35/src/time.rs diff --git a/components/axplat_crates/axplat/CHANGELOG.md b/platforms/ax-plat/CHANGELOG.md similarity index 100% rename from components/axplat_crates/axplat/CHANGELOG.md rename to platforms/ax-plat/CHANGELOG.md diff --git a/components/axplat_crates/axplat/Cargo.toml b/platforms/ax-plat/Cargo.toml similarity index 100% rename from components/axplat_crates/axplat/Cargo.toml rename to platforms/ax-plat/Cargo.toml diff --git a/components/axplat_crates/axplat/README.md b/platforms/ax-plat/README.md similarity index 98% rename from components/axplat_crates/axplat/README.md rename to platforms/ax-plat/README.md index fc4d2b5ba2..318fbdde31 100644 --- a/components/axplat_crates/axplat/README.md +++ b/platforms/ax-plat/README.md @@ -35,7 +35,7 @@ ax-plat = "0.5.1" ```bash # Enter the crate directory -cd components/axplat_crates/axplat +cd platforms/ax-plat # Format code cargo fmt --all diff --git a/components/axplat_crates/axplat/README_CN.md b/platforms/ax-plat/README_CN.md similarity index 97% rename from components/axplat_crates/axplat/README_CN.md rename to platforms/ax-plat/README_CN.md index 0ea9151912..dd970bd7e1 100644 --- a/components/axplat_crates/axplat/README_CN.md +++ b/platforms/ax-plat/README_CN.md @@ -35,7 +35,7 @@ ax-plat = "0.5.1" ```bash # 进入 crate 目录 -cd components/axplat_crates/axplat +cd platforms/ax-plat # 代码格式化 cargo fmt --all diff --git a/components/axplat_crates/axplat/src/console.rs b/platforms/ax-plat/src/console.rs similarity index 100% rename from components/axplat_crates/axplat/src/console.rs rename to platforms/ax-plat/src/console.rs diff --git a/components/axplat_crates/axplat/src/init.rs b/platforms/ax-plat/src/init.rs similarity index 100% rename from components/axplat_crates/axplat/src/init.rs rename to platforms/ax-plat/src/init.rs diff --git a/components/axplat_crates/axplat/src/irq.rs b/platforms/ax-plat/src/irq.rs similarity index 100% rename from components/axplat_crates/axplat/src/irq.rs rename to platforms/ax-plat/src/irq.rs diff --git a/components/axplat_crates/axplat/src/lib.rs b/platforms/ax-plat/src/lib.rs similarity index 100% rename from components/axplat_crates/axplat/src/lib.rs rename to platforms/ax-plat/src/lib.rs diff --git a/components/axplat_crates/axplat/src/mem.rs b/platforms/ax-plat/src/mem.rs similarity index 100% rename from components/axplat_crates/axplat/src/mem.rs rename to platforms/ax-plat/src/mem.rs diff --git a/components/axplat_crates/axplat/src/percpu.rs b/platforms/ax-plat/src/percpu.rs similarity index 100% rename from components/axplat_crates/axplat/src/percpu.rs rename to platforms/ax-plat/src/percpu.rs diff --git a/components/axplat_crates/axplat/src/power.rs b/platforms/ax-plat/src/power.rs similarity index 100% rename from components/axplat_crates/axplat/src/power.rs rename to platforms/ax-plat/src/power.rs diff --git a/components/axplat_crates/axplat/src/time.rs b/platforms/ax-plat/src/time.rs similarity index 100% rename from components/axplat_crates/axplat/src/time.rs rename to platforms/ax-plat/src/time.rs diff --git a/platform/somehal/CHANGELOG.md b/platforms/somehal/CHANGELOG.md similarity index 100% rename from platform/somehal/CHANGELOG.md rename to platforms/somehal/CHANGELOG.md diff --git a/platform/somehal/Cargo.toml b/platforms/somehal/Cargo.toml similarity index 100% rename from platform/somehal/Cargo.toml rename to platforms/somehal/Cargo.toml diff --git a/platform/somehal/README.md b/platforms/somehal/README.md similarity index 99% rename from platform/somehal/README.md rename to platforms/somehal/README.md index a09a947586..b6bb416211 100644 --- a/platform/somehal/README.md +++ b/platforms/somehal/README.md @@ -308,7 +308,7 @@ somehal = { version = "0.5", features = ["hv", "uspace"] } #### 1. 定义内核类型 ```rust -// platform/my-platform/src/lib.rs +// platforms/my-platforms/src/lib.rs use somehal::KernelOp; pub struct Kernel; diff --git a/platform/somehal/build.rs b/platforms/somehal/build.rs similarity index 100% rename from platform/somehal/build.rs rename to platforms/somehal/build.rs diff --git a/platform/somehal/link.ld b/platforms/somehal/link.ld similarity index 100% rename from platform/somehal/link.ld rename to platforms/somehal/link.ld diff --git a/platform/somehal/src/arch/aarch64/gic/mod.rs b/platforms/somehal/src/arch/aarch64/gic/mod.rs similarity index 100% rename from platform/somehal/src/arch/aarch64/gic/mod.rs rename to platforms/somehal/src/arch/aarch64/gic/mod.rs diff --git a/platform/somehal/src/arch/aarch64/gic/v2.rs b/platforms/somehal/src/arch/aarch64/gic/v2.rs similarity index 100% rename from platform/somehal/src/arch/aarch64/gic/v2.rs rename to platforms/somehal/src/arch/aarch64/gic/v2.rs diff --git a/platform/somehal/src/arch/aarch64/gic/v3.rs b/platforms/somehal/src/arch/aarch64/gic/v3.rs similarity index 100% rename from platform/somehal/src/arch/aarch64/gic/v3.rs rename to platforms/somehal/src/arch/aarch64/gic/v3.rs diff --git a/platform/somehal/src/arch/aarch64/mod.rs b/platforms/somehal/src/arch/aarch64/mod.rs similarity index 100% rename from platform/somehal/src/arch/aarch64/mod.rs rename to platforms/somehal/src/arch/aarch64/mod.rs diff --git a/platform/somehal/src/arch/aarch64/systick.rs b/platforms/somehal/src/arch/aarch64/systick.rs similarity index 100% rename from platform/somehal/src/arch/aarch64/systick.rs rename to platforms/somehal/src/arch/aarch64/systick.rs diff --git a/platform/somehal/src/arch/loongarch64/mod.rs b/platforms/somehal/src/arch/loongarch64/mod.rs similarity index 100% rename from platform/somehal/src/arch/loongarch64/mod.rs rename to platforms/somehal/src/arch/loongarch64/mod.rs diff --git a/platform/somehal/src/arch/riscv64/mod.rs b/platforms/somehal/src/arch/riscv64/mod.rs similarity index 100% rename from platform/somehal/src/arch/riscv64/mod.rs rename to platforms/somehal/src/arch/riscv64/mod.rs diff --git a/platform/somehal/src/arch/x86_64/mod.rs b/platforms/somehal/src/arch/x86_64/mod.rs similarity index 100% rename from platform/somehal/src/arch/x86_64/mod.rs rename to platforms/somehal/src/arch/x86_64/mod.rs diff --git a/platform/somehal/src/common.rs b/platforms/somehal/src/common.rs similarity index 100% rename from platform/somehal/src/common.rs rename to platforms/somehal/src/common.rs diff --git a/platform/somehal/src/driver.rs b/platforms/somehal/src/driver.rs similarity index 100% rename from platform/somehal/src/driver.rs rename to platforms/somehal/src/driver.rs diff --git a/platform/somehal/src/irq.rs b/platforms/somehal/src/irq.rs similarity index 100% rename from platform/somehal/src/irq.rs rename to platforms/somehal/src/irq.rs diff --git a/platform/somehal/src/lib.rs b/platforms/somehal/src/lib.rs similarity index 100% rename from platform/somehal/src/lib.rs rename to platforms/somehal/src/lib.rs diff --git a/platform/somehal/src/setup.rs b/platforms/somehal/src/setup.rs similarity index 100% rename from platform/somehal/src/setup.rs rename to platforms/somehal/src/setup.rs diff --git a/reports/external-spin-audit.md b/reports/external-spin-audit.md index d972c71b75..a7e9a9cbb1 100644 --- a/reports/external-spin-audit.md +++ b/reports/external-spin-audit.md @@ -62,7 +62,7 @@ axaddrspace 0.5.10 -> spin 0.10.0 axbacktrace 0.3.9 -> spin 0.10.0 axdevice 0.4.9 -> spin 0.10.0 axfs-ng-vfs 0.4.1 -> spin 0.10.0 -axplat-dyn 0.6.1 -> spin 0.10.0 +ax-plat-dyn 0.6.1 -> spin 0.10.0 axpoll 0.3.9 -> spin 0.10.0 axvisor 0.5.7 -> spin 0.10.0 axvm 0.5.8 -> spin 0.10.0 @@ -119,7 +119,7 @@ components/axdriver_crates/axdriver_net/Cargo.toml:29: spin = "0.9" components/axfs-ng-vfs/Cargo.toml:20: spin = { version = "0.10", default-features = false, features = ["mutex"] } components/axfs_crates/axfs_devfs/Cargo.toml:14: spin = "0.9" components/axfs_crates/axfs_ramfs/Cargo.toml:14: spin = "0.9" -components/axplat_crates/platforms/axplat-aarch64-peripherals/Cargo.toml:18: spin = "0.10" +platforms/ax-plat-aarch64-peripherals/Cargo.toml:18: spin = "0.10" components/axpoll/Cargo.toml:22: spin = { version = "0.10", default-features = false, features = ["lazy", ...] } components/axvm/Cargo.toml:21: spin = "0.10" components/loongarch_vcpu/Cargo.toml:17: spin = "0.10" @@ -144,8 +144,8 @@ os/arceos/modules/axfs/Cargo.toml:27: spin = { workspace = true } os/arceos/modules/axnet-ng/Cargo.toml:34: spin = { workspace = true } os/arceos/modules/axtask/Cargo.toml:70: spin = { workspace = true, optional = true } os/axvisor/Cargo.toml:57: spin = "0.10" -platform/axplat-dyn/Cargo.toml:84: spin = "0.10" -platform/somehal/Cargo.toml:31: spin = "0.10" +platforms/ax-plat-dyn/Cargo.toml:84: spin = "0.10" +platforms/somehal/Cargo.toml:31: spin = "0.10" ``` The workspace root also defines: @@ -170,7 +170,7 @@ Grouped by area: ```text 17 os/arceos/modules 14 os/StarryOS/kernel - 8 platform/axplat-dyn/src + 8 platforms/ax-plat-dyn/src 8 drivers/usb/usb-host 4 drivers/rdrive/src 4 components/arm_vgic/src @@ -180,7 +180,7 @@ Grouped by area: 2 drivers/tpu/sg2002-tpu 2 drivers/firmware/arm-scmi-rs 2 components/axfs_crates/axfs_devfs - 1 platform/somehal/src + 1 platforms/somehal/src 1 drivers/soc/rockchip 1 drivers/serial/some-serial 1 drivers/net/realtek-rtl8125 @@ -272,12 +272,12 @@ os/arceos/modules/axnet/src/smoltcp_impl/udp.rs os/axvisor/src/hal/arch/loongarch64/mod.rs os/axvisor/src/vmm/fdt/mod.rs os/axvisor/src/vmm/vm_list.rs -platform/axplat-dyn/src/drivers/blk/mod.rs -platform/axplat-dyn/src/drivers/blk/virtio_pci.rs -platform/axplat-dyn/src/drivers/mod.rs -platform/axplat-dyn/src/drivers/net/virtio_pci.rs -platform/axplat-dyn/src/drivers/pci.rs -platform/axplat-dyn/src/drivers/soc/scmi.rs +platforms/ax-plat-dyn/src/drivers/blk/mod.rs +platforms/ax-plat-dyn/src/drivers/blk/virtio_pci.rs +platforms/ax-plat-dyn/src/drivers/mod.rs +platforms/ax-plat-dyn/src/drivers/net/virtio_pci.rs +platforms/ax-plat-dyn/src/drivers/pci.rs +platforms/ax-plat-dyn/src/drivers/soc/scmi.rs ``` ## Initialization-only source uses @@ -307,9 +307,9 @@ os/arceos/modules/axhal/src/mem.rs os/arceos/modules/axnet-ng/src/lib.rs os/arceos/modules/axnet-ng/src/tcp.rs os/arceos/modules/axtask/src/api.rs -platform/axplat-dyn/src/drivers/blk/rockchip_mmc.rs -platform/axplat-dyn/src/mem.rs -platform/somehal/src/arch/aarch64/systick.rs +platforms/ax-plat-dyn/src/drivers/blk/rockchip_mmc.rs +platforms/ax-plat-dyn/src/mem.rs +platforms/somehal/src/arch/aarch64/systick.rs ``` ## Test-only source uses diff --git a/reports/external-spin-migration-plan.md b/reports/external-spin-migration-plan.md index 8e4ae0874a..ca4b240a59 100644 --- a/reports/external-spin-migration-plan.md +++ b/reports/external-spin-migration-plan.md @@ -222,7 +222,7 @@ The production migration covered these groups: - networking and Starry runtime paths: `ax-net-ng`, Starry timer/netlink/usbfs and camera locks; - virtualization and platform components: `axvisor`, `axvm`, `riscv_vplic`, - `loongarch_vcpu`, `arm_vgic`, `axplat-dyn`; + `loongarch_vcpu`, `arm_vgic`, `ax-plat-dyn`; - portable driver components: `rdrive`, `arm-scmi-rs`, `ramdisk`, `nvme-driver`, `rdif-serial`, `realtek-rtl8125`, `sg2002-tpu`, `crab-usb`; - shared support crates: `dma-api`, `ax-driver-net`, `axdevice`. diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index 3caf0bbfc7..6b80f68708 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -287,6 +287,7 @@ impl BuildInfo { target, plat_dyn, detect_ax_feature_prefix_family(package, metadata), + Some(metadata), ); } @@ -296,6 +297,7 @@ impl BuildInfo { target: &str, plat_dyn: bool, prefix_family: anyhow::Result, + metadata: Option<&Metadata>, ) { let prefix_family = self.resolve_ax_feature_prefix_family(package, prefix_family); let has_myplat = has_myplat_feature(&self.features); @@ -330,23 +332,30 @@ impl BuildInfo { if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) { self.features.push(format!("{}smp", prefix_family.prefix())); } - self.push_platform_feature(target, plat_dyn, has_myplat); + self.push_platform_feature(target, plat_dyn, has_myplat, metadata); self.features.sort(); self.features.dedup(); } - fn push_platform_feature(&mut self, target: &str, plat_dyn: bool, has_myplat: bool) { - if has_myplat || has_ax_hal_platform_feature(&self.features) { + fn push_platform_feature( + &mut self, + target: &str, + plat_dyn: bool, + has_myplat: bool, + metadata: Option<&Metadata>, + ) { + if has_myplat || has_ax_hal_platform_feature(&self.features, metadata) { return; } let feature = if plat_dyn { - "ax-hal/plat-dyn" + "ax-hal/plat-dyn".to_string() } else { - default_ax_hal_platform_feature(target).unwrap_or("ax-hal/defplat") + default_ax_hal_platform_feature(target, metadata) + .unwrap_or_else(|_| "ax-hal/defplat".to_string()) }; - self.features.push(feature.to_string()); + self.features.push(feature); } fn resolve_ax_feature_prefix_family( @@ -400,6 +409,7 @@ impl BuildInfo { target, plat_dyn, Err(err.context("failed to load workspace metadata")), + None, ), } } @@ -524,8 +534,8 @@ pub(crate) fn prepare_std_build_env( metadata: &Metadata, ) -> anyhow::Result<()> { let arch = target_arch_name(target)?; - let platform_package = default_platform_package(arch); - let platform_config = resolve_platform_config_by_package(platform_package, metadata)?; + let platform_package = require_default_platform_package(metadata, arch)?; + let platform_config = resolve_platform_config_by_package(&platform_package, metadata)?; let out_config = generated_axconfig_path("arceos-rust", target)?; generate_axconfig( &crate::context::workspace_root_path()?, @@ -912,54 +922,199 @@ fn has_defplat_feature(features: &[String]) -> bool { }) } -fn ax_hal_platform_feature_name(feature: &str) -> Option<&str> { +fn ax_hal_platform_feature_name<'a>( + feature: &'a str, + metadata: Option<&Metadata>, +) -> Option<&'a str> { let platform = feature.strip_prefix("ax-hal/")?; match platform { - "plat-dyn" - | "x86-pc" - | "aarch64-qemu-virt" - | "aarch64-raspi" - | "aarch64-bsta1000b" - | "aarch64-phytium-pi" - | "riscv64-qemu-virt" - | "riscv64-sg2002" - | "riscv64-visionfive2" - | "riscv64-qemu-virt-hv" - | "loongarch64-qemu-virt" - | "x86-qemu-q35" => Some(platform), + "plat-dyn" => Some(platform), + "riscv64-qemu-virt-hv" => Some(platform), + _ if metadata + .map(|metadata| platform_package_by_name(metadata, platform).is_some()) + .unwrap_or_else(|| is_known_ax_hal_platform_feature(platform)) => + { + Some(platform) + } _ => None, } } -fn has_ax_hal_platform_feature(features: &[String]) -> bool { +fn is_known_ax_hal_platform_feature(platform: &str) -> bool { + matches!( + platform, + "x86-pc" + | "aarch64-qemu-virt" + | "aarch64-raspi" + | "aarch64-bsta1000b" + | "aarch64-phytium-pi" + | "riscv64-qemu-virt" + | "riscv64-sg2002" + | "riscv64-visionfive2" + | "loongarch64-qemu-virt" + | "x86-qemu-q35" + ) +} + +fn has_ax_hal_platform_feature(features: &[String], metadata: Option<&Metadata>) -> bool { features .iter() - .any(|feature| ax_hal_platform_feature_name(feature).is_some()) + .any(|feature| ax_hal_platform_feature_name(feature, metadata).is_some()) } -fn default_ax_hal_platform_feature(target: &str) -> anyhow::Result<&'static str> { - Ok(match target_arch_name(target)? { +fn default_ax_hal_platform_feature( + target: &str, + metadata: Option<&Metadata>, +) -> anyhow::Result { + let arch = target_arch_name(target)?; + if let Some(metadata) = metadata + && let Some(platform) = platform_packages(metadata) + .into_iter() + .find(|platform| { + platform.metadata.arch == arch + && platform.metadata.default_for_arch + && !platform.metadata.dynamic + }) + .map(|platform| platform.metadata.platform) + { + return Ok(format!("ax-hal/{platform}")); + } + + Ok(match arch { "x86_64" => "ax-hal/x86-pc", "aarch64" => "ax-hal/aarch64-qemu-virt", "riscv64" => "ax-hal/riscv64-qemu-virt", "loongarch64" => "ax-hal/loongarch64-qemu-virt", _ => unreachable!("unsupported arch"), + } + .to_string()) +} + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] +#[serde(rename_all = "kebab-case")] +struct AxplatMetadata { + platform: String, + arch: String, + config: Option, + default_for_arch: bool, + dynamic: bool, +} + +#[derive(Debug, Clone)] +struct PlatformPackage { + package: String, + manifest_dir: PathBuf, + metadata: AxplatMetadata, +} + +fn platform_metadata(package: &Package) -> Option { + package + .metadata + .get("axplat") + .cloned() + .and_then(|metadata| serde_json::from_value(metadata).ok()) +} + +fn platform_packages(metadata: &Metadata) -> Vec { + metadata + .packages + .iter() + .filter_map(|package| { + let metadata = platform_metadata(package)?; + let manifest_dir = Path::new(package.manifest_path.as_std_path()) + .parent()? + .to_path_buf(); + Some(PlatformPackage { + package: package.name.to_string(), + manifest_dir, + metadata, + }) + }) + .collect() +} + +fn platform_package_by_name(metadata: &Metadata, platform_name: &str) -> Option { + if platform_name == "riscv64-qemu-virt-hv" { + return platform_package_by_name(metadata, "riscv64-qemu-virt"); + } + + platform_packages(metadata) + .into_iter() + .find(|platform| platform.metadata.platform == platform_name) + .map(|platform| platform.package) +} + +fn platform_package_by_name_with_workspace_fallback( + metadata: &Metadata, + platform_name: &str, +) -> Option { + platform_package_by_name(metadata, platform_name).or_else(|| { + cached_workspace_metadata() + .ok() + .and_then(|metadata| platform_package_by_name(metadata, platform_name)) }) } -fn ax_hal_platform_package(platform: &str) -> Option<&'static str> { - match platform { - "x86-pc" => Some("ax-plat-x86-pc"), - "aarch64-qemu-virt" => Some("ax-plat-aarch64-qemu-virt"), - "aarch64-raspi" => Some("ax-plat-aarch64-raspi"), - "aarch64-bsta1000b" => Some("ax-plat-aarch64-bsta1000b"), - "aarch64-phytium-pi" => Some("ax-plat-aarch64-phytium-pi"), - "riscv64-qemu-virt" => Some("ax-plat-riscv64-qemu-virt"), - "riscv64-sg2002" => Some("ax-plat-riscv64-sg2002"), - "riscv64-visionfive2" => Some("axplat-riscv64-visionfive2"), - "riscv64-qemu-virt-hv" => Some("ax-plat-riscv64-qemu-virt"), - "loongarch64-qemu-virt" => Some("ax-plat-loongarch64-qemu-virt"), - "x86-qemu-q35" => Some("axplat-x86-qemu-q35"), +fn default_platform_package(metadata: &Metadata, arch: &str) -> Option { + platform_packages(metadata) + .into_iter() + .find(|platform| { + platform.metadata.arch == arch + && platform.metadata.default_for_arch + && !platform.metadata.dynamic + }) + .map(|platform| platform.package) +} + +fn default_platform_package_with_workspace_fallback( + metadata: &Metadata, + arch: &str, +) -> Option { + default_platform_package(metadata, arch).or_else(|| { + cached_workspace_metadata() + .ok() + .and_then(|metadata| default_platform_package(metadata, arch)) + }) +} + +fn platform_config_path_from_metadata( + platform_package: &str, + metadata: &Metadata, +) -> Option { + platform_packages(metadata) + .into_iter() + .find(|platform| platform.package == platform_package) + .and_then(|platform| { + platform + .metadata + .config + .map(|config| platform.manifest_dir.join(config)) + }) + .filter(|path| path.exists()) +} + +fn ax_hal_platform_package(platform: &str, metadata: &Metadata) -> Option { + platform_package_by_name_with_workspace_fallback(metadata, platform) +} + +fn require_default_platform_package(metadata: &Metadata, arch: &str) -> anyhow::Result { + default_platform_package_with_workspace_fallback(metadata, arch) + .ok_or_else(|| anyhow!("no default platform package is registered for arch `{arch}`")) +} + +fn explicit_myplat_platform_package( + package: &str, + arch: &str, + metadata: &Metadata, +) -> Option { + match (package, arch) { + ("axvisor", "x86_64") => { + platform_package_by_name_with_workspace_fallback(metadata, "x86-qemu-q35") + } + ("axvisor", "riscv64") => { + platform_package_by_name_with_workspace_fallback(metadata, "riscv64-qemu-virt") + } _ => None, } } @@ -975,10 +1130,10 @@ fn resolve_platform_package( if let Some(platform) = features .iter() - .find_map(|feature| ax_hal_platform_feature_name(feature)) - .and_then(ax_hal_platform_package) + .find_map(|feature| ax_hal_platform_feature_name(feature, Some(metadata))) + .and_then(|platform| ax_hal_platform_package(platform, metadata)) { - return Ok(platform.to_string()); + return Ok(platform); } let explicit_platform_features: Vec<_> = features @@ -1004,13 +1159,13 @@ fn resolve_platform_package( } if has_myplat_feature(features) { - if let Some(dep_name) = explicit_myplat_platform_package(package, arch) + if let Some(dep_name) = explicit_myplat_platform_package(package, arch, metadata) && package_info .dependencies .iter() .any(|dep| dep.name == dep_name) { - return Ok(dep_name.to_string()); + return Ok(dep_name); } if let Some(dep) = package_info @@ -1022,7 +1177,7 @@ fn resolve_platform_package( } } - Ok(default_platform_package(arch).to_string()) + require_default_platform_package(metadata, arch) } fn target_arch_name(target: &str) -> anyhow::Result<&'static str> { @@ -1039,24 +1194,6 @@ fn target_arch_name(target: &str) -> anyhow::Result<&'static str> { } } -fn default_platform_package(arch: &str) -> &'static str { - match arch { - "x86_64" => "ax-plat-x86-pc", - "aarch64" => "ax-plat-aarch64-qemu-virt", - "riscv64" => "ax-plat-riscv64-qemu-virt", - "loongarch64" => "ax-plat-loongarch64-qemu-virt", - _ => unreachable!("unsupported arch"), - } -} - -fn explicit_myplat_platform_package(package: &str, arch: &str) -> Option<&'static str> { - match (package, arch) { - ("axvisor", "x86_64") => Some("axplat-x86-qemu-q35"), - ("axvisor", "riscv64") => Some("ax-plat-riscv64-qemu-virt"), - _ => None, - } -} - fn explicit_platform_package_from_features( package_info: &Package, explicit_features: &[&str], @@ -1179,6 +1316,10 @@ fn find_local_platform_config_path( platform_package: &str, metadata: &Metadata, ) -> anyhow::Result> { + if let Some(candidate) = platform_config_path_from_metadata(platform_package, metadata) { + return Ok(Some(candidate)); + } + if let Some(pkg) = metadata_package(metadata, platform_package) { let candidate = Path::new(pkg.manifest_path.as_std_path()) .parent() @@ -1191,16 +1332,12 @@ fn find_local_platform_config_path( } let workspace_root = crate::context::workspace_root_path()?; - let platform_dir_name = platform_package - .strip_prefix("ax-plat-") - .map(|suffix| format!("axplat-{suffix}")) - .unwrap_or_else(|| platform_package.to_string()); - let component_candidate = workspace_root - .join("components/axplat_crates/platforms") - .join(platform_dir_name) + let platform_candidate = workspace_root + .join("platforms") + .join(platform_package) .join("axconfig.toml"); - Ok(component_candidate.exists().then_some(component_candidate)) + Ok(platform_candidate.exists().then_some(platform_candidate)) } fn read_platform_name(platform_config: &Path) -> Option { @@ -1313,7 +1450,7 @@ mod tests { package_name: &str, config_package_name: &str, ) -> anyhow::Result<()> { - let platform_dir = workspace.join("platform"); + let platform_dir = workspace.join("platforms"); fs::create_dir_all(platform_dir.join("src"))?; fs::write( platform_dir.join("Cargo.toml"), @@ -1555,9 +1692,7 @@ mod tests { .unwrap() .expect("workspace platform config should exist"); - assert!(path.ends_with( - "components/axplat_crates/platforms/axplat-riscv64-qemu-virt/axconfig.toml" - )); + assert!(path.ends_with("platforms/ax-plat-riscv64-qemu-virt/axconfig.toml")); } #[test] @@ -1568,16 +1703,14 @@ mod tests { resolve_platform_config_path("ax-plat-riscv64-qemu-virt", &metadata, &deps_metadata) .unwrap(); - assert!(path.ends_with( - "components/axplat_crates/platforms/axplat-riscv64-qemu-virt/axconfig.toml" - )); + assert!(path.ends_with("platforms/ax-plat-riscv64-qemu-virt/axconfig.toml")); } #[test] fn resolve_platform_config_path_uses_dependency_config() { let workspace = temp_workspace( "custom-app", - "ax-plat-aarch64-custom = { path = \"../platform\" }\n", + "ax-plat-aarch64-custom = { path = \"../platforms\" }\n", ) .unwrap(); add_platform_package( @@ -1594,6 +1727,6 @@ mod tests { resolve_platform_config_path("ax-plat-aarch64-custom", &metadata, &deps_metadata) .unwrap(); - assert!(path.ends_with("platform/axconfig.toml")); + assert!(path.ends_with("platforms/axconfig.toml")); } } diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index d8921e07d4..70027d3bd6 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -38,7 +38,6 @@ https://github.com/arceos-org/axmm_crates,,components/axmm_crates,ArceOS, https://github.com/arceos-org/page_table_multiarch,,components/page_table_multiarch,ArceOS, https://github.com/arceos-org/percpu,dev,components/percpu,ArceOS, https://github.com/arceos-org/timer_list,,components/timer_list,ArceOS, -https://github.com/arceos-org/axplat_crates,dev,components/axplat_crates,ArceOS, https://github.com/arceos-org/ax-int-ratio,,components/int_ratio,ArceOS, https://github.com/arceos-org/cap_access,,components/cap_access,ArceOS, https://github.com/arceos-org/arm_pl031,,drivers/rtc/arm_pl031,ArceOS, From ab0e219a839af052fb2676bb5e8b1615d09fb1bf Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Wed, 27 May 2026 14:12:14 +0800 Subject: [PATCH 54/71] Refactor code structure for improved readability and maintainability (#982) * Add range allocator tests and implement range operations - Introduced tests for the RangeAllocator including simple allocation, out of memory scenarios, fragmentation and merging, and alignment gaps. - Created a CHANGELOG.md for the ranges-ext module to document notable changes. - Added Cargo.toml for the ranges-ext module with dependencies and features. - Implemented VecOp trait for heapless::Vec and alloc::vec::Vec to support range operations. - Developed the core functionality for merging overlapping ranges and handling conflicts in the ranges-ext module. - Added comprehensive tests for merging ranges, ensuring correct behavior for various scenarios including overlaps and conflicts. * Refactor code structure for improved readability and maintainability * Refactor workspace dependencies and update component paths for consistency * refactor: restructure axplat-dyn platform module - Remove obsolete files related to platform initialization, IRQ handling, memory management, power management, and generic timer. - Introduce new files for boot, console, drivers, and memory management with updated implementations. - Implement a new build script and linker script for dynamic platform support. - Update Cargo.toml to reflect the new structure and dependencies. - Add CHANGELOG.md to document changes and versioning. - Ensure compatibility with the latest spin crate version. --- Cargo.lock | 48 ++--- Cargo.toml | 93 +++----- .../axmm_crates/.github/workflows/check.yml | 43 ---- .../axmm_crates/.github/workflows/deploy.yml | 57 ----- .../axmm_crates/.github/workflows/release.yml | 147 ------------- .../axmm_crates/.github/workflows/test.yml | 21 -- components/axmm_crates/README.md | 43 ---- components/axmm_crates/README_CN.md | 43 ---- .../.github/workflows/ci.yml | 56 ----- components/page_table_multiarch/.gitignore | 4 - components/page_table_multiarch/CHANGELOG.md | 131 ------------ components/page_table_multiarch/README.md | 43 ---- components/page_table_multiarch/README_CN.md | 43 ---- components/page_table_multiarch/rustfmt.toml | 17 -- components/x86_vcpu/LICENSE | 201 ------------------ components/x86_vlapic/.gitignore | 4 - components/x86_vlapic/LICENSE | 201 ------------------ docs/docs/architecture/axvisor.md | 6 +- docs/docs/architecture/overview.md | 6 +- docs/docs/architecture/rdrive-rdif.md | 12 +- docs/docs/components/crates/aarch64-sysreg.md | 4 +- docs/docs/components/crates/arm-vcpu.md | 8 +- docs/docs/components/crates/arm-vgic.md | 8 +- docs/docs/components/crates/ax-alloc.md | 2 +- docs/docs/components/crates/ax-allocator.md | 2 +- docs/docs/components/crates/ax-cpumask.md | 2 +- docs/docs/components/crates/ax-dma.md | 4 +- docs/docs/components/crates/ax-driver.md | 2 +- docs/docs/components/crates/ax-lazyinit.md | 2 +- docs/docs/components/crates/ax-memory-addr.md | 2 +- docs/docs/components/crates/ax-memory-set.md | 4 +- .../components/crates/ax-page-table-entry.md | 2 +- .../crates/ax-page-table-multiarch.md | 2 +- docs/docs/components/crates/axaddrspace.md | 2 +- docs/docs/components/crates/axdevice-base.md | 2 +- docs/docs/components/crates/axdevice.md | 4 +- docs/docs/components/crates/axhvc.md | 2 +- docs/docs/components/crates/axklib.md | 10 +- docs/docs/components/crates/axplat-dyn.md | 36 ++-- docs/docs/components/crates/axvcpu.md | 2 +- .../components/crates/axvisor-api-proc.md | 8 +- docs/docs/components/crates/axvisor-api.md | 2 +- docs/docs/components/crates/axvm.md | 2 +- docs/docs/components/crates/axvmconfig.md | 12 +- .../components/crates/bitmap-allocator.md | 6 +- .../components/crates/impl-simple-traits.md | 2 +- .../components/crates/range-alloc-arceos.md | 4 +- docs/docs/components/crates/riscv-h.md | 2 +- docs/docs/components/crates/riscv-vcpu.md | 4 +- docs/docs/components/crates/riscv-vplic.md | 2 +- docs/docs/components/crates/x86-vcpu.md | 2 +- docs/docs/components/crates/x86-vlapic.md | 6 +- docs/docs/components/layers.md | 74 +++---- docs/docs/components/overview.md | 52 ++--- docs/docs/debug/check-mechanisms-summary.md | 2 +- docs/docs/debug/platform.md | 2 +- docs/docs/development/arceos.md | 2 +- docs/docs/development/axvisor.md | 10 +- docs/docs/development/components.md | 22 +- docs/docs/introduction/overview.md | 2 +- .../axaddrspace/.github/config.json | 0 .../axaddrspace}/.github/workflows/check.yml | 0 .../axaddrspace}/.github/workflows/deploy.yml | 0 .../axaddrspace}/.github/workflows/push.yml | 0 .../.github/workflows/release.yml | 0 .../axaddrspace}/.github/workflows/test.yml | 0 {components => memory}/axaddrspace/.gitignore | 0 .../axaddrspace/CHANGELOG.md | 0 {components => memory}/axaddrspace/Cargo.toml | 0 .../axaddrspace}/LICENSE | 0 {components => memory}/axaddrspace/README.md | 2 +- .../axaddrspace/README_CN.md | 2 +- .../axaddrspace/scripts/check.sh | 0 .../axaddrspace/scripts/test.sh | 0 .../axaddrspace/src/addr.rs | 0 .../src/address_space/backend/alloc.rs | 0 .../src/address_space/backend/linear.rs | 0 .../src/address_space/backend/mod.rs | 0 .../axaddrspace/src/address_space/mod.rs | 0 .../axaddrspace/src/device/device_addr.rs | 0 .../axaddrspace/src/device/mod.rs | 0 .../axaddrspace/src/frame.rs | 0 {components => memory}/axaddrspace/src/hal.rs | 0 {components => memory}/axaddrspace/src/lib.rs | 0 .../axaddrspace/src/memory_accessor.rs | 0 .../axaddrspace/src/npt/arch/aarch64.rs | 0 .../axaddrspace/src/npt/arch/loongarch64.rs | 0 .../axaddrspace/src/npt/arch/mod.rs | 0 .../axaddrspace/src/npt/arch/riscv.rs | 0 .../axaddrspace/src/npt/arch/x86_64.rs | 0 .../axaddrspace/src/npt/mod.rs | 0 .../axaddrspace/tests/address_space.rs | 0 .../axaddrspace/tests/frame.rs | 0 .../axaddrspace/tests/memory_accessor.rs | 0 .../axaddrspace/tests/test_utils/mod.rs | 0 .../axallocator/.github/workflows/ci.yml | 0 {components => memory}/axallocator/.gitignore | 0 .../axallocator/CHANGELOG.md | 0 {components => memory}/axallocator/Cargo.toml | 0 .../arm_vcpu => memory/axallocator}/LICENSE | 0 {components => memory}/axallocator/README.md | 2 +- .../axallocator/README_CN.md | 2 +- .../axallocator/benches/collections.rs | 0 .../axallocator/benches/utils/mod.rs | 0 .../axallocator/src/bitmap.rs | 0 .../axallocator/src/buddy.rs | 0 {components => memory}/axallocator/src/lib.rs | 0 .../axallocator/src/slab.rs | 0 .../axallocator/src/tlsf.rs | 0 .../axallocator/tests/allocator.rs | 0 .../.github/workflows/main.yml | 0 .../bitmap-allocator/.gitignore | 0 .../bitmap-allocator/CHANGELOG.md | 0 .../bitmap-allocator/Cargo.toml | 0 .../bitmap-allocator}/LICENSE | 0 .../bitmap-allocator/README.md | 2 +- .../bitmap-allocator/README_CN.md | 2 +- .../bitmap-allocator/src/lib.rs | 0 .../.github/workflows/check.yml | 0 .../.github/workflows/ci.yml | 0 .../.github/workflows/release-plz.yml | 0 .../.github/workflows/test.yml | 0 .../buddy-slab-allocator/.gitignore | 0 .../buddy-slab-allocator/CHANGELOG.md | 0 .../buddy-slab-allocator/Cargo.toml | 0 .../buddy-slab-allocator/LICENSE.Apache2 | 0 .../buddy-slab-allocator/README.md | 0 .../buddy-slab-allocator/README_CN.md | 0 .../buddy-slab-allocator/benches/README_CN.md | 0 .../benches/buddy_allocator.rs | 0 .../buddy-slab-allocator/benches/common.rs | 0 .../benches/global_allocator.rs | 0 .../benches/slab_allocator.rs | 0 .../buddy-slab-allocator/docs/design.md | 0 .../buddy-slab-allocator/rust-toolchain.toml | 0 .../buddy-slab-allocator/src/buddy/mod.rs | 0 .../src/buddy/page_meta.rs | 0 .../buddy-slab-allocator/src/error.rs | 0 .../buddy-slab-allocator/src/global.rs | 0 .../buddy-slab-allocator/src/lib.rs | 0 .../buddy-slab-allocator/src/slab/cache.rs | 0 .../buddy-slab-allocator/src/slab/mod.rs | 0 .../buddy-slab-allocator/src/slab/page.rs | 0 .../src/slab/size_class.rs | 0 .../buddy-slab-allocator/tests/README.md | 0 .../buddy-slab-allocator/tests/common/mod.rs | 0 .../tests/dma32_pages_test.rs | 0 .../tests/integration_test.rs | 0 .../buddy-slab-allocator/tests/stress_test.rs | 0 {components => memory}/dma-api/CHANGELOG.md | 0 {components => memory}/dma-api/Cargo.toml | 0 {components => memory}/dma-api/README.md | 0 {components => memory}/dma-api/src/array.rs | 0 {components => memory}/dma-api/src/common.rs | 0 {components => memory}/dma-api/src/dbox.rs | 0 {components => memory}/dma-api/src/def.rs | 0 {components => memory}/dma-api/src/lib.rs | 0 .../dma-api/src/op/aarch64.rs | 0 {components => memory}/dma-api/src/op/mod.rs | 0 {components => memory}/dma-api/src/op/nop.rs | 0 {components => memory}/dma-api/src/pool.rs | 0 .../dma-api/src/streaming.rs | 0 {components => memory}/dma-api/tests/test.rs | 0 .../dma-api/tests/test_helpers.rs | 0 .../memory_addr/Cargo.toml | 0 .../memory_addr/README.md | 2 +- .../memory_addr/README_CN.md | 2 +- .../memory_addr/src/addr.rs | 0 .../memory_addr/src/iter.rs | 0 .../memory_addr/src/lib.rs | 0 .../memory_addr/src/range.rs | 0 .../memory_set/CHANGELOG.md | 0 .../memory_set/Cargo.toml | 0 .../memory_set/README.md | 2 +- .../memory_set/README_CN.md | 2 +- .../memory_set/src/area.rs | 0 .../memory_set/src/backend.rs | 0 .../memory_set/src/lib.rs | 0 .../memory_set/src/set.rs | 0 .../memory_set/src/tests.rs | 0 {components => memory}/mmio-api/CHANGELOG.md | 0 {components => memory}/mmio-api/Cargo.toml | 0 {components => memory}/mmio-api/src/lib.rs | 0 .../page-table-generic/CHANGELOG.md | 0 .../page-table-generic/Cargo.toml | 0 .../page-table-generic/src/def.rs | 0 .../page-table-generic/src/frame.rs | 0 .../page-table-generic/src/lib.rs | 0 .../page-table-generic/src/map.rs | 0 .../page-table-generic/src/table.rs | 0 .../page-table-generic/src/walk.rs | 0 .../page-table-generic/tests/bugfixes.rs | 0 .../page-table-generic/tests/drop.rs | 0 .../page-table-generic/tests/flags.rs | 0 .../tests/loongarch64_simple.rs | 2 +- .../tests/loongarch64_simulation.rs | 2 +- .../page-table-generic/tests/map.rs | 12 +- .../page-table-generic/tests/mocks/mod.rs | 0 .../page-table-generic/tests/translate.rs | 0 .../page_table_entry/Cargo.toml | 0 .../page_table_entry/README.md | 2 +- .../page_table_entry/README_CN.md | 2 +- .../page_table_entry/src/arch/aarch64.rs | 0 .../page_table_entry/src/arch/arm.rs | 0 .../page_table_entry/src/arch/loongarch64.rs | 0 .../page_table_entry/src/arch/mod.rs | 0 .../page_table_entry/src/arch/riscv.rs | 0 .../page_table_entry/src/arch/x86_64.rs | 0 .../page_table_entry/src/lib.rs | 0 .../page_table_multiarch/CHANGELOG.md | 0 .../page_table_multiarch/Cargo.toml | 0 .../page_table_multiarch/README.md | 2 +- .../page_table_multiarch/README_CN.md | 2 +- .../page_table_multiarch/src/arch/aarch64.rs | 0 .../page_table_multiarch/src/arch/arm.rs | 0 .../src/arch/loongarch64.rs | 0 .../page_table_multiarch/src/arch/mod.rs | 0 .../page_table_multiarch/src/arch/riscv.rs | 0 .../page_table_multiarch/src/arch/x86_64.rs | 0 .../page_table_multiarch/src/bits32.rs | 0 .../page_table_multiarch/src/bits64.rs | 0 .../page_table_multiarch/src/lib.rs | 0 .../page_table_multiarch/tests/alloc_tests.rs | 0 .../range-alloc-arceos/.github/config.json | 0 .../.github/workflows/check.yml | 0 .../.github/workflows/deploy.yml | 0 .../.github/workflows/push.yml | 0 .../.github/workflows/release.yml | 0 .../.github/workflows/test.yml | 0 .../range-alloc-arceos/.gitignore | 0 .../range-alloc-arceos/CHANGELOG.md | 0 .../range-alloc-arceos/Cargo.toml | 0 .../range-alloc-arceos}/LICENSE | 0 .../range-alloc-arceos/LICENSE.APACHE | 0 .../range-alloc-arceos/LICENSE.MIT | 0 .../range-alloc-arceos/README.md | 2 +- .../range-alloc-arceos/README_CN.md | 2 +- .../range-alloc-arceos/src/lib.rs | 0 .../range-alloc-arceos/tests/test.rs | 0 .../ranges-ext/CHANGELOG.md | 0 {components => memory}/ranges-ext/Cargo.toml | 0 {components => memory}/ranges-ext/src/less.rs | 0 {components => memory}/ranges-ext/src/lib.rs | 0 .../ranges-ext/src/test_helper.rs | 0 {components => memory}/ranges-ext/src/vec.rs | 0 .../ranges-ext/tests/test.rs | 0 os/StarryOS/kernel/Cargo.toml | 2 +- os/StarryOS/starryos/Cargo.toml | 4 +- os/arceos/modules/axhal/Cargo.toml | 16 +- os/arceos/modules/axhal/build.rs | 4 +- os/arceos/modules/axhal/src/mem.rs | 2 +- os/arceos/modules/axhal/src/time.rs | 2 +- os/arceos/scripts/make/cargo.mk | 8 +- os/axvisor/Cargo.toml | 4 +- .../{ax-plat-dyn => axplat-dyn}/CHANGELOG.md | 0 .../{ax-plat-dyn => axplat-dyn}/Cargo.toml | 4 +- .../{ax-plat-dyn => axplat-dyn}/build.rs | 0 platforms/{ax-plat-dyn => axplat-dyn}/link.ld | 0 .../{ax-plat-dyn => axplat-dyn}/src/boot.rs | 0 .../src/console.rs | 0 .../src/drivers/mod.rs | 0 .../src/generic_timer.rs | 0 .../{ax-plat-dyn => axplat-dyn}/src/init.rs | 4 +- .../{ax-plat-dyn => axplat-dyn}/src/irq.rs | 0 .../{ax-plat-dyn => axplat-dyn}/src/lib.rs | 0 .../{ax-plat-dyn => axplat-dyn}/src/mem.rs | 0 .../{ax-plat-dyn => axplat-dyn}/src/power.rs | 0 reports/external-spin-audit.md | 76 +++---- reports/external-spin-migration-plan.md | 2 +- ...all_preadv_pwritev2_modification_report.md | 2 +- scripts/repo/repos.csv | 40 ++-- .../aarch64_sysreg/.github/config.json | 0 .../.github/workflows/check.yml | 0 .../.github/workflows/deploy.yml | 0 .../.github/workflows/push.yml | 0 .../.github/workflows/release.yml | 0 .../aarch64_sysreg/.github/workflows/test.yml | 0 .../aarch64_sysreg/.gitignore | 0 .../aarch64_sysreg/Cargo.toml | 0 .../aarch64_sysreg}/LICENSE | 0 .../aarch64_sysreg/README.md | 2 +- .../aarch64_sysreg/README_CN.md | 2 +- .../aarch64_sysreg/scripts/check.sh | 0 .../aarch64_sysreg/scripts/test.sh | 0 .../aarch64_sysreg/src/lib.rs | 0 .../aarch64_sysreg/src/operation_type.rs | 0 .../aarch64_sysreg/src/registers_type.rs | 0 .../aarch64_sysreg/src/system_reg_type.rs | 0 .../tests/operation_type_tests.rs | 0 .../tests/registers_type_tests.rs | 0 .../tests/system_reg_type_tests.rs | 0 .../arm_vcpu/.cargo/config.toml | 0 .../arm_vcpu/.github/config.json | 0 .../arm_vcpu}/.github/workflows/check.yml | 0 .../arm_vcpu}/.github/workflows/deploy.yml | 0 .../arm_vcpu/.github/workflows/push.yml | 0 .../arm_vcpu}/.github/workflows/release.yml | 0 .../arm_vcpu}/.github/workflows/test.yml | 0 .../arm_vcpu/.gitignore | 0 .../arm_vcpu/CHANGELOG.md | 0 .../arm_vcpu/Cargo.toml | 0 .../arm_vcpu}/LICENSE | 0 .../arm_vcpu/README.md | 2 +- .../arm_vcpu/README_CN.md | 2 +- .../arm_vcpu/scripts/check.sh | 0 .../arm_vcpu/scripts/test.sh | 0 .../arm_vcpu/src/context_frame.rs | 0 .../arm_vcpu/src/exception.S | 0 .../arm_vcpu/src/exception.rs | 0 .../arm_vcpu/src/exception_utils.rs | 0 .../arm_vcpu/src/lib.rs | 0 .../arm_vcpu/src/pcpu.rs | 0 .../arm_vcpu/src/smc.rs | 0 .../arm_vcpu/src/vcpu.rs | 0 .../arm_vcpu/tests/axci_flow_test.rs | 0 .../arm_vcpu/tests/context_frame_test.rs | 0 .../arm_vcpu/tests/hardware_support_test.rs | 0 .../arm_vcpu/tests/mpidr_test.rs | 0 .../arm_vcpu/tests/psci_test.rs | 0 .../arm_vcpu/tests/script_test.rs | 0 .../arm_vcpu/tests/spsr_test.rs | 0 .../arm_vcpu/tests/vcpu_config_test.rs | 0 .../arm_vcpu/tests/vmcpu_registers_test.rs | 0 .../arm_vgic/.github/config.json | 0 .../arm_vgic}/.github/workflows/check.yml | 0 .../arm_vgic/.github/workflows/deploy.yml | 0 .../arm_vgic}/.github/workflows/push.yml | 0 .../arm_vgic}/.github/workflows/release.yml | 0 .../arm_vgic}/.github/workflows/test.yml | 0 .../arm_vgic/.gitignore | 0 .../arm_vgic/CHANGELOG.md | 0 .../arm_vgic/Cargo.toml | 0 .../arm_vgic}/LICENSE | 0 .../arm_vgic/README.md | 2 +- .../arm_vgic/README_CN.md | 2 +- .../arm_vgic/src/consts.rs | 0 .../arm_vgic/src/devops_impl.rs | 0 .../arm_vgic/src/interrupt.rs | 0 .../arm_vgic/src/lib.rs | 0 .../arm_vgic/src/list_register.rs | 0 .../arm_vgic/src/registers.rs | 0 .../arm_vgic/src/v3/gits.rs | 0 .../arm_vgic/src/v3/mod.rs | 0 .../arm_vgic/src/v3/registers.rs | 0 .../arm_vgic/src/v3/utils.rs | 0 .../arm_vgic/src/v3/vgicd.rs | 0 .../arm_vgic/src/v3/vgicr.rs | 0 .../arm_vgic/src/vgic.rs | 0 .../arm_vgic/src/vgicd.rs | 0 .../arm_vgic/src/vtimer/cntp_ctl_el0.rs | 0 .../arm_vgic/src/vtimer/cntp_tval_el0.rs | 0 .../arm_vgic/src/vtimer/cntpct_el0.rs | 0 .../arm_vgic/src/vtimer/mod.rs | 0 .../axdevice/.github/config.json | 0 .../axdevice/.github/workflows/check.yml | 0 .../axdevice/.github/workflows/deploy.yml | 0 .../axdevice/.github/workflows/push.yml | 0 .../axdevice/.github/workflows/release.yml | 0 .../axdevice/.github/workflows/test.yml | 0 .../axdevice/.gitignore | 0 .../axdevice/CHANGELOG.md | 0 .../axdevice/Cargo.toml | 0 .../axhvc => virtualization/axdevice}/LICENSE | 0 .../axdevice/README.md | 2 +- .../axdevice/README_CN.md | 2 +- .../axdevice/scripts/check.sh | 0 .../axdevice/scripts/test.sh | 0 .../axdevice/src/config.rs | 0 .../axdevice/src/device.rs | 0 .../axdevice/src/lib.rs | 0 .../axdevice/tests/test.rs | 0 .../axdevice_base/.github/config.json | 0 .../axdevice_base/.github/workflows/check.yml | 0 .../.github/workflows/deploy.yml | 0 .../axdevice_base/.github/workflows/push.yml | 0 .../.github/workflows/release.yml | 0 .../axdevice_base/.github/workflows/test.yml | 0 .../axdevice_base/.gitignore | 0 .../axdevice_base/CHANGELOG.md | 0 .../axdevice_base/Cargo.toml | 0 .../axdevice_base}/LICENSE | 0 .../axdevice_base/README.md | 2 +- .../axdevice_base/README_CN.md | 2 +- .../axdevice_base/scripts/check.sh | 0 .../axdevice_base/scripts/test.sh | 0 .../axdevice_base/src/lib.rs | 0 .../axdevice_base/tests/test.rs | 0 .../axhvc/.github/config.json | 0 .../axhvc/.github/workflows/check.yml | 0 .../axhvc/.github/workflows/deploy.yml | 0 .../axhvc/.github/workflows/push.yml | 0 .../axhvc/.github/workflows/release.yml | 0 .../axhvc/.github/workflows/test.yml | 0 .../axhvc/.gitignore | 0 .../axhvc/CHANGELOG.md | 0 .../axhvc/Cargo.toml | 0 .../axvcpu => virtualization/axhvc}/LICENSE | 0 .../axhvc/README.md | 2 +- .../axhvc/README_CN.md | 2 +- .../axhvc/scripts/check.sh | 0 .../axhvc/scripts/test.sh | 0 .../axhvc/src/lib.rs | 0 .../axhvc/tests/test.rs | 0 .../axvcpu/.github/config.json | 0 .../axvcpu}/.github/workflows/check.yml | 0 .../axvcpu/.github/workflows/deploy.yml | 0 .../axvcpu}/.github/workflows/push.yml | 0 .../axvcpu}/.github/workflows/release.yml | 0 .../axvcpu}/.github/workflows/test.yml | 0 .../axvcpu/.gitignore | 0 .../axvcpu/CHANGELOG.md | 0 .../axvcpu/Cargo.toml | 0 .../axvcpu}/LICENSE | 0 .../axvcpu/README.md | 2 +- .../axvcpu/README_CN.md | 2 +- .../axvcpu/src/arch_vcpu.rs | 0 .../axvcpu/src/exit.rs | 0 .../axvcpu/src/lib.rs | 0 .../axvcpu/src/percpu.rs | 0 .../axvcpu/src/test.rs | 0 .../axvcpu/src/vcpu.rs | 0 .../axvisor_api/.github/config.json | 0 .../axvisor_api}/.github/workflows/check.yml | 0 .../axvisor_api/.github/workflows/deploy.yml | 0 .../axvisor_api}/.github/workflows/push.yml | 0 .../.github/workflows/release.yml | 0 .../axvisor_api}/.github/workflows/test.yml | 0 .../axvisor_api}/.gitignore | 0 .../axvisor_api/.rustfmt.toml | 0 .../axvisor_api/CHANGELOG.md | 0 .../axvisor_api/Cargo.toml | 0 .../axvisor_api}/LICENSE | 0 .../axvisor_api/README.md | 2 +- .../axvisor_api/README.zh-cn.md | 0 .../axvisor_api/README_CN.md | 2 +- .../axvisor_api/examples/example.rs | 0 .../axvisor_api/src/arch.rs | 0 .../axvisor_api/src/host.rs | 0 .../axvisor_api/src/lib.rs | 0 .../axvisor_api/src/memory.rs | 0 .../axvisor_api/src/test.rs | 0 .../axvisor_api/src/time.rs | 0 .../axvisor_api/src/vmm.rs | 0 .../axvisor_api_proc/Cargo.toml | 0 .../axvisor_api_proc/README.md | 2 +- .../axvisor_api_proc/README_CN.md | 2 +- .../axvisor_api_proc/src/lib.rs | 0 .../axvm/.github/config.json | 0 .../axvm}/.github/workflows/check.yml | 0 .../axvm}/.github/workflows/deploy.yml | 0 .../axvm}/.github/workflows/push.yml | 0 .../axvm}/.github/workflows/release.yml | 0 .../axvm}/.github/workflows/test.yml | 0 .../axvm/.gitignore | 0 .../axvm/CHANGELOG.md | 0 .../axvm/Cargo.toml | 0 .../axvm}/LICENSE | 0 {components => virtualization}/axvm/README.md | 2 +- .../axvm/README_CN.md | 2 +- .../axvm/src/config.rs | 0 .../axvm/src/hal.rs | 0 .../axvm/src/lib.rs | 0 .../axvm/src/vcpu.rs | 0 {components => virtualization}/axvm/src/vm.rs | 0 .../axvmconfig/.github/config.json | 0 .../axvmconfig}/.github/workflows/check.yml | 0 .../axvmconfig/.github/workflows/deploy.yml | 0 .../axvmconfig}/.github/workflows/push.yml | 0 .../axvmconfig}/.github/workflows/release.yml | 0 .../axvmconfig}/.github/workflows/test.yml | 0 .../axvmconfig}/.gitignore | 0 .../axvmconfig/CHANGELOG.md | 0 .../axvmconfig/Cargo.toml | 0 .../axvmconfig}/LICENSE | 0 .../axvmconfig/README.md | 2 +- .../axvmconfig/README_CN.md | 2 +- .../axvmconfig/src/lib.rs | 0 .../axvmconfig/src/main.rs | 0 .../axvmconfig/src/templates.rs | 0 .../axvmconfig/src/test.rs | 0 .../axvmconfig/src/tool.rs | 0 .../axvmconfig/templates/aarch64.toml | 0 .../axvmconfig/templates/riscv64.toml | 0 .../axvmconfig/templates/x86_64.toml | 0 .../loongarch_vcpu/CHANGELOG.md | 0 .../loongarch_vcpu/Cargo.toml | 0 .../loongarch_vcpu/src/context_frame.rs | 0 .../loongarch_vcpu/src/exception.S | 0 .../loongarch_vcpu/src/exception.rs | 0 .../loongarch_vcpu/src/lib.rs | 0 .../loongarch_vcpu/src/pcpu.rs | 0 .../loongarch_vcpu/src/registers.rs | 0 .../loongarch_vcpu/src/vcpu.rs | 0 .../riscv-h/.github/config.json | 0 .../riscv-h/.github/workflows/check.yml | 0 .../riscv-h/.github/workflows/deploy.yml | 0 .../riscv-h/.github/workflows/push.yml | 0 .../riscv-h/.github/workflows/release.yml | 0 .../riscv-h/.github/workflows/test.yml | 0 .../riscv-h/.gitignore | 0 .../riscv-h/CHANGELOG.md | 0 .../riscv-h/Cargo.toml | 0 .../riscv-h}/LICENSE | 0 .../riscv-h/README.md | 2 +- .../riscv-h/README_CN.md | 2 +- .../riscv-h/scripts/check.sh | 0 .../riscv-h/scripts/test.sh | 0 .../riscv-h/src/lib.rs | 0 .../src/register/hypervisorx64/hcounteren.rs | 0 .../src/register/hypervisorx64/hedeleg.rs | 0 .../src/register/hypervisorx64/hgatp.rs | 0 .../src/register/hypervisorx64/hgeie.rs | 0 .../src/register/hypervisorx64/hgeip.rs | 0 .../src/register/hypervisorx64/hideleg.rs | 0 .../riscv-h/src/register/hypervisorx64/hie.rs | 0 .../riscv-h/src/register/hypervisorx64/hip.rs | 0 .../src/register/hypervisorx64/hstatus.rs | 0 .../src/register/hypervisorx64/htimedelta.rs | 0 .../src/register/hypervisorx64/htimedeltah.rs | 0 .../src/register/hypervisorx64/htinst.rs | 0 .../src/register/hypervisorx64/htval.rs | 0 .../src/register/hypervisorx64/hvip.rs | 0 .../riscv-h/src/register/hypervisorx64/mod.rs | 0 .../src/register/hypervisorx64/vsatp.rs | 0 .../src/register/hypervisorx64/vscause.rs | 0 .../src/register/hypervisorx64/vsepc.rs | 0 .../src/register/hypervisorx64/vsie.rs | 0 .../src/register/hypervisorx64/vsip.rs | 0 .../src/register/hypervisorx64/vsscratch.rs | 0 .../src/register/hypervisorx64/vsstatus.rs | 0 .../src/register/hypervisorx64/vstimecmp.rs | 0 .../src/register/hypervisorx64/vstval.rs | 0 .../src/register/hypervisorx64/vstvec.rs | 0 .../riscv-h/src/register/mod.rs | 0 .../riscv-h/tests/integration_tests.rs | 0 .../riscv-h/tests/register_tests.rs | 0 .../riscv_vcpu/.cargo/config.toml | 0 .../riscv_vcpu/.github/config.json | 0 .../riscv_vcpu/.github/workflows/check.yml | 0 .../riscv_vcpu/.github/workflows/deploy.yml | 0 .../riscv_vcpu/.github/workflows/push.yml | 0 .../riscv_vcpu/.github/workflows/release.yml | 0 .../riscv_vcpu/.github/workflows/test.yml | 0 .../riscv_vcpu/.gitignore | 0 .../riscv_vcpu/CHANGELOG.md | 0 .../riscv_vcpu/Cargo.toml | 0 .../riscv_vcpu}/LICENSE | 0 .../riscv_vcpu/README.md | 2 +- .../riscv_vcpu/README_CN.md | 2 +- .../riscv_vcpu/src/consts.rs | 0 .../riscv_vcpu/src/detect.rs | 0 .../riscv_vcpu/src/guest_mem.rs | 0 .../riscv_vcpu/src/lib.rs | 0 .../riscv_vcpu/src/mem_extable.S | 0 .../riscv_vcpu/src/percpu.rs | 0 .../riscv_vcpu/src/regs.rs | 0 .../riscv_vcpu/src/sbi_console.rs | 0 .../riscv_vcpu/src/trap.S | 0 .../riscv_vcpu/src/trap.rs | 0 .../riscv_vcpu/src/vcpu.rs | 0 .../riscv_vcpu/src/vpmu.rs | 0 .../riscv_vplic/.github/config.json | 0 .../riscv_vplic/.github/workflows/check.yml | 0 .../riscv_vplic/.github/workflows/deploy.yml | 0 .../riscv_vplic/.github/workflows/push.yml | 0 .../riscv_vplic/.github/workflows/release.yml | 0 .../riscv_vplic/.github/workflows/test.yml | 0 .../riscv_vplic/.gitignore | 0 .../riscv_vplic/CHANGELOG.md | 0 .../riscv_vplic/Cargo.toml | 0 .../riscv_vplic}/LICENSE | 0 .../riscv_vplic/README.md | 2 +- .../riscv_vplic/README_CN.md | 2 +- .../riscv_vplic/scripts/check.sh | 0 .../riscv_vplic/scripts/test.sh | 0 .../riscv_vplic/src/consts.rs | 0 .../riscv_vplic/src/devops_impl.rs | 0 .../riscv_vplic/src/lib.rs | 0 .../riscv_vplic/src/utils.rs | 0 .../riscv_vplic/src/vplic.rs | 0 .../riscv_vplic/tests/consts_tests.rs | 0 .../riscv_vplic/tests/vplic_tests.rs | 0 .../x86_vcpu/.github/config.json | 0 .../x86_vcpu/.github/workflows/check.yml | 0 .../x86_vcpu/.github/workflows/deploy.yml | 0 .../x86_vcpu/.github/workflows/push.yml | 0 .../x86_vcpu/.github/workflows/release.yml | 0 .../x86_vcpu/.github/workflows/test.yml | 0 .../x86_vcpu/.gitignore | 0 .../x86_vcpu/CHANGELOG.md | 0 .../x86_vcpu/Cargo.toml | 0 .../x86_vcpu}/LICENSE | 0 .../x86_vcpu/README.md | 2 +- .../x86_vcpu/README_CN.md | 2 +- .../x86_vcpu/src/ept.rs | 0 .../x86_vcpu/src/lib.rs | 0 .../x86_vcpu/src/msr.rs | 0 .../x86_vcpu/src/regs/accessors.rs | 0 .../x86_vcpu/src/regs/diff.rs | 0 .../x86_vcpu/src/regs/mod.rs | 0 .../x86_vcpu/src/svm/definitions.rs | 0 .../x86_vcpu/src/svm/flags.rs | 0 .../x86_vcpu/src/svm/frame.rs | 0 .../x86_vcpu/src/svm/instructions.rs | 0 .../x86_vcpu/src/svm/mod.rs | 0 .../x86_vcpu/src/svm/percpu.rs | 0 .../x86_vcpu/src/svm/structs.rs | 0 .../x86_vcpu/src/svm/vcpu.rs | 0 .../x86_vcpu/src/svm/vmcb.rs | 0 .../x86_vcpu/src/test_utils.rs | 0 .../x86_vcpu/src/vmx/definitions.rs | 0 .../x86_vcpu/src/vmx/instructions.rs | 0 .../x86_vcpu/src/vmx/mod.rs | 0 .../x86_vcpu/src/vmx/percpu.rs | 0 .../x86_vcpu/src/vmx/structs.rs | 0 .../x86_vcpu/src/vmx/vcpu.rs | 0 .../x86_vcpu/src/vmx/vmcs.rs | 0 .../x86_vcpu/src/xstate.rs | 0 .../x86_vlapic/.github/config.json | 0 .../x86_vlapic/.github/workflows/check.yml | 0 .../x86_vlapic/.github/workflows/deploy.yml | 0 .../x86_vlapic/.github/workflows/release.yml | 0 .../x86_vlapic/.github/workflows/test.yml | 0 .../x86_vlapic}/.gitignore | 0 .../x86_vlapic/CHANGELOG.md | 0 .../x86_vlapic/Cargo.toml | 0 .../x86_vlapic}/LICENSE | 0 .../x86_vlapic/README.md | 2 +- .../x86_vlapic/README_CN.md | 2 +- .../x86_vlapic/src/consts.rs | 0 .../x86_vlapic/src/lib.rs | 0 .../x86_vlapic/src/regs/apic_base.rs | 0 .../x86_vlapic/src/regs/dfr.rs | 0 .../x86_vlapic/src/regs/esr.rs | 0 .../x86_vlapic/src/regs/icr.rs | 0 .../x86_vlapic/src/regs/lvt/cmci.rs | 0 .../x86_vlapic/src/regs/lvt/error.rs | 0 .../x86_vlapic/src/regs/lvt/lint0.rs | 0 .../x86_vlapic/src/regs/lvt/lint1.rs | 0 .../x86_vlapic/src/regs/lvt/mod.rs | 0 .../x86_vlapic/src/regs/lvt/perfmon.rs | 0 .../x86_vlapic/src/regs/lvt/thermal.rs | 0 .../x86_vlapic/src/regs/lvt/timer.rs | 0 .../x86_vlapic/src/regs/mod.rs | 0 .../x86_vlapic/src/regs/svr.rs | 0 .../x86_vlapic/src/regs/timer/dcr.rs | 0 .../x86_vlapic/src/regs/timer/mod.rs | 0 .../x86_vlapic/src/timer.rs | 0 .../x86_vlapic/src/utils.rs | 0 .../x86_vlapic/src/vlapic.rs | 0 650 files changed, 369 insertions(+), 1452 deletions(-) delete mode 100644 components/axmm_crates/.github/workflows/check.yml delete mode 100644 components/axmm_crates/.github/workflows/deploy.yml delete mode 100644 components/axmm_crates/.github/workflows/release.yml delete mode 100644 components/axmm_crates/.github/workflows/test.yml delete mode 100644 components/axmm_crates/README.md delete mode 100644 components/axmm_crates/README_CN.md delete mode 100644 components/page_table_multiarch/.github/workflows/ci.yml delete mode 100644 components/page_table_multiarch/.gitignore delete mode 100644 components/page_table_multiarch/CHANGELOG.md delete mode 100644 components/page_table_multiarch/README.md delete mode 100644 components/page_table_multiarch/README_CN.md delete mode 100644 components/page_table_multiarch/rustfmt.toml delete mode 100644 components/x86_vcpu/LICENSE delete mode 100644 components/x86_vlapic/.gitignore delete mode 100644 components/x86_vlapic/LICENSE rename {components => memory}/axaddrspace/.github/config.json (100%) rename {components/arm_vcpu => memory/axaddrspace}/.github/workflows/check.yml (100%) rename {components/arm_vcpu => memory/axaddrspace}/.github/workflows/deploy.yml (100%) rename {components/aarch64_sysreg => memory/axaddrspace}/.github/workflows/push.yml (100%) rename {components/aarch64_sysreg => memory/axaddrspace}/.github/workflows/release.yml (100%) rename {components/arm_vcpu => memory/axaddrspace}/.github/workflows/test.yml (100%) rename {components => memory}/axaddrspace/.gitignore (100%) rename {components => memory}/axaddrspace/CHANGELOG.md (100%) rename {components => memory}/axaddrspace/Cargo.toml (100%) rename {components/aarch64_sysreg => memory/axaddrspace}/LICENSE (100%) rename {components => memory}/axaddrspace/README.md (98%) rename {components => memory}/axaddrspace/README_CN.md (98%) rename {components => memory}/axaddrspace/scripts/check.sh (100%) rename {components => memory}/axaddrspace/scripts/test.sh (100%) rename {components => memory}/axaddrspace/src/addr.rs (100%) rename {components => memory}/axaddrspace/src/address_space/backend/alloc.rs (100%) rename {components => memory}/axaddrspace/src/address_space/backend/linear.rs (100%) rename {components => memory}/axaddrspace/src/address_space/backend/mod.rs (100%) rename {components => memory}/axaddrspace/src/address_space/mod.rs (100%) rename {components => memory}/axaddrspace/src/device/device_addr.rs (100%) rename {components => memory}/axaddrspace/src/device/mod.rs (100%) rename {components => memory}/axaddrspace/src/frame.rs (100%) rename {components => memory}/axaddrspace/src/hal.rs (100%) rename {components => memory}/axaddrspace/src/lib.rs (100%) rename {components => memory}/axaddrspace/src/memory_accessor.rs (100%) rename {components => memory}/axaddrspace/src/npt/arch/aarch64.rs (100%) rename {components => memory}/axaddrspace/src/npt/arch/loongarch64.rs (100%) rename {components => memory}/axaddrspace/src/npt/arch/mod.rs (100%) rename {components => memory}/axaddrspace/src/npt/arch/riscv.rs (100%) rename {components => memory}/axaddrspace/src/npt/arch/x86_64.rs (100%) rename {components => memory}/axaddrspace/src/npt/mod.rs (100%) rename {components => memory}/axaddrspace/tests/address_space.rs (100%) rename {components => memory}/axaddrspace/tests/frame.rs (100%) rename {components => memory}/axaddrspace/tests/memory_accessor.rs (100%) rename {components => memory}/axaddrspace/tests/test_utils/mod.rs (100%) rename {components => memory}/axallocator/.github/workflows/ci.yml (100%) rename {components => memory}/axallocator/.gitignore (100%) rename {components => memory}/axallocator/CHANGELOG.md (100%) rename {components => memory}/axallocator/Cargo.toml (100%) rename {components/arm_vcpu => memory/axallocator}/LICENSE (100%) rename {components => memory}/axallocator/README.md (98%) rename {components => memory}/axallocator/README_CN.md (98%) rename {components => memory}/axallocator/benches/collections.rs (100%) rename {components => memory}/axallocator/benches/utils/mod.rs (100%) rename {components => memory}/axallocator/src/bitmap.rs (100%) rename {components => memory}/axallocator/src/buddy.rs (100%) rename {components => memory}/axallocator/src/lib.rs (100%) rename {components => memory}/axallocator/src/slab.rs (100%) rename {components => memory}/axallocator/src/tlsf.rs (100%) rename {components => memory}/axallocator/tests/allocator.rs (100%) rename {components => memory}/bitmap-allocator/.github/workflows/main.yml (100%) rename {components => memory}/bitmap-allocator/.gitignore (100%) rename {components => memory}/bitmap-allocator/CHANGELOG.md (100%) rename {components => memory}/bitmap-allocator/Cargo.toml (100%) rename {components/arm_vgic => memory/bitmap-allocator}/LICENSE (100%) rename {components => memory}/bitmap-allocator/README.md (98%) rename {components => memory}/bitmap-allocator/README_CN.md (98%) rename {components => memory}/bitmap-allocator/src/lib.rs (100%) rename {components => memory}/buddy-slab-allocator/.github/workflows/check.yml (100%) rename {components => memory}/buddy-slab-allocator/.github/workflows/ci.yml (100%) rename {components => memory}/buddy-slab-allocator/.github/workflows/release-plz.yml (100%) rename {components => memory}/buddy-slab-allocator/.github/workflows/test.yml (100%) rename {components => memory}/buddy-slab-allocator/.gitignore (100%) rename {components => memory}/buddy-slab-allocator/CHANGELOG.md (100%) rename {components => memory}/buddy-slab-allocator/Cargo.toml (100%) rename {components => memory}/buddy-slab-allocator/LICENSE.Apache2 (100%) rename {components => memory}/buddy-slab-allocator/README.md (100%) rename {components => memory}/buddy-slab-allocator/README_CN.md (100%) rename {components => memory}/buddy-slab-allocator/benches/README_CN.md (100%) rename {components => memory}/buddy-slab-allocator/benches/buddy_allocator.rs (100%) rename {components => memory}/buddy-slab-allocator/benches/common.rs (100%) rename {components => memory}/buddy-slab-allocator/benches/global_allocator.rs (100%) rename {components => memory}/buddy-slab-allocator/benches/slab_allocator.rs (100%) rename {components => memory}/buddy-slab-allocator/docs/design.md (100%) rename {components => memory}/buddy-slab-allocator/rust-toolchain.toml (100%) rename {components => memory}/buddy-slab-allocator/src/buddy/mod.rs (100%) rename {components => memory}/buddy-slab-allocator/src/buddy/page_meta.rs (100%) rename {components => memory}/buddy-slab-allocator/src/error.rs (100%) rename {components => memory}/buddy-slab-allocator/src/global.rs (100%) rename {components => memory}/buddy-slab-allocator/src/lib.rs (100%) rename {components => memory}/buddy-slab-allocator/src/slab/cache.rs (100%) rename {components => memory}/buddy-slab-allocator/src/slab/mod.rs (100%) rename {components => memory}/buddy-slab-allocator/src/slab/page.rs (100%) rename {components => memory}/buddy-slab-allocator/src/slab/size_class.rs (100%) rename {components => memory}/buddy-slab-allocator/tests/README.md (100%) rename {components => memory}/buddy-slab-allocator/tests/common/mod.rs (100%) rename {components => memory}/buddy-slab-allocator/tests/dma32_pages_test.rs (100%) rename {components => memory}/buddy-slab-allocator/tests/integration_test.rs (100%) rename {components => memory}/buddy-slab-allocator/tests/stress_test.rs (100%) rename {components => memory}/dma-api/CHANGELOG.md (100%) rename {components => memory}/dma-api/Cargo.toml (100%) rename {components => memory}/dma-api/README.md (100%) rename {components => memory}/dma-api/src/array.rs (100%) rename {components => memory}/dma-api/src/common.rs (100%) rename {components => memory}/dma-api/src/dbox.rs (100%) rename {components => memory}/dma-api/src/def.rs (100%) rename {components => memory}/dma-api/src/lib.rs (100%) rename {components => memory}/dma-api/src/op/aarch64.rs (100%) rename {components => memory}/dma-api/src/op/mod.rs (100%) rename {components => memory}/dma-api/src/op/nop.rs (100%) rename {components => memory}/dma-api/src/pool.rs (100%) rename {components => memory}/dma-api/src/streaming.rs (100%) rename {components => memory}/dma-api/tests/test.rs (100%) rename {components => memory}/dma-api/tests/test_helpers.rs (100%) rename {components/axmm_crates => memory}/memory_addr/Cargo.toml (100%) rename {components/axmm_crates => memory}/memory_addr/README.md (97%) rename {components/axmm_crates => memory}/memory_addr/README_CN.md (97%) rename {components/axmm_crates => memory}/memory_addr/src/addr.rs (100%) rename {components/axmm_crates => memory}/memory_addr/src/iter.rs (100%) rename {components/axmm_crates => memory}/memory_addr/src/lib.rs (100%) rename {components/axmm_crates => memory}/memory_addr/src/range.rs (100%) rename {components/axmm_crates => memory}/memory_set/CHANGELOG.md (100%) rename {components/axmm_crates => memory}/memory_set/Cargo.toml (100%) rename {components/axmm_crates => memory}/memory_set/README.md (98%) rename {components/axmm_crates => memory}/memory_set/README_CN.md (97%) rename {components/axmm_crates => memory}/memory_set/src/area.rs (100%) rename {components/axmm_crates => memory}/memory_set/src/backend.rs (100%) rename {components/axmm_crates => memory}/memory_set/src/lib.rs (100%) rename {components/axmm_crates => memory}/memory_set/src/set.rs (100%) rename {components/axmm_crates => memory}/memory_set/src/tests.rs (100%) rename {components => memory}/mmio-api/CHANGELOG.md (100%) rename {components => memory}/mmio-api/Cargo.toml (100%) rename {components => memory}/mmio-api/src/lib.rs (100%) rename {components => memory}/page-table-generic/CHANGELOG.md (100%) rename {components => memory}/page-table-generic/Cargo.toml (100%) rename {components => memory}/page-table-generic/src/def.rs (100%) rename {components => memory}/page-table-generic/src/frame.rs (100%) rename {components => memory}/page-table-generic/src/lib.rs (100%) rename {components => memory}/page-table-generic/src/map.rs (100%) rename {components => memory}/page-table-generic/src/table.rs (100%) rename {components => memory}/page-table-generic/src/walk.rs (100%) rename {components => memory}/page-table-generic/tests/bugfixes.rs (100%) rename {components => memory}/page-table-generic/tests/drop.rs (100%) rename {components => memory}/page-table-generic/tests/flags.rs (100%) rename {components => memory}/page-table-generic/tests/loongarch64_simple.rs (98%) rename {components => memory}/page-table-generic/tests/loongarch64_simulation.rs (99%) rename {components => memory}/page-table-generic/tests/map.rs (98%) rename {components => memory}/page-table-generic/tests/mocks/mod.rs (100%) rename {components => memory}/page-table-generic/tests/translate.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/Cargo.toml (100%) rename {components/page_table_multiarch => memory}/page_table_entry/README.md (97%) rename {components/page_table_multiarch => memory}/page_table_entry/README_CN.md (97%) rename {components/page_table_multiarch => memory}/page_table_entry/src/arch/aarch64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/src/arch/arm.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/src/arch/loongarch64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/src/arch/mod.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/src/arch/riscv.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/src/arch/x86_64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_entry/src/lib.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/CHANGELOG.md (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/Cargo.toml (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/README.md (97%) rename {components/page_table_multiarch => memory}/page_table_multiarch/README_CN.md (97%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/arch/aarch64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/arch/arm.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/arch/loongarch64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/arch/mod.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/arch/riscv.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/arch/x86_64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/bits32.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/bits64.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/src/lib.rs (100%) rename {components/page_table_multiarch => memory}/page_table_multiarch/tests/alloc_tests.rs (100%) rename {components => memory}/range-alloc-arceos/.github/config.json (100%) rename {components/arm_vgic => memory/range-alloc-arceos}/.github/workflows/check.yml (100%) rename {components/axvm => memory/range-alloc-arceos}/.github/workflows/deploy.yml (100%) rename {components/arm_vgic => memory/range-alloc-arceos}/.github/workflows/push.yml (100%) rename {components/arm_vgic => memory/range-alloc-arceos}/.github/workflows/release.yml (100%) rename {components/arm_vgic => memory/range-alloc-arceos}/.github/workflows/test.yml (100%) rename {components => memory}/range-alloc-arceos/.gitignore (100%) rename {components => memory}/range-alloc-arceos/CHANGELOG.md (100%) rename {components => memory}/range-alloc-arceos/Cargo.toml (100%) rename {components/axaddrspace => memory/range-alloc-arceos}/LICENSE (100%) rename {components => memory}/range-alloc-arceos/LICENSE.APACHE (100%) rename {components => memory}/range-alloc-arceos/LICENSE.MIT (100%) rename {components => memory}/range-alloc-arceos/README.md (98%) rename {components => memory}/range-alloc-arceos/README_CN.md (98%) rename {components => memory}/range-alloc-arceos/src/lib.rs (100%) rename {components => memory}/range-alloc-arceos/tests/test.rs (100%) rename {components => memory}/ranges-ext/CHANGELOG.md (100%) rename {components => memory}/ranges-ext/Cargo.toml (100%) rename {components => memory}/ranges-ext/src/less.rs (100%) rename {components => memory}/ranges-ext/src/lib.rs (100%) rename {components => memory}/ranges-ext/src/test_helper.rs (100%) rename {components => memory}/ranges-ext/src/vec.rs (100%) rename {components => memory}/ranges-ext/tests/test.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/CHANGELOG.md (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/Cargo.toml (96%) rename platforms/{ax-plat-dyn => axplat-dyn}/build.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/link.ld (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/boot.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/console.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/drivers/mod.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/generic_timer.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/init.rs (93%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/irq.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/lib.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/mem.rs (100%) rename platforms/{ax-plat-dyn => axplat-dyn}/src/power.rs (100%) rename {components => virtualization}/aarch64_sysreg/.github/config.json (100%) rename {components => virtualization}/aarch64_sysreg/.github/workflows/check.yml (100%) rename {components => virtualization}/aarch64_sysreg/.github/workflows/deploy.yml (100%) rename {components/axaddrspace => virtualization/aarch64_sysreg}/.github/workflows/push.yml (100%) rename {components/arm_vcpu => virtualization/aarch64_sysreg}/.github/workflows/release.yml (100%) rename {components => virtualization}/aarch64_sysreg/.github/workflows/test.yml (100%) rename {components => virtualization}/aarch64_sysreg/.gitignore (100%) rename {components => virtualization}/aarch64_sysreg/Cargo.toml (100%) rename {components/axallocator => virtualization/aarch64_sysreg}/LICENSE (100%) rename {components => virtualization}/aarch64_sysreg/README.md (98%) rename {components => virtualization}/aarch64_sysreg/README_CN.md (98%) rename {components => virtualization}/aarch64_sysreg/scripts/check.sh (100%) rename {components => virtualization}/aarch64_sysreg/scripts/test.sh (100%) rename {components => virtualization}/aarch64_sysreg/src/lib.rs (100%) rename {components => virtualization}/aarch64_sysreg/src/operation_type.rs (100%) rename {components => virtualization}/aarch64_sysreg/src/registers_type.rs (100%) rename {components => virtualization}/aarch64_sysreg/src/system_reg_type.rs (100%) rename {components => virtualization}/aarch64_sysreg/tests/operation_type_tests.rs (100%) rename {components => virtualization}/aarch64_sysreg/tests/registers_type_tests.rs (100%) rename {components => virtualization}/aarch64_sysreg/tests/system_reg_type_tests.rs (100%) rename {components => virtualization}/arm_vcpu/.cargo/config.toml (100%) rename {components => virtualization}/arm_vcpu/.github/config.json (100%) rename {components/axaddrspace => virtualization/arm_vcpu}/.github/workflows/check.yml (100%) rename {components/axaddrspace => virtualization/arm_vcpu}/.github/workflows/deploy.yml (100%) rename {components => virtualization}/arm_vcpu/.github/workflows/push.yml (100%) rename {components/axaddrspace => virtualization/arm_vcpu}/.github/workflows/release.yml (100%) rename {components/axaddrspace => virtualization/arm_vcpu}/.github/workflows/test.yml (100%) rename {components => virtualization}/arm_vcpu/.gitignore (100%) rename {components => virtualization}/arm_vcpu/CHANGELOG.md (100%) rename {components => virtualization}/arm_vcpu/Cargo.toml (100%) rename {components/axdevice => virtualization/arm_vcpu}/LICENSE (100%) rename {components => virtualization}/arm_vcpu/README.md (98%) rename {components => virtualization}/arm_vcpu/README_CN.md (98%) rename {components => virtualization}/arm_vcpu/scripts/check.sh (100%) rename {components => virtualization}/arm_vcpu/scripts/test.sh (100%) rename {components => virtualization}/arm_vcpu/src/context_frame.rs (100%) rename {components => virtualization}/arm_vcpu/src/exception.S (100%) rename {components => virtualization}/arm_vcpu/src/exception.rs (100%) rename {components => virtualization}/arm_vcpu/src/exception_utils.rs (100%) rename {components => virtualization}/arm_vcpu/src/lib.rs (100%) rename {components => virtualization}/arm_vcpu/src/pcpu.rs (100%) rename {components => virtualization}/arm_vcpu/src/smc.rs (100%) rename {components => virtualization}/arm_vcpu/src/vcpu.rs (100%) rename {components => virtualization}/arm_vcpu/tests/axci_flow_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/context_frame_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/hardware_support_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/mpidr_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/psci_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/script_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/spsr_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/vcpu_config_test.rs (100%) rename {components => virtualization}/arm_vcpu/tests/vmcpu_registers_test.rs (100%) rename {components => virtualization}/arm_vgic/.github/config.json (100%) rename {components/axvcpu => virtualization/arm_vgic}/.github/workflows/check.yml (100%) rename {components => virtualization}/arm_vgic/.github/workflows/deploy.yml (100%) rename {components/axvcpu => virtualization/arm_vgic}/.github/workflows/push.yml (100%) rename {components/axvcpu => virtualization/arm_vgic}/.github/workflows/release.yml (100%) rename {components/axvcpu => virtualization/arm_vgic}/.github/workflows/test.yml (100%) rename {components => virtualization}/arm_vgic/.gitignore (100%) rename {components => virtualization}/arm_vgic/CHANGELOG.md (100%) rename {components => virtualization}/arm_vgic/Cargo.toml (100%) rename {components/axdevice_base => virtualization/arm_vgic}/LICENSE (100%) rename {components => virtualization}/arm_vgic/README.md (98%) rename {components => virtualization}/arm_vgic/README_CN.md (98%) rename {components => virtualization}/arm_vgic/src/consts.rs (100%) rename {components => virtualization}/arm_vgic/src/devops_impl.rs (100%) rename {components => virtualization}/arm_vgic/src/interrupt.rs (100%) rename {components => virtualization}/arm_vgic/src/lib.rs (100%) rename {components => virtualization}/arm_vgic/src/list_register.rs (100%) rename {components => virtualization}/arm_vgic/src/registers.rs (100%) rename {components => virtualization}/arm_vgic/src/v3/gits.rs (100%) rename {components => virtualization}/arm_vgic/src/v3/mod.rs (100%) rename {components => virtualization}/arm_vgic/src/v3/registers.rs (100%) rename {components => virtualization}/arm_vgic/src/v3/utils.rs (100%) rename {components => virtualization}/arm_vgic/src/v3/vgicd.rs (100%) rename {components => virtualization}/arm_vgic/src/v3/vgicr.rs (100%) rename {components => virtualization}/arm_vgic/src/vgic.rs (100%) rename {components => virtualization}/arm_vgic/src/vgicd.rs (100%) rename {components => virtualization}/arm_vgic/src/vtimer/cntp_ctl_el0.rs (100%) rename {components => virtualization}/arm_vgic/src/vtimer/cntp_tval_el0.rs (100%) rename {components => virtualization}/arm_vgic/src/vtimer/cntpct_el0.rs (100%) rename {components => virtualization}/arm_vgic/src/vtimer/mod.rs (100%) rename {components => virtualization}/axdevice/.github/config.json (100%) rename {components => virtualization}/axdevice/.github/workflows/check.yml (100%) rename {components => virtualization}/axdevice/.github/workflows/deploy.yml (100%) rename {components => virtualization}/axdevice/.github/workflows/push.yml (100%) rename {components => virtualization}/axdevice/.github/workflows/release.yml (100%) rename {components => virtualization}/axdevice/.github/workflows/test.yml (100%) rename {components => virtualization}/axdevice/.gitignore (100%) rename {components => virtualization}/axdevice/CHANGELOG.md (100%) rename {components => virtualization}/axdevice/Cargo.toml (100%) rename {components/axhvc => virtualization/axdevice}/LICENSE (100%) rename {components => virtualization}/axdevice/README.md (98%) rename {components => virtualization}/axdevice/README_CN.md (98%) rename {components => virtualization}/axdevice/scripts/check.sh (100%) rename {components => virtualization}/axdevice/scripts/test.sh (100%) rename {components => virtualization}/axdevice/src/config.rs (100%) rename {components => virtualization}/axdevice/src/device.rs (100%) rename {components => virtualization}/axdevice/src/lib.rs (100%) rename {components => virtualization}/axdevice/tests/test.rs (100%) rename {components => virtualization}/axdevice_base/.github/config.json (100%) rename {components => virtualization}/axdevice_base/.github/workflows/check.yml (100%) rename {components => virtualization}/axdevice_base/.github/workflows/deploy.yml (100%) rename {components => virtualization}/axdevice_base/.github/workflows/push.yml (100%) rename {components => virtualization}/axdevice_base/.github/workflows/release.yml (100%) rename {components => virtualization}/axdevice_base/.github/workflows/test.yml (100%) rename {components => virtualization}/axdevice_base/.gitignore (100%) rename {components => virtualization}/axdevice_base/CHANGELOG.md (100%) rename {components => virtualization}/axdevice_base/Cargo.toml (100%) rename {components/axmm_crates => virtualization/axdevice_base}/LICENSE (100%) rename {components => virtualization}/axdevice_base/README.md (98%) rename {components => virtualization}/axdevice_base/README_CN.md (98%) rename {components => virtualization}/axdevice_base/scripts/check.sh (100%) rename {components => virtualization}/axdevice_base/scripts/test.sh (100%) rename {components => virtualization}/axdevice_base/src/lib.rs (100%) rename {components => virtualization}/axdevice_base/tests/test.rs (100%) rename {components => virtualization}/axhvc/.github/config.json (100%) rename {components => virtualization}/axhvc/.github/workflows/check.yml (100%) rename {components => virtualization}/axhvc/.github/workflows/deploy.yml (100%) rename {components => virtualization}/axhvc/.github/workflows/push.yml (100%) rename {components => virtualization}/axhvc/.github/workflows/release.yml (100%) rename {components => virtualization}/axhvc/.github/workflows/test.yml (100%) rename {components => virtualization}/axhvc/.gitignore (100%) rename {components => virtualization}/axhvc/CHANGELOG.md (100%) rename {components => virtualization}/axhvc/Cargo.toml (100%) rename {components/axvcpu => virtualization/axhvc}/LICENSE (100%) rename {components => virtualization}/axhvc/README.md (98%) rename {components => virtualization}/axhvc/README_CN.md (98%) rename {components => virtualization}/axhvc/scripts/check.sh (100%) rename {components => virtualization}/axhvc/scripts/test.sh (100%) rename {components => virtualization}/axhvc/src/lib.rs (100%) rename {components => virtualization}/axhvc/tests/test.rs (100%) rename {components => virtualization}/axvcpu/.github/config.json (100%) rename {components/axvisor_api => virtualization/axvcpu}/.github/workflows/check.yml (100%) rename {components => virtualization}/axvcpu/.github/workflows/deploy.yml (100%) rename {components/axvisor_api => virtualization/axvcpu}/.github/workflows/push.yml (100%) rename {components/axvisor_api => virtualization/axvcpu}/.github/workflows/release.yml (100%) rename {components/axvisor_api => virtualization/axvcpu}/.github/workflows/test.yml (100%) rename {components => virtualization}/axvcpu/.gitignore (100%) rename {components => virtualization}/axvcpu/CHANGELOG.md (100%) rename {components => virtualization}/axvcpu/Cargo.toml (100%) rename {components/axvisor_api => virtualization/axvcpu}/LICENSE (100%) rename {components => virtualization}/axvcpu/README.md (98%) rename {components => virtualization}/axvcpu/README_CN.md (98%) rename {components => virtualization}/axvcpu/src/arch_vcpu.rs (100%) rename {components => virtualization}/axvcpu/src/exit.rs (100%) rename {components => virtualization}/axvcpu/src/lib.rs (100%) rename {components => virtualization}/axvcpu/src/percpu.rs (100%) rename {components => virtualization}/axvcpu/src/test.rs (100%) rename {components => virtualization}/axvcpu/src/vcpu.rs (100%) rename {components => virtualization}/axvisor_api/.github/config.json (100%) rename {components/axvm => virtualization/axvisor_api}/.github/workflows/check.yml (100%) rename {components => virtualization}/axvisor_api/.github/workflows/deploy.yml (100%) rename {components/axvm => virtualization/axvisor_api}/.github/workflows/push.yml (100%) rename {components/axvm => virtualization/axvisor_api}/.github/workflows/release.yml (100%) rename {components/axvm => virtualization/axvisor_api}/.github/workflows/test.yml (100%) rename {components/axmm_crates => virtualization/axvisor_api}/.gitignore (100%) rename {components => virtualization}/axvisor_api/.rustfmt.toml (100%) rename {components => virtualization}/axvisor_api/CHANGELOG.md (100%) rename {components => virtualization}/axvisor_api/Cargo.toml (100%) rename {components/axvm => virtualization/axvisor_api}/LICENSE (100%) rename {components => virtualization}/axvisor_api/README.md (97%) rename {components => virtualization}/axvisor_api/README.zh-cn.md (100%) rename {components => virtualization}/axvisor_api/README_CN.md (96%) rename {components => virtualization}/axvisor_api/examples/example.rs (100%) rename {components => virtualization}/axvisor_api/src/arch.rs (100%) rename {components => virtualization}/axvisor_api/src/host.rs (100%) rename {components => virtualization}/axvisor_api/src/lib.rs (100%) rename {components => virtualization}/axvisor_api/src/memory.rs (100%) rename {components => virtualization}/axvisor_api/src/test.rs (100%) rename {components => virtualization}/axvisor_api/src/time.rs (100%) rename {components => virtualization}/axvisor_api/src/vmm.rs (100%) rename {components/axvisor_api => virtualization}/axvisor_api_proc/Cargo.toml (100%) rename {components/axvisor_api => virtualization}/axvisor_api_proc/README.md (97%) rename {components/axvisor_api => virtualization}/axvisor_api_proc/README_CN.md (97%) rename {components/axvisor_api => virtualization}/axvisor_api_proc/src/lib.rs (100%) rename {components => virtualization}/axvm/.github/config.json (100%) rename {components/axvmconfig => virtualization/axvm}/.github/workflows/check.yml (100%) rename {components/range-alloc-arceos => virtualization/axvm}/.github/workflows/deploy.yml (100%) rename {components/axvmconfig => virtualization/axvm}/.github/workflows/push.yml (100%) rename {components/axvmconfig => virtualization/axvm}/.github/workflows/release.yml (100%) rename {components/axvmconfig => virtualization/axvm}/.github/workflows/test.yml (100%) rename {components => virtualization}/axvm/.gitignore (100%) rename {components => virtualization}/axvm/CHANGELOG.md (100%) rename {components => virtualization}/axvm/Cargo.toml (100%) rename {components/axvmconfig => virtualization/axvm}/LICENSE (100%) rename {components => virtualization}/axvm/README.md (98%) rename {components => virtualization}/axvm/README_CN.md (98%) rename {components => virtualization}/axvm/src/config.rs (100%) rename {components => virtualization}/axvm/src/hal.rs (100%) rename {components => virtualization}/axvm/src/lib.rs (100%) rename {components => virtualization}/axvm/src/vcpu.rs (100%) rename {components => virtualization}/axvm/src/vm.rs (100%) rename {components => virtualization}/axvmconfig/.github/config.json (100%) rename {components/range-alloc-arceos => virtualization/axvmconfig}/.github/workflows/check.yml (100%) rename {components => virtualization}/axvmconfig/.github/workflows/deploy.yml (100%) rename {components/range-alloc-arceos => virtualization/axvmconfig}/.github/workflows/push.yml (100%) rename {components/range-alloc-arceos => virtualization/axvmconfig}/.github/workflows/release.yml (100%) rename {components/range-alloc-arceos => virtualization/axvmconfig}/.github/workflows/test.yml (100%) rename {components/axvisor_api => virtualization/axvmconfig}/.gitignore (100%) rename {components => virtualization}/axvmconfig/CHANGELOG.md (100%) rename {components => virtualization}/axvmconfig/Cargo.toml (100%) rename {components/bitmap-allocator => virtualization/axvmconfig}/LICENSE (100%) rename {components => virtualization}/axvmconfig/README.md (98%) rename {components => virtualization}/axvmconfig/README_CN.md (98%) rename {components => virtualization}/axvmconfig/src/lib.rs (100%) rename {components => virtualization}/axvmconfig/src/main.rs (100%) rename {components => virtualization}/axvmconfig/src/templates.rs (100%) rename {components => virtualization}/axvmconfig/src/test.rs (100%) rename {components => virtualization}/axvmconfig/src/tool.rs (100%) rename {components => virtualization}/axvmconfig/templates/aarch64.toml (100%) rename {components => virtualization}/axvmconfig/templates/riscv64.toml (100%) rename {components => virtualization}/axvmconfig/templates/x86_64.toml (100%) rename {components => virtualization}/loongarch_vcpu/CHANGELOG.md (100%) rename {components => virtualization}/loongarch_vcpu/Cargo.toml (100%) rename {components => virtualization}/loongarch_vcpu/src/context_frame.rs (100%) rename {components => virtualization}/loongarch_vcpu/src/exception.S (100%) rename {components => virtualization}/loongarch_vcpu/src/exception.rs (100%) rename {components => virtualization}/loongarch_vcpu/src/lib.rs (100%) rename {components => virtualization}/loongarch_vcpu/src/pcpu.rs (100%) rename {components => virtualization}/loongarch_vcpu/src/registers.rs (100%) rename {components => virtualization}/loongarch_vcpu/src/vcpu.rs (100%) rename {components => virtualization}/riscv-h/.github/config.json (100%) rename {components => virtualization}/riscv-h/.github/workflows/check.yml (100%) rename {components => virtualization}/riscv-h/.github/workflows/deploy.yml (100%) rename {components => virtualization}/riscv-h/.github/workflows/push.yml (100%) rename {components => virtualization}/riscv-h/.github/workflows/release.yml (100%) rename {components => virtualization}/riscv-h/.github/workflows/test.yml (100%) rename {components => virtualization}/riscv-h/.gitignore (100%) rename {components => virtualization}/riscv-h/CHANGELOG.md (100%) rename {components => virtualization}/riscv-h/Cargo.toml (100%) rename {components/page_table_multiarch => virtualization/riscv-h}/LICENSE (100%) rename {components => virtualization}/riscv-h/README.md (98%) rename {components => virtualization}/riscv-h/README_CN.md (98%) rename {components => virtualization}/riscv-h/scripts/check.sh (100%) rename {components => virtualization}/riscv-h/scripts/test.sh (100%) rename {components => virtualization}/riscv-h/src/lib.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hcounteren.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hedeleg.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hgatp.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hgeie.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hgeip.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hideleg.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hie.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hip.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hstatus.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/htimedelta.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/htimedeltah.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/htinst.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/htval.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/hvip.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/mod.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vsatp.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vscause.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vsepc.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vsie.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vsip.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vsscratch.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vsstatus.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vstimecmp.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vstval.rs (100%) rename {components => virtualization}/riscv-h/src/register/hypervisorx64/vstvec.rs (100%) rename {components => virtualization}/riscv-h/src/register/mod.rs (100%) rename {components => virtualization}/riscv-h/tests/integration_tests.rs (100%) rename {components => virtualization}/riscv-h/tests/register_tests.rs (100%) rename {components => virtualization}/riscv_vcpu/.cargo/config.toml (100%) rename {components => virtualization}/riscv_vcpu/.github/config.json (100%) rename {components => virtualization}/riscv_vcpu/.github/workflows/check.yml (100%) rename {components => virtualization}/riscv_vcpu/.github/workflows/deploy.yml (100%) rename {components => virtualization}/riscv_vcpu/.github/workflows/push.yml (100%) rename {components => virtualization}/riscv_vcpu/.github/workflows/release.yml (100%) rename {components => virtualization}/riscv_vcpu/.github/workflows/test.yml (100%) rename {components => virtualization}/riscv_vcpu/.gitignore (100%) rename {components => virtualization}/riscv_vcpu/CHANGELOG.md (100%) rename {components => virtualization}/riscv_vcpu/Cargo.toml (100%) rename {components/range-alloc-arceos => virtualization/riscv_vcpu}/LICENSE (100%) rename {components => virtualization}/riscv_vcpu/README.md (98%) rename {components => virtualization}/riscv_vcpu/README_CN.md (98%) rename {components => virtualization}/riscv_vcpu/src/consts.rs (100%) rename {components => virtualization}/riscv_vcpu/src/detect.rs (100%) rename {components => virtualization}/riscv_vcpu/src/guest_mem.rs (100%) rename {components => virtualization}/riscv_vcpu/src/lib.rs (100%) rename {components => virtualization}/riscv_vcpu/src/mem_extable.S (100%) rename {components => virtualization}/riscv_vcpu/src/percpu.rs (100%) rename {components => virtualization}/riscv_vcpu/src/regs.rs (100%) rename {components => virtualization}/riscv_vcpu/src/sbi_console.rs (100%) rename {components => virtualization}/riscv_vcpu/src/trap.S (100%) rename {components => virtualization}/riscv_vcpu/src/trap.rs (100%) rename {components => virtualization}/riscv_vcpu/src/vcpu.rs (100%) rename {components => virtualization}/riscv_vcpu/src/vpmu.rs (100%) rename {components => virtualization}/riscv_vplic/.github/config.json (100%) rename {components => virtualization}/riscv_vplic/.github/workflows/check.yml (100%) rename {components => virtualization}/riscv_vplic/.github/workflows/deploy.yml (100%) rename {components => virtualization}/riscv_vplic/.github/workflows/push.yml (100%) rename {components => virtualization}/riscv_vplic/.github/workflows/release.yml (100%) rename {components => virtualization}/riscv_vplic/.github/workflows/test.yml (100%) rename {components => virtualization}/riscv_vplic/.gitignore (100%) rename {components => virtualization}/riscv_vplic/CHANGELOG.md (100%) rename {components => virtualization}/riscv_vplic/Cargo.toml (100%) rename {components/riscv-h => virtualization/riscv_vplic}/LICENSE (100%) rename {components => virtualization}/riscv_vplic/README.md (98%) rename {components => virtualization}/riscv_vplic/README_CN.md (98%) rename {components => virtualization}/riscv_vplic/scripts/check.sh (100%) rename {components => virtualization}/riscv_vplic/scripts/test.sh (100%) rename {components => virtualization}/riscv_vplic/src/consts.rs (100%) rename {components => virtualization}/riscv_vplic/src/devops_impl.rs (100%) rename {components => virtualization}/riscv_vplic/src/lib.rs (100%) rename {components => virtualization}/riscv_vplic/src/utils.rs (100%) rename {components => virtualization}/riscv_vplic/src/vplic.rs (100%) rename {components => virtualization}/riscv_vplic/tests/consts_tests.rs (100%) rename {components => virtualization}/riscv_vplic/tests/vplic_tests.rs (100%) rename {components => virtualization}/x86_vcpu/.github/config.json (100%) rename {components => virtualization}/x86_vcpu/.github/workflows/check.yml (100%) rename {components => virtualization}/x86_vcpu/.github/workflows/deploy.yml (100%) rename {components => virtualization}/x86_vcpu/.github/workflows/push.yml (100%) rename {components => virtualization}/x86_vcpu/.github/workflows/release.yml (100%) rename {components => virtualization}/x86_vcpu/.github/workflows/test.yml (100%) rename {components => virtualization}/x86_vcpu/.gitignore (100%) rename {components => virtualization}/x86_vcpu/CHANGELOG.md (100%) rename {components => virtualization}/x86_vcpu/Cargo.toml (100%) rename {components/riscv_vcpu => virtualization/x86_vcpu}/LICENSE (100%) rename {components => virtualization}/x86_vcpu/README.md (98%) rename {components => virtualization}/x86_vcpu/README_CN.md (98%) rename {components => virtualization}/x86_vcpu/src/ept.rs (100%) rename {components => virtualization}/x86_vcpu/src/lib.rs (100%) rename {components => virtualization}/x86_vcpu/src/msr.rs (100%) rename {components => virtualization}/x86_vcpu/src/regs/accessors.rs (100%) rename {components => virtualization}/x86_vcpu/src/regs/diff.rs (100%) rename {components => virtualization}/x86_vcpu/src/regs/mod.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/definitions.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/flags.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/frame.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/instructions.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/mod.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/percpu.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/structs.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/vcpu.rs (100%) rename {components => virtualization}/x86_vcpu/src/svm/vmcb.rs (100%) rename {components => virtualization}/x86_vcpu/src/test_utils.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/definitions.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/instructions.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/mod.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/percpu.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/structs.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/vcpu.rs (100%) rename {components => virtualization}/x86_vcpu/src/vmx/vmcs.rs (100%) rename {components => virtualization}/x86_vcpu/src/xstate.rs (100%) rename {components => virtualization}/x86_vlapic/.github/config.json (100%) rename {components => virtualization}/x86_vlapic/.github/workflows/check.yml (100%) rename {components => virtualization}/x86_vlapic/.github/workflows/deploy.yml (100%) rename {components => virtualization}/x86_vlapic/.github/workflows/release.yml (100%) rename {components => virtualization}/x86_vlapic/.github/workflows/test.yml (100%) rename {components/axvmconfig => virtualization/x86_vlapic}/.gitignore (100%) rename {components => virtualization}/x86_vlapic/CHANGELOG.md (100%) rename {components => virtualization}/x86_vlapic/Cargo.toml (100%) rename {components/riscv_vplic => virtualization/x86_vlapic}/LICENSE (100%) rename {components => virtualization}/x86_vlapic/README.md (98%) rename {components => virtualization}/x86_vlapic/README_CN.md (98%) rename {components => virtualization}/x86_vlapic/src/consts.rs (100%) rename {components => virtualization}/x86_vlapic/src/lib.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/apic_base.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/dfr.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/esr.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/icr.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/cmci.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/error.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/lint0.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/lint1.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/mod.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/perfmon.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/thermal.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/lvt/timer.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/mod.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/svr.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/timer/dcr.rs (100%) rename {components => virtualization}/x86_vlapic/src/regs/timer/mod.rs (100%) rename {components => virtualization}/x86_vlapic/src/timer.rs (100%) rename {components => virtualization}/x86_vlapic/src/utils.rs (100%) rename {components => virtualization}/x86_vlapic/src/vlapic.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index d8cdadcb4c..a39050d632 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1033,13 +1033,13 @@ dependencies = [ "ax-plat-aarch64-phytium-pi", "ax-plat-aarch64-qemu-virt", "ax-plat-aarch64-raspi", - "ax-plat-dyn", "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", "ax-plat-riscv64-sg2002", "ax-plat-riscv64-visionfive2", "ax-plat-x86-pc", "ax-plat-x86-qemu-q35", + "axplat-dyn", "cfg-if", "fdt-parser", "heapless 0.9.3", @@ -1395,26 +1395,6 @@ dependencies = [ "log", ] -[[package]] -name = "ax-plat-dyn" -version = "0.6.2" -dependencies = [ - "anyhow", - "ax-config-macros", - "ax-cpu", - "ax-driver", - "ax-errno", - "ax-memory-addr", - "ax-percpu", - "ax-plat", - "axklib", - "heapless 0.9.3", - "log", - "rdrive", - "somehal", - "spin", -] - [[package]] name = "ax-plat-loongarch64-qemu-virt" version = "0.5.10" @@ -1866,6 +1846,26 @@ dependencies = [ name = "axpanic" version = "0.1.0" +[[package]] +name = "axplat-dyn" +version = "0.6.2" +dependencies = [ + "anyhow", + "ax-config-macros", + "ax-cpu", + "ax-driver", + "ax-errno", + "ax-memory-addr", + "ax-percpu", + "ax-plat", + "axklib", + "heapless 0.9.3", + "log", + "rdrive", + "somehal", + "spin", +] + [[package]] name = "axpoll" version = "0.3.9" @@ -1961,7 +1961,6 @@ dependencies = [ "ax-page-table-entry", "ax-page-table-multiarch", "ax-percpu", - "ax-plat-dyn", "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", "ax-plat-x86-qemu-q35", @@ -1973,6 +1972,7 @@ dependencies = [ "axdevice_base", "axhvc", "axklib", + "axplat-dyn", "axvcpu", "axvisor_api", "axvm", @@ -7893,12 +7893,12 @@ dependencies = [ "ax-net-ng", "ax-page-table-multiarch", "ax-percpu", - "ax-plat-dyn", "ax-runtime", "ax-sync", "ax-task", "axbacktrace", "axfs-ng-vfs", + "axplat-dyn", "axpoll", "bitflags 2.11.1", "bitmaps", @@ -7993,8 +7993,8 @@ dependencies = [ "ax-driver", "ax-feat", "ax-hal", - "ax-plat-dyn", "axbuild", + "axplat-dyn", "clap", "starry-kernel", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 3d507b75f6..cb0aca1c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,34 +15,19 @@ resolver = "3" members = [ "apps/starry/orangepi-5-plus-uvc/uvc-fps", - "components/aarch64_sysreg", - "components/arm_vcpu", - "components/arm_vgic", "components/ax-lazyinit", - "components/axaddrspace", - "components/axallocator", "components/axbacktrace", "components/axcpu", - "components/axdevice", - "components/axdevice_base", "components/axerrno", "components/axfs-ng-vfs", - "components/axhvc", "components/axio", "components/axklib", "components/axpanic", "components/axpoll", "components/axsched", - "components/axvcpu", - "components/axvisor_api", - "components/axvm", - "components/axvmconfig", - "components/bitmap-allocator", - "components/buddy-slab-allocator", "components/cap_access", "components/cpumask", "components/crate_interface", - "components/dma-api", "components/handler_table", "components/int_ratio", "components/kasm-aarch64", @@ -51,14 +36,6 @@ members = [ "components/kspin", "components/linked_list_r4l", "components/lockdep", - "components/loongarch_vcpu", - "components/mmio-api", - "components/page-table-generic", - "components/range-alloc-arceos", - "components/ranges-ext", - "components/riscv-h", - "components/riscv_vcpu", - "components/riscv_vplic", "components/rsext4", "components/scope-local", "components/someboot", @@ -67,22 +44,18 @@ members = [ "components/starry-signal", "components/starry-vm", "components/timer_list", - "components/x86_vcpu", - "components/x86_vlapic", "components/axconfig-gen/axconfig-gen", "components/axconfig-gen/axconfig-macros", "components/axfs_crates/axfs_devfs", "components/axfs_crates/axfs_ramfs", "components/axfs_crates/axfs_vfs", - "components/axmm_crates/memory_addr", - "components/axmm_crates/memory_set", "platforms/*", "components/crate_interface/crate_interface_lite", "components/crate_interface/test_crates/*", "components/ctor_bare/ctor_bare", "components/ctor_bare/ctor_bare_macros", - "components/page_table_multiarch/page_table_entry", - "components/page_table_multiarch/page_table_multiarch", + "memory/*", + "virtualization/*", "components/percpu/percpu", "components/percpu/percpu_macros", @@ -161,7 +134,7 @@ members = [ [workspace.dependencies] # Local workspace crates -aarch64_sysreg = { version = "0.3.7", path = "components/aarch64_sysreg" } +aarch64_sysreg = { version = "0.3.7", path = "virtualization/aarch64_sysreg" } arm-scmi-rs = { version = "0.1.2", path = "drivers/firmware/arm-scmi-rs" } arceos-affinity = { version = "0.3.1", path = "test-suit/arceos/rust/task/affinity" } arceos-backtrace-raw-basic = { version = "0.3.1", path = "test-suit/arceos/rust/backtrace-raw-basic" } @@ -186,10 +159,10 @@ arceos-sleep = { version = "0.3.1", path = "test-suit/arceos/rust/task/sleep" } arceos-tls = { version = "0.3.1", path = "test-suit/arceos/rust/task/tls" } arceos-wait-queue = { version = "0.3.1", path = "test-suit/arceos/rust/task/wait_queue" } arceos-yield = { version = "0.3.1", path = "test-suit/arceos/rust/task/yield" } -arm_vcpu = { version = "0.5.9", path = "components/arm_vcpu" } -arm_vgic = { version = "0.4.10", path = "components/arm_vgic" } +arm_vcpu = { version = "0.5.9", path = "virtualization/arm_vcpu" } +arm_vgic = { version = "0.4.10", path = "virtualization/arm_vgic" } ax-alloc = { version = "0.7.2", path = "os/arceos/modules/axalloc", default-features = false } -ax-allocator = { version = "0.5.2", path = "components/axallocator" } +ax-allocator = { version = "0.5.2", path = "memory/axallocator" } ax-api = { version = "0.5.16", path = "os/arceos/api/arceos_api" } ax-arm-pl031 = { version = "0.4.7", path = "drivers/rtc/arm_pl031" } ax-cap-access = { version = "0.3.5", path = "components/cap_access" } @@ -230,14 +203,14 @@ ax-lazyinit = { version = "0.4.7", path = "components/ax-lazyinit" } ax-libc = { version = "0.5.15", path = "os/arceos/ulib/axlibc" } ax-linked-list-r4l = { version = "0.5.5", path = "components/linked_list_r4l" } ax-log = { version = "0.5.12", path = "os/arceos/modules/axlog" } -ax-memory-addr = { version = "0.6.8", path = "components/axmm_crates/memory_addr" } -ax-memory-set = { version = "0.6.9", path = "components/axmm_crates/memory_set" } +ax-memory-addr = { version = "0.6.8", path = "memory/memory_addr" } +ax-memory-set = { version = "0.6.9", path = "memory/memory_set" } axpanic = { version = "0.1.0", path = "components/axpanic" } ax-mm = { version = "0.5.15", path = "os/arceos/modules/axmm" } ax-net = { version = "0.5.14", path = "os/arceos/modules/axnet" } ax-net-ng = { version = "0.6.1", path = "os/arceos/modules/axnet-ng" } -ax-page-table-entry = { version = "0.8.9", path = "components/page_table_multiarch/page_table_entry" } -ax-page-table-multiarch = { version = "0.8.10", path = "components/page_table_multiarch/page_table_multiarch" } +ax-page-table-entry = { version = "0.8.9", path = "memory/page_table_entry" } +ax-page-table-multiarch = { version = "0.8.10", path = "memory/page_table_multiarch" } ax-percpu = { version = "0.4.11", path = "components/percpu/percpu" } ax-percpu-macros = { version = "0.4.10", path = "components/percpu/percpu_macros" } ax-plat = { version = "0.5.8", path = "platforms/ax-plat" } @@ -260,25 +233,25 @@ ax-std = { version = "0.5.15", path = "os/arceos/ulib/axstd" } ax-sync = { version = "0.5.15", path = "os/arceos/modules/axsync" } ax-task = { version = "0.5.16", path = "os/arceos/modules/axtask" } ax-timer-list = { version = "0.3.5", path = "components/timer_list" } -axaddrspace = { version = "0.5.11", path = "components/axaddrspace" } +axaddrspace = { version = "0.5.11", path = "memory/axaddrspace" } axbacktrace = { version = "0.4.0", path = "components/axbacktrace" } axbuild = { version = "0.4.8", path = "scripts/axbuild" } -axdevice = { version = "0.4.10", path = "components/axdevice" } -axdevice_base = { version = "0.4.11", path = "components/axdevice_base" } +axdevice = { version = "0.4.10", path = "virtualization/axdevice" } +axdevice_base = { version = "0.4.11", path = "virtualization/axdevice_base" } axfs-ng-vfs = { version = "0.4.2", path = "components/axfs-ng-vfs" } -axhvc = { version = "0.4.8", path = "components/axhvc" } +axhvc = { version = "0.4.8", path = "virtualization/axhvc" } axklib = { version = "0.5.9", path = "components/axklib" } -ax-plat-dyn = { version = "0.6.2", path = "platforms/ax-plat-dyn", default-features = false } +axplat-dyn = { version = "0.6.2", path = "platforms/axplat-dyn", default-features = false } ax-plat-riscv64-visionfive2 = { version = "0.1.4", path = "platforms/ax-plat-riscv64-visionfive2" } ax-plat-x86-qemu-q35 = { version = "0.4.8", path = "platforms/ax-plat-x86-qemu-q35", default-features = false } axpoll = { version = "0.3.9", path = "components/axpoll" } -axvcpu = { version = "0.5.9", path = "components/axvcpu" } +axvcpu = { version = "0.5.9", path = "virtualization/axvcpu" } axvisor = { version = "0.5.8", path = "os/axvisor" } -axvisor_api = { version = "0.5.11", path = "components/axvisor_api" } -axvisor_api_proc = { version = "0.5.7", path = "components/axvisor_api/axvisor_api_proc" } -axvm = { version = "0.5.9", path = "components/axvm", default-features = false } -axvmconfig = { version = "0.5.2", path = "components/axvmconfig", default-features = false } -bitmap-allocator = { version = "0.4.5", path = "components/bitmap-allocator" } +axvisor_api = { version = "0.5.11", path = "virtualization/axvisor_api" } +axvisor_api_proc = { version = "0.5.7", path = "virtualization/axvisor_api_proc" } +axvm = { version = "0.5.9", path = "virtualization/axvm", default-features = false } +axvmconfig = { version = "0.5.2", path = "virtualization/axvmconfig", default-features = false } +bitmap-allocator = { version = "0.4.5", path = "memory/bitmap-allocator" } bwbench-client = { version = "0.3.1", path = "os/arceos/tools/bwbench_client" } define-simple-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/define-simple-traits" } define-weak-traits = { version = "0.3.4", path = "components/crate_interface/test_crates/define-weak-traits" } @@ -290,12 +263,12 @@ impl-weak-traits = { version = "0.3.4", path = "components/crate_interface/test_ arm-gic-driver = { version = "0.17.1", path = "drivers/intc/arm-gic-driver" } enumerate = { version = "0.1.1", path = "drivers/examples/enumerate" } eth-intel = { version = "0.1.2", path = "drivers/net/eth-intel" } -loongarch_vcpu = { version = "0.5.3", path = "components/loongarch_vcpu" } +loongarch_vcpu = { version = "0.5.3", path = "virtualization/loongarch_vcpu" } mingo = { version = "0.8.3", path = "os/arceos/tools/raspi4/chainloader" } -range-alloc-arceos = { version = "0.3.9", path = "components/range-alloc-arceos" } -riscv-h = { version = "0.4.7", path = "components/riscv-h" } -riscv_vcpu = { version = "0.5.9", path = "components/riscv_vcpu" } -riscv_vplic = { version = "0.4.12", path = "components/riscv_vplic" } +range-alloc-arceos = { version = "0.3.9", path = "memory/range-alloc-arceos" } +riscv-h = { version = "0.4.7", path = "virtualization/riscv-h" } +riscv_vcpu = { version = "0.5.9", path = "virtualization/riscv_vcpu" } +riscv_vplic = { version = "0.4.12", path = "virtualization/riscv_vplic" } nvme-driver = { version = "0.4.2", path = "drivers/blk/nvme-driver" } pcie = { version = "0.6.1", path = "drivers/pci/pcie" } ramdisk = { version = "0.1.1", path = "drivers/blk/ramdisk" } @@ -344,13 +317,13 @@ test-simple = { version = "0.3.4", path = "components/crate_interface/test_crate test-weak = { version = "0.3.4", path = "components/crate_interface/test_crates/test-weak" } test-weak-partial = { version = "0.3.4", path = "components/crate_interface/test_crates/test-weak-partial" } tg-xtask = { version = "0.5.11", path = "xtask" } -x86_vcpu = { version = "0.5.9", path = "components/x86_vcpu", default-features = false } -x86_vlapic = { version = "0.4.10", path = "components/x86_vlapic" } -page-table-generic = { version = "0.7.2", path = "components/page-table-generic" } +x86_vcpu = { version = "0.5.9", path = "virtualization/x86_vcpu", default-features = false } +x86_vlapic = { version = "0.4.10", path = "virtualization/x86_vlapic" } +page-table-generic = { version = "0.7.2", path = "memory/page-table-generic" } someboot = { version = "0.1.15", path = "components/someboot" } some-serial = { version = "0.4.1", path = "drivers/serial/some-serial" } kernutil = { path = "components/kernutil", version = "0.2.2" } -ranges-ext = { version = "0.6.4", path = "components/ranges-ext" } +ranges-ext = { version = "0.6.4", path = "memory/ranges-ext" } somehal-macros = { path = "components/somehal-macros", version = "0.1.3" } kasm-aarch64 = { path = "components/kasm-aarch64", version = "0.2.1" } somehal = { version = "0.6.7", path = "platforms/somehal" } @@ -362,7 +335,7 @@ bitflags = "2.11" byte-unit = { version = "5", default-features = false, features = ["byte"] } derive_more = { version = "2.1", default-features = false, features = ["full"] } bindgen = "0.72" -buddy-slab-allocator = { version = "0.4", path = "components/buddy-slab-allocator" } +buddy-slab-allocator = { version = "0.4", path = "memory/buddy-slab-allocator" } cfg-if = "1.0" chrono = { version = "0.4", default-features = false } enum_dispatch = "0.3" @@ -372,8 +345,8 @@ futures = { version = "0.3", default-features = false } heapless = "0.9" schemars = "1.2.1" toml = "1" -dma-api = { version = "0.7.3", path = "components/dma-api" } -mmio-api = { version = "0.2.2", path = "components/mmio-api" } +dma-api = { version = "0.7.3", path = "memory/dma-api" } +mmio-api = { version = "0.2.2", path = "memory/mmio-api" } lock_api = { version = "0.4", default-features = false } log = "0.4" spin = "0.10" diff --git a/components/axmm_crates/.github/workflows/check.yml b/components/axmm_crates/.github/workflows/check.yml deleted file mode 100644 index f918f0d6b5..0000000000 --- a/components/axmm_crates/.github/workflows/check.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Quality Checks - -on: - workflow_call: - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - -jobs: - check: - name: Quality Checks - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - target: - - x86_64-unknown-linux-gnu - - x86_64-unknown-none - - riscv64gc-unknown-none-elf - - aarch64-unknown-none-softfloat - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: rust-src, clippy, rustfmt - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -A clippy::new_without_default - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features diff --git a/components/axmm_crates/.github/workflows/deploy.yml b/components/axmm_crates/.github/workflows/deploy.yml deleted file mode 100644 index 102ac7be86..0000000000 --- a/components/axmm_crates/.github/workflows/deploy.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Deploy - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -jobs: - check: - uses: ./.github/workflows/check.yml - - test: - uses: ./.github/workflows/test.yml - - build-doc: - name: Build documentation - runs-on: ubuntu-latest - needs: [check, test] - env: - RUSTDOCFLAGS: "-Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs" - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - run: cargo doc --no-deps --all-features - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: target/doc - - deploy-doc: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build-doc - if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch) - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/components/axmm_crates/.github/workflows/release.yml b/components/axmm_crates/.github/workflows/release.yml deleted file mode 100644 index b58f00bcdd..0000000000 --- a/components/axmm_crates/.github/workflows/release.yml +++ /dev/null @@ -1,147 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*.*.*' - - 'v*.*.*-pre.*' - -permissions: - contents: write - -jobs: - check: - uses: ./.github/workflows/check.yml - - test: - uses: ./.github/workflows/test.yml - - create-release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [check, test] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Validate tag is on main branch - shell: bash - run: | - set -e - - TAG="${{ github.ref_name }}" - TAG_COMMIT=$(git rev-list -n 1 "$TAG") - - git fetch origin main - - MAIN_HEAD=$(git rev-parse origin/main) - - echo "Tag: $TAG" - echo "Tag commit: $TAG_COMMIT" - echo "main HEAD: $MAIN_HEAD" - - if [ "$TAG_COMMIT" != "$MAIN_HEAD" ]; then - echo "❌ release tag must be created from main HEAD" - exit 1 - fi - echo "✅ release tag validated on main" - - - name: Validate version consistency - shell: bash - run: | - set -e - - TAG="${{ github.ref_name }}" - # Remove 'v' prefix from tag - TAG_VERSION="${TAG#v}" - - # Get version from Cargo.toml - CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') - - echo "Tag version: $TAG_VERSION" - echo "Cargo version: $CARGO_VERSION" - - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "❌ Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✅ Version consistency validated" - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ contains(github.ref_name, '-pre.') }} - body: | - ## ${{ github.ref_name }} - - - [memory_addr on crates.io](https://crates.io/crates/memory_addr) - - [memory_set on crates.io](https://crates.io/crates/memory_set) - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-crates: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [check, test] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Validate tag is on main branch - shell: bash - run: | - set -e - - TAG="${{ github.ref_name }}" - TAG_COMMIT=$(git rev-list -n 1 "$TAG") - - git fetch origin main - - MAIN_HEAD=$(git rev-parse origin/main) - - echo "Tag: $TAG" - echo "Tag commit: $TAG_COMMIT" - echo "main HEAD: $MAIN_HEAD" - - if [ "$TAG_COMMIT" != "$MAIN_HEAD" ]; then - echo "❌ release tag must be created from main HEAD" - exit 1 - fi - echo "✅ release tag validated on main" - - - name: Validate version consistency - shell: bash - run: | - set -e - - TAG="${{ github.ref_name }}" - # Remove 'v' prefix from tag - TAG_VERSION="${TAG#v}" - - # Get version from Cargo.toml - CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') - - echo "Tag version: $TAG_VERSION" - echo "Cargo version: $CARGO_VERSION" - - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "❌ Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✅ Version consistency validated" - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Publish memory_addr to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} -p memory_addr - - - name: Publish memory_set to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} -p memory_set diff --git a/components/axmm_crates/.github/workflows/test.yml b/components/axmm_crates/.github/workflows/test.yml deleted file mode 100644 index 1e3fbcb5e4..0000000000 --- a/components/axmm_crates/.github/workflows/test.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Test - -on: - workflow_call: - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Run tests - run: cargo test --target x86_64-unknown-linux-gnu -- --nocapture diff --git a/components/axmm_crates/README.md b/components/axmm_crates/README.md deleted file mode 100644 index f643aaf54b..0000000000 --- a/components/axmm_crates/README.md +++ /dev/null @@ -1,43 +0,0 @@ -

axmm_crates

- -

Workspace for memory management crates used by ArceOS

- -
- -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`axmm_crates` is a workspace that groups related TGOSKits components under a unified layout. It helps organize closely related crates that are typically developed, versioned, and used together. - -> axmm_crates was derived from https://github.com/arceos-org/axmm_crates - -## Workspace Members - -- `memory_addr` -- `memory_set` - -## Quick Start - -```bash -# Enter the workspace directory -cd components/axmm_crates - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --workspace --all-targets --all-features - -# Run tests -cargo test --workspace --all-features -``` - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axmm_crates/README_CN.md b/components/axmm_crates/README_CN.md deleted file mode 100644 index 6c83c2e08c..0000000000 --- a/components/axmm_crates/README_CN.md +++ /dev/null @@ -1,43 +0,0 @@ -

axmm_crates

- -

ArceOS 内存管理 crate 工作区

- -
- -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`axmm_crates` 是一个工作区,用于将相关的 TGOSKits 组件放在统一的目录结构下,便于协同开发、版本管理与组合使用。 - -> axmm_crates 派生自 https://github.com/arceos-org/axmm_crates - -## 工作区成员 - -- `memory_addr` -- `memory_set` - -## 快速开始 - -```bash -# 进入工作区目录 -cd components/axmm_crates - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --workspace --all-targets --all-features - -# 运行测试 -cargo test --workspace --all-features -``` - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/page_table_multiarch/.github/workflows/ci.yml b/components/page_table_multiarch/.github/workflows/ci.yml deleted file mode 100644 index b0c2034793..0000000000 --- a/components/page_table_multiarch/.github/workflows/ci.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: CI - -on: [push, pull_request] - -jobs: - ci: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - rust-toolchain: [nightly] - targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat, loongarch64-unknown-none-softfloat, armv7a-none-eabi] - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - toolchain: ${{ matrix.rust-toolchain }} - components: rust-src, clippy, rustfmt - targets: ${{ matrix.targets }} - - name: Check rust version - run: rustc --version --verbose - - name: Check code format - run: cargo fmt --all -- --check - - name: Clippy - run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default - - name: Build - run: cargo build --target ${{ matrix.targets }} --all-features - - name: Unit test - if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }} - env: - RUSTFLAGS: --cfg docsrs - run: cargo test --target ${{ matrix.targets }} -- --nocapture - - doc: - runs-on: ubuntu-latest - strategy: - fail-fast: false - permissions: - contents: write - env: - default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} - RUSTFLAGS: --cfg docsrs - RUSTDOCFLAGS: -Zunstable-options --enable-index-page -D rustdoc::broken_intra_doc_links -D missing-docs - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - - name: Build docs - continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} - run: cargo doc --no-deps --all-features - - name: Deploy to Github Pages - if: ${{ github.ref == env.default-branch }} - uses: JamesIves/github-pages-deploy-action@v4 - with: - single-commit: true - branch: gh-pages - folder: target/doc diff --git a/components/page_table_multiarch/.gitignore b/components/page_table_multiarch/.gitignore deleted file mode 100644 index 6e2ba627a1..0000000000 --- a/components/page_table_multiarch/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/.vscode -.DS_Store -/Cargo.lock diff --git a/components/page_table_multiarch/CHANGELOG.md b/components/page_table_multiarch/CHANGELOG.md deleted file mode 100644 index 7d3f5a60a4..0000000000 --- a/components/page_table_multiarch/CHANGELOG.md +++ /dev/null @@ -1,131 +0,0 @@ -# Changelog - -## 0.6.1 - -### Other - -- [Update `rand` to 0.10.0](https://github.com/arceos-org/page_table_multiarch/pull/41). -- [Update `aarch64-cpu` to 11.0](https://github.com/arceos-org/page_table_multiarch/pull/42). -- [Update `riscv` to 0.16](https://github.com/arceos-org/page_table_multiarch/pull/42). - -## 0.6.0 - -### New Features - -- [Add ARMv7-A support with page table structures and tests](https://github.com/arceos-org/page_table_multiarch/pull/31). -- [Add xuantie-c9xx special pte attr support](https://github.com/arceos-org/page_table_multiarch/pull/39). -- [Update `PagingHandler`, adding methods for allocating/deallocating multiple frames](https://github.com/arceos-org/page_table_multiarch/pull/37). - -### Refactoring - -- [Refactor page table cursor](https://github.com/arceos-org/page_table_multiarch/pull/35). - -### Style - -- [Style: cleanup](https://github.com/arceos-org/page_table_multiarch/pull/34). - -## 0.5.7 - -### New Features - -- [Add ax-errno compatibility](https://github.com/arceos-org/page_table_multiarch/pull/25). - -### Bug Fixes - -- [Fix: use `--cfg docsrs` for document](https://github.com/arceos-org/page_table_multiarch/pull/27). - -## 0.5.6 - -### Bug Fixes - -- Fix doc_auto_cfg. -- [Fix LoongArch64: set PTEFlags::D with MappingFlags::WRITE](https://github.com/arceos-org/page_table_multiarch/commit/e537bd4). -- [Fix documentation: clarify alignment behavior and fix typo in map function](https://github.com/arceos-org/page_table_multiarch/commit/9049ac7). - -## 0.5.5 - -### Bug Fixes - -- [Fix memory leak in `PageTable64::dealloc_tree`](https://github.com/arceos-org/page_table_multiarch/pull/22). - -## 0.5.4 - -### New Features - -- [Fix invalid query result](https://github.com/arceos-org/page_table_multiarch/pull/17). -- [Fix incorrect TLB flush VA bits on aarch64](https://github.com/arceos-org/page_table_multiarch/pull/21). -- [Introduce feature `copy-from` and fix page table drop after `copy-from`](https://github.com/arceos-org/page_table_multiarch/pull/20). - -## 0.5.3 - -### New Features - -- Add `empty` method to page table entries for all architectures. - -## 0.5.2 - -### Minor Changes - -- [Make LoongArch64's page table default to 4 levels](https://github.com/arceos-org/page_table_multiarch/pull/12). -- [Do not link to alloc crate](https://github.com/arceos-org/page_table_multiarch/pull/13). -- [Implement `Clone` and `Copy` for `PagingError`](https://github.com/arceos-org/page_table_multiarch/pull/14). - -## 0.5.1 - -### LoongArch64 - -- [Add LoongArch64 support](https://github.com/arceos-org/page_table_multiarch/pull/11). - -## 0.5.0 - -### Breaking Changes - -- Upgrade to Rust edition 2024, which requires Rust v1.85 or later. - -## 0.4.2 - -- Fix [x86_64](https://crates.io/crates/x86_64) dependency version as v0.15.1. - -## 0.4.1 - -### RISC-V - -- Add trait `SvVirtAddr` for custom virtual address types. - -## 0.4.0 - -### Breaking Changes - -- Update `memory_addr` to `0.3.0`, which is not backward compatible with `0.2.0`. - -## 0.3.3 - -- Support the use of `ax-page-table-entry` at the ARM EL2 privilege level (via the `arm-el2` feature). - -## 0.3.2 - -- Fix the Rust documentation for `TlbFlush` and `TlbFlushAll`. - -## 0.3.1 - -- Allow generic virtual address types in `PageTable64`. - -## 0.3.0 - -### New Features - -- Allow users to control the TLB flush behavior. - + Return structures `TlbFlush`/`TlbFlushAll` after mapping change (e.g., call `PageTable64::map`). - + Add a parameter `flush_tlb_by_page` to `map_region`/`unmap_region`/`protect_region` in `PageTable64`. - -## 0.2.0 - -### New Features - -- No longer collect intermediate tables into a `Vec`, walk the page table and -deallocate them on drop. -- Replace the `update` method of `PageTable64` with `remap` and `protect`, also add `protect_region` and `copy_from`. - -## 0.1.0 - -Initial release. diff --git a/components/page_table_multiarch/README.md b/components/page_table_multiarch/README.md deleted file mode 100644 index 805313ed88..0000000000 --- a/components/page_table_multiarch/README.md +++ /dev/null @@ -1,43 +0,0 @@ -

page_table_multiarch

- -

Workspace for multi-architecture page table crates

- -
- -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`page_table_multiarch` is a workspace that groups related TGOSKits components under a unified layout. It helps organize closely related crates that are typically developed, versioned, and used together. - -> page_table_multiarch was derived from https://github.com/arceos-org/page_table_multiarch - -## Workspace Members - -- `page_table_multiarch` -- `page_table_entry` - -## Quick Start - -```bash -# Enter the workspace directory -cd components/page_table_multiarch - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --workspace --all-targets --all-features - -# Run tests -cargo test --workspace --all-features -``` - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/page_table_multiarch/README_CN.md b/components/page_table_multiarch/README_CN.md deleted file mode 100644 index 58b8baa128..0000000000 --- a/components/page_table_multiarch/README_CN.md +++ /dev/null @@ -1,43 +0,0 @@ -

page_table_multiarch

- -

面向多架构页表 crate 的工作区

- -
- -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`page_table_multiarch` 是一个工作区,用于将相关的 TGOSKits 组件放在统一的目录结构下,便于协同开发、版本管理与组合使用。 - -> page_table_multiarch 派生自 https://github.com/arceos-org/page_table_multiarch - -## 工作区成员 - -- `page_table_multiarch` -- `page_table_entry` - -## 快速开始 - -```bash -# 进入工作区目录 -cd components/page_table_multiarch - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --workspace --all-targets --all-features - -# 运行测试 -cargo test --workspace --all-features -``` - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/page_table_multiarch/rustfmt.toml b/components/page_table_multiarch/rustfmt.toml deleted file mode 100644 index 09a7f3e3de..0000000000 --- a/components/page_table_multiarch/rustfmt.toml +++ /dev/null @@ -1,17 +0,0 @@ -unstable_features = true - -style_edition = "2024" - -group_imports = "StdExternalCrate" -imports_granularity = "Crate" - -normalize_comments = true -wrap_comments = true - -condense_wildcard_suffixes = true -enum_discrim_align_threshold = 20 -use_field_init_shorthand = true - -format_strings = true -format_code_in_doc_comments = true -format_macro_matchers = true diff --git a/components/x86_vcpu/LICENSE b/components/x86_vcpu/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/components/x86_vcpu/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/components/x86_vlapic/.gitignore b/components/x86_vlapic/.gitignore deleted file mode 100644 index ff78c42af5..0000000000 --- a/components/x86_vlapic/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target -/.vscode -.DS_Store -Cargo.lock diff --git a/components/x86_vlapic/LICENSE b/components/x86_vlapic/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/components/x86_vlapic/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/docs/architecture/axvisor.md b/docs/docs/architecture/axvisor.md index 6b894bca66..81b1c9992c 100644 --- a/docs/docs/architecture/axvisor.md +++ b/docs/docs/architecture/axvisor.md @@ -16,7 +16,7 @@ Axvisor 与 ArceOS/StarryOS 的最大差异在于:**代码、配置和 Guest | 目标 | 含义 | 典型落点 | | --- | --- | --- | | 统一 | 尽可能用同一套代码覆盖多架构平台 | `hal/arch/*`、`configs/board/*` | -| 组件化 | 将 VM、vCPU、虚拟设备、地址空间、API 注入等能力拆成独立组件 | `components/axvm`、`axvcpu`、`axdevice`、`axaddrspace`、`axvisor_api` | +| 组件化 | 将 VM、vCPU、虚拟设备、地址空间、API 注入等能力拆成独立组件 | `virtualization/axvm`、`axvcpu`、`axdevice`、`axaddrspace`、`axvisor_api` | | 可配置 | 通过板级配置与 VM 配置控制构建与运行行为 | `configs/board/*.toml`、`configs/vms/*.toml` | | 可验证 | 通过 xtask、QEMU workflow 和统一测试入口形成闭环 | `cargo xtask axvisor test qemu ...` | @@ -58,8 +58,8 @@ Axvisor 从底部宿主运行时到顶部 Guest 系统,依次经过五层。 | 层次 | 目录 | 职责 | | --- | --- | --- | | 宿主运行时层 | `ax-std`、`ax-hal`、`ax-alloc`、`ax-task` | 提供宿主机上的调度、内存、时间、控制台与硬件抽象 | -| 虚拟化能力层 | `components/axvm`、`axvcpu`、`axdevice`、`axaddrspace` | 抽象 VM、vCPU、设备模拟/直通与客户机地址空间 | -| API 注入层 | `components/axvisor_api`、`src/hal` 中的 `api_mod_impl` | 将 ArceOS 的能力注入到更底层虚拟化组件 | +| 虚拟化能力层 | `virtualization/axvm`、`axvcpu`、`axdevice`、`axaddrspace` | 抽象 VM、vCPU、设备模拟/直通与客户机地址空间 | +| API 注入层 | `virtualization/axvisor_api`、`src/hal` 中的 `api_mod_impl` | 将 ArceOS 的能力注入到更底层虚拟化组件 | | Axvisor 编排层 | `os/axvisor/src/*` | 初始化、VMM、shell、任务组织、Guest 启停 | | 配置与镜像层 | `configs/board/*`、`configs/vms/*`、`tmp/*`、镜像仓库 | 控制"构建什么"和"启动哪个 Guest" | diff --git a/docs/docs/architecture/overview.md b/docs/docs/architecture/overview.md index 4281ab98a9..ea78a97fb9 100644 --- a/docs/docs/architecture/overview.md +++ b/docs/docs/architecture/overview.md @@ -12,7 +12,7 @@ flowchart TD subgraph Components["组件层 (components/)"] direction LR C1["架构组件
aarch64_sysreg arm_vcpu ..."] - C2["内存组件
axaddrspace axmm_crates ..."] + C2["内存组件
axaddrspace ax-memory-addr ax-memory-set ..."] C3["设备组件
drivers/ axdevice ..."] C4["文件系统组件
axfs_crates axfs-ng-vfs rsext4"] C5["平台组件
axplat_crates percpu ..."] @@ -46,7 +46,7 @@ flowchart TD direction LR P1["riscv64-qemu-virt"] P2["x86-qemu-q35"] - P3["ax-plat-dyn"] + P3["axplat-dyn"] end Components --> ArceOS @@ -117,7 +117,7 @@ TGOSKits 按职责将 crate 组织为六个核心层次和一个辅助层,每 辅助层: -- `platforms/` — 平台实现,当前包含 `riscv64-qemu-virt`、`x86-qemu-q35`、`ax-plat-dyn` +- `platforms/` — 平台实现,当前包含 `riscv64-qemu-virt`、`x86-qemu-q35`、`axplat-dyn` - `drivers/` — SoC 专用驱动(blk、net、npu、pci、soc) - `test-suit/` — 系统级测试入口(ArceOS、StarryOS、Axvisor) diff --git a/docs/docs/architecture/rdrive-rdif.md b/docs/docs/architecture/rdrive-rdif.md index 45a71463db..9a6c2727c8 100644 --- a/docs/docs/architecture/rdrive-rdif.md +++ b/docs/docs/architecture/rdrive-rdif.md @@ -226,9 +226,9 @@ IRQ 路径只返回稳定事件和唤醒等待方;不能在 IRQ handler 中执 | 层 | 位置 | 允许依赖 | 不允许 | | --- | --- | --- | --- | -| Driver Core | `drivers//` | `no_std`、寄存器/队列/描述符、`mmio-api`、`dma-api` 小边界 | `ax-driver`、`ax-hal`、`ax-plat-dyn`、`rdrive::PlatformDevice` | +| Driver Core | `drivers//` | `no_std`、寄存器/队列/描述符、`mmio-api`、`dma-api` 小边界 | `ax-driver`、`ax-hal`、`axplat-dyn`、`rdrive::PlatformDevice` | | Capability Boundary | `drivers/interface/rdif-*` | `rdif-base`、小型错误和事件类型 | 平台、runtime、任务调度 | -| OS Glue | `platforms/ax-plat-dyn/src/drivers/*` 或平台 crate | `rdrive::module_driver!`、FDT/PCI/Static probe、iomap、IRQ 注册、DMA op | 上层 FS/NET 策略 | +| OS Glue | `platforms/axplat-dyn/src/drivers/*` 或平台 crate | `rdrive::module_driver!`、FDT/PCI/Static probe、iomap、IRQ 注册、DMA op | 上层 FS/NET 策略 | | Runtime | `drivers/*/rd-*` | `rdif-*`、waker、poll/blocking wrapper、buffer pool | probe、设备树、ACPI、平台选择 | Driver Core 只推进硬件状态机。OS Glue 将硬件实例包装成 `rdif-*::Interface` 后通过 `PlatformDevice::register(...)` 注册。Runtime wrapper 从 `rdif-*::Interface` 构建领域运行时对象,供服务层和上层模块使用。 @@ -315,10 +315,10 @@ src/ | 文件 | 当前问题 | 拆分方向 | | --- | --- | --- | -| `platforms/ax-plat-dyn/src/drivers/pci/rk3588.rs` | 单文件超过 600 行 | RC init、ATU/window、MSI/IRQ、config space、FDT glue | -| `platforms/ax-plat-dyn/src/drivers/blk/rockchip_sd.rs` | 单文件超过 600 行 | probe/FDT、clock/tuning、card init、rd-block adapter | -| `platforms/ax-plat-dyn/src/drivers/blk/mod.rs` | 容器、adapter、IRQ、FDT decode 混杂 | registry、adapter、irq、probe | -| `platforms/ax-plat-dyn/src/drivers/mod.rs` | 设备收集、iomap、DMA 混杂 | device collection、iomap、dma | +| `platforms/axplat-dyn/src/drivers/pci/rk3588.rs` | 单文件超过 600 行 | RC init、ATU/window、MSI/IRQ、config space、FDT glue | +| `platforms/axplat-dyn/src/drivers/blk/rockchip_sd.rs` | 单文件超过 600 行 | probe/FDT、clock/tuning、card init、rd-block adapter | +| `platforms/axplat-dyn/src/drivers/blk/mod.rs` | 容器、adapter、IRQ、FDT decode 混杂 | registry、adapter、irq、probe | +| `platforms/axplat-dyn/src/drivers/mod.rs` | 设备收集、iomap、DMA 混杂 | device collection、iomap、dma | `lib.rs` 只做模块声明和 re-export,不承载核心实现。 diff --git a/docs/docs/components/crates/aarch64-sysreg.md b/docs/docs/components/crates/aarch64-sysreg.md index d88352d3f3..884c941989 100644 --- a/docs/docs/components/crates/aarch64-sysreg.md +++ b/docs/docs/components/crates/aarch64-sysreg.md @@ -1,10 +1,10 @@ # `aarch64_sysreg` -> 路径:`components/aarch64_sysreg` +> 路径:`virtualization/aarch64_sysreg` > 类型:库 crate > 分层:组件层 / AArch64 编码字典组件 > 版本:`0.1.1` -> 文档依据:当前仓库源码、`Cargo.toml`、`src/lib.rs`、`src/operation_type.rs`、`src/registers_type.rs`、`src/system_reg_type.rs`、`components/arm_vgic/src/vtimer/*` +> 文档依据:当前仓库源码、`Cargo.toml`、`src/lib.rs`、`src/operation_type.rs`、`src/registers_type.rs`、`src/system_reg_type.rs`、`virtualization/arm_vgic/src/vtimer/*` `aarch64_sysreg` 不是“系统寄存器读写库”,也不是一套完整的 AArch64 指令仿真框架。它的真实定位更接近一个**AArch64 指令/寄存器编码字典**:把若干数值编码稳定地映射成 Rust 枚举,并提供名称格式化与数值转换能力。当前仓库里,最直接的真实用途是被 `arm_vgic` 用来给虚拟计时器相关的系统寄存器构造 `SysRegAddr`。 diff --git a/docs/docs/components/crates/arm-vcpu.md b/docs/docs/components/crates/arm-vcpu.md index 5737c40f0b..57820bd59b 100644 --- a/docs/docs/components/crates/arm-vcpu.md +++ b/docs/docs/components/crates/arm-vcpu.md @@ -1,6 +1,6 @@ # `arm_vcpu` -> 路径:`components/arm_vcpu` +> 路径:`virtualization/arm_vcpu` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.2.2` @@ -187,11 +187,11 @@ arm_vcpu = { workspace = true } `arm_vcpu` 是 Axvisor 在 AArch64 路径上的关键执行引擎之一。`axvm` 负责统一 VM 资源模型,而真正把 guest 扔进 EL1 执行、再把 VM exit 带回宿主的,就是 `arm_vcpu`。 # `arm_vcpu` 技术文档 -> 路径:`components/arm_vcpu` +> 路径:`virtualization/arm_vcpu` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.2.2` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/arm_vcpu/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `virtualization/arm_vcpu/README.md` `arm_vcpu` 的核心定位是:Aarch64 VCPU implementation for Arceos Hypervisor @@ -273,7 +273,7 @@ graph LR arm_vcpu = { workspace = true } # 如果在仓库外独立验证,也可以显式绑定本地路径: -# arm_vcpu = { path = "components/arm_vcpu" } +# arm_vcpu = { path = "virtualization/arm_vcpu" } ``` ### 初始化 diff --git a/docs/docs/components/crates/arm-vgic.md b/docs/docs/components/crates/arm-vgic.md index eb6030fb13..418a850c57 100644 --- a/docs/docs/components/crates/arm-vgic.md +++ b/docs/docs/components/crates/arm-vgic.md @@ -1,6 +1,6 @@ # `arm_vgic` -> 路径:`components/arm_vgic` +> 路径:`virtualization/arm_vgic` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.2.1` @@ -187,11 +187,11 @@ arm_vgic = { workspace = true, features = ["vgicv3"] } `arm_vgic` 是 Axvisor ARM 虚拟化栈中的关键设备组件之一。`axdevice` 负责把它挂进 VM,`axvm` 负责更高层装配,`arm_vcpu` 负责执行态注入,三者共同构成 ARM guest 中断虚拟化主线。 # `arm_vgic` 技术文档 -> 路径:`components/arm_vgic` +> 路径:`virtualization/arm_vgic` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.2.1` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/arm_vgic/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `virtualization/arm_vgic/README.md` `arm_vgic` 的核心定位是:ARM Virtual Generic Interrupt Controller (VGIC) implementation. @@ -276,7 +276,7 @@ graph LR arm_vgic = { workspace = true } # 如果在仓库外独立验证,也可以显式绑定本地路径: -# arm_vgic = { path = "components/arm_vgic" } +# arm_vgic = { path = "virtualization/arm_vgic" } ``` ### 初始化 diff --git a/docs/docs/components/crates/ax-alloc.md b/docs/docs/components/crates/ax-alloc.md index ae8610335b..ae3796d641 100644 --- a/docs/docs/components/crates/ax-alloc.md +++ b/docs/docs/components/crates/ax-alloc.md @@ -94,7 +94,7 @@ graph LR ax-alloc --> axfsng["ax-fs-ng"] ax-alloc --> ax-api["ax-api"] ax-alloc --> starry_kernel["starry-kernel"] - ax-alloc --> ax_plat_dyn["ax-plat-dyn"] + ax-alloc --> axplat_dyn["axplat-dyn"] ``` ### 直接依赖 diff --git a/docs/docs/components/crates/ax-allocator.md b/docs/docs/components/crates/ax-allocator.md index 8b10c2535d..ffc9b1ad8d 100644 --- a/docs/docs/components/crates/ax-allocator.md +++ b/docs/docs/components/crates/ax-allocator.md @@ -1,6 +1,6 @@ # `ax-allocator` -> 路径:`components/axallocator` +> 路径:`memory/axallocator` > 类型:库 crate > 分层:组件层 / 分配算法基础件 > 版本:`0.2.0` diff --git a/docs/docs/components/crates/ax-cpumask.md b/docs/docs/components/crates/ax-cpumask.md index d2df1bdfc0..f0187172c8 100644 --- a/docs/docs/components/crates/ax-cpumask.md +++ b/docs/docs/components/crates/ax-cpumask.md @@ -48,7 +48,7 @@ ### 使用场景 - `CpuMask::new()` / `set()`:`ax-task/src/api.rs` 用来构造 `AxCpuMask`。 - `get()` / `is_empty()`:`ax-task/src/run_queue.rs` 用于根据 affinity 选择运行队列。 -- `CpuMask`:`components/axvm/src/vm.rs` 和 `os/axvisor/src/vmm/vcpus.rs` 直接使用,用来描述 vCPU 目标集合。 +- `CpuMask`:`virtualization/axvm/src/vm.rs` 和 `os/axvisor/src/vmm/vcpus.rs` 直接使用,用来描述 vCPU 目标集合。 ### 边界说明 - `ax-cpumask` 不负责验证 CPU 是否在线;它只存位。 diff --git a/docs/docs/components/crates/ax-dma.md b/docs/docs/components/crates/ax-dma.md index 9bc908cbea..61359c14aa 100644 --- a/docs/docs/components/crates/ax-dma.md +++ b/docs/docs/components/crates/ax-dma.md @@ -4,7 +4,7 @@ > 类型:库 crate > 分层:ArceOS 层 / DMA 内存服务层 > 版本:`0.3.0-preview.3` -> 文档依据:`Cargo.toml`、`src/lib.rs`、`src/dma.rs`、`drivers/ax-driver/src/ixgbe.rs`、`drivers/ax-driver/src/drivers.rs`、`os/arceos/api/ax-api/src/imp/mem.rs`、`platforms/ax-plat-dyn/src/drivers/mod.rs`、`os/axvisor/src/driver/blk/mod.rs` +> 文档依据:`Cargo.toml`、`src/lib.rs`、`src/dma.rs`、`drivers/ax-driver/src/ixgbe.rs`、`drivers/ax-driver/src/drivers.rs`、`os/arceos/api/ax-api/src/imp/mem.rs`、`platforms/axplat-dyn/src/drivers/mod.rs`、`os/axvisor/src/driver/blk/mod.rs` `ax-dma` 不是驱动聚合层,也不是某类设备驱动。它的真实职责是为 ArceOS 内核提供一套全局一致的 DMA 一致性内存分配服务:从页分配器拿到内存、把页表属性改成 `UNCACHED`、给设备返回可用的总线地址,并在释放时尽可能恢复映射属性。它位于 `ax-alloc` / `ax-mm` / `ax-hal` 等内存基础设施之上,位于需要软件管理 DMA 缓冲的驱动之下。 @@ -81,7 +81,7 @@ ### 1.7 与其它 DMA 实现的边界 仓库里至少还有两条独立 DMA 路径: -- `platforms/ax-plat-dyn/src/drivers/mod.rs`:提供 `dma_api::DmaOp` 实现,服务动态平台块设备探测。 +- `platforms/axplat-dyn/src/drivers/mod.rs`:提供 `dma_api::DmaOp` 实现,服务动态平台块设备探测。 - `os/axvisor/src/driver/blk/mod.rs`:提供 Axvisor 自己的 `rdif_block::dma_api::DmaOp` 实现。 因此不能把 `ax-dma` 写成“整个仓库唯一的 DMA 抽象层”;它是 ArceOS 主线中的一个内核 DMA 内存服务模块。 diff --git a/docs/docs/components/crates/ax-driver.md b/docs/docs/components/crates/ax-driver.md index b7bc207d85..ad31157a6a 100644 --- a/docs/docs/components/crates/ax-driver.md +++ b/docs/docs/components/crates/ax-driver.md @@ -52,7 +52,7 @@ graph LR ```bash cargo xtask clippy --package ax-driver -cargo xtask clippy --package ax-plat-dyn +cargo xtask clippy --package axplat-dyn ``` 涉及 ArceOS 运行路径时,继续跑对应 `cargo xtask arceos test qemu ...` 用例。 diff --git a/docs/docs/components/crates/ax-lazyinit.md b/docs/docs/components/crates/ax-lazyinit.md index 92210453af..d6cc421e3d 100644 --- a/docs/docs/components/crates/ax-lazyinit.md +++ b/docs/docs/components/crates/ax-lazyinit.md @@ -60,7 +60,7 @@ stateDiagram-v2 ### 使用场景 - `LazyInit::new()`:大量平台和模块静态对象都以它声明,如 `ax-task` 的运行队列、`ax-mm` 的内核地址空间、`ax-ipi` 的 IPI 队列。 - `init_once()`:平台 UART、IO APIC、GIC、显示/输入设备等对象初始化时广泛使用。 -- `call_once()`:用于“按需首次构造”的场景,如 `ax-plat-dyn` 内存区域表、`ax-std` 标准 IO 包装器、`axbacktrace` 的地址范围缓存。 +- `call_once()`:用于“按需首次构造”的场景,如 `axplat-dyn` 内存区域表、`ax-std` 标准 IO 包装器、`axbacktrace` 的地址范围缓存。 - `get()` / `Deref`:初始化完成后作为普通全局对象读取。 ### 边界说明 diff --git a/docs/docs/components/crates/ax-memory-addr.md b/docs/docs/components/crates/ax-memory-addr.md index f413fb29b3..03df217b87 100644 --- a/docs/docs/components/crates/ax-memory-addr.md +++ b/docs/docs/components/crates/ax-memory-addr.md @@ -1,6 +1,6 @@ # `ax-memory-addr` -> 路径:`components/axmm_crates/memory_addr` +> 路径:`memory/memory_addr` > 类型:库 crate > 分层:组件层 / 地址建模基础库 > 版本:`0.4.1` diff --git a/docs/docs/components/crates/ax-memory-set.md b/docs/docs/components/crates/ax-memory-set.md index 8305b6b057..a2772d1344 100644 --- a/docs/docs/components/crates/ax-memory-set.md +++ b/docs/docs/components/crates/ax-memory-set.md @@ -1,6 +1,6 @@ # `ax-memory-set` -> 路径:`components/axmm_crates/memory_set` +> 路径:`memory/memory_set` > 类型:库 crate > 分层:组件层 / 地址区间集合与映射元数据层 > 版本:`0.4.1` @@ -183,7 +183,7 @@ - `os/arceos/modules/axmm` - `os/StarryOS/kernel` -- `components/axaddrspace` +- `memory/axaddrspace` ### 3.3 关系示意 diff --git a/docs/docs/components/crates/ax-page-table-entry.md b/docs/docs/components/crates/ax-page-table-entry.md index f8f65aedf9..8001226dd4 100644 --- a/docs/docs/components/crates/ax-page-table-entry.md +++ b/docs/docs/components/crates/ax-page-table-entry.md @@ -1,6 +1,6 @@ # `ax-page-table-entry` -> 路径:`components/page_table_multiarch/page_table_entry` +> 路径:`memory/page_table_entry` > 类型:库 crate > 分层:组件层 / 页表项编码层 > 版本:`0.6.1` diff --git a/docs/docs/components/crates/ax-page-table-multiarch.md b/docs/docs/components/crates/ax-page-table-multiarch.md index 56371110ac..a296c91190 100644 --- a/docs/docs/components/crates/ax-page-table-multiarch.md +++ b/docs/docs/components/crates/ax-page-table-multiarch.md @@ -1,6 +1,6 @@ # `ax-page-table-multiarch` -> 路径:`components/page_table_multiarch/page_table_multiarch` +> 路径:`memory/page_table_multiarch` > 类型:库 crate > 分层:组件层 / 通用页表引擎 > 版本:`0.6.1` diff --git a/docs/docs/components/crates/axaddrspace.md b/docs/docs/components/crates/axaddrspace.md index 2c649be1f2..8d88bbec5e 100644 --- a/docs/docs/components/crates/axaddrspace.md +++ b/docs/docs/components/crates/axaddrspace.md @@ -1,6 +1,6 @@ # `axaddrspace` -> 路径:`components/axaddrspace` +> 路径:`memory/axaddrspace` > 类型:库 crate > 分层:组件层 / 客户机地址空间与嵌套页表 > 版本:`0.3.0` diff --git a/docs/docs/components/crates/axdevice-base.md b/docs/docs/components/crates/axdevice-base.md index 3e11969c53..d403f72c96 100644 --- a/docs/docs/components/crates/axdevice-base.md +++ b/docs/docs/components/crates/axdevice-base.md @@ -1,6 +1,6 @@ # `axdevice_base` -> 路径:`components/axdevice_base` +> 路径:`virtualization/axdevice_base` > 类型:库 crate > 分层:组件层 / 虚拟设备契约层 > 版本:`0.2.1` diff --git a/docs/docs/components/crates/axdevice.md b/docs/docs/components/crates/axdevice.md index 1945950343..952d2d431f 100644 --- a/docs/docs/components/crates/axdevice.md +++ b/docs/docs/components/crates/axdevice.md @@ -1,6 +1,6 @@ # `axdevice` -> 路径:`components/axdevice` +> 路径:`virtualization/axdevice` > 类型:库 crate > 分层:组件层 / 虚拟化设备分发层 > 版本:`0.2.1` @@ -218,7 +218,7 @@ let mut devices = AxVmDevices::new(config); ### 主要消费者 -- `components/axvm`:直接持有 `AxVmDevices` 并在 VM exit 中调用分发接口。 +- `virtualization/axvm`:直接持有 `AxVmDevices` 并在 VM exit 中调用分发接口。 - `os/axvisor`:通过 `axvm` 间接消费,是当前仓库中的核心落地点。 - 测试代码:`tests/test.rs` 直接用 mock MMIO 设备验证分发语义。 diff --git a/docs/docs/components/crates/axhvc.md b/docs/docs/components/crates/axhvc.md index 2059cd9149..87d3bf1812 100644 --- a/docs/docs/components/crates/axhvc.md +++ b/docs/docs/components/crates/axhvc.md @@ -1,6 +1,6 @@ # `axhvc` -> 路径:`components/axhvc` +> 路径:`virtualization/axhvc` > 类型:库 crate > 分层:组件层 / HyperCall ABI 定义组件 > 版本:`0.2.0` diff --git a/docs/docs/components/crates/axklib.md b/docs/docs/components/crates/axklib.md index bf24b8ddd4..25428ec6d7 100644 --- a/docs/docs/components/crates/axklib.md +++ b/docs/docs/components/crates/axklib.md @@ -16,7 +16,7 @@ - 调用点必须足够直白,便于平台代码或驱动代码直接复用。 - ABI 必须尽量稳定,避免每个项目都重新发明一套 helper trait。 -在当前仓库里,`ax-runtime/src/klib.rs` 就是 `axklib::Klib` 的一个真实实现方;`platforms/ax-plat-dyn` 和 `os/axvisor` 的驱动代码则是典型使用方。 +在当前仓库里,`ax-runtime/src/klib.rs` 就是 `axklib::Klib` 的一个真实实现方;`platforms/axplat-dyn` 和 `os/axvisor` 的驱动代码则是典型使用方。 ### 1.2 核心接口 `axklib` 只有一个核心 trait: @@ -68,7 +68,7 @@ flowchart TD - 让 ArceOS 与 Axvisor 等项目共享一套极小的 helper 抽象。 ### 使用场景 -- `mem::iomap()`:被 `platforms/ax-plat-dyn` 和 `os/axvisor/src/driver/*` 的驱动代码直接使用。 +- `mem::iomap()`:被 `platforms/axplat-dyn` 和 `os/axvisor/src/driver/*` 的驱动代码直接使用。 - `time::busy_wait()`:被 Axvisor 驱动中的短延时场景直接使用。 - `irq::register()` / `irq::set_enable()`:由具体运行时实现映射到 HAL。 - `Klib` trait:由 `os/arceos/modules/axruntime/src/klib.rs` 真实实现。 @@ -86,7 +86,7 @@ graph LR trait_ffi["trait-ffi"] --> axklib axklib --> ax-runtime["ax-runtime 实现方"] - axklib --> ax_plat_dyn["ax-plat-dyn 使用方"] + axklib --> axplat_dyn["axplat-dyn 使用方"] axklib --> axvisor["axvisor 使用方"] ``` @@ -97,7 +97,7 @@ graph LR ### 主要消费者 - `ax-runtime`:当前 ArceOS 侧的主要实现方。 -- `ax-plat-dyn`:动态平台与设备接入路径的调用方。 +- `axplat-dyn`:动态平台与设备接入路径的调用方。 - `axvisor`:若干驱动直接通过 `axklib` 访问 iomap 与 busy-wait。 ## 开发指南 @@ -123,7 +123,7 @@ axklib = { workspace = true } `axklib` 本体没有独立测试;当前验证主要依赖实现方和调用方: - `ax-runtime` 对 `Klib` 的实现是否与 `ax-mm` / `ax-hal` 对齐; -- `ax-plat-dyn` 和 Axvisor 驱动是否能通过 `iomap`、`busy_wait` 正常工作。 +- `axplat-dyn` 和 Axvisor 驱动是否能通过 `iomap`、`busy_wait` 正常工作。 ### 单元测试 - trait 桥接代码的签名稳定性。 diff --git a/docs/docs/components/crates/axplat-dyn.md b/docs/docs/components/crates/axplat-dyn.md index fc50868030..8d67c3cd75 100644 --- a/docs/docs/components/crates/axplat-dyn.md +++ b/docs/docs/components/crates/axplat-dyn.md @@ -1,18 +1,18 @@ -# `ax-plat-dyn` +# `axplat-dyn` -> 路径:`platforms/ax-plat-dyn` +> 路径:`platforms/axplat-dyn` > 类型:库 crate > 分层:平台层 / 动态平台桥接层 > 版本:`0.3.0-preview.3` > 文档依据:当前仓库源码、`Cargo.toml`、`build.rs`、`link.ld` 及 `os/arceos/modules/axhal`/`ax-driver` 的接入路径 -`ax-plat-dyn` 不是那类“用 `axconfig.toml` 固化板级常量”的常规 `axplat-*` 平台包。它更像一层桥接适配器:把 `somehal` 已经建立好的启动入口、FDT 地址、内存图、时钟、IRQ、电源与 SMP 元数据转译成 `axplat` 的统一契约;同时再补上一条 `ax-driver` 动态设备模型所需的设备探测与 DMA glue。这里的 `dyn` 真正表示“平台事实来自运行时抽象层和探测结果”,而不是“把平台包当作运行时可装卸模块加载”。 +`axplat-dyn` 不是那类“用 `axconfig.toml` 固化板级常量”的常规 `axplat-*` 平台包。它更像一层桥接适配器:把 `somehal` 已经建立好的启动入口、FDT 地址、内存图、时钟、IRQ、电源与 SMP 元数据转译成 `axplat` 的统一契约;同时再补上一条 `ax-driver` 动态设备模型所需的设备探测与 DMA glue。这里的 `dyn` 真正表示“平台事实来自运行时抽象层和探测结果”,而不是“把平台包当作运行时可装卸模块加载”。 ## 架构设计 ### 设计定位 -`ax-plat-dyn` 在当前仓库里的位置可以概括为: +`axplat-dyn` 在当前仓库里的位置可以概括为: - 向下:依赖 `somehal` 提供的入口宏、内存映射、控制台、时钟、中断、电源和 CPU 元数据。 - 向上:实现 `InitIf`、`ConsoleIf`、`MemIf`、`TimeIf`、`PowerIf` 以及可选 `IrqIf`,并通过 `ax_plat::call_main()` / `call_secondary_main()` 把控制权交给内核入口。 @@ -22,7 +22,7 @@ 这决定了它与普通板级包的一个根本差异: - 普通 `axplat-*` 平台包主要把编译期 `axconfig.toml` 变成板级常量,再围绕这些常量实现 `axplat` 接口。 -- `ax-plat-dyn` 则把 `somehal` 暴露的运行时事实直接转成 `axplat` 接口,不以 `axconfig.toml` 为主线。 +- `axplat-dyn` 则把 `somehal` 暴露的运行时事实直接转成 `axplat` 接口,不以 `axconfig.toml` 为主线。 ### 模块结构 @@ -40,7 +40,7 @@ ### 1.3 平台实现装配方式 -`ax-plat-dyn` 的实现不是单点完成的,而是由四层 glue 组合出来: +`axplat-dyn` 的实现不是单点完成的,而是由四层 glue 组合出来: | 装配层 | 依赖来源 | 本 crate 的落点 | 作用 | | --- | --- | --- | --- | @@ -78,7 +78,7 @@ flowchart TD - `init_later()`:在分页建立更完整后调用 `somehal::post_paging()`,随后使能定时器 IRQ。 - `init_later_secondary()`:次核路径上只补做定时器 IRQ 使能。 -这里有一个重要前提:`ax-plat-dyn` 默认假设“更早的架构级 bring-up 已经由 `somehal` 完成”,它不自己构造早期页表,也不自己处理最底层的 CPU 模式切换。 +这里有一个重要前提:`axplat-dyn` 默认假设“更早的架构级 bring-up 已经由 `somehal` 完成”,它不自己构造早期页表,也不自己处理最底层的 CPU 模式切换。 ### 1.5 内存、时间、中断与电源 glue @@ -122,11 +122,11 @@ flowchart TD - `system_off()` 调用 `somehal::power::shutdown()`。 - `cpu_num()` 通过 `somehal::smp::cpu_meta_list()` 统计 CPU 数。 -这里还暴露出一个细节:`cpu_boot()` 当前忽略了 `stack_top_paddr` 参数,说明次核启动栈安排不是由 `ax-plat-dyn` 独立决定,而是纳入了 `somehal` 的启动协议。 +这里还暴露出一个细节:`cpu_boot()` 当前忽略了 `stack_top_paddr` 参数,说明次核启动栈安排不是由 `axplat-dyn` 独立决定,而是纳入了 `somehal` 的启动协议。 ### 1.6 动态设备探测路径 -`drivers` 模块是 `ax-plat-dyn` 与普通平台包最不一样的部分之一。它不是简单地“列出 MMIO 区间”,而是主动承担一部分动态设备发现职责: +`drivers` 模块是 `axplat-dyn` 与普通平台包最不一样的部分之一。它不是简单地“列出 MMIO 区间”,而是主动承担一部分动态设备发现职责: 1. `probe_all_devices()` 先清空本地块设备注册表。 2. 调用 `rdrive::probe_all(true)` 触发探测。 @@ -146,17 +146,17 @@ flowchart TD 这一层的关键价值在于: -- `ax-plat-dyn` 不只是“平台初始化 glue”,还是 `ax-driver` 动态设备模型的探测前端。 +- `axplat-dyn` 不只是“平台初始化 glue”,还是 `ax-driver` 动态设备模型的探测前端。 - 当前有效覆盖面主要是块设备;网络、显示等类别并没有在本 crate 中提供同等级的动态探测路径。 - `VirtIO` block 路径里的 `enable_irq()` / `disable_irq()` 仍是 `todo!()`,说明它更偏向当前可用的基础探测和阻塞 I/O 路径,而非完整中断驱动栈。 ### 1.7 与 `axplat`、`ax-plat-macros` 和工具链的边界 -`ax-plat-dyn` 的边界必须明确区分: +`axplat-dyn` 的边界必须明确区分: -- 与 `axplat` 的边界:`axplat` 定义的是稳定平台契约和入口调用面;`ax-plat-dyn` 只是其中一个实现者,并不改变接口定义。 +- 与 `axplat` 的边界:`axplat` 定义的是稳定平台契约和入口调用面;`axplat-dyn` 只是其中一个实现者,并不改变接口定义。 - 与 `ax-plat-macros` 的边界:本 crate 不直接依赖 `ax-plat-macros`,只通过 `axplat` 重新导出的 `#[impl_plat_interface]` 和入口宏参与体系。 -- 与 `somehal` 的边界:真正的“平台事实来源”在 `somehal`,包括入口、内存图、时钟、IRQ、电源与 CPU 元数据;`ax-plat-dyn` 负责转译,而不是重新探测 CPU 模式或自己管理整套启动环境。 +- 与 `somehal` 的边界:真正的“平台事实来源”在 `somehal`,包括入口、内存图、时钟、IRQ、电源与 CPU 元数据;`axplat-dyn` 负责转译,而不是重新探测 CPU 模式或自己管理整套启动环境。 - 与 `ax-config-gen` 的边界:当前源码中保留了一段被注释掉的 `config` 模块草稿,但现行实现并没有启用 `axconfig.toml -> AX_CONFIG_PATH -> include_configs!` 这条常规平台包主线,因此它不属于典型 `axplat-*` 配置化平台生态。 ## 核心功能 @@ -211,7 +211,7 @@ flowchart TD ```mermaid graph TD - A[somehal / ax-cpu / axklib] --> B[ax-plat-dyn] + A[somehal / ax-cpu / axklib] --> B[axplat-dyn] C[axplat] --> B D[rdrive / rd-block / dma-api / ax-driver] --> B @@ -227,7 +227,7 @@ graph TD ### 4.1 何时应使用这条路径 -适合使用 `ax-plat-dyn` 的情况是: +适合使用 `axplat-dyn` 的情况是: - 你已经有 `somehal` 这层更底部的平台抽象,希望把它接进 `axplat`/`ax-hal`。 - 你需要的是“运行时探测 + 动态设备模型”,而不是“固定板级参数 + 静态平台包”。 @@ -243,7 +243,7 @@ graph TD 2. 确保目标是裸机环境,而不是 `unix`/`windows` 宿主机构建路径。 3. 让 `somehal` 提供入口、FDT、内存图、控制台、时钟、中断和电源实现。 4. 由 `boot.rs` 把控制流统一转到 `ax_plat::call_main()`,随后上层只通过 `axplat` 接口使用平台能力。 -5. 若需要动态块设备,在适当阶段调用 `ax-driver::init_drivers()`,其内部会落到 `ax_plat_dyn::drivers::probe_all_devices()`。 +5. 若需要动态块设备,在适当阶段调用 `ax-driver::init_drivers()`,其内部会落到 `axplat_dyn::drivers::probe_all_devices()`。 ### 4.3 维护注意事项 @@ -271,7 +271,7 @@ graph TD ### 5.3 重点风险 -- 一旦 `somehal` 的内存图语义变化,`ax-plat-dyn` 的 RAM/保留区/MMIO 划分会整体漂移。 +- 一旦 `somehal` 的内存图语义变化,`axplat-dyn` 的 RAM/保留区/MMIO 划分会整体漂移。 - 该 crate 同时承担“平台契约 glue”和“设备探测 glue”两类职责,回归面比普通平台包更宽。 - 当前动态设备路径主要覆盖块设备,若上层以为 `dyn` 模式天然涵盖所有设备类型,容易产生错误预期。 @@ -285,4 +285,4 @@ graph TD ## 总结 -`ax-plat-dyn` 的价值不在“又实现了一套新的板级常量配置”,而在它把 `somehal` 的运行时平台事实和 `rdrive` 的设备探测能力拼成了 `axplat`/`ax-driver` 能消费的标准形态。它既不是常规 `axplat-*` 平台包,也不是运行时装卸模块,而是一条面向动态平台事实和动态驱动模型的桥接路径。理解这一点,是读懂它与 `axplat`、`ax-plat-macros` 及上层构建系统边界的关键。 +`axplat-dyn` 的价值不在“又实现了一套新的板级常量配置”,而在它把 `somehal` 的运行时平台事实和 `rdrive` 的设备探测能力拼成了 `axplat`/`ax-driver` 能消费的标准形态。它既不是常规 `axplat-*` 平台包,也不是运行时装卸模块,而是一条面向动态平台事实和动态驱动模型的桥接路径。理解这一点,是读懂它与 `axplat`、`ax-plat-macros` 及上层构建系统边界的关键。 diff --git a/docs/docs/components/crates/axvcpu.md b/docs/docs/components/crates/axvcpu.md index 88dfb29758..fb14718f6f 100644 --- a/docs/docs/components/crates/axvcpu.md +++ b/docs/docs/components/crates/axvcpu.md @@ -1,6 +1,6 @@ # `axvcpu` -> 路径:`components/axvcpu` +> 路径:`virtualization/axvcpu` > 类型:库 crate > 分层:组件层 / 通用 vCPU 抽象 > 版本:`0.2.2` diff --git a/docs/docs/components/crates/axvisor-api-proc.md b/docs/docs/components/crates/axvisor-api-proc.md index 91a0438e80..7e41400acb 100644 --- a/docs/docs/components/crates/axvisor-api-proc.md +++ b/docs/docs/components/crates/axvisor-api-proc.md @@ -1,10 +1,10 @@ # `axvisor_api_proc` -> 路径:`components/axvisor_api/axvisor_api_proc` +> 路径:`virtualization/axvisor_api_proc` > 类型:过程宏库 > 分层:组件层 / 编译期 API 生成辅助 > 版本:`0.1.0` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/items.rs`、`components/axvisor_api/src/lib.rs`、`components/axvisor_api/src/test.rs` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/items.rs`、`virtualization/axvisor_api/src/lib.rs`、`virtualization/axvisor_api/src/test.rs` `axvisor_api_proc` 不是运行时 API 库,而是 `axvisor_api` 背后的**过程宏辅助 crate**。它的职责是把一组带 `extern fn` 语法标记的模块,展开成可调用的 API 包装函数和对应的 `crate_interface` trait/impl 胶水代码。因此它属于“编译期 glue”,而不是 Hypervisor 子系统本体。 @@ -90,7 +90,7 @@ ### 1.6 与 `axvisor_api` 的真实关系 -`components/axvisor_api/src/lib.rs` 直接: +`virtualization/axvisor_api/src/lib.rs` 直接: - `pub use axvisor_api_proc::{api_mod, api_mod_impl};` @@ -217,7 +217,7 @@ 该 crate 自身没有单独的 `tests/`。当前主要依赖: - `axvisor_api/src/test.rs` -- `components/axvisor_api/examples/example.rs` +- `virtualization/axvisor_api/examples/example.rs` 来间接验证宏的可用性。 diff --git a/docs/docs/components/crates/axvisor-api.md b/docs/docs/components/crates/axvisor-api.md index 592a29eee6..0a56f6b0b1 100644 --- a/docs/docs/components/crates/axvisor-api.md +++ b/docs/docs/components/crates/axvisor-api.md @@ -1,6 +1,6 @@ # `axvisor_api` -> 路径:`components/axvisor_api` +> 路径:`virtualization/axvisor_api` > 类型:库 crate > 分层:组件层 / Hypervisor 公共 API 契约层 > 版本:`0.1.0` diff --git a/docs/docs/components/crates/axvm.md b/docs/docs/components/crates/axvm.md index 0f5ac7ad06..55a62c2a51 100644 --- a/docs/docs/components/crates/axvm.md +++ b/docs/docs/components/crates/axvm.md @@ -1,6 +1,6 @@ # `axvm` -> 路径:`components/axvm` +> 路径:`virtualization/axvm` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.2.3` diff --git a/docs/docs/components/crates/axvmconfig.md b/docs/docs/components/crates/axvmconfig.md index a53edb3aef..d7f7a48a40 100644 --- a/docs/docs/components/crates/axvmconfig.md +++ b/docs/docs/components/crates/axvmconfig.md @@ -1,10 +1,10 @@ # `axvmconfig` -> 路径:`components/axvmconfig` +> 路径:`virtualization/axvmconfig` > 类型:库 + 二进制混合 crate > 分层:组件层 / 虚拟机配置模型 > 版本:`0.2.2` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/tool.rs`、`components/axvm/src/config.rs` 与 `os/axvisor/src/vmm/config.rs` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`src/tool.rs`、`virtualization/axvm/src/config.rs` 与 `os/axvisor/src/vmm/config.rs` `axvmconfig` 是 Axvisor 虚拟机配置链路的静态模型层。它的职责不是直接创建 VM,也不是直接操作页表或设备,而是把 TOML 中描述的 VM 元信息、镜像布局、内存区域、模拟设备、直通设备和中断模式转换成一组稳定的数据结构;这些结构随后被 `axvm` 转成运行时 `AxVMConfig`,再由 `os/axvisor` 继续执行内存分配、镜像加载、FDT 处理和 VM 实例化。 @@ -167,7 +167,7 @@ flowchart TD ### 1.6 与 `axvm` 和 `os/axvisor` 的分工 -`components/axvm/src/config.rs` 中的 `AxVMConfig::from()` 做了几件关键转换: +`virtualization/axvm/src/config.rs` 中的 `AxVMConfig::from()` 做了几件关键转换: - `base.vm_type: usize` 转为 `VMType` - `entry_point` 转为 BSP/AP 两个 GPA 入口 @@ -237,8 +237,8 @@ cargo run -p axvmconfig -- generate --arch aarch64 --output vm.toml ### 主要消费者 -- `components/axvm`:通过 `AxVMConfig::from()` 把静态配置转成运行时配置 -- `components/axdevice` / `axdevice_base`:复用 `EmulatedDeviceType` 和设备配置模型 +- `virtualization/axvm`:通过 `AxVMConfig::from()` 把静态配置转成运行时配置 +- `virtualization/axdevice` / `axdevice_base`:复用 `EmulatedDeviceType` 和设备配置模型 - `os/axvisor`:直接解析 TOML、分配内存、创建 VM、加载镜像 - `scripts/axbuild`:生成 `AxVMCrateConfig` 的 JSON Schema @@ -260,7 +260,7 @@ graph TD 若要为 VM 增加新配置项,推荐沿以下顺序推进: 1. 在 `AxVMCrateConfig` 相关结构中添加字段,并补齐 `serde` 与文档注释。 -2. 如果该字段需要进入运行时,则同步更新 `components/axvm/src/config.rs` 中的 `From for AxVMConfig`。 +2. 如果该字段需要进入运行时,则同步更新 `virtualization/axvm/src/config.rs` 中的 `From for AxVMConfig`。 3. 如果该字段只在装载或管理阶段使用,则保留在 crate 配置层,并在 `os/axvisor` 的创建链路中消费。 4. 若宿主工具需要暴露该字段,再同步更新 `tool.rs` 和 `templates.rs`。 diff --git a/docs/docs/components/crates/bitmap-allocator.md b/docs/docs/components/crates/bitmap-allocator.md index 4c222d2d74..4e5eff1c29 100644 --- a/docs/docs/components/crates/bitmap-allocator.md +++ b/docs/docs/components/crates/bitmap-allocator.md @@ -1,10 +1,10 @@ # `bitmap-allocator` -> 路径:`components/bitmap-allocator` +> 路径:`memory/bitmap-allocator` > 类型:库 crate > 分层:组件层 / 位图分配算法组件 > 版本:`0.2.1` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`components/axallocator/src/bitmap.rs`、`components/axallocator/Cargo.toml` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`memory/axallocator/src/bitmap.rs`、`memory/axallocator/Cargo.toml` `bitmap-allocator` 的真实定位是一个**按位管理资源索引的分配算法组件**。它管理的是“哪些 bit 目前可用”,而不是直接管理页表、物理页或虚拟地址空间。当前仓库里,它被 `ax-allocator` 拿来实现页粒度分配器,但“页分配语义”是上层赋予它的,而不是这个 crate 自己具备的。 @@ -119,7 +119,7 @@ ### 1.7 与 `ax-allocator` 的真实关系 -当前仓库中的真实消费者是 `components/axallocator/src/bitmap.rs`。上层 `BitmapPageAllocator` 会: +当前仓库中的真实消费者是 `memory/axallocator/src/bitmap.rs`。上层 `BitmapPageAllocator` 会: - 把“页号”映射成 bit 索引 - 处理地址到 bit 索引的转换 diff --git a/docs/docs/components/crates/impl-simple-traits.md b/docs/docs/components/crates/impl-simple-traits.md index 73f3cd3ef1..3bdeafae01 100644 --- a/docs/docs/components/crates/impl-simple-traits.md +++ b/docs/docs/components/crates/impl-simple-traits.md @@ -148,7 +148,7 @@ | --- | --- | --- | | ArceOS | `os/arceos/modules/axlog`、`ax-runtime`、`ax-task`、`ax-driver` 等真实模块直接使用 `crate_interface`,但不会链接本 crate | 本 crate 间接保护“定义端与实现端分离”的底层语义不回退 | | StarryOS | 当前源码下未见 `os/StarryOS` 直接引用 `crate_interface` | 本 crate 对 StarryOS 的价值是仓库级基础设施回归,而不是运行时依赖 | -| Axvisor | `components/axvisor_api` 把 `crate_interface` 封装成 API 宏能力,但不会直接消费本 crate | 本 crate 为 Axvisor 相关封装提供底层实现端语义的回归样本 | +| Axvisor | `virtualization/axvisor_api` 把 `crate_interface` 封装成 API 宏能力,但不会直接消费本 crate | 本 crate 为 Axvisor 相关封装提供底层实现端语义的回归样本 | ## 总结 diff --git a/docs/docs/components/crates/range-alloc-arceos.md b/docs/docs/components/crates/range-alloc-arceos.md index 40ff702540..7152decedf 100644 --- a/docs/docs/components/crates/range-alloc-arceos.md +++ b/docs/docs/components/crates/range-alloc-arceos.md @@ -1,10 +1,10 @@ # `range-alloc-arceos` -> 路径:`components/range-alloc-arceos` +> 路径:`memory/range-alloc-arceos` > 类型:库 crate > 分层:组件层 / 区间分配算法组件 > 版本:`0.1.4` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`tests/test.rs`、`components/axdevice/src/device.rs`、`components/axvm/src/vm.rs`、`os/axvisor/src/vmm/hvc.rs` +> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`tests/test.rs`、`virtualization/axdevice/src/device.rs`、`virtualization/axvm/src/vm.rs`、`os/axvisor/src/vmm/hvc.rs` `range-alloc-arceos` 的真实定位是一个**泛型连续区间分配器**。它管理的是一个初始范围上的“哪些子区间空闲、哪些子区间已占用”,并提供 best-fit 分配与回收合并逻辑。它不是页分配器,不处理地址映射,不理解设备,不负责对齐策略,更不是完整的内存管理子系统。 diff --git a/docs/docs/components/crates/riscv-h.md b/docs/docs/components/crates/riscv-h.md index a6595eeff6..ee3131f1c7 100644 --- a/docs/docs/components/crates/riscv-h.md +++ b/docs/docs/components/crates/riscv-h.md @@ -1,6 +1,6 @@ # `riscv-h` -> 路径:`components/riscv-h` +> 路径:`virtualization/riscv-h` > 类型:库 crate > 分层:组件层 / RISC-V H 扩展寄存器封装层 > 版本:`0.2.0` diff --git a/docs/docs/components/crates/riscv-vcpu.md b/docs/docs/components/crates/riscv-vcpu.md index 238c77099d..e6ce183eaf 100644 --- a/docs/docs/components/crates/riscv-vcpu.md +++ b/docs/docs/components/crates/riscv-vcpu.md @@ -1,6 +1,6 @@ # `riscv_vcpu` -> 路径:`components/riscv_vcpu` +> 路径:`virtualization/riscv_vcpu` > 类型:库 crate > 分层:组件层 / RISC-V 虚拟 CPU 实现 > 版本:`0.2.2` @@ -238,7 +238,7 @@ flowchart TD ### 主要消费者 -- `components/axvm`:把它作为 RISC-V 架构后端重导出为 `AxArchVCpuImpl` 与 `AxVMArchPerCpuImpl`。 +- `virtualization/axvm`:把它作为 RISC-V 架构后端重导出为 `AxArchVCpuImpl` 与 `AxVMArchPerCpuImpl`。 - `os/axvisor`:通过 `axvm` 间接使用,是当前仓库中的实际落地对象。 ### 3.3 关系示意 diff --git a/docs/docs/components/crates/riscv-vplic.md b/docs/docs/components/crates/riscv-vplic.md index ef63f41e1b..5fb2785270 100644 --- a/docs/docs/components/crates/riscv-vplic.md +++ b/docs/docs/components/crates/riscv-vplic.md @@ -1,6 +1,6 @@ # `riscv_vplic` -> 路径:`components/riscv_vplic` +> 路径:`virtualization/riscv_vplic` > 类型:库 crate > 分层:组件层 / RISC-V 虚拟中断控制器 > 版本:`0.2.1` diff --git a/docs/docs/components/crates/x86-vcpu.md b/docs/docs/components/crates/x86-vcpu.md index e99f3a7405..5372fe24b6 100644 --- a/docs/docs/components/crates/x86-vcpu.md +++ b/docs/docs/components/crates/x86-vcpu.md @@ -1,6 +1,6 @@ # `x86_vcpu` -> 路径:`components/x86_vcpu` +> 路径:`virtualization/x86_vcpu` > 类型:库 crate > 分层:组件层 / x86 虚拟 CPU 后端 > 版本:`0.2.2` diff --git a/docs/docs/components/crates/x86-vlapic.md b/docs/docs/components/crates/x86-vlapic.md index 5b8729af11..c19ab341aa 100644 --- a/docs/docs/components/crates/x86-vlapic.md +++ b/docs/docs/components/crates/x86-vlapic.md @@ -1,10 +1,10 @@ # `x86_vlapic` -> 路径:`components/x86_vlapic` +> 路径:`virtualization/x86_vlapic` > 类型:库 crate > 分层:组件层 / 可复用基础组件 > 版本:`0.4.2` -> 文档依据:当前仓库源码、`Cargo.toml` 与 `components/x86_vlapic/README.md` +> 文档依据:当前仓库源码、`Cargo.toml` 与 `virtualization/x86_vlapic/README.md` `x86_vlapic` 的核心定位是:x86 Virtual Local APIC @@ -81,7 +81,7 @@ graph LR x86_vlapic = { workspace = true } # 如果在仓库外独立验证,也可以显式绑定本地路径: -# x86_vlapic = { path = "components/x86_vlapic" } +# x86_vlapic = { path = "virtualization/x86_vlapic" } ``` ### 初始化 diff --git a/docs/docs/components/layers.md b/docs/docs/components/layers.md index 6fdc75e10c..c781aa0ccc 100644 --- a/docs/docs/components/layers.md +++ b/docs/docs/components/layers.md @@ -48,7 +48,7 @@ flowchart TB L5["层级 5
堆叠层(依赖更底层 crate)
`arm_vgic`、`ax-plat-aarch64-bsta1000b`、`ax-plat-aarch64-phytium-pi`、`ax-plat-aarch64-qemu-virt`、`ax-plat-aarch64-raspi`、`ax-plat-riscv64-qemu-virt`、`ax-plat-riscv64-qemu-virt`、`axvcpu`、`riscv_vplic`、`x86_vlapic`"] classDef ls5 fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000 class L5 ls5 - L4["层级 4
堆叠层(依赖更底层 crate)
`ax-plat-aarch64-peripherals`、`ax-plat-loongarch64-qemu-virt`、`ax-plat-x86-pc`、`axdevice_base`、`ax-plat-dyn`、`ax-plat-x86-qemu-q35`、`axvisor_api`、`starry-signal`"] + L4["层级 4
堆叠层(依赖更底层 crate)
`ax-plat-aarch64-peripherals`、`ax-plat-loongarch64-qemu-virt`、`ax-plat-x86-pc`、`axdevice_base`、`axplat-dyn`、`ax-plat-x86-qemu-q35`、`axvisor_api`、`starry-signal`"] classDef ls4 fill:#e1bee7,stroke:#6a1b9a,stroke-width:2px,color:#000 class L4 ls4 L3["层级 3
堆叠层(依赖更底层 crate)
`ax-alloc`、`ax-cpu`、`ax-log`、`ax-plat`、`axaddrspace`、`scope-local`、`starry-process`、`test-simple`、`test-weak`、`test-weak-partial`、`tg-xtask`"] @@ -89,7 +89,7 @@ flowchart TB | 0 | 基础层(无仓库内直接依赖) | ArceOS 层 | `bwbench-client` | `0.3.0` | `os/arceos/tools/bwbench_client` | | 0 | 基础层(无仓库内直接依赖) | ArceOS 层 | `deptool` | `0.3.0` | `os/arceos/tools/deptool` | | 0 | 基础层(无仓库内直接依赖) | ArceOS 层 | `mingo` | `0.8.0` | `os/arceos/tools/raspi4/chainloader` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `aarch64_sysreg` | `0.3.1` | `components/aarch64_sysreg` | +| 0 | 基础层(无仓库内直接依赖) | 组件层 | `aarch64_sysreg` | `0.3.1` | `virtualization/aarch64_sysreg` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-arm-pl031` | `0.4.1` | `drivers/rtc/arm_pl031` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-cap-access` | `0.3.0` | `components/cap_access` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-config-gen` | `0.4.1` | `components/axconfig-gen/axconfig-gen` | @@ -102,32 +102,32 @@ flowchart TB | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-int-ratio` | `0.3.2` | `components/int_ratio` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-lazyinit` | `0.4.2` | `components/ax-lazyinit` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-linked-list-r4l` | `0.5.0` | `components/linked_list_r4l` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-memory-addr` | `0.6.1` | `components/axmm_crates/memory_addr` | +| 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-memory-addr` | `0.6.1` | `memory/memory_addr` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-percpu-macros` | `0.4.3` | `components/percpu/percpu_macros` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-riscv-plic` | `0.4.0` | `drivers/intc/riscv_plic` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `ax-timer-list` | `0.3.0` | `components/timer_list` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `axbacktrace` | `0.3.2` | `components/axbacktrace` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `axpoll` | `0.3.2` | `components/axpoll` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `axvisor_api_proc` | `0.5.0` | `components/axvisor_api/axvisor_api_proc` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `bitmap-allocator` | `0.4.1` | `components/bitmap-allocator` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `range-alloc-arceos` | `0.3.4` | `components/range-alloc-arceos` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `riscv-h` | `0.4.0` | `components/riscv-h` | +| 0 | 基础层(无仓库内直接依赖) | 组件层 | `axvisor_api_proc` | `0.5.0` | `virtualization/axvisor_api_proc` | +| 0 | 基础层(无仓库内直接依赖) | 组件层 | `bitmap-allocator` | `0.4.1` | `memory/bitmap-allocator` | +| 0 | 基础层(无仓库内直接依赖) | 组件层 | `range-alloc-arceos` | `0.3.4` | `memory/range-alloc-arceos` | +| 0 | 基础层(无仓库内直接依赖) | 组件层 | `riscv-h` | `0.4.0` | `virtualization/riscv-h` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `rsext4` | `0.3.0` | `components/rsext4` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `smoltcp` | `0.14.0` | `components/starry-smoltcp` | -| 1 | 堆叠层 | 组件层 | `ax-allocator` | `0.4.0` | `components/axallocator` | +| 1 | 堆叠层 | 组件层 | `ax-allocator` | `0.4.0` | `memory/axallocator` | | 1 | 堆叠层 | 组件层 | `ax-config-macros` | `0.4.1` | `components/axconfig-gen/axconfig-macros` | | 1 | 堆叠层 | 组件层 | `ax-ctor-bare` | `0.4.1` | `components/ctor_bare/ctor_bare` | | 1 | 堆叠层 | 组件层 | `ax-fs-vfs` | `0.3.2` | `components/axfs_crates/axfs_vfs` | | 1 | 堆叠层 | 组件层 | `ax-io` | `0.5.0` | `components/axio` | | 1 | 堆叠层 | 组件层 | `ax-kernel-guard` | `0.3.3` | `components/kernel_guard` | -| 1 | 堆叠层 | 组件层 | `ax-memory-set` | `0.6.1` | `components/axmm_crates/memory_set` | -| 1 | 堆叠层 | 组件层 | `ax-page-table-entry` | `0.8.1` | `components/page_table_multiarch/page_table_entry` | -| 1 | 堆叠层 | 组件层 | `ax-plat-macros` | `0.3.0` | `platforms/ax-plat-macros` | +| 1 | 堆叠层 | 组件层 | `ax-memory-set` | `0.6.1` | `memory/memory_set` | +| 1 | 堆叠层 | 组件层 | `ax-page-table-entry` | `0.8.1` | `memory/page_table_entry` | +| 1 | 堆叠层 | 组件层 | `ax-plat-macros` | `0.3.0` | `components/axplat_crates/axplat-macros` | | 1 | 堆叠层 | 组件层 | `ax-sched` | `0.5.1` | `components/axsched` | | 1 | 堆叠层 | 组件层 | `axfs-ng-vfs` | `0.3.1` | `components/axfs-ng-vfs` | -| 1 | 堆叠层 | 组件层 | `axhvc` | `0.4.0` | `components/axhvc` | +| 1 | 堆叠层 | 组件层 | `axhvc` | `0.4.0` | `virtualization/axhvc` | | 1 | 堆叠层 | 组件层 | `axklib` | `0.5.0` | `components/axklib` | -| 1 | 堆叠层 | 组件层 | `axvmconfig` | `0.4.2` | `components/axvmconfig` | +| 1 | 堆叠层 | 组件层 | `axvmconfig` | `0.4.2` | `virtualization/axvmconfig` | | 1 | 堆叠层 | 组件层 | `define-simple-traits` | `0.3.0` | `components/crate_interface/test_crates/define-simple-traits` | | 1 | 堆叠层 | 组件层 | `define-weak-traits` | `0.3.0` | `components/crate_interface/test_crates/define-weak-traits` | | 1 | 堆叠层 | 组件层 | `fxmac_rs` | `0.4.1` | `drivers/net/fxmac_rs` | @@ -138,7 +138,7 @@ flowchart TB | 2 | 堆叠层 | 组件层 | `ax-fs-devfs` | `0.3.2` | `components/axfs_crates/axfs_devfs` | | 2 | 堆叠层 | 组件层 | `ax-fs-ramfs` | `0.3.2` | `components/axfs_crates/axfs_ramfs` | | 2 | 堆叠层 | 组件层 | `ax-kspin` | `0.3.1` | `components/kspin` | -| 2 | 堆叠层 | 组件层 | `ax-page-table-multiarch` | `0.8.1` | `components/page_table_multiarch/page_table_multiarch` | +| 2 | 堆叠层 | 组件层 | `ax-page-table-multiarch` | `0.8.1` | `memory/page_table_multiarch` | | 2 | 堆叠层 | 组件层 | `ax-percpu` | `0.4.3` | `components/percpu/percpu` | | 2 | 堆叠层 | 组件层 | `impl-simple-traits` | `0.3.0` | `components/crate_interface/test_crates/impl-simple-traits` | | 2 | 堆叠层 | 组件层 | `impl-weak-partial` | `0.3.0` | `components/crate_interface/test_crates/impl-weak-partial` | @@ -154,32 +154,32 @@ flowchart TB | 3 | 堆叠层 | 组件层 | `test-simple` | `0.3.0` | `components/crate_interface/test_crates/test-simple` | | 3 | 堆叠层 | 组件层 | `test-weak` | `0.3.0` | `components/crate_interface/test_crates/test-weak` | | 3 | 堆叠层 | 组件层 | `test-weak-partial` | `0.3.0` | `components/crate_interface/test_crates/test-weak-partial` | -| 4 | 堆叠层 | 平台层 | `ax-plat-dyn` | `0.5.0` | `platforms/ax-plat-dyn` | +| 4 | 堆叠层 | 平台层 | `axplat-dyn` | `0.5.0` | `platforms/axplat-dyn` | | 4 | 堆叠层 | 平台层 | `ax-plat-x86-qemu-q35` | `0.4.0` | `platforms/ax-plat-x86-qemu-q35` | | 4 | 堆叠层 | 组件层 | `ax-plat-aarch64-peripherals` | `0.5.1` | `platforms/ax-plat-aarch64-peripherals` | | 4 | 堆叠层 | 组件层 | `ax-plat-loongarch64-qemu-virt` | `0.5.1` | `platforms/ax-plat-loongarch64-qemu-virt` | | 4 | 堆叠层 | 组件层 | `ax-plat-x86-pc` | `0.5.1` | `platforms/ax-plat-x86-pc` | -| 4 | 堆叠层 | 组件层 | `axdevice_base` | `0.4.2` | `components/axdevice_base` | -| 4 | 堆叠层 | 组件层 | `axvisor_api` | `0.5.0` | `components/axvisor_api` | +| 4 | 堆叠层 | 组件层 | `axdevice_base` | `0.4.2` | `virtualization/axdevice_base` | +| 4 | 堆叠层 | 组件层 | `axvisor_api` | `0.5.0` | `virtualization/axvisor_api` | | 4 | 堆叠层 | 组件层 | `starry-signal` | `0.5.0` | `components/starry-signal` | -| 5 | 堆叠层 | 组件层 | `arm_vgic` | `0.4.2` | `components/arm_vgic` | +| 5 | 堆叠层 | 组件层 | `arm_vgic` | `0.4.2` | `virtualization/arm_vgic` | | 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-bsta1000b` | `0.5.1` | `platforms/ax-plat-aarch64-bsta1000b` | | 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-phytium-pi` | `0.5.1` | `platforms/ax-plat-aarch64-phytium-pi` | | 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-qemu-virt` | `0.5.1` | `platforms/ax-plat-aarch64-qemu-virt` | | 5 | 堆叠层 | 组件层 | `ax-plat-aarch64-raspi` | `0.5.1` | `platforms/ax-plat-aarch64-raspi` | | 5 | 堆叠层 | 组件层 | `ax-plat-riscv64-qemu-virt` | `0.5.1` | `platforms/ax-plat-riscv64-qemu-virt` | -| 5 | 堆叠层 | 组件层 | `axvcpu` | `0.5.0` | `components/axvcpu` | -| 5 | 堆叠层 | 组件层 | `riscv_vplic` | `0.4.2` | `components/riscv_vplic` | -| 5 | 堆叠层 | 组件层 | `x86_vlapic` | `0.4.2` | `components/x86_vlapic` | +| 5 | 堆叠层 | 组件层 | `axvcpu` | `0.5.0` | `virtualization/axvcpu` | +| 5 | 堆叠层 | 组件层 | `riscv_vplic` | `0.4.2` | `virtualization/riscv_vplic` | +| 5 | 堆叠层 | 组件层 | `x86_vlapic` | `0.4.2` | `virtualization/x86_vlapic` | | 6 | 堆叠层 | ArceOS 层 | `ax-hal` | `0.5.0` | `os/arceos/modules/axhal` | -| 6 | 堆叠层 | 组件层 | `arm_vcpu` | `0.5.0` | `components/arm_vcpu` | -| 6 | 堆叠层 | 组件层 | `axdevice` | `0.4.2` | `components/axdevice` | -| 6 | 堆叠层 | 组件层 | `riscv_vcpu` | `0.5.0` | `components/riscv_vcpu` | -| 6 | 堆叠层 | 组件层 | `x86_vcpu` | `0.5.0` | `components/x86_vcpu` | +| 6 | 堆叠层 | 组件层 | `arm_vcpu` | `0.5.0` | `virtualization/arm_vcpu` | +| 6 | 堆叠层 | 组件层 | `axdevice` | `0.4.2` | `virtualization/axdevice` | +| 6 | 堆叠层 | 组件层 | `riscv_vcpu` | `0.5.0` | `virtualization/riscv_vcpu` | +| 6 | 堆叠层 | 组件层 | `x86_vcpu` | `0.5.0` | `virtualization/x86_vcpu` | | 7 | 堆叠层 | ArceOS 层 | `ax-ipi` | `0.5.0` | `os/arceos/modules/axipi` | | 7 | 堆叠层 | ArceOS 层 | `ax-mm` | `0.5.0` | `os/arceos/modules/axmm` | | 7 | 堆叠层 | ArceOS 层 | `ax-task` | `0.5.0` | `os/arceos/modules/axtask` | -| 7 | 堆叠层 | 组件层 | `axvm` | `0.5.0` | `components/axvm` | +| 7 | 堆叠层 | 组件层 | `axvm` | `0.5.0` | `virtualization/axvm` | | 8 | 堆叠层 | ArceOS 层 | `ax-dma` | `0.5.0` | `os/arceos/modules/axdma` | | 8 | 堆叠层 | ArceOS 层 | `ax-sync` | `0.5.0` | `os/arceos/modules/axsync` | | 9 | 堆叠层 | ArceOS 层 | `ax-driver` | `0.5.0` | `drivers/ax-driver` | @@ -229,7 +229,7 @@ flowchart TB | 1 | 19 | `ax-allocator` `ax-config-macros` `ax-ctor-bare` `ax-fs-vfs` `ax-io` `ax-kernel-guard` `ax-memory-set` `ax-page-table-entry` `ax-plat-macros` `ax-sched` `axfs-ng-vfs` `axhvc` `axklib` `axvmconfig` `define-simple-traits` `define-weak-traits` `fxmac_rs` `smoltcp-fuzz` `starry-vm` | | 2 | 10 | `ax-config` `ax-fs-devfs` `ax-fs-ramfs` `ax-kspin` `ax-page-table-multiarch` `ax-percpu` `axbuild` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` | | 3 | 11 | `ax-alloc` `ax-cpu` `ax-log` `ax-plat` `axaddrspace` `scope-local` `starry-process` `test-simple` `test-weak` `test-weak-partial` `tg-xtask` | -| 4 | 8 | `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-x86-pc` `axdevice_base` `ax-plat-dyn` `ax-plat-x86-qemu-q35` `axvisor_api` `starry-signal` | +| 4 | 8 | `ax-plat-aarch64-peripherals` `ax-plat-loongarch64-qemu-virt` `ax-plat-x86-pc` `axdevice_base` `axplat-dyn` `ax-plat-x86-qemu-q35` `axvisor_api` `starry-signal` | | 5 | 10 | `arm_vgic` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-riscv64-qemu-virt` `ax-plat-riscv64-qemu-virt` `axvcpu` `riscv_vplic` `x86_vlapic` | | 6 | 5 | `arm_vcpu` `ax-hal` `axdevice` `riscv_vcpu` `x86_vcpu` | | 7 | 4 | `ax-ipi` `ax-mm` `ax-task` `axvm` | @@ -269,15 +269,15 @@ flowchart TB | `arceos-yield` | 16 | 系统级测试与回归入口 | `ax-std` | — | | `arm_vcpu` | 6 | Aarch64 VCPU implementation for Arceos Hypervisor | `ax-errno` `ax-percpu` `axaddrspace` `axdevice_base` `axvcpu` `axvisor_api` | `axvm` | | `arm_vgic` | 5 | ARM Virtual Generic Interrupt Controller (VGIC) i… | `aarch64_sysreg` `ax-errno` `ax-memory-addr` `axaddrspace` `axdevice_base` `axvisor_api` | `axdevice` `axvm` | -| `ax-alloc` | 3 | ArceOS global memory allocator | `ax-allocator` `ax-errno` `ax-kspin` `ax-memory-addr` `ax-percpu` `axbacktrace` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs-ng` `ax-hal` `ax-mm` `ax-posix-api` `ax-runtime` `ax-plat-dyn` `starry-kernel` | +| `ax-alloc` | 3 | ArceOS global memory allocator | `ax-allocator` `ax-errno` `ax-kspin` `ax-memory-addr` `ax-percpu` `axbacktrace` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs-ng` `ax-hal` `ax-mm` `ax-posix-api` `ax-runtime` `axplat-dyn` `starry-kernel` | | `ax-allocator` | 1 | Various allocator algorithms in a unified interfa… | `ax-errno` `bitmap-allocator` | `ax-alloc` `ax-dma` | | `ax-api` | 14 | Public APIs and types for ArceOS modules | `ax-alloc` `ax-config` `ax-display` `ax-dma` `ax-driver` `ax-errno` `ax-feat` `ax-fs` `ax-hal` `ax-io` `ax-ipi` `ax-log` `ax-mm` `ax-net` `ax-runtime` `ax-sync` `ax-task` | `ax-std` | | `ax-arm-pl031` | 0 | System Real Time Clock (RTC) Drivers for aarch64 … | — | `ax-plat-aarch64-peripherals` | | `ax-cap-access` | 0 | Provide basic capability-based access control to … | — | `ax-fs` | | `ax-config` | 2 | Platform-specific constants and parameters for Ar… | `ax-config-macros` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-hal` `ax-ipi` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | | `ax-config-gen` | 0 | A TOML-based configuration generation tool for Ar… | — | `ax-config-macros` | -| `ax-config-macros` | 1 | Procedural macros for converting TOML format conf… | `ax-config-gen` | `ax-config` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-plat-dyn` `ax-plat-x86-qemu-q35` | -| `ax-cpu` | 3 | Privileged instruction and structure abstractions… | `ax-lazyinit` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `axbacktrace` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-plat-dyn` `ax-plat-x86-qemu-q35` `starry-signal` | +| `ax-config-macros` | 1 | Procedural macros for converting TOML format conf… | `ax-config-gen` | `ax-config` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `axplat-dyn` `ax-plat-x86-qemu-q35` | +| `ax-cpu` | 3 | Privileged instruction and structure abstractions… | `ax-lazyinit` `ax-memory-addr` `ax-page-table-entry` `ax-page-table-multiarch` `ax-percpu` `axbacktrace` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `axplat-dyn` `ax-plat-x86-qemu-q35` `starry-signal` | | `ax-cpumask` | 0 | CPU mask library in Rust | — | `ax-task` `axvisor` `axvisor_api` `axvm` | | `ax-crate-interface` | 0 | Provides a way to define an interface (trait) in … | — | `arceos-fs-shell` `ax-driver` `ax-kernel-guard` `ax-log` `ax-plat` `ax-plat-macros` `ax-plat-riscv64-qemu-virt` `ax-runtime` `ax-task` `axvisor` `axvisor_api` `define-simple-traits` `define-weak-traits` `fxmac_rs` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` `riscv_vcpu` `test-simple` `test-weak` `test-weak-partial` `x86_vcpu` | | `ax-crate-interface-lite` | 0 | Provides a way to define an interface (trait) in … | — | — | @@ -286,14 +286,14 @@ flowchart TB | `ax-display` | 10 | ArceOS graphics module | `ax-driver` `ax-lazyinit` `ax-sync` | `ax-api` `ax-feat` `ax-runtime` `starry-kernel` | | `ax-dma` | 8 | ArceOS global DMA allocator | `ax-alloc` `ax-allocator` `ax-config` `ax-hal` `ax-kspin` `ax-memory-addr` `ax-mm` | `ax-api` `ax-driver` | | `ax-driver` | 9 | ArceOS device drivers | `anyhow` `ax-alloc` `ax-crate-interface` `ax-dma` `ax-errno` `ax-kernel-guard` `ax-kspin` `axklib` `dma-api` `mmio-api` `rdrive` `rd-block` `rd-net` `rdif-*` `virtio-drivers` | `ax-api` `ax-display` `ax-feat` `ax-fs` `ax-fs-ng` `ax-input` `ax-net` `ax-net-ng` `ax-runtime` `starry-kernel` | -| `ax-errno` | 0 | Generic error code representation. | — | `arm_vcpu` `arm_vgic` `ax-alloc` `ax-allocator` `ax-api` `ax-driver` `ax-fs` `ax-fs-ng` `ax-fs-vfs` `ax-io` `ax-libc` `ax-memory-set` `ax-mm` `ax-net` `ax-net-ng` `ax-page-table-multiarch` `ax-posix-api` `ax-std` `ax-task` `axaddrspace` `axdevice` `axdevice_base` `axfs-ng-vfs` `axhvc` `axklib` `ax-plat-dyn` `axvcpu` `axvisor` `axvm` `axvmconfig` `riscv_vcpu` `riscv_vplic` `starry-kernel` `starry-vm` `x86_vcpu` `x86_vlapic` | +| `ax-errno` | 0 | Generic error code representation. | — | `arm_vcpu` `arm_vgic` `ax-alloc` `ax-allocator` `ax-api` `ax-driver` `ax-fs` `ax-fs-ng` `ax-fs-vfs` `ax-io` `ax-libc` `ax-memory-set` `ax-mm` `ax-net` `ax-net-ng` `ax-page-table-multiarch` `ax-posix-api` `ax-std` `ax-task` `axaddrspace` `axdevice` `axdevice_base` `axfs-ng-vfs` `axhvc` `axklib` `axplat-dyn` `axvcpu` `axvisor` `axvm` `axvmconfig` `riscv_vcpu` `riscv_vplic` `starry-kernel` `starry-vm` `x86_vcpu` `x86_vlapic` | | `ax-feat` | 13 | Top-level feature selection for ArceOS | `ax-alloc` `ax-config` `ax-display` `ax-driver` `ax-fs` `ax-fs-ng` `ax-hal` `ax-input` `ax-ipi` `ax-kspin` `ax-log` `ax-net` `ax-runtime` `ax-sync` `ax-task` `axbacktrace` | `ax-api` `ax-libc` `ax-posix-api` `ax-std` `starry-kernel` `starryos` `starryos-test` | | `ax-fs` | 10 | ArceOS filesystem module | `ax-cap-access` `ax-driver` `ax-errno` `ax-fs-devfs` `ax-fs-ramfs` `ax-fs-vfs` `ax-hal` `ax-io` `ax-lazyinit` `rsext4` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` | | `ax-fs-devfs` | 2 | Device filesystem used by ArceOS | `ax-fs-vfs` | `ax-fs` | | `ax-fs-ng` | 10 | ArceOS filesystem module | `ax-alloc` `ax-driver` `ax-errno` `ax-hal` `ax-io` `ax-kspin` `ax-sync` `axfs-ng-vfs` `axpoll` `scope-local` | `ax-feat` `ax-net-ng` `ax-runtime` `starry-kernel` | | `ax-fs-ramfs` | 2 | RAM filesystem used by ArceOS | `ax-fs-vfs` | `arceos-fs-shell` `ax-fs` | | `ax-fs-vfs` | 1 | Virtual filesystem interfaces used by ArceOS | `ax-errno` | `arceos-fs-shell` `ax-fs` `ax-fs-devfs` `ax-fs-ramfs` | -| `ax-hal` | 6 | ArceOS hardware abstraction layer, provides unifi… | `ax-alloc` `ax-config` `ax-cpu` `ax-kernel-guard` `ax-memory-addr` `ax-page-table-multiarch` `ax-percpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-plat-dyn` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs` `ax-fs-ng` `ax-ipi` `ax-mm` `ax-net` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | +| `ax-hal` | 6 | ArceOS hardware abstraction layer, provides unifi… | `ax-alloc` `ax-config` `ax-cpu` `ax-kernel-guard` `ax-memory-addr` `ax-page-table-multiarch` `ax-percpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `axplat-dyn` | `ax-api` `ax-dma` `ax-driver` `ax-feat` `ax-fs` `ax-fs-ng` `ax-ipi` `ax-mm` `ax-net` `ax-net-ng` `ax-posix-api` `ax-runtime` `ax-task` `axvisor` `starry-kernel` | | `ax-handler-table` | 0 | A lock-free table of event handlers | — | `ax-plat` | | `ax-helloworld` | 16 | ArceOS 示例程序 | `ax-std` | — | | `ax-helloworld-myplat` | 16 | ArceOS 示例程序 | `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-std` | — | @@ -309,16 +309,16 @@ flowchart TB | `ax-libc` | 15 | ArceOS user program library for C apps | `ax-errno` `ax-feat` `ax-io` `ax-posix-api` | — | | `ax-linked-list-r4l` | 0 | Linked lists that supports arbitrary removal in c… | — | `ax-sched` | | `ax-log` | 3 | Macros for multi-level formatted logging used by … | `ax-crate-interface` `ax-kspin` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` `starry-kernel` | -| `ax-memory-addr` | 0 | Wrappers and helper functions for physical and vi… | — | `arm_vgic` `ax-alloc` `ax-cpu` `ax-dma` `ax-hal` `ax-memory-set` `ax-mm` `ax-page-table-entry` `ax-page-table-multiarch` `ax-plat` `ax-task` `axaddrspace` `axdevice` `axklib` `ax-plat-dyn` `axvcpu` `axvisor` `axvisor_api` `axvm` `riscv_vcpu` `starry-kernel` `x86_vcpu` `x86_vlapic` | +| `ax-memory-addr` | 0 | Wrappers and helper functions for physical and vi… | — | `arm_vgic` `ax-alloc` `ax-cpu` `ax-dma` `ax-hal` `ax-memory-set` `ax-mm` `ax-page-table-entry` `ax-page-table-multiarch` `ax-plat` `ax-task` `axaddrspace` `axdevice` `axklib` `axplat-dyn` `axvcpu` `axvisor` `axvisor_api` `axvm` `riscv_vcpu` `starry-kernel` `x86_vcpu` `x86_vlapic` | | `ax-memory-set` | 1 | Data structures and operations for managing memor… | `ax-errno` `ax-memory-addr` | `ax-mm` `axaddrspace` `starry-kernel` | | `ax-mm` | 7 | ArceOS virtual memory management module | `ax-alloc` `ax-errno` `ax-hal` `ax-kspin` `ax-lazyinit` `ax-memory-addr` `ax-memory-set` `ax-page-table-multiarch` | `ax-api` `ax-dma` `ax-runtime` `starry-kernel` | | `ax-net` | 10 | ArceOS network module | `ax-driver` `ax-errno` `ax-hal` `ax-io` `ax-lazyinit` `ax-sync` `ax-task` `smoltcp` | `ax-api` `ax-feat` `ax-posix-api` `ax-runtime` | | `ax-net-ng` | 11 | ArceOS network module | `ax-config` `ax-driver` `ax-errno` `ax-fs-ng` `ax-hal` `ax-io` `ax-sync` `ax-task` `axfs-ng-vfs` `axpoll` `smoltcp` | `ax-runtime` `starry-kernel` | | `ax-page-table-entry` | 1 | Page table entry definition for various hardware … | `ax-memory-addr` | `ax-cpu` `ax-page-table-multiarch` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `axaddrspace` `axvisor` `axvm` `riscv_vcpu` `x86_vcpu` | | `ax-page-table-multiarch` | 2 | Generic page table structures for various hardwar… | `ax-errno` `ax-memory-addr` `ax-page-table-entry` | `ax-cpu` `ax-hal` `ax-mm` `axaddrspace` `axvisor` `axvm` `starry-kernel` | -| `ax-percpu` | 2 | Define and access per-CPU data structures | `ax-kernel-guard` `ax-percpu-macros` | `arm_vcpu` `ax-alloc` `ax-cpu` `ax-hal` `ax-ipi` `ax-plat` `ax-plat-x86-pc` `ax-runtime` `ax-task` `ax-plat-dyn` `ax-plat-x86-qemu-q35` `axvcpu` `axvisor` `axvm` `scope-local` `starry-kernel` | +| `ax-percpu` | 2 | Define and access per-CPU data structures | `ax-kernel-guard` `ax-percpu-macros` | `arm_vcpu` `ax-alloc` `ax-cpu` `ax-hal` `ax-ipi` `ax-plat` `ax-plat-x86-pc` `ax-runtime` `ax-task` `axplat-dyn` `ax-plat-x86-qemu-q35` `axvcpu` `axvisor` `axvm` `scope-local` `starry-kernel` | | `ax-percpu-macros` | 0 | Macros to define and access a per-CPU data struct… | — | `ax-percpu` | -| `ax-plat` | 3 | This crate provides a unified abstraction layer f… | `ax-crate-interface` `ax-handler-table` `ax-kspin` `ax-memory-addr` `ax-percpu` `ax-plat-macros` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-runtime` `ax-plat-dyn` `ax-plat-x86-qemu-q35` | +| `ax-plat` | 3 | This crate provides a unified abstraction layer f… | `ax-crate-interface` `ax-handler-table` `ax-kspin` `ax-memory-addr` `ax-percpu` `ax-plat-macros` | `ax-hal` `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-peripherals` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` `ax-runtime` `axplat-dyn` `ax-plat-x86-qemu-q35` | | `ax-plat-aarch64-bsta1000b` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-kspin` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | | `ax-plat-aarch64-peripherals` | 4 | ARM64 common peripheral drivers with `axplat` com… | `ax-arm-pl031` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-plat` `some-serial` | `ax-plat-aarch64-bsta1000b` `ax-plat-aarch64-phytium-pi` `ax-plat-aarch64-qemu-virt` `ax-plat-aarch64-raspi` | | `ax-plat-aarch64-phytium-pi` | 5 | Implementation of `axplat` hardware abstraction l… | `ax-config-macros` `ax-cpu` `ax-page-table-entry` `ax-plat` `ax-plat-aarch64-peripherals` | `ax-helloworld-myplat` | @@ -345,8 +345,8 @@ flowchart TB | `axdevice_base` | 4 | Basic traits and structures for emulated devices … | `ax-errno` `axaddrspace` `axvmconfig` | `arm_vcpu` `arm_vgic` `axdevice` `axvisor` `axvm` `riscv_vplic` `x86_vcpu` `x86_vlapic` | | `axfs-ng-vfs` | 1 | Virtual filesystem layer for ArceOS | `ax-errno` `axpoll` | `ax-fs-ng` `ax-net-ng` `starry-kernel` | | `axhvc` | 1 | AxVisor HyperCall definitions for guest-hyperviso… | `ax-errno` | `axvisor` | -| `axklib` | 1 | Small kernel-helper abstractions used across the … | `ax-errno` `ax-memory-addr` | `ax-runtime` `ax-plat-dyn` `axvisor` | -| `ax-plat-dyn` | 4 | A dynamic platform module for ArceOS, providing r… | `ax-cpu` `ax-driver` `ax-errno` `ax-memory-addr` `ax-percpu` `ax-plat` `axklib` `rdrive` `somehal` | `ax-driver` `ax-hal` | +| `axklib` | 1 | Small kernel-helper abstractions used across the … | `ax-errno` `ax-memory-addr` | `ax-runtime` `axplat-dyn` `axvisor` | +| `axplat-dyn` | 4 | A dynamic platform module for ArceOS, providing r… | `ax-cpu` `ax-driver` `ax-errno` `ax-memory-addr` `ax-percpu` `ax-plat` `axklib` `rdrive` `somehal` | `ax-driver` `ax-hal` | | `ax-plat-x86-qemu-q35` | 4 | Hardware platform implementation for x86_64 QEMU … | `ax-config-macros` `ax-cpu` `ax-int-ratio` `ax-kspin` `ax-lazyinit` `ax-percpu` `ax-plat` | `axvisor` | | `axpoll` | 0 | A library for polling I/O events and waking up ta… | — | `ax-fs-ng` `ax-net-ng` `ax-task` `axfs-ng-vfs` `starry-kernel` | | `axvcpu` | 5 | Virtual CPU abstraction for ArceOS hypervisor | `ax-errno` `ax-memory-addr` `ax-percpu` `axaddrspace` `axvisor_api` | `arm_vcpu` `axvisor` `axvm` `riscv_vcpu` `x86_vcpu` | diff --git a/docs/docs/components/overview.md b/docs/docs/components/overview.md index 1e559caadb..aad60b4895 100644 --- a/docs/docs/components/overview.md +++ b/docs/docs/components/overview.md @@ -43,7 +43,7 @@ flowchart TB end subgraph sg_plat["平台层"] direction LR - p1["ax-plat-dyn"] + p1["axplat-dyn"] p2["ax-plat-x86-qemu-q35"] end subgraph sg_comp["组件层 (93 crates)"] @@ -88,7 +88,7 @@ flowchart TB | Crate | 分类 | 路径 | 直接依赖 | 被依赖 | 文档 | | --- | --- | --- | ---: | ---: | --- | -| `aarch64_sysreg` | 组件层 | `components/aarch64_sysreg` | 0 | 1 | [查看](crates/aarch64-sysreg) | +| `aarch64_sysreg` | 组件层 | `virtualization/aarch64_sysreg` | 0 | 1 | [查看](crates/aarch64-sysreg) | | `arceos-affinity` | 测试层 | `test-suit/arceos/rust/task/affinity` | 1 | 0 | [查看](crates/arceos-affinity) | | `arceos-display` | 测试层 | `test-suit/arceos/rust/display` | 1 | 0 | [查看](crates/arceos-display) | | `arceos-exception` | 测试层 | `test-suit/arceos/rust/exception` | 1 | 0 | [查看](crates/arceos-exception) | @@ -105,10 +105,10 @@ flowchart TB | `arceos-tls` | 测试层 | `test-suit/arceos/rust/task/tls` | 1 | 0 | [查看](crates/arceos-tls) | | `arceos-wait-queue` | 测试层 | `test-suit/arceos/rust/task/wait_queue` | 1 | 0 | [查看](crates/arceos-wait-queue) | | `arceos-yield` | 测试层 | `test-suit/arceos/rust/task/yield` | 1 | 0 | [查看](crates/arceos-yield) | -| `arm_vcpu` | 组件层 | `components/arm_vcpu` | 6 | 1 | [查看](crates/arm-vcpu) | -| `arm_vgic` | 组件层 | `components/arm_vgic` | 6 | 2 | [查看](crates/arm-vgic) | +| `arm_vcpu` | 组件层 | `virtualization/arm_vcpu` | 6 | 1 | [查看](crates/arm-vcpu) | +| `arm_vgic` | 组件层 | `virtualization/arm_vgic` | 6 | 2 | [查看](crates/arm-vgic) | | `ax-alloc` | ArceOS 层 | `os/arceos/modules/axalloc` | 6 | 11 | [查看](crates/ax-alloc) | -| `ax-allocator` | 组件层 | `components/axallocator` | 2 | 2 | [查看](crates/ax-allocator) | +| `ax-allocator` | 组件层 | `memory/axallocator` | 2 | 2 | [查看](crates/ax-allocator) | | `ax-api` | ArceOS 层 | `os/arceos/api/arceos_api` | 17 | 1 | [查看](crates/ax-api) | | `ax-arm-pl031` | 组件层 | `drivers/rtc/arm_pl031` | 0 | 1 | [查看](crates/ax-arm-pl031) | | `ax-cap-access` | 组件层 | `components/cap_access` | 0 | 1 | [查看](crates/ax-cap-access) | @@ -147,13 +147,13 @@ flowchart TB | `ax-libc` | ArceOS 层 | `os/arceos/ulib/axlibc` | 4 | 0 | [查看](crates/ax-libc) | | `ax-linked-list-r4l` | 组件层 | `components/linked_list_r4l` | 0 | 1 | [查看](crates/ax-linked-list-r4l) | | `ax-log` | ArceOS 层 | `os/arceos/modules/axlog` | 2 | 5 | [查看](crates/ax-log) | -| `ax-memory-addr` | 组件层 | `components/axmm_crates/memory_addr` | 0 | 24 | [查看](crates/ax-memory-addr) | -| `ax-memory-set` | 组件层 | `components/axmm_crates/memory_set` | 2 | 3 | [查看](crates/ax-memory-set) | +| `ax-memory-addr` | 组件层 | `memory/memory_addr` | 0 | 24 | [查看](crates/ax-memory-addr) | +| `ax-memory-set` | 组件层 | `memory/memory_set` | 2 | 3 | [查看](crates/ax-memory-set) | | `ax-mm` | ArceOS 层 | `os/arceos/modules/axmm` | 8 | 4 | [查看](crates/ax-mm) | | `ax-net` | ArceOS 层 | `os/arceos/modules/axnet` | 8 | 4 | [查看](crates/ax-net) | | `ax-net-ng` | ArceOS 层 | `os/arceos/modules/axnet-ng` | 11 | 2 | [查看](crates/ax-net-ng) | -| `ax-page-table-entry` | 组件层 | `components/page_table_multiarch/page_table_entry` | 1 | 12 | [查看](crates/ax-page-table-entry) | -| `ax-page-table-multiarch` | 组件层 | `components/page_table_multiarch/page_table_multiarch` | 3 | 7 | [查看](crates/ax-page-table-multiarch) | +| `ax-page-table-entry` | 组件层 | `memory/page_table_entry` | 1 | 12 | [查看](crates/ax-page-table-entry) | +| `ax-page-table-multiarch` | 组件层 | `memory/page_table_multiarch` | 3 | 7 | [查看](crates/ax-page-table-multiarch) | | `ax-percpu` | 组件层 | `components/percpu/percpu` | 2 | 17 | [查看](crates/ax-percpu) | | `ax-percpu-macros` | 组件层 | `components/percpu/percpu_macros` | 0 | 1 | [查看](crates/ax-percpu-macros) | | `ax-plat` | 组件层 | `platforms/ax-plat` | 6 | 15 | [查看](crates/ax-plat) | @@ -173,24 +173,24 @@ flowchart TB | `ax-std` | ArceOS 层 | `os/arceos/ulib/axstd` | 6 | 22 | [查看](crates/ax-std) | | `ax-sync` | ArceOS 层 | `os/arceos/modules/axsync` | 2 | 9 | [查看](crates/ax-sync) | | `ax-task` | ArceOS 层 | `os/arceos/modules/axtask` | 13 | 8 | [查看](crates/ax-task) | -| `axaddrspace` | 组件层 | `components/axaddrspace` | 6 | 12 | [查看](crates/axaddrspace) | +| `axaddrspace` | 组件层 | `memory/axaddrspace` | 6 | 12 | [查看](crates/axaddrspace) | | `axbacktrace` | 组件层 | `components/axbacktrace` | 0 | 5 | [查看](crates/axbacktrace) | | `axbuild` | 工具层 | `scripts/axbuild` | 1 | 3 | [查看](crates/axbuild) | -| `axdevice` | 组件层 | `components/axdevice` | 8 | 2 | [查看](crates/axdevice) | -| `axdevice_base` | 组件层 | `components/axdevice_base` | 3 | 8 | [查看](crates/axdevice-base) | +| `axdevice` | 组件层 | `virtualization/axdevice` | 8 | 2 | [查看](crates/axdevice) | +| `axdevice_base` | 组件层 | `virtualization/axdevice_base` | 3 | 8 | [查看](crates/axdevice-base) | | `axfs-ng-vfs` | 组件层 | `components/axfs-ng-vfs` | 2 | 3 | [查看](crates/axfs-ng-vfs) | -| `axhvc` | 组件层 | `components/axhvc` | 1 | 1 | [查看](crates/axhvc) | +| `axhvc` | 组件层 | `virtualization/axhvc` | 1 | 1 | [查看](crates/axhvc) | | `axklib` | 组件层 | `components/axklib` | 2 | 3 | [查看](crates/axklib) | -| `ax-plat-dyn` | 平台层 | `platforms/ax-plat-dyn` | 11 | 2 | [查看](crates/ax-plat-dyn) | +| `axplat-dyn` | 平台层 | `platforms/axplat-dyn` | 11 | 2 | [查看](crates/axplat-dyn) | | `ax-plat-x86-qemu-q35` | 平台层 | `platforms/ax-plat-x86-qemu-q35` | 7 | 1 | [查看](crates/ax-plat-x86-qemu-q35) | | `axpoll` | 组件层 | `components/axpoll` | 0 | 5 | [查看](crates/axpoll) | -| `axvcpu` | 组件层 | `components/axvcpu` | 5 | 5 | [查看](crates/axvcpu) | +| `axvcpu` | 组件层 | `virtualization/axvcpu` | 5 | 5 | [查看](crates/axvcpu) | | `axvisor` | Axvisor 层 | `os/axvisor` | 27 | 0 | [查看](crates/axvisor) | -| `axvisor_api` | 组件层 | `components/axvisor_api` | 5 | 10 | [查看](crates/axvisor-api) | -| `axvisor_api_proc` | 组件层 | `components/axvisor_api/axvisor_api_proc` | 0 | 1 | [查看](crates/axvisor-api-proc) | -| `axvm` | 组件层 | `components/axvm` | 16 | 1 | [查看](crates/axvm) | -| `axvmconfig` | 组件层 | `components/axvmconfig` | 1 | 4 | [查看](crates/axvmconfig) | -| `bitmap-allocator` | 组件层 | `components/bitmap-allocator` | 0 | 1 | [查看](crates/bitmap-allocator) | +| `axvisor_api` | 组件层 | `virtualization/axvisor_api` | 5 | 10 | [查看](crates/axvisor-api) | +| `axvisor_api_proc` | 组件层 | `virtualization/axvisor_api_proc` | 0 | 1 | [查看](crates/axvisor-api-proc) | +| `axvm` | 组件层 | `virtualization/axvm` | 16 | 1 | [查看](crates/axvm) | +| `axvmconfig` | 组件层 | `virtualization/axvmconfig` | 1 | 4 | [查看](crates/axvmconfig) | +| `bitmap-allocator` | 组件层 | `memory/bitmap-allocator` | 0 | 1 | [查看](crates/bitmap-allocator) | | `bwbench-client` | ArceOS 层 | `os/arceos/tools/bwbench_client` | 0 | 0 | [查看](crates/bwbench-client) | | `define-simple-traits` | 组件层 | `components/crate_interface/test_crates/define-simple-traits` | 1 | 2 | [查看](crates/define-simple-traits) | | `define-weak-traits` | 组件层 | `components/crate_interface/test_crates/define-weak-traits` | 1 | 4 | [查看](crates/define-weak-traits) | @@ -200,11 +200,11 @@ flowchart TB | `impl-weak-partial` | 组件层 | `components/crate_interface/test_crates/impl-weak-partial` | 2 | 1 | [查看](crates/impl-weak-partial) | | `impl-weak-traits` | 组件层 | `components/crate_interface/test_crates/impl-weak-traits` | 2 | 1 | [查看](crates/impl-weak-traits) | | `mingo` | ArceOS 层 | `os/arceos/tools/raspi4/chainloader` | 0 | 0 | [查看](crates/mingo) | -| `range-alloc-arceos` | 组件层 | `components/range-alloc-arceos` | 0 | 1 | [查看](crates/range-alloc-arceos) | -| `riscv-h` | 组件层 | `components/riscv-h` | 0 | 2 | [查看](crates/riscv-h) | +| `range-alloc-arceos` | 组件层 | `memory/range-alloc-arceos` | 0 | 1 | [查看](crates/range-alloc-arceos) | +| `riscv-h` | 组件层 | `virtualization/riscv-h` | 0 | 2 | [查看](crates/riscv-h) | | `ax-riscv-plic` | 组件层 | `drivers/intc/riscv_plic` | 0 | 1 | [查看](crates/ax-riscv-plic) | -| `riscv_vcpu` | 组件层 | `components/riscv_vcpu` | 8 | 2 | [查看](crates/riscv-vcpu) | -| `riscv_vplic` | 组件层 | `components/riscv_vplic` | 5 | 2 | [查看](crates/riscv-vplic) | +| `riscv_vcpu` | 组件层 | `virtualization/riscv_vcpu` | 8 | 2 | [查看](crates/riscv-vcpu) | +| `riscv_vplic` | 组件层 | `virtualization/riscv_vplic` | 5 | 2 | [查看](crates/riscv-vplic) | | `rsext4` | 组件层 | `components/rsext4` | 0 | 1 | [查看](crates/rsext4) | | `scope-local` | 组件层 | `components/scope-local` | 1 | 3 | [查看](crates/scope-local) | | `smoltcp` | 组件层 | `components/starry-smoltcp` | 0 | 3 | [查看](crates/smoltcp) | @@ -220,5 +220,5 @@ flowchart TB | `test-weak-partial` | 组件层 | `components/crate_interface/test_crates/test-weak-partial` | 3 | 0 | [查看](crates/test-weak-partial) | | `tg-xtask` | 工具层 | `xtask` | 1 | 0 | [查看](crates/tg-xtask) | | `ax-timer-list` | 组件层 | `components/timer_list` | 0 | 2 | [查看](crates/ax-timer-list) | -| `x86_vcpu` | 组件层 | `components/x86_vcpu` | 9 | 1 | [查看](crates/x86-vcpu) | -| `x86_vlapic` | 组件层 | `components/x86_vlapic` | 5 | 1 | [查看](crates/x86-vlapic) | +| `x86_vcpu` | 组件层 | `virtualization/x86_vcpu` | 9 | 1 | [查看](crates/x86-vcpu) | +| `x86_vlapic` | 组件层 | `virtualization/x86_vlapic` | 5 | 1 | [查看](crates/x86-vlapic) | diff --git a/docs/docs/debug/check-mechanisms-summary.md b/docs/docs/debug/check-mechanisms-summary.md index b4c1b95c11..b0051690d8 100644 --- a/docs/docs/debug/check-mechanisms-summary.md +++ b/docs/docs/debug/check-mechanisms-summary.md @@ -117,7 +117,7 @@ boot/current 栈,也不覆盖未来可能引入的独立 IRQ stack、exception - `os/arceos/modules/axtask/src/task.rs` - `os/arceos/modules/axtask/src/run_queue.rs` - `os/arceos/modules/axruntime/src/mp.rs` -- `platforms/ax-plat-dyn/src/boot.rs` +- `platforms/axplat-dyn/src/boot.rs` 后续改进方向: diff --git a/docs/docs/debug/platform.md b/docs/docs/debug/platform.md index e755891851..ed9aa14f34 100644 --- a/docs/docs/debug/platform.md +++ b/docs/docs/debug/platform.md @@ -134,7 +134,7 @@ sidebar_label: "平台实现" | ArceOS Main | 单个软件断点 + `continue` | `os/arceos/examples/helloworld/src/main.rs:8` | | ArceOS Boot | 多个符号/行号断点(不自动 continue) | `ax_plat::call_main`、`axruntime/src/lib.rs:141`、`main.rs:8` | | Axvisor Main | 单个软件断点 + `continue` | `os/axvisor/src/main.rs:42` | -| Axvisor Boot | 多个行号断点(不自动 continue) | `platforms/ax-plat-dyn/src/boot.rs:8`、`axvisor/src/main.rs:42` | +| Axvisor Boot | 多个行号断点(不自动 continue) | `platforms/axplat-dyn/src/boot.rs:8`、`axvisor/src/main.rs:42` | | StarryOS Main | **单个硬件断点** + `continue` | `os/StarryOS/starryos/src/main.rs:12` | | StarryOS Boot | 混合符号/行号断点(不自动 continue) | `ax_plat::call_main`、`axruntime/src/lib.rs:141`、`starry_kernel::entry::init`、`starryos/src/main.rs:12` | diff --git a/docs/docs/development/arceos.md b/docs/docs/development/arceos.md index 488fa73501..d602272504 100644 --- a/docs/docs/development/arceos.md +++ b/docs/docs/development/arceos.md @@ -387,7 +387,7 @@ platforms/ax-plat-aarch64-qemu-virt/ | 目录 | 内容 | |------|------| | `platforms/` | 工作区内 `ax-plat-*` 平台 crate | -| `platforms/ax-plat-dyn/` | 动态平台加载(设备树驱动) | +| `platforms/axplat-dyn/` | 动态平台加载(设备树驱动) | | `platforms/ax-plat-x86-qemu-q35/` | x86_64 QEMU Q35 独立平台 | 已有平台: diff --git a/docs/docs/development/axvisor.md b/docs/docs/development/axvisor.md index db2f297a0b..d5567b2635 100644 --- a/docs/docs/development/axvisor.md +++ b/docs/docs/development/axvisor.md @@ -158,7 +158,7 @@ main() ### 4.1 设备模型 -Axvisor 的虚拟设备框架(`components/axdevice/`)支持三种设备模式: +Axvisor 的虚拟设备框架(`virtualization/axdevice/`)支持三种设备模式: | 模式 | 配置字段 | 说明 | |------|---------|------| @@ -183,7 +183,7 @@ passthrough_devices = [["/"]] # 直通所有设备 要添加一个新的虚拟设备(如虚拟串口、虚拟块设备),需要: -1. 在 `components/axdevice/` 中实现设备模拟逻辑 +1. 在 `virtualization/axdevice/` 中实现设备模拟逻辑 2. 在 VM 配置的 `emu_devices` 中注册 3. 在 `vmm` 中处理对应的 VM Exit 事件 4. 通过 Guest 驱动验证 @@ -194,7 +194,7 @@ passthrough_devices = [["/"]] # 直通所有设备 ### 5.1 vCPU 状态机 -vCPU 的生命周期由 `components/axvcpu/` 管理: +vCPU 的生命周期由 `virtualization/axvcpu/` 管理: ``` Created → Free → Ready → Running ⇄ Suspended → Halted @@ -229,7 +229,7 @@ match exit_reason { ### 5.3 per-CPU 虚拟化状态 -`components/axvcpu/src/percpu.rs` 管理每个物理 CPU 上的虚拟化状态,包括当前运行的 vCPU、虚拟化控制寄存器等。 +`virtualization/axvcpu/src/percpu.rs` 管理每个物理 CPU 上的虚拟化状态,包括当前运行的 vCPU、虚拟化控制寄存器等。 --- @@ -540,7 +540,7 @@ Axvisor 构建在 ArceOS 基础能力之上,改动共享模块时的验证策 | 改动位置 | 先验证 | 再验证 | |----------|--------|--------| -| `components/axvm`、`axvcpu`、`axdevice` | `cargo xtask axvisor build` | 准备好 Guest 后 QEMU 测试 | +| `virtualization/axvm`、`axvcpu`、`axdevice` | `cargo xtask axvisor build` | 准备好 Guest 后 QEMU 测试 | | `os/arceos/modules/axhal` | ArceOS helloworld | Axvisor build + QEMU | | `os/arceos/modules/axtask` | ArceOS helloworld | Axvisor build + QEMU | | `os/axvisor/src/*` | `cargo xtask axvisor build` | QEMU 测试 | diff --git a/docs/docs/development/components.md b/docs/docs/development/components.md index a76bddc44e..65df9748f8 100644 --- a/docs/docs/development/components.md +++ b/docs/docs/development/components.md @@ -39,8 +39,8 @@ flowchart TD ArceosApi["os/arceos/api/* + os/arceos/ulib/*"] ArceosApps["ArceOS examples + test-suit/arceos"] StarryKernel["os/StarryOS/kernel + components/starry-*"] - AxvisorRuntime["os/axvisor + components/axvm/axvcpu/axdevice/*"] - PlatformCrates["platforms/*"] + AxvisorRuntime["os/axvisor + virtualization/axvm/axvcpu/axdevice/*"] + PlatformCrates["components/axplat_crates/platforms/* + platform/*"] ReusableCrate --> ArceosModules ArceosModules --> ArceosApi @@ -57,7 +57,7 @@ flowchart TD 上述流程图表示常见的三种组件依赖路径: 1. 纯复用 crate 直接被系统包依赖 - 例如 `components/starry-process`、`components/axvm` + 例如 `components/starry-process`、`virtualization/axvm` 2. 先经过 ArceOS 模块层,再被上层系统消费 例如 `ax-hal`、`ax-task`、`ax-driver`、`ax-net` @@ -90,12 +90,12 @@ flowchart TD | 你要改什么 | 优先看哪里 | 常见影响面 | | --- | --- | --- | -| 通用基础能力:错误、锁、页表、Per-CPU、容器 | `components/axerrno`、`components/kspin`、`components/page_table_multiarch`、`components/percpu` | 三套系统都可能受影响 | -| ArceOS 内核服务:调度、HAL、驱动、网络、文件系统 | `os/arceos/modules/*`、`drivers/*`,以及相关 `axmm_crates` / `axplat_crates` | ArceOS,且可能波及 StarryOS / Axvisor | +| 通用基础能力:错误、锁、页表、Per-CPU、容器 | `components/axerrno`、`components/kspin`、`memory/page_table_multiarch`、`components/percpu` | 三套系统都可能受影响 | +| ArceOS 内核服务:调度、HAL、驱动、网络、文件系统 | `os/arceos/modules/*`、`drivers/*`,以及相关 `memory/*` / `axplat_crates` | ArceOS,且可能波及 StarryOS / Axvisor | | ArceOS 的 feature 或应用接口 | `os/arceos/api/axfeat`、`os/arceos/ulib/axstd`、`os/arceos/ulib/axlibc` | ArceOS 应用与上层系统 | | StarryOS 的 Linux 兼容行为 | `components/starry-*`、`os/StarryOS/kernel/*` | StarryOS | -| Hypervisor、vCPU、虚拟设备、VM 管理 | `components/axvm`、`components/axvcpu`、`components/axdevice`、`components/axvisor_api`、`os/axvisor/src/*` | Axvisor | -| 平台、板级适配或 VM 启动配置 | `platforms/*`、`os/axvisor/configs/*` | 一到多个系统 | +| Hypervisor、vCPU、虚拟设备、VM 管理 | `virtualization/axvm`、`virtualization/axvcpu`、`virtualization/axdevice`、`virtualization/axvisor_api`、`os/axvisor/src/*` | Axvisor | +| 平台、板级适配或 VM 启动配置 | `components/axplat_crates/platforms/*`、`platform/*`、`os/axvisor/configs/*` | 一到多个系统 | 若不确定某个 crate 的维护者或来源仓库,可查看 `scripts/repo/repos.csv`,该文件记录了所有 subtree 组件的来源信息。 @@ -122,7 +122,7 @@ flowchart TD | `components/axerrno`、`components/kspin`、`components/ax-lazyinit` 这类基础 crate | `cargo test -p ` | `cargo xtask arceos run --package ax-helloworld --arch riscv64` | | `os/arceos/modules/*` | `cargo xtask arceos run --package ax-helloworld --arch riscv64` | 需要功能时换成 `ax-httpserver --net` 或 `ax-shell --blk` | | `components/starry-*`、`os/StarryOS/kernel/*` | `cargo xtask starry run --arch riscv64 --package starryos` | `cargo starry test qemu --target riscv64` | -| `components/axvm`、`components/axvcpu`、`components/axdevice`、`os/axvisor/src/*` | `cd os/axvisor && cargo xtask build` | 准备好 Guest 后运行 `./scripts/setup_qemu.sh arceos`,再执行 `cargo xtask qemu --build-config ... --qemu-config ... --vmconfigs ...` | +| `virtualization/axvm`、`virtualization/axvcpu`、`virtualization/axdevice`、`os/axvisor/src/*` | `cd os/axvisor && cargo xtask build` | 准备好 Guest 后运行 `./scripts/setup_qemu.sh arceos`,再执行 `cargo xtask qemu --build-config ... --qemu-config ... --vmconfigs ...` | ### 4.3 补充统一测试 @@ -271,11 +271,11 @@ my_component = { path = "components/my_component" } ### 5.5 遇到嵌套 workspace 时不要照抄 -`components/axmm_crates` 这类目录本身是独立 workspace。给这类目录加新 crate 时,需要特别注意处理方式: +历史上部分组件曾放在嵌套 workspace 中;迁移到 `memory/*`、`virtualization/*` 这类根目录分类后,给这些分类加新 crate 时,需要特别注意处理方式: - 先在它自己的 workspace 里接好 -- 再在根 `Cargo.toml` 里为具体 leaf crate 增加 patch 或 member -- 不要把整个父目录直接重新塞回根 workspace +- 再在根 `Cargo.toml` 里为具体 leaf crate 增加 dependency path;若已被 `memory/*`、`virtualization/*` 这类 glob 覆盖,就不要重复添加显式 member +- 不要把已经迁走的历史父目录重新塞回根 workspace ### 5.6 什么时候需要改 `repos.csv` diff --git a/docs/docs/introduction/overview.md b/docs/docs/introduction/overview.md index 7d9c60ba4f..573f25674c 100644 --- a/docs/docs/introduction/overview.md +++ b/docs/docs/introduction/overview.md @@ -98,7 +98,7 @@ tgoskits/ │ ├── configs/ # 板级 / VM / 测试配置 │ └── xtask/ # Axvisor 专用构建任务 ├── platforms/ # 平台适配层 -│ ├── ax-plat-dyn/ # 动态平台支持 +│ ├── axplat-dyn/ # 动态平台支持 │ ├── riscv64-qemu-virt/ # RISC-V QEMU virt 平台 │ └── x86-qemu-q35/ # x86 Q35 平台 ├── drivers/ # SoC 专用驱动(RK3588 时钟 / NPU / 电源管理) diff --git a/components/axaddrspace/.github/config.json b/memory/axaddrspace/.github/config.json similarity index 100% rename from components/axaddrspace/.github/config.json rename to memory/axaddrspace/.github/config.json diff --git a/components/arm_vcpu/.github/workflows/check.yml b/memory/axaddrspace/.github/workflows/check.yml similarity index 100% rename from components/arm_vcpu/.github/workflows/check.yml rename to memory/axaddrspace/.github/workflows/check.yml diff --git a/components/arm_vcpu/.github/workflows/deploy.yml b/memory/axaddrspace/.github/workflows/deploy.yml similarity index 100% rename from components/arm_vcpu/.github/workflows/deploy.yml rename to memory/axaddrspace/.github/workflows/deploy.yml diff --git a/components/aarch64_sysreg/.github/workflows/push.yml b/memory/axaddrspace/.github/workflows/push.yml similarity index 100% rename from components/aarch64_sysreg/.github/workflows/push.yml rename to memory/axaddrspace/.github/workflows/push.yml diff --git a/components/aarch64_sysreg/.github/workflows/release.yml b/memory/axaddrspace/.github/workflows/release.yml similarity index 100% rename from components/aarch64_sysreg/.github/workflows/release.yml rename to memory/axaddrspace/.github/workflows/release.yml diff --git a/components/arm_vcpu/.github/workflows/test.yml b/memory/axaddrspace/.github/workflows/test.yml similarity index 100% rename from components/arm_vcpu/.github/workflows/test.yml rename to memory/axaddrspace/.github/workflows/test.yml diff --git a/components/axaddrspace/.gitignore b/memory/axaddrspace/.gitignore similarity index 100% rename from components/axaddrspace/.gitignore rename to memory/axaddrspace/.gitignore diff --git a/components/axaddrspace/CHANGELOG.md b/memory/axaddrspace/CHANGELOG.md similarity index 100% rename from components/axaddrspace/CHANGELOG.md rename to memory/axaddrspace/CHANGELOG.md diff --git a/components/axaddrspace/Cargo.toml b/memory/axaddrspace/Cargo.toml similarity index 100% rename from components/axaddrspace/Cargo.toml rename to memory/axaddrspace/Cargo.toml diff --git a/components/aarch64_sysreg/LICENSE b/memory/axaddrspace/LICENSE similarity index 100% rename from components/aarch64_sysreg/LICENSE rename to memory/axaddrspace/LICENSE diff --git a/components/axaddrspace/README.md b/memory/axaddrspace/README.md similarity index 98% rename from components/axaddrspace/README.md rename to memory/axaddrspace/README.md index c5dbf19667..a639a0de54 100644 --- a/components/axaddrspace/README.md +++ b/memory/axaddrspace/README.md @@ -32,7 +32,7 @@ axaddrspace = "0.5.0" ```bash # Enter the crate directory -cd components/axaddrspace +cd memory/axaddrspace # Format code cargo fmt --all diff --git a/components/axaddrspace/README_CN.md b/memory/axaddrspace/README_CN.md similarity index 98% rename from components/axaddrspace/README_CN.md rename to memory/axaddrspace/README_CN.md index 3964163984..62f1c0b51a 100644 --- a/components/axaddrspace/README_CN.md +++ b/memory/axaddrspace/README_CN.md @@ -32,7 +32,7 @@ axaddrspace = "0.5.0" ```bash # 进入 crate 目录 -cd components/axaddrspace +cd memory/axaddrspace # 代码格式化 cargo fmt --all diff --git a/components/axaddrspace/scripts/check.sh b/memory/axaddrspace/scripts/check.sh similarity index 100% rename from components/axaddrspace/scripts/check.sh rename to memory/axaddrspace/scripts/check.sh diff --git a/components/axaddrspace/scripts/test.sh b/memory/axaddrspace/scripts/test.sh similarity index 100% rename from components/axaddrspace/scripts/test.sh rename to memory/axaddrspace/scripts/test.sh diff --git a/components/axaddrspace/src/addr.rs b/memory/axaddrspace/src/addr.rs similarity index 100% rename from components/axaddrspace/src/addr.rs rename to memory/axaddrspace/src/addr.rs diff --git a/components/axaddrspace/src/address_space/backend/alloc.rs b/memory/axaddrspace/src/address_space/backend/alloc.rs similarity index 100% rename from components/axaddrspace/src/address_space/backend/alloc.rs rename to memory/axaddrspace/src/address_space/backend/alloc.rs diff --git a/components/axaddrspace/src/address_space/backend/linear.rs b/memory/axaddrspace/src/address_space/backend/linear.rs similarity index 100% rename from components/axaddrspace/src/address_space/backend/linear.rs rename to memory/axaddrspace/src/address_space/backend/linear.rs diff --git a/components/axaddrspace/src/address_space/backend/mod.rs b/memory/axaddrspace/src/address_space/backend/mod.rs similarity index 100% rename from components/axaddrspace/src/address_space/backend/mod.rs rename to memory/axaddrspace/src/address_space/backend/mod.rs diff --git a/components/axaddrspace/src/address_space/mod.rs b/memory/axaddrspace/src/address_space/mod.rs similarity index 100% rename from components/axaddrspace/src/address_space/mod.rs rename to memory/axaddrspace/src/address_space/mod.rs diff --git a/components/axaddrspace/src/device/device_addr.rs b/memory/axaddrspace/src/device/device_addr.rs similarity index 100% rename from components/axaddrspace/src/device/device_addr.rs rename to memory/axaddrspace/src/device/device_addr.rs diff --git a/components/axaddrspace/src/device/mod.rs b/memory/axaddrspace/src/device/mod.rs similarity index 100% rename from components/axaddrspace/src/device/mod.rs rename to memory/axaddrspace/src/device/mod.rs diff --git a/components/axaddrspace/src/frame.rs b/memory/axaddrspace/src/frame.rs similarity index 100% rename from components/axaddrspace/src/frame.rs rename to memory/axaddrspace/src/frame.rs diff --git a/components/axaddrspace/src/hal.rs b/memory/axaddrspace/src/hal.rs similarity index 100% rename from components/axaddrspace/src/hal.rs rename to memory/axaddrspace/src/hal.rs diff --git a/components/axaddrspace/src/lib.rs b/memory/axaddrspace/src/lib.rs similarity index 100% rename from components/axaddrspace/src/lib.rs rename to memory/axaddrspace/src/lib.rs diff --git a/components/axaddrspace/src/memory_accessor.rs b/memory/axaddrspace/src/memory_accessor.rs similarity index 100% rename from components/axaddrspace/src/memory_accessor.rs rename to memory/axaddrspace/src/memory_accessor.rs diff --git a/components/axaddrspace/src/npt/arch/aarch64.rs b/memory/axaddrspace/src/npt/arch/aarch64.rs similarity index 100% rename from components/axaddrspace/src/npt/arch/aarch64.rs rename to memory/axaddrspace/src/npt/arch/aarch64.rs diff --git a/components/axaddrspace/src/npt/arch/loongarch64.rs b/memory/axaddrspace/src/npt/arch/loongarch64.rs similarity index 100% rename from components/axaddrspace/src/npt/arch/loongarch64.rs rename to memory/axaddrspace/src/npt/arch/loongarch64.rs diff --git a/components/axaddrspace/src/npt/arch/mod.rs b/memory/axaddrspace/src/npt/arch/mod.rs similarity index 100% rename from components/axaddrspace/src/npt/arch/mod.rs rename to memory/axaddrspace/src/npt/arch/mod.rs diff --git a/components/axaddrspace/src/npt/arch/riscv.rs b/memory/axaddrspace/src/npt/arch/riscv.rs similarity index 100% rename from components/axaddrspace/src/npt/arch/riscv.rs rename to memory/axaddrspace/src/npt/arch/riscv.rs diff --git a/components/axaddrspace/src/npt/arch/x86_64.rs b/memory/axaddrspace/src/npt/arch/x86_64.rs similarity index 100% rename from components/axaddrspace/src/npt/arch/x86_64.rs rename to memory/axaddrspace/src/npt/arch/x86_64.rs diff --git a/components/axaddrspace/src/npt/mod.rs b/memory/axaddrspace/src/npt/mod.rs similarity index 100% rename from components/axaddrspace/src/npt/mod.rs rename to memory/axaddrspace/src/npt/mod.rs diff --git a/components/axaddrspace/tests/address_space.rs b/memory/axaddrspace/tests/address_space.rs similarity index 100% rename from components/axaddrspace/tests/address_space.rs rename to memory/axaddrspace/tests/address_space.rs diff --git a/components/axaddrspace/tests/frame.rs b/memory/axaddrspace/tests/frame.rs similarity index 100% rename from components/axaddrspace/tests/frame.rs rename to memory/axaddrspace/tests/frame.rs diff --git a/components/axaddrspace/tests/memory_accessor.rs b/memory/axaddrspace/tests/memory_accessor.rs similarity index 100% rename from components/axaddrspace/tests/memory_accessor.rs rename to memory/axaddrspace/tests/memory_accessor.rs diff --git a/components/axaddrspace/tests/test_utils/mod.rs b/memory/axaddrspace/tests/test_utils/mod.rs similarity index 100% rename from components/axaddrspace/tests/test_utils/mod.rs rename to memory/axaddrspace/tests/test_utils/mod.rs diff --git a/components/axallocator/.github/workflows/ci.yml b/memory/axallocator/.github/workflows/ci.yml similarity index 100% rename from components/axallocator/.github/workflows/ci.yml rename to memory/axallocator/.github/workflows/ci.yml diff --git a/components/axallocator/.gitignore b/memory/axallocator/.gitignore similarity index 100% rename from components/axallocator/.gitignore rename to memory/axallocator/.gitignore diff --git a/components/axallocator/CHANGELOG.md b/memory/axallocator/CHANGELOG.md similarity index 100% rename from components/axallocator/CHANGELOG.md rename to memory/axallocator/CHANGELOG.md diff --git a/components/axallocator/Cargo.toml b/memory/axallocator/Cargo.toml similarity index 100% rename from components/axallocator/Cargo.toml rename to memory/axallocator/Cargo.toml diff --git a/components/arm_vcpu/LICENSE b/memory/axallocator/LICENSE similarity index 100% rename from components/arm_vcpu/LICENSE rename to memory/axallocator/LICENSE diff --git a/components/axallocator/README.md b/memory/axallocator/README.md similarity index 98% rename from components/axallocator/README.md rename to memory/axallocator/README.md index d1f2096fe3..3163ee1ba6 100644 --- a/components/axallocator/README.md +++ b/memory/axallocator/README.md @@ -35,7 +35,7 @@ ax-allocator = "0.4.0" ```bash # Enter the crate directory -cd components/axallocator +cd memory/axallocator # Format code cargo fmt --all diff --git a/components/axallocator/README_CN.md b/memory/axallocator/README_CN.md similarity index 98% rename from components/axallocator/README_CN.md rename to memory/axallocator/README_CN.md index 3a98cc3173..fe1c603160 100644 --- a/components/axallocator/README_CN.md +++ b/memory/axallocator/README_CN.md @@ -35,7 +35,7 @@ ax-allocator = "0.4.0" ```bash # 进入 crate 目录 -cd components/axallocator +cd memory/axallocator # 代码格式化 cargo fmt --all diff --git a/components/axallocator/benches/collections.rs b/memory/axallocator/benches/collections.rs similarity index 100% rename from components/axallocator/benches/collections.rs rename to memory/axallocator/benches/collections.rs diff --git a/components/axallocator/benches/utils/mod.rs b/memory/axallocator/benches/utils/mod.rs similarity index 100% rename from components/axallocator/benches/utils/mod.rs rename to memory/axallocator/benches/utils/mod.rs diff --git a/components/axallocator/src/bitmap.rs b/memory/axallocator/src/bitmap.rs similarity index 100% rename from components/axallocator/src/bitmap.rs rename to memory/axallocator/src/bitmap.rs diff --git a/components/axallocator/src/buddy.rs b/memory/axallocator/src/buddy.rs similarity index 100% rename from components/axallocator/src/buddy.rs rename to memory/axallocator/src/buddy.rs diff --git a/components/axallocator/src/lib.rs b/memory/axallocator/src/lib.rs similarity index 100% rename from components/axallocator/src/lib.rs rename to memory/axallocator/src/lib.rs diff --git a/components/axallocator/src/slab.rs b/memory/axallocator/src/slab.rs similarity index 100% rename from components/axallocator/src/slab.rs rename to memory/axallocator/src/slab.rs diff --git a/components/axallocator/src/tlsf.rs b/memory/axallocator/src/tlsf.rs similarity index 100% rename from components/axallocator/src/tlsf.rs rename to memory/axallocator/src/tlsf.rs diff --git a/components/axallocator/tests/allocator.rs b/memory/axallocator/tests/allocator.rs similarity index 100% rename from components/axallocator/tests/allocator.rs rename to memory/axallocator/tests/allocator.rs diff --git a/components/bitmap-allocator/.github/workflows/main.yml b/memory/bitmap-allocator/.github/workflows/main.yml similarity index 100% rename from components/bitmap-allocator/.github/workflows/main.yml rename to memory/bitmap-allocator/.github/workflows/main.yml diff --git a/components/bitmap-allocator/.gitignore b/memory/bitmap-allocator/.gitignore similarity index 100% rename from components/bitmap-allocator/.gitignore rename to memory/bitmap-allocator/.gitignore diff --git a/components/bitmap-allocator/CHANGELOG.md b/memory/bitmap-allocator/CHANGELOG.md similarity index 100% rename from components/bitmap-allocator/CHANGELOG.md rename to memory/bitmap-allocator/CHANGELOG.md diff --git a/components/bitmap-allocator/Cargo.toml b/memory/bitmap-allocator/Cargo.toml similarity index 100% rename from components/bitmap-allocator/Cargo.toml rename to memory/bitmap-allocator/Cargo.toml diff --git a/components/arm_vgic/LICENSE b/memory/bitmap-allocator/LICENSE similarity index 100% rename from components/arm_vgic/LICENSE rename to memory/bitmap-allocator/LICENSE diff --git a/components/bitmap-allocator/README.md b/memory/bitmap-allocator/README.md similarity index 98% rename from components/bitmap-allocator/README.md rename to memory/bitmap-allocator/README.md index 3b6180ad8c..6e9d1c3507 100644 --- a/components/bitmap-allocator/README.md +++ b/memory/bitmap-allocator/README.md @@ -32,7 +32,7 @@ bitmap-allocator = "0.4.1" ```bash # Enter the crate directory -cd components/bitmap-allocator +cd memory/bitmap-allocator # Format code cargo fmt --all diff --git a/components/bitmap-allocator/README_CN.md b/memory/bitmap-allocator/README_CN.md similarity index 98% rename from components/bitmap-allocator/README_CN.md rename to memory/bitmap-allocator/README_CN.md index 53ed569d3b..3d5e394442 100644 --- a/components/bitmap-allocator/README_CN.md +++ b/memory/bitmap-allocator/README_CN.md @@ -32,7 +32,7 @@ bitmap-allocator = "0.4.1" ```bash # 进入 crate 目录 -cd components/bitmap-allocator +cd memory/bitmap-allocator # 代码格式化 cargo fmt --all diff --git a/components/bitmap-allocator/src/lib.rs b/memory/bitmap-allocator/src/lib.rs similarity index 100% rename from components/bitmap-allocator/src/lib.rs rename to memory/bitmap-allocator/src/lib.rs diff --git a/components/buddy-slab-allocator/.github/workflows/check.yml b/memory/buddy-slab-allocator/.github/workflows/check.yml similarity index 100% rename from components/buddy-slab-allocator/.github/workflows/check.yml rename to memory/buddy-slab-allocator/.github/workflows/check.yml diff --git a/components/buddy-slab-allocator/.github/workflows/ci.yml b/memory/buddy-slab-allocator/.github/workflows/ci.yml similarity index 100% rename from components/buddy-slab-allocator/.github/workflows/ci.yml rename to memory/buddy-slab-allocator/.github/workflows/ci.yml diff --git a/components/buddy-slab-allocator/.github/workflows/release-plz.yml b/memory/buddy-slab-allocator/.github/workflows/release-plz.yml similarity index 100% rename from components/buddy-slab-allocator/.github/workflows/release-plz.yml rename to memory/buddy-slab-allocator/.github/workflows/release-plz.yml diff --git a/components/buddy-slab-allocator/.github/workflows/test.yml b/memory/buddy-slab-allocator/.github/workflows/test.yml similarity index 100% rename from components/buddy-slab-allocator/.github/workflows/test.yml rename to memory/buddy-slab-allocator/.github/workflows/test.yml diff --git a/components/buddy-slab-allocator/.gitignore b/memory/buddy-slab-allocator/.gitignore similarity index 100% rename from components/buddy-slab-allocator/.gitignore rename to memory/buddy-slab-allocator/.gitignore diff --git a/components/buddy-slab-allocator/CHANGELOG.md b/memory/buddy-slab-allocator/CHANGELOG.md similarity index 100% rename from components/buddy-slab-allocator/CHANGELOG.md rename to memory/buddy-slab-allocator/CHANGELOG.md diff --git a/components/buddy-slab-allocator/Cargo.toml b/memory/buddy-slab-allocator/Cargo.toml similarity index 100% rename from components/buddy-slab-allocator/Cargo.toml rename to memory/buddy-slab-allocator/Cargo.toml diff --git a/components/buddy-slab-allocator/LICENSE.Apache2 b/memory/buddy-slab-allocator/LICENSE.Apache2 similarity index 100% rename from components/buddy-slab-allocator/LICENSE.Apache2 rename to memory/buddy-slab-allocator/LICENSE.Apache2 diff --git a/components/buddy-slab-allocator/README.md b/memory/buddy-slab-allocator/README.md similarity index 100% rename from components/buddy-slab-allocator/README.md rename to memory/buddy-slab-allocator/README.md diff --git a/components/buddy-slab-allocator/README_CN.md b/memory/buddy-slab-allocator/README_CN.md similarity index 100% rename from components/buddy-slab-allocator/README_CN.md rename to memory/buddy-slab-allocator/README_CN.md diff --git a/components/buddy-slab-allocator/benches/README_CN.md b/memory/buddy-slab-allocator/benches/README_CN.md similarity index 100% rename from components/buddy-slab-allocator/benches/README_CN.md rename to memory/buddy-slab-allocator/benches/README_CN.md diff --git a/components/buddy-slab-allocator/benches/buddy_allocator.rs b/memory/buddy-slab-allocator/benches/buddy_allocator.rs similarity index 100% rename from components/buddy-slab-allocator/benches/buddy_allocator.rs rename to memory/buddy-slab-allocator/benches/buddy_allocator.rs diff --git a/components/buddy-slab-allocator/benches/common.rs b/memory/buddy-slab-allocator/benches/common.rs similarity index 100% rename from components/buddy-slab-allocator/benches/common.rs rename to memory/buddy-slab-allocator/benches/common.rs diff --git a/components/buddy-slab-allocator/benches/global_allocator.rs b/memory/buddy-slab-allocator/benches/global_allocator.rs similarity index 100% rename from components/buddy-slab-allocator/benches/global_allocator.rs rename to memory/buddy-slab-allocator/benches/global_allocator.rs diff --git a/components/buddy-slab-allocator/benches/slab_allocator.rs b/memory/buddy-slab-allocator/benches/slab_allocator.rs similarity index 100% rename from components/buddy-slab-allocator/benches/slab_allocator.rs rename to memory/buddy-slab-allocator/benches/slab_allocator.rs diff --git a/components/buddy-slab-allocator/docs/design.md b/memory/buddy-slab-allocator/docs/design.md similarity index 100% rename from components/buddy-slab-allocator/docs/design.md rename to memory/buddy-slab-allocator/docs/design.md diff --git a/components/buddy-slab-allocator/rust-toolchain.toml b/memory/buddy-slab-allocator/rust-toolchain.toml similarity index 100% rename from components/buddy-slab-allocator/rust-toolchain.toml rename to memory/buddy-slab-allocator/rust-toolchain.toml diff --git a/components/buddy-slab-allocator/src/buddy/mod.rs b/memory/buddy-slab-allocator/src/buddy/mod.rs similarity index 100% rename from components/buddy-slab-allocator/src/buddy/mod.rs rename to memory/buddy-slab-allocator/src/buddy/mod.rs diff --git a/components/buddy-slab-allocator/src/buddy/page_meta.rs b/memory/buddy-slab-allocator/src/buddy/page_meta.rs similarity index 100% rename from components/buddy-slab-allocator/src/buddy/page_meta.rs rename to memory/buddy-slab-allocator/src/buddy/page_meta.rs diff --git a/components/buddy-slab-allocator/src/error.rs b/memory/buddy-slab-allocator/src/error.rs similarity index 100% rename from components/buddy-slab-allocator/src/error.rs rename to memory/buddy-slab-allocator/src/error.rs diff --git a/components/buddy-slab-allocator/src/global.rs b/memory/buddy-slab-allocator/src/global.rs similarity index 100% rename from components/buddy-slab-allocator/src/global.rs rename to memory/buddy-slab-allocator/src/global.rs diff --git a/components/buddy-slab-allocator/src/lib.rs b/memory/buddy-slab-allocator/src/lib.rs similarity index 100% rename from components/buddy-slab-allocator/src/lib.rs rename to memory/buddy-slab-allocator/src/lib.rs diff --git a/components/buddy-slab-allocator/src/slab/cache.rs b/memory/buddy-slab-allocator/src/slab/cache.rs similarity index 100% rename from components/buddy-slab-allocator/src/slab/cache.rs rename to memory/buddy-slab-allocator/src/slab/cache.rs diff --git a/components/buddy-slab-allocator/src/slab/mod.rs b/memory/buddy-slab-allocator/src/slab/mod.rs similarity index 100% rename from components/buddy-slab-allocator/src/slab/mod.rs rename to memory/buddy-slab-allocator/src/slab/mod.rs diff --git a/components/buddy-slab-allocator/src/slab/page.rs b/memory/buddy-slab-allocator/src/slab/page.rs similarity index 100% rename from components/buddy-slab-allocator/src/slab/page.rs rename to memory/buddy-slab-allocator/src/slab/page.rs diff --git a/components/buddy-slab-allocator/src/slab/size_class.rs b/memory/buddy-slab-allocator/src/slab/size_class.rs similarity index 100% rename from components/buddy-slab-allocator/src/slab/size_class.rs rename to memory/buddy-slab-allocator/src/slab/size_class.rs diff --git a/components/buddy-slab-allocator/tests/README.md b/memory/buddy-slab-allocator/tests/README.md similarity index 100% rename from components/buddy-slab-allocator/tests/README.md rename to memory/buddy-slab-allocator/tests/README.md diff --git a/components/buddy-slab-allocator/tests/common/mod.rs b/memory/buddy-slab-allocator/tests/common/mod.rs similarity index 100% rename from components/buddy-slab-allocator/tests/common/mod.rs rename to memory/buddy-slab-allocator/tests/common/mod.rs diff --git a/components/buddy-slab-allocator/tests/dma32_pages_test.rs b/memory/buddy-slab-allocator/tests/dma32_pages_test.rs similarity index 100% rename from components/buddy-slab-allocator/tests/dma32_pages_test.rs rename to memory/buddy-slab-allocator/tests/dma32_pages_test.rs diff --git a/components/buddy-slab-allocator/tests/integration_test.rs b/memory/buddy-slab-allocator/tests/integration_test.rs similarity index 100% rename from components/buddy-slab-allocator/tests/integration_test.rs rename to memory/buddy-slab-allocator/tests/integration_test.rs diff --git a/components/buddy-slab-allocator/tests/stress_test.rs b/memory/buddy-slab-allocator/tests/stress_test.rs similarity index 100% rename from components/buddy-slab-allocator/tests/stress_test.rs rename to memory/buddy-slab-allocator/tests/stress_test.rs diff --git a/components/dma-api/CHANGELOG.md b/memory/dma-api/CHANGELOG.md similarity index 100% rename from components/dma-api/CHANGELOG.md rename to memory/dma-api/CHANGELOG.md diff --git a/components/dma-api/Cargo.toml b/memory/dma-api/Cargo.toml similarity index 100% rename from components/dma-api/Cargo.toml rename to memory/dma-api/Cargo.toml diff --git a/components/dma-api/README.md b/memory/dma-api/README.md similarity index 100% rename from components/dma-api/README.md rename to memory/dma-api/README.md diff --git a/components/dma-api/src/array.rs b/memory/dma-api/src/array.rs similarity index 100% rename from components/dma-api/src/array.rs rename to memory/dma-api/src/array.rs diff --git a/components/dma-api/src/common.rs b/memory/dma-api/src/common.rs similarity index 100% rename from components/dma-api/src/common.rs rename to memory/dma-api/src/common.rs diff --git a/components/dma-api/src/dbox.rs b/memory/dma-api/src/dbox.rs similarity index 100% rename from components/dma-api/src/dbox.rs rename to memory/dma-api/src/dbox.rs diff --git a/components/dma-api/src/def.rs b/memory/dma-api/src/def.rs similarity index 100% rename from components/dma-api/src/def.rs rename to memory/dma-api/src/def.rs diff --git a/components/dma-api/src/lib.rs b/memory/dma-api/src/lib.rs similarity index 100% rename from components/dma-api/src/lib.rs rename to memory/dma-api/src/lib.rs diff --git a/components/dma-api/src/op/aarch64.rs b/memory/dma-api/src/op/aarch64.rs similarity index 100% rename from components/dma-api/src/op/aarch64.rs rename to memory/dma-api/src/op/aarch64.rs diff --git a/components/dma-api/src/op/mod.rs b/memory/dma-api/src/op/mod.rs similarity index 100% rename from components/dma-api/src/op/mod.rs rename to memory/dma-api/src/op/mod.rs diff --git a/components/dma-api/src/op/nop.rs b/memory/dma-api/src/op/nop.rs similarity index 100% rename from components/dma-api/src/op/nop.rs rename to memory/dma-api/src/op/nop.rs diff --git a/components/dma-api/src/pool.rs b/memory/dma-api/src/pool.rs similarity index 100% rename from components/dma-api/src/pool.rs rename to memory/dma-api/src/pool.rs diff --git a/components/dma-api/src/streaming.rs b/memory/dma-api/src/streaming.rs similarity index 100% rename from components/dma-api/src/streaming.rs rename to memory/dma-api/src/streaming.rs diff --git a/components/dma-api/tests/test.rs b/memory/dma-api/tests/test.rs similarity index 100% rename from components/dma-api/tests/test.rs rename to memory/dma-api/tests/test.rs diff --git a/components/dma-api/tests/test_helpers.rs b/memory/dma-api/tests/test_helpers.rs similarity index 100% rename from components/dma-api/tests/test_helpers.rs rename to memory/dma-api/tests/test_helpers.rs diff --git a/components/axmm_crates/memory_addr/Cargo.toml b/memory/memory_addr/Cargo.toml similarity index 100% rename from components/axmm_crates/memory_addr/Cargo.toml rename to memory/memory_addr/Cargo.toml diff --git a/components/axmm_crates/memory_addr/README.md b/memory/memory_addr/README.md similarity index 97% rename from components/axmm_crates/memory_addr/README.md rename to memory/memory_addr/README.md index fb1496af2e..1e78264406 100644 --- a/components/axmm_crates/memory_addr/README.md +++ b/memory/memory_addr/README.md @@ -35,7 +35,7 @@ ax-memory-addr = "0.6.1" ```bash # Enter the crate directory -cd components/axmm_crates/memory_addr +cd memory/memory_addr # Format code cargo fmt --all diff --git a/components/axmm_crates/memory_addr/README_CN.md b/memory/memory_addr/README_CN.md similarity index 97% rename from components/axmm_crates/memory_addr/README_CN.md rename to memory/memory_addr/README_CN.md index 41a02d6b96..9f87360b13 100644 --- a/components/axmm_crates/memory_addr/README_CN.md +++ b/memory/memory_addr/README_CN.md @@ -35,7 +35,7 @@ ax-memory-addr = "0.6.1" ```bash # 进入 crate 目录 -cd components/axmm_crates/memory_addr +cd memory/memory_addr # 代码格式化 cargo fmt --all diff --git a/components/axmm_crates/memory_addr/src/addr.rs b/memory/memory_addr/src/addr.rs similarity index 100% rename from components/axmm_crates/memory_addr/src/addr.rs rename to memory/memory_addr/src/addr.rs diff --git a/components/axmm_crates/memory_addr/src/iter.rs b/memory/memory_addr/src/iter.rs similarity index 100% rename from components/axmm_crates/memory_addr/src/iter.rs rename to memory/memory_addr/src/iter.rs diff --git a/components/axmm_crates/memory_addr/src/lib.rs b/memory/memory_addr/src/lib.rs similarity index 100% rename from components/axmm_crates/memory_addr/src/lib.rs rename to memory/memory_addr/src/lib.rs diff --git a/components/axmm_crates/memory_addr/src/range.rs b/memory/memory_addr/src/range.rs similarity index 100% rename from components/axmm_crates/memory_addr/src/range.rs rename to memory/memory_addr/src/range.rs diff --git a/components/axmm_crates/memory_set/CHANGELOG.md b/memory/memory_set/CHANGELOG.md similarity index 100% rename from components/axmm_crates/memory_set/CHANGELOG.md rename to memory/memory_set/CHANGELOG.md diff --git a/components/axmm_crates/memory_set/Cargo.toml b/memory/memory_set/Cargo.toml similarity index 100% rename from components/axmm_crates/memory_set/Cargo.toml rename to memory/memory_set/Cargo.toml diff --git a/components/axmm_crates/memory_set/README.md b/memory/memory_set/README.md similarity index 98% rename from components/axmm_crates/memory_set/README.md rename to memory/memory_set/README.md index 6547b04b9c..05cdea922e 100644 --- a/components/axmm_crates/memory_set/README.md +++ b/memory/memory_set/README.md @@ -35,7 +35,7 @@ ax-memory-set = "0.6.1" ```bash # Enter the crate directory -cd components/axmm_crates/memory_set +cd memory/memory_set # Format code cargo fmt --all diff --git a/components/axmm_crates/memory_set/README_CN.md b/memory/memory_set/README_CN.md similarity index 97% rename from components/axmm_crates/memory_set/README_CN.md rename to memory/memory_set/README_CN.md index 1e962c6c04..175adfb979 100644 --- a/components/axmm_crates/memory_set/README_CN.md +++ b/memory/memory_set/README_CN.md @@ -35,7 +35,7 @@ ax-memory-set = "0.6.1" ```bash # 进入 crate 目录 -cd components/axmm_crates/memory_set +cd memory/memory_set # 代码格式化 cargo fmt --all diff --git a/components/axmm_crates/memory_set/src/area.rs b/memory/memory_set/src/area.rs similarity index 100% rename from components/axmm_crates/memory_set/src/area.rs rename to memory/memory_set/src/area.rs diff --git a/components/axmm_crates/memory_set/src/backend.rs b/memory/memory_set/src/backend.rs similarity index 100% rename from components/axmm_crates/memory_set/src/backend.rs rename to memory/memory_set/src/backend.rs diff --git a/components/axmm_crates/memory_set/src/lib.rs b/memory/memory_set/src/lib.rs similarity index 100% rename from components/axmm_crates/memory_set/src/lib.rs rename to memory/memory_set/src/lib.rs diff --git a/components/axmm_crates/memory_set/src/set.rs b/memory/memory_set/src/set.rs similarity index 100% rename from components/axmm_crates/memory_set/src/set.rs rename to memory/memory_set/src/set.rs diff --git a/components/axmm_crates/memory_set/src/tests.rs b/memory/memory_set/src/tests.rs similarity index 100% rename from components/axmm_crates/memory_set/src/tests.rs rename to memory/memory_set/src/tests.rs diff --git a/components/mmio-api/CHANGELOG.md b/memory/mmio-api/CHANGELOG.md similarity index 100% rename from components/mmio-api/CHANGELOG.md rename to memory/mmio-api/CHANGELOG.md diff --git a/components/mmio-api/Cargo.toml b/memory/mmio-api/Cargo.toml similarity index 100% rename from components/mmio-api/Cargo.toml rename to memory/mmio-api/Cargo.toml diff --git a/components/mmio-api/src/lib.rs b/memory/mmio-api/src/lib.rs similarity index 100% rename from components/mmio-api/src/lib.rs rename to memory/mmio-api/src/lib.rs diff --git a/components/page-table-generic/CHANGELOG.md b/memory/page-table-generic/CHANGELOG.md similarity index 100% rename from components/page-table-generic/CHANGELOG.md rename to memory/page-table-generic/CHANGELOG.md diff --git a/components/page-table-generic/Cargo.toml b/memory/page-table-generic/Cargo.toml similarity index 100% rename from components/page-table-generic/Cargo.toml rename to memory/page-table-generic/Cargo.toml diff --git a/components/page-table-generic/src/def.rs b/memory/page-table-generic/src/def.rs similarity index 100% rename from components/page-table-generic/src/def.rs rename to memory/page-table-generic/src/def.rs diff --git a/components/page-table-generic/src/frame.rs b/memory/page-table-generic/src/frame.rs similarity index 100% rename from components/page-table-generic/src/frame.rs rename to memory/page-table-generic/src/frame.rs diff --git a/components/page-table-generic/src/lib.rs b/memory/page-table-generic/src/lib.rs similarity index 100% rename from components/page-table-generic/src/lib.rs rename to memory/page-table-generic/src/lib.rs diff --git a/components/page-table-generic/src/map.rs b/memory/page-table-generic/src/map.rs similarity index 100% rename from components/page-table-generic/src/map.rs rename to memory/page-table-generic/src/map.rs diff --git a/components/page-table-generic/src/table.rs b/memory/page-table-generic/src/table.rs similarity index 100% rename from components/page-table-generic/src/table.rs rename to memory/page-table-generic/src/table.rs diff --git a/components/page-table-generic/src/walk.rs b/memory/page-table-generic/src/walk.rs similarity index 100% rename from components/page-table-generic/src/walk.rs rename to memory/page-table-generic/src/walk.rs diff --git a/components/page-table-generic/tests/bugfixes.rs b/memory/page-table-generic/tests/bugfixes.rs similarity index 100% rename from components/page-table-generic/tests/bugfixes.rs rename to memory/page-table-generic/tests/bugfixes.rs diff --git a/components/page-table-generic/tests/drop.rs b/memory/page-table-generic/tests/drop.rs similarity index 100% rename from components/page-table-generic/tests/drop.rs rename to memory/page-table-generic/tests/drop.rs diff --git a/components/page-table-generic/tests/flags.rs b/memory/page-table-generic/tests/flags.rs similarity index 100% rename from components/page-table-generic/tests/flags.rs rename to memory/page-table-generic/tests/flags.rs diff --git a/components/page-table-generic/tests/loongarch64_simple.rs b/memory/page-table-generic/tests/loongarch64_simple.rs similarity index 98% rename from components/page-table-generic/tests/loongarch64_simple.rs rename to memory/page-table-generic/tests/loongarch64_simple.rs index 8b0b0daa64..f877791cbc 100644 --- a/components/page-table-generic/tests/loongarch64_simple.rs +++ b/memory/page-table-generic/tests/loongarch64_simple.rs @@ -12,7 +12,7 @@ mod mocks; #[derive(Debug, Clone, Copy)] pub struct LoongArch64Generic; -impl TableGeneric for LoongArch64Generic { +impl TableMeta for LoongArch64Generic { type P = mocks::PteImpl; const PAGE_SIZE: usize = 0x1000; // 4KB diff --git a/components/page-table-generic/tests/loongarch64_simulation.rs b/memory/page-table-generic/tests/loongarch64_simulation.rs similarity index 99% rename from components/page-table-generic/tests/loongarch64_simulation.rs rename to memory/page-table-generic/tests/loongarch64_simulation.rs index 648ac12008..2b390c215d 100644 --- a/components/page-table-generic/tests/loongarch64_simulation.rs +++ b/memory/page-table-generic/tests/loongarch64_simulation.rs @@ -20,7 +20,7 @@ use mocks::*; #[derive(Debug, Clone, Copy)] pub struct LoongArch64Generic; -impl TableGeneric for LoongArch64Generic { +impl TableMeta for LoongArch64Generic { type P = PteImpl; const PAGE_SIZE: usize = 0x1000; // 4KB diff --git a/components/page-table-generic/tests/map.rs b/memory/page-table-generic/tests/map.rs similarity index 98% rename from components/page-table-generic/tests/map.rs rename to memory/page-table-generic/tests/map.rs index c9952aaa72..d47a81d951 100644 --- a/components/page-table-generic/tests/map.rs +++ b/memory/page-table-generic/tests/map.rs @@ -28,7 +28,7 @@ fn test_pte() { assert_eq!(want.to_config(false).paddr, addr); } -fn test_high( +fn test_high( pte: PteConfig, alloc: A, test_vaddr: VirtAddr, @@ -219,7 +219,7 @@ fn test_new_l5() { ); } -fn test_huge(pte: PteConfig, alloc: A) { +fn test_huge(pte: PteConfig, alloc: A) { let mut pg = PageTable::::new(alloc).unwrap(); pg.map(&MapConfig { @@ -301,7 +301,7 @@ fn test_huge(pte: PteConfig, alloc: A) { assert!(has_full_coverage, "映射应该覆盖到地址{:#x}", end_vaddr); } -fn test_huge_not_align(pte: PteConfig, alloc: A) { +fn test_huge_not_align(pte: PteConfig, alloc: A) { let mut pg = PageTable::::new(alloc).unwrap(); let addr = 2 * MB - 0x1000usize; @@ -404,7 +404,7 @@ fn test_huge_not_align(pte: PteConfig, alloc ); } -fn test_high_huge_not_align(pte: PteConfig, alloc: A) { +fn test_high_huge_not_align(pte: PteConfig, alloc: A) { let mut pg = PageTable::::new(alloc).unwrap(); // 注意:在48位虚拟地址空间中,0xffffffff80000000 会被截断为 0x0000ffff80000000 @@ -663,7 +663,7 @@ fn test_huge_not_align_l4() { test_huge_not_align::(PteImpl::user_mode_config(), Fram4k); } -fn test_huge_big(pte: PteConfig, alloc: A) { +fn test_huge_big(pte: PteConfig, alloc: A) { let mut pg = PageTable::::new(alloc).unwrap(); pg.map(&MapConfig { @@ -796,7 +796,7 @@ fn test_v_p_not_align_l3() { test_v_p_not_align::(PteImpl::user_mode_config(), Fram4k); } -fn test_v_p_not_align(pte: PteConfig, alloc: A) { +fn test_v_p_not_align(pte: PteConfig, alloc: A) { let _ = env_logger::builder() .is_test(true) .filter_level(log::LevelFilter::Trace) diff --git a/components/page-table-generic/tests/mocks/mod.rs b/memory/page-table-generic/tests/mocks/mod.rs similarity index 100% rename from components/page-table-generic/tests/mocks/mod.rs rename to memory/page-table-generic/tests/mocks/mod.rs diff --git a/components/page-table-generic/tests/translate.rs b/memory/page-table-generic/tests/translate.rs similarity index 100% rename from components/page-table-generic/tests/translate.rs rename to memory/page-table-generic/tests/translate.rs diff --git a/components/page_table_multiarch/page_table_entry/Cargo.toml b/memory/page_table_entry/Cargo.toml similarity index 100% rename from components/page_table_multiarch/page_table_entry/Cargo.toml rename to memory/page_table_entry/Cargo.toml diff --git a/components/page_table_multiarch/page_table_entry/README.md b/memory/page_table_entry/README.md similarity index 97% rename from components/page_table_multiarch/page_table_entry/README.md rename to memory/page_table_entry/README.md index b8902b16de..412b21d711 100644 --- a/components/page_table_multiarch/page_table_entry/README.md +++ b/memory/page_table_entry/README.md @@ -35,7 +35,7 @@ ax-page-table-entry = "0.8.1" ```bash # Enter the crate directory -cd components/page_table_multiarch/page_table_entry +cd memory/page_table_entry # Format code cargo fmt --all diff --git a/components/page_table_multiarch/page_table_entry/README_CN.md b/memory/page_table_entry/README_CN.md similarity index 97% rename from components/page_table_multiarch/page_table_entry/README_CN.md rename to memory/page_table_entry/README_CN.md index 473aa09dc4..3807c05895 100644 --- a/components/page_table_multiarch/page_table_entry/README_CN.md +++ b/memory/page_table_entry/README_CN.md @@ -35,7 +35,7 @@ ax-page-table-entry = "0.8.1" ```bash # 进入 crate 目录 -cd components/page_table_multiarch/page_table_entry +cd memory/page_table_entry # 代码格式化 cargo fmt --all diff --git a/components/page_table_multiarch/page_table_entry/src/arch/aarch64.rs b/memory/page_table_entry/src/arch/aarch64.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/arch/aarch64.rs rename to memory/page_table_entry/src/arch/aarch64.rs diff --git a/components/page_table_multiarch/page_table_entry/src/arch/arm.rs b/memory/page_table_entry/src/arch/arm.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/arch/arm.rs rename to memory/page_table_entry/src/arch/arm.rs diff --git a/components/page_table_multiarch/page_table_entry/src/arch/loongarch64.rs b/memory/page_table_entry/src/arch/loongarch64.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/arch/loongarch64.rs rename to memory/page_table_entry/src/arch/loongarch64.rs diff --git a/components/page_table_multiarch/page_table_entry/src/arch/mod.rs b/memory/page_table_entry/src/arch/mod.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/arch/mod.rs rename to memory/page_table_entry/src/arch/mod.rs diff --git a/components/page_table_multiarch/page_table_entry/src/arch/riscv.rs b/memory/page_table_entry/src/arch/riscv.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/arch/riscv.rs rename to memory/page_table_entry/src/arch/riscv.rs diff --git a/components/page_table_multiarch/page_table_entry/src/arch/x86_64.rs b/memory/page_table_entry/src/arch/x86_64.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/arch/x86_64.rs rename to memory/page_table_entry/src/arch/x86_64.rs diff --git a/components/page_table_multiarch/page_table_entry/src/lib.rs b/memory/page_table_entry/src/lib.rs similarity index 100% rename from components/page_table_multiarch/page_table_entry/src/lib.rs rename to memory/page_table_entry/src/lib.rs diff --git a/components/page_table_multiarch/page_table_multiarch/CHANGELOG.md b/memory/page_table_multiarch/CHANGELOG.md similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/CHANGELOG.md rename to memory/page_table_multiarch/CHANGELOG.md diff --git a/components/page_table_multiarch/page_table_multiarch/Cargo.toml b/memory/page_table_multiarch/Cargo.toml similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/Cargo.toml rename to memory/page_table_multiarch/Cargo.toml diff --git a/components/page_table_multiarch/page_table_multiarch/README.md b/memory/page_table_multiarch/README.md similarity index 97% rename from components/page_table_multiarch/page_table_multiarch/README.md rename to memory/page_table_multiarch/README.md index ba4c4e13ff..90892f04ad 100644 --- a/components/page_table_multiarch/page_table_multiarch/README.md +++ b/memory/page_table_multiarch/README.md @@ -35,7 +35,7 @@ ax-page-table-multiarch = "0.8.1" ```bash # Enter the crate directory -cd components/page_table_multiarch/page_table_multiarch +cd memory/page_table_multiarch # Format code cargo fmt --all diff --git a/components/page_table_multiarch/page_table_multiarch/README_CN.md b/memory/page_table_multiarch/README_CN.md similarity index 97% rename from components/page_table_multiarch/page_table_multiarch/README_CN.md rename to memory/page_table_multiarch/README_CN.md index 2e2d6918e9..5c9c30e9de 100644 --- a/components/page_table_multiarch/page_table_multiarch/README_CN.md +++ b/memory/page_table_multiarch/README_CN.md @@ -35,7 +35,7 @@ ax-page-table-multiarch = "0.8.1" ```bash # 进入 crate 目录 -cd components/page_table_multiarch/page_table_multiarch +cd memory/page_table_multiarch # 代码格式化 cargo fmt --all diff --git a/components/page_table_multiarch/page_table_multiarch/src/arch/aarch64.rs b/memory/page_table_multiarch/src/arch/aarch64.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/arch/aarch64.rs rename to memory/page_table_multiarch/src/arch/aarch64.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/arch/arm.rs b/memory/page_table_multiarch/src/arch/arm.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/arch/arm.rs rename to memory/page_table_multiarch/src/arch/arm.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/arch/loongarch64.rs b/memory/page_table_multiarch/src/arch/loongarch64.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/arch/loongarch64.rs rename to memory/page_table_multiarch/src/arch/loongarch64.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/arch/mod.rs b/memory/page_table_multiarch/src/arch/mod.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/arch/mod.rs rename to memory/page_table_multiarch/src/arch/mod.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/arch/riscv.rs b/memory/page_table_multiarch/src/arch/riscv.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/arch/riscv.rs rename to memory/page_table_multiarch/src/arch/riscv.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/arch/x86_64.rs b/memory/page_table_multiarch/src/arch/x86_64.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/arch/x86_64.rs rename to memory/page_table_multiarch/src/arch/x86_64.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/bits32.rs b/memory/page_table_multiarch/src/bits32.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/bits32.rs rename to memory/page_table_multiarch/src/bits32.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/bits64.rs b/memory/page_table_multiarch/src/bits64.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/bits64.rs rename to memory/page_table_multiarch/src/bits64.rs diff --git a/components/page_table_multiarch/page_table_multiarch/src/lib.rs b/memory/page_table_multiarch/src/lib.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/src/lib.rs rename to memory/page_table_multiarch/src/lib.rs diff --git a/components/page_table_multiarch/page_table_multiarch/tests/alloc_tests.rs b/memory/page_table_multiarch/tests/alloc_tests.rs similarity index 100% rename from components/page_table_multiarch/page_table_multiarch/tests/alloc_tests.rs rename to memory/page_table_multiarch/tests/alloc_tests.rs diff --git a/components/range-alloc-arceos/.github/config.json b/memory/range-alloc-arceos/.github/config.json similarity index 100% rename from components/range-alloc-arceos/.github/config.json rename to memory/range-alloc-arceos/.github/config.json diff --git a/components/arm_vgic/.github/workflows/check.yml b/memory/range-alloc-arceos/.github/workflows/check.yml similarity index 100% rename from components/arm_vgic/.github/workflows/check.yml rename to memory/range-alloc-arceos/.github/workflows/check.yml diff --git a/components/axvm/.github/workflows/deploy.yml b/memory/range-alloc-arceos/.github/workflows/deploy.yml similarity index 100% rename from components/axvm/.github/workflows/deploy.yml rename to memory/range-alloc-arceos/.github/workflows/deploy.yml diff --git a/components/arm_vgic/.github/workflows/push.yml b/memory/range-alloc-arceos/.github/workflows/push.yml similarity index 100% rename from components/arm_vgic/.github/workflows/push.yml rename to memory/range-alloc-arceos/.github/workflows/push.yml diff --git a/components/arm_vgic/.github/workflows/release.yml b/memory/range-alloc-arceos/.github/workflows/release.yml similarity index 100% rename from components/arm_vgic/.github/workflows/release.yml rename to memory/range-alloc-arceos/.github/workflows/release.yml diff --git a/components/arm_vgic/.github/workflows/test.yml b/memory/range-alloc-arceos/.github/workflows/test.yml similarity index 100% rename from components/arm_vgic/.github/workflows/test.yml rename to memory/range-alloc-arceos/.github/workflows/test.yml diff --git a/components/range-alloc-arceos/.gitignore b/memory/range-alloc-arceos/.gitignore similarity index 100% rename from components/range-alloc-arceos/.gitignore rename to memory/range-alloc-arceos/.gitignore diff --git a/components/range-alloc-arceos/CHANGELOG.md b/memory/range-alloc-arceos/CHANGELOG.md similarity index 100% rename from components/range-alloc-arceos/CHANGELOG.md rename to memory/range-alloc-arceos/CHANGELOG.md diff --git a/components/range-alloc-arceos/Cargo.toml b/memory/range-alloc-arceos/Cargo.toml similarity index 100% rename from components/range-alloc-arceos/Cargo.toml rename to memory/range-alloc-arceos/Cargo.toml diff --git a/components/axaddrspace/LICENSE b/memory/range-alloc-arceos/LICENSE similarity index 100% rename from components/axaddrspace/LICENSE rename to memory/range-alloc-arceos/LICENSE diff --git a/components/range-alloc-arceos/LICENSE.APACHE b/memory/range-alloc-arceos/LICENSE.APACHE similarity index 100% rename from components/range-alloc-arceos/LICENSE.APACHE rename to memory/range-alloc-arceos/LICENSE.APACHE diff --git a/components/range-alloc-arceos/LICENSE.MIT b/memory/range-alloc-arceos/LICENSE.MIT similarity index 100% rename from components/range-alloc-arceos/LICENSE.MIT rename to memory/range-alloc-arceos/LICENSE.MIT diff --git a/components/range-alloc-arceos/README.md b/memory/range-alloc-arceos/README.md similarity index 98% rename from components/range-alloc-arceos/README.md rename to memory/range-alloc-arceos/README.md index dc0ee8ff16..f9d2507153 100644 --- a/components/range-alloc-arceos/README.md +++ b/memory/range-alloc-arceos/README.md @@ -32,7 +32,7 @@ range-alloc-arceos = "0.3.4" ```bash # Enter the crate directory -cd components/range-alloc-arceos +cd memory/range-alloc-arceos # Format code cargo fmt --all diff --git a/components/range-alloc-arceos/README_CN.md b/memory/range-alloc-arceos/README_CN.md similarity index 98% rename from components/range-alloc-arceos/README_CN.md rename to memory/range-alloc-arceos/README_CN.md index 4bd878338c..2ec6e1ef78 100644 --- a/components/range-alloc-arceos/README_CN.md +++ b/memory/range-alloc-arceos/README_CN.md @@ -32,7 +32,7 @@ range-alloc-arceos = "0.3.4" ```bash # 进入 crate 目录 -cd components/range-alloc-arceos +cd memory/range-alloc-arceos # 代码格式化 cargo fmt --all diff --git a/components/range-alloc-arceos/src/lib.rs b/memory/range-alloc-arceos/src/lib.rs similarity index 100% rename from components/range-alloc-arceos/src/lib.rs rename to memory/range-alloc-arceos/src/lib.rs diff --git a/components/range-alloc-arceos/tests/test.rs b/memory/range-alloc-arceos/tests/test.rs similarity index 100% rename from components/range-alloc-arceos/tests/test.rs rename to memory/range-alloc-arceos/tests/test.rs diff --git a/components/ranges-ext/CHANGELOG.md b/memory/ranges-ext/CHANGELOG.md similarity index 100% rename from components/ranges-ext/CHANGELOG.md rename to memory/ranges-ext/CHANGELOG.md diff --git a/components/ranges-ext/Cargo.toml b/memory/ranges-ext/Cargo.toml similarity index 100% rename from components/ranges-ext/Cargo.toml rename to memory/ranges-ext/Cargo.toml diff --git a/components/ranges-ext/src/less.rs b/memory/ranges-ext/src/less.rs similarity index 100% rename from components/ranges-ext/src/less.rs rename to memory/ranges-ext/src/less.rs diff --git a/components/ranges-ext/src/lib.rs b/memory/ranges-ext/src/lib.rs similarity index 100% rename from components/ranges-ext/src/lib.rs rename to memory/ranges-ext/src/lib.rs diff --git a/components/ranges-ext/src/test_helper.rs b/memory/ranges-ext/src/test_helper.rs similarity index 100% rename from components/ranges-ext/src/test_helper.rs rename to memory/ranges-ext/src/test_helper.rs diff --git a/components/ranges-ext/src/vec.rs b/memory/ranges-ext/src/vec.rs similarity index 100% rename from components/ranges-ext/src/vec.rs rename to memory/ranges-ext/src/vec.rs diff --git a/components/ranges-ext/tests/test.rs b/memory/ranges-ext/tests/test.rs similarity index 100% rename from components/ranges-ext/tests/test.rs rename to memory/ranges-ext/tests/test.rs diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index b153f395a4..ef124c89aa 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -68,7 +68,7 @@ ax-log.workspace = true ax-mm.workspace = true axnet = { version = "0.6.1", path = "../../arceos/modules/axnet-ng", package = "ax-net-ng" } ax-driver = { workspace = true, optional = true } -ax-plat-dyn = { workspace = true, optional = true } +axplat-dyn = { workspace = true, optional = true } ax-runtime.workspace = true ax-sync.workspace = true ax-task.workspace = true diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index 4b91741f37..c9fb4538aa 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -24,7 +24,7 @@ qemu = [ "starry-kernel/input", "starry-kernel/vsock", # auxilary features ] -smp = ["ax-feat/smp", "ax-plat-dyn?/smp", "ax-hal/smp"] +smp = ["ax-feat/smp", "axplat-dyn?/smp", "ax-hal/smp"] sg2002 = [ "ax-hal/riscv64-sg2002", @@ -64,7 +64,7 @@ path = "xtask/main.rs" ax-feat = { workspace = true, features = ["fs-ng-times", "irq"] } ax-driver.workspace = true ax-hal.workspace = true -ax-plat-dyn = { workspace = true, optional = true } +axplat-dyn = { workspace = true, optional = true } starry-kernel = { workspace = true, features = ["dev-log", "ext4"] } [target.'cfg(any(windows,unix))'.dependencies] diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index c36324bb97..9e4fb56fd9 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -19,7 +19,7 @@ smp = [ "ax-plat-riscv64-visionfive2?/smp", "ax-plat-loongarch64-qemu-virt?/smp", "ax-plat-x86-qemu-q35?/smp", - "ax-plat-dyn?/smp", + "axplat-dyn?/smp", "ax-plat/smp", ] irq = [ @@ -34,7 +34,7 @@ irq = [ "ax-plat-loongarch64-qemu-virt?/irq", "ax-plat-x86-qemu-q35?/irq", "ax-plat/irq", - "ax-plat-dyn?/irq", + "axplat-dyn?/irq", ] fp-simd = [ "ax-cpu/fp-simd", @@ -48,7 +48,7 @@ fp-simd = [ "ax-plat-riscv64-visionfive2?/fp-simd", "ax-plat-loongarch64-qemu-virt?/fp-simd", "ax-plat-x86-qemu-q35?/fp-simd", - "ax-plat-dyn?/fp-simd", + "axplat-dyn?/fp-simd", ] rtc = [ "ax-plat-x86-pc?/rtc", @@ -61,7 +61,7 @@ rtc = [ "ax-plat-riscv64-visionfive2?/rtc", "ax-plat-loongarch64-qemu-virt?/rtc", "ax-plat-x86-qemu-q35?/rtc", - "ax-plat-dyn?/rtc", + "axplat-dyn?/rtc", ] # Forward the GICv3 toggle to the aarch64 qemu-virt platform. # `?` lets non-aarch64 builds pass the feature through without @@ -81,19 +81,19 @@ paging = [ "ax-plat-riscv64-qemu-virt?/paging", ] tls = ["ax-cpu/tls"] -uspace = ["paging", "ax-cpu/uspace", "ax-plat-dyn?/uspace"] +uspace = ["paging", "ax-cpu/uspace", "axplat-dyn?/uspace"] hv = [ "paging", "ax-cpu/arm-el2", "ax-cpu/exception-table", "ax-percpu/arm-el2", - "ax-plat-dyn?/hv", + "axplat-dyn?/hv", ] axvisor-linker = [] # Custom or default platforms myplat = [] -plat-dyn = ["ax-plat-dyn"] +plat-dyn = ["axplat-dyn"] x86-pc = ["dep:ax-plat-x86-pc"] aarch64-qemu-virt = ["dep:ax-plat-aarch64-qemu-virt"] aarch64-raspi = ["dep:ax-plat-aarch64-raspi"] @@ -139,7 +139,7 @@ ax-plat-x86-pc = { workspace = true, optional = true } ax-plat-x86-qemu-q35 = { workspace = true, optional = true } [target.'cfg(target_os = "none")'.dependencies] -ax-plat-dyn = { workspace = true, default-features = false, optional = true } +axplat-dyn = { workspace = true, default-features = false, optional = true } [target.'cfg(target_arch = "aarch64")'.dependencies] ax-plat-aarch64-qemu-virt = { workspace = true, optional = true } diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index b2c5e393e8..116ca0125c 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -18,7 +18,7 @@ const PLATFORM_FEATURES: &[PlatformFeature] = &[ PlatformFeature { feature: "plat-dyn", target_arch: None, - crate_name: "ax_plat_dyn", + crate_name: "axplat_dyn", }, PlatformFeature { feature: "x86-pc", @@ -188,7 +188,7 @@ fn gen_selected_platform( None }; - if crate_name == Some("ax_plat_dyn") { + if crate_name == Some("axplat_dyn") { println!("cargo:rustc-cfg=plat_dyn"); } diff --git a/os/arceos/modules/axhal/src/mem.rs b/os/arceos/modules/axhal/src/mem.rs index 0a02a1b2fb..e52c1b868a 100644 --- a/os/arceos/modules/axhal/src/mem.rs +++ b/os/arceos/modules/axhal/src/mem.rs @@ -113,7 +113,7 @@ pub fn memory_regions() -> impl Iterator { pub fn boot_stack_bounds(cpu_id: usize) -> (VirtAddr, usize) { #[cfg(plat_dyn)] { - ax_plat_dyn::boot_stack_bounds(cpu_id) + axplat_dyn::boot_stack_bounds(cpu_id) } #[cfg(not(plat_dyn))] { diff --git a/os/arceos/modules/axhal/src/time.rs b/os/arceos/modules/axhal/src/time.rs index 97017619b5..76f305f7d1 100644 --- a/os/arceos/modules/axhal/src/time.rs +++ b/os/arceos/modules/axhal/src/time.rs @@ -11,7 +11,7 @@ pub use ax_plat::time::{irq_num, set_oneshot_timer}; pub fn try_init_epoch_offset(epoch_time_nanos: u64) -> bool { #[cfg(plat_dyn)] { - ax_plat_dyn::try_init_epoch_offset(epoch_time_nanos) + axplat_dyn::try_init_epoch_offset(epoch_time_nanos) } #[cfg(not(plat_dyn))] { diff --git a/os/arceos/scripts/make/cargo.mk b/os/arceos/scripts/make/cargo.mk index 2c41632725..cab1882fb9 100644 --- a/os/arceos/scripts/make/cargo.mk +++ b/os/arceos/scripts/make/cargo.mk @@ -41,16 +41,16 @@ endef clippy_args := -A unsafe_op_in_unsafe_fn define cargo_clippy - $(call run_cmd,cargo clippy,--workspace --exclude ax-log --exclude ax-plat-dyn --exclude "arceos-*" $(1) $(verbose) -- $(clippy_args)) + $(call run_cmd,cargo clippy,--workspace --exclude ax-log --exclude axplat-dyn --exclude "arceos-*" $(1) $(verbose) -- $(clippy_args)) $(call run_cmd,cargo clippy,-p ax-log $(1) $(verbose) -- $(clippy_args)) endef all_packages := \ - $(filter-out ax-plat-dyn,$(shell ls $(CURDIR)/modules)) \ + $(filter-out axplat-dyn,$(shell ls $(CURDIR)/modules)) \ ax-feat ax-api ax-std ax-libc define cargo_doc - $(call run_cmd,cargo doc,--no-deps --all-features --workspace --exclude "arceos-*" --exclude ax-plat-dyn $(verbose)) + $(call run_cmd,cargo doc,--no-deps --all-features --workspace --exclude "arceos-*" --exclude axplat-dyn $(verbose)) @# run twice to fix broken hyperlinks $(foreach p,$(all_packages), \ $(call run_cmd,cargo rustdoc,--all-features -p $(p) $(verbose)) @@ -59,5 +59,5 @@ endef define unit_test $(call run_cmd,cargo test,-p ax-fs $(1) $(verbose) -- --nocapture) - $(call run_cmd,cargo test,--workspace --exclude ax-fs --exclude ax-plat-dyn $(1) $(verbose) -- --nocapture) + $(call run_cmd,cargo test,--workspace --exclude ax-fs --exclude axplat-dyn $(1) $(verbose) -- --nocapture) endef diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 18fb804ab3..296e6fa451 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -39,7 +39,7 @@ fs = ["ax-std/fs"] vmx = ["axvm/vmx"] svm = ["axvm/svm"] sstc = ["riscv_vcpu/sstc"] -dyn-plat = ["ax-std/plat-dyn", "dep:ax-plat-dyn"] +dyn-plat = ["ax-std/plat-dyn", "dep:axplat-dyn"] rockchip-soc = ["ax-driver/rockchip-soc"] rockchip-sdhci = ["ax-driver/rockchip-sdhci", "ax-driver/rockchip-soc"] rockchip-dwmmc = ["ax-driver/rockchip-dwmmc", "ax-driver/rockchip-soc"] @@ -93,7 +93,7 @@ ax-percpu = { workspace = true, features = ["arm-el2", "non-zero-vma"] } rdif-intc.workspace = true rdrive.workspace = true ax-driver.workspace = true -ax-plat-dyn = { workspace = true, optional = true, default-features = false } +axplat-dyn = { workspace = true, optional = true, default-features = false } [target.'cfg(target_arch = "x86_64")'.dependencies] ax-plat-x86-qemu-q35 = { workspace = true, default-features = false, features = ["reboot-on-system-off", "irq", "smp"] } diff --git a/platforms/ax-plat-dyn/CHANGELOG.md b/platforms/axplat-dyn/CHANGELOG.md similarity index 100% rename from platforms/ax-plat-dyn/CHANGELOG.md rename to platforms/axplat-dyn/CHANGELOG.md diff --git a/platforms/ax-plat-dyn/Cargo.toml b/platforms/axplat-dyn/Cargo.toml similarity index 96% rename from platforms/ax-plat-dyn/Cargo.toml rename to platforms/axplat-dyn/Cargo.toml index ef216f627d..a345b10498 100644 --- a/platforms/ax-plat-dyn/Cargo.toml +++ b/platforms/axplat-dyn/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ax-plat-dyn" +name = "axplat-dyn" version = "0.6.2" authors = ["周睿 "] categories.workspace = true @@ -37,5 +37,5 @@ spin = "0.10" [package.metadata.axplat] platform = "dyn" arch = "aarch64" -crate = "ax_plat_dyn" +crate = "axplat_dyn" dynamic = true diff --git a/platforms/ax-plat-dyn/build.rs b/platforms/axplat-dyn/build.rs similarity index 100% rename from platforms/ax-plat-dyn/build.rs rename to platforms/axplat-dyn/build.rs diff --git a/platforms/ax-plat-dyn/link.ld b/platforms/axplat-dyn/link.ld similarity index 100% rename from platforms/ax-plat-dyn/link.ld rename to platforms/axplat-dyn/link.ld diff --git a/platforms/ax-plat-dyn/src/boot.rs b/platforms/axplat-dyn/src/boot.rs similarity index 100% rename from platforms/ax-plat-dyn/src/boot.rs rename to platforms/axplat-dyn/src/boot.rs diff --git a/platforms/ax-plat-dyn/src/console.rs b/platforms/axplat-dyn/src/console.rs similarity index 100% rename from platforms/ax-plat-dyn/src/console.rs rename to platforms/axplat-dyn/src/console.rs diff --git a/platforms/ax-plat-dyn/src/drivers/mod.rs b/platforms/axplat-dyn/src/drivers/mod.rs similarity index 100% rename from platforms/ax-plat-dyn/src/drivers/mod.rs rename to platforms/axplat-dyn/src/drivers/mod.rs diff --git a/platforms/ax-plat-dyn/src/generic_timer.rs b/platforms/axplat-dyn/src/generic_timer.rs similarity index 100% rename from platforms/ax-plat-dyn/src/generic_timer.rs rename to platforms/axplat-dyn/src/generic_timer.rs diff --git a/platforms/ax-plat-dyn/src/init.rs b/platforms/axplat-dyn/src/init.rs similarity index 93% rename from platforms/ax-plat-dyn/src/init.rs rename to platforms/axplat-dyn/src/init.rs index 46d6be0532..d68349a3e8 100644 --- a/platforms/ax-plat-dyn/src/init.rs +++ b/platforms/axplat-dyn/src/init.rs @@ -14,7 +14,7 @@ impl InitIf for InitIfImpl { #[cfg(all(target_arch = "aarch64", feature = "fp-simd"))] { ax_cpu::asm::enable_fp(); - debug!("ax-plat-dyn: fp/simd enabled"); + debug!("axplat-dyn: fp/simd enabled"); } somehal::timer::enable(); } @@ -26,7 +26,7 @@ impl InitIf for InitIfImpl { #[cfg(all(target_arch = "aarch64", feature = "fp-simd"))] { ax_cpu::asm::enable_fp(); - debug!("ax-plat-dyn: secondary fp/simd enabled"); + debug!("axplat-dyn: secondary fp/simd enabled"); } somehal::timer::enable(); } diff --git a/platforms/ax-plat-dyn/src/irq.rs b/platforms/axplat-dyn/src/irq.rs similarity index 100% rename from platforms/ax-plat-dyn/src/irq.rs rename to platforms/axplat-dyn/src/irq.rs diff --git a/platforms/ax-plat-dyn/src/lib.rs b/platforms/axplat-dyn/src/lib.rs similarity index 100% rename from platforms/ax-plat-dyn/src/lib.rs rename to platforms/axplat-dyn/src/lib.rs diff --git a/platforms/ax-plat-dyn/src/mem.rs b/platforms/axplat-dyn/src/mem.rs similarity index 100% rename from platforms/ax-plat-dyn/src/mem.rs rename to platforms/axplat-dyn/src/mem.rs diff --git a/platforms/ax-plat-dyn/src/power.rs b/platforms/axplat-dyn/src/power.rs similarity index 100% rename from platforms/ax-plat-dyn/src/power.rs rename to platforms/axplat-dyn/src/power.rs diff --git a/reports/external-spin-audit.md b/reports/external-spin-audit.md index a7e9a9cbb1..80a390cb21 100644 --- a/reports/external-spin-audit.md +++ b/reports/external-spin-audit.md @@ -62,7 +62,7 @@ axaddrspace 0.5.10 -> spin 0.10.0 axbacktrace 0.3.9 -> spin 0.10.0 axdevice 0.4.9 -> spin 0.10.0 axfs-ng-vfs 0.4.1 -> spin 0.10.0 -ax-plat-dyn 0.6.1 -> spin 0.10.0 +axplat-dyn 0.6.1 -> spin 0.10.0 axpoll 0.3.9 -> spin 0.10.0 axvisor 0.5.7 -> spin 0.10.0 axvm 0.5.8 -> spin 0.10.0 @@ -110,24 +110,24 @@ test/demo packages, and third-party packages. Workspace manifests that declare external `spin` directly: ```text -components/arm_vcpu/Cargo.toml:23: spin = "0.10" -components/arm_vgic/Cargo.toml:34: spin = "0.10" -components/axaddrspace/Cargo.toml:41: spin = "0.10" +virtualization/arm_vcpu/Cargo.toml:23: spin = "0.10" +virtualization/arm_vgic/Cargo.toml:34: spin = "0.10" +memory/axaddrspace/Cargo.toml:41: spin = "0.10" components/axbacktrace/Cargo.toml:20: spin = { version = "0.10", default-features = false, features = ["once"] } -components/axdevice/Cargo.toml:20: spin = "0.10" +virtualization/axdevice/Cargo.toml:20: spin = "0.10" components/axdriver_crates/axdriver_net/Cargo.toml:29: spin = "0.9" components/axfs-ng-vfs/Cargo.toml:20: spin = { version = "0.10", default-features = false, features = ["mutex"] } components/axfs_crates/axfs_devfs/Cargo.toml:14: spin = "0.9" components/axfs_crates/axfs_ramfs/Cargo.toml:14: spin = "0.9" platforms/ax-plat-aarch64-peripherals/Cargo.toml:18: spin = "0.10" components/axpoll/Cargo.toml:22: spin = { version = "0.10", default-features = false, features = ["lazy", ...] } -components/axvm/Cargo.toml:21: spin = "0.10" -components/loongarch_vcpu/Cargo.toml:17: spin = "0.10" +virtualization/axvm/Cargo.toml:21: spin = "0.10" +virtualization/loongarch_vcpu/Cargo.toml:17: spin = "0.10" components/percpu/percpu/Cargo.toml:41: spin = "0.10" -components/riscv_vplic/Cargo.toml:24: spin = "0.10" +virtualization/riscv_vplic/Cargo.toml:24: spin = "0.10" components/scope-local/Cargo.toml:13: spin = { version = "0.10", default-features = false, features = ["lazy"] } components/someboot/Cargo.toml:43: spin = "0.10" -components/x86_vcpu/Cargo.toml:39: spin = { version = "0.10", default-features = false } +virtualization/x86_vcpu/Cargo.toml:39: spin = { version = "0.10", default-features = false } drivers/blk/nvme-driver/Cargo.toml:18: spin = "0.10" drivers/blk/ramdisk/Cargo.toml:15: spin = "0.10" drivers/firmware/arm-scmi-rs/Cargo.toml:20: spin = "0.10" @@ -144,7 +144,7 @@ os/arceos/modules/axfs/Cargo.toml:27: spin = { workspace = true } os/arceos/modules/axnet-ng/Cargo.toml:34: spin = { workspace = true } os/arceos/modules/axtask/Cargo.toml:70: spin = { workspace = true, optional = true } os/axvisor/Cargo.toml:57: spin = "0.10" -platforms/ax-plat-dyn/Cargo.toml:84: spin = "0.10" +platforms/axplat-dyn/Cargo.toml:84: spin = "0.10" platforms/somehal/Cargo.toml:31: spin = "0.10" ``` @@ -170,10 +170,10 @@ Grouped by area: ```text 17 os/arceos/modules 14 os/StarryOS/kernel - 8 platforms/ax-plat-dyn/src + 8 platforms/axplat-dyn/src 8 drivers/usb/usb-host 4 drivers/rdrive/src - 4 components/arm_vgic/src + 4 virtualization/arm_vgic/src 3 os/axvisor/src 3 os/arceos/api 3 components/axfs_crates/axfs_ramfs @@ -187,20 +187,20 @@ Grouped by area: 1 drivers/interface/rdif-serial 1 drivers/blk/ramdisk 1 drivers/blk/nvme-driver - 1 components/x86_vcpu/src + 1 virtualization/x86_vcpu/src 1 components/scope-local/src - 1 components/riscv_vplic/src + 1 virtualization/riscv_vplic/src 1 components/percpu/percpu - 1 components/loongarch_vcpu/src + 1 virtualization/loongarch_vcpu/src 1 components/kspin/src - 1 components/dma-api/src - 1 components/axvm/src + 1 memory/dma-api/src + 1 virtualization/axvm/src 1 components/axpoll/src 1 components/axfs-ng-vfs/src 1 components/axdriver_crates/axdriver_net - 1 components/axdevice/src + 1 virtualization/axdevice/src 1 components/axbacktrace/src - 1 components/axaddrspace/tests + 1 memory/axaddrspace/tests ``` The `components/kspin/src/base.rs` entry is documentation text referencing @@ -212,20 +212,20 @@ These are direct external `spin::{Mutex,RwLock}` uses in kernel/runtime code that are more likely to matter for lockdep visibility: ```text -components/arm_vgic/src/v3/gits.rs -components/arm_vgic/src/v3/vgicd.rs -components/arm_vgic/src/v3/vgicr.rs -components/arm_vgic/src/vgic.rs -components/axdevice/src/device.rs +virtualization/arm_vgic/src/v3/gits.rs +virtualization/arm_vgic/src/v3/vgicd.rs +virtualization/arm_vgic/src/v3/vgicr.rs +virtualization/arm_vgic/src/vgic.rs +virtualization/axdevice/src/device.rs components/axdriver_crates/axdriver_net/src/net_buf.rs components/axfs-ng-vfs/src/lib.rs components/axfs_crates/axfs_devfs/src/dir.rs components/axfs_crates/axfs_ramfs/src/dir.rs components/axfs_crates/axfs_ramfs/src/file.rs -components/axvm/src/vm.rs -components/dma-api/src/pool.rs -components/loongarch_vcpu/src/registers.rs -components/riscv_vplic/src/vplic.rs +virtualization/axvm/src/vm.rs +memory/dma-api/src/pool.rs +virtualization/loongarch_vcpu/src/registers.rs +virtualization/riscv_vplic/src/vplic.rs drivers/blk/nvme-driver/src/block.rs drivers/blk/ramdisk/src/lib.rs drivers/firmware/arm-scmi-rs/src/lib.rs @@ -272,12 +272,12 @@ os/arceos/modules/axnet/src/smoltcp_impl/udp.rs os/axvisor/src/hal/arch/loongarch64/mod.rs os/axvisor/src/vmm/fdt/mod.rs os/axvisor/src/vmm/vm_list.rs -platforms/ax-plat-dyn/src/drivers/blk/mod.rs -platforms/ax-plat-dyn/src/drivers/blk/virtio_pci.rs -platforms/ax-plat-dyn/src/drivers/mod.rs -platforms/ax-plat-dyn/src/drivers/net/virtio_pci.rs -platforms/ax-plat-dyn/src/drivers/pci.rs -platforms/ax-plat-dyn/src/drivers/soc/scmi.rs +platforms/axplat-dyn/src/drivers/blk/mod.rs +platforms/axplat-dyn/src/drivers/blk/virtio_pci.rs +platforms/axplat-dyn/src/drivers/mod.rs +platforms/axplat-dyn/src/drivers/net/virtio_pci.rs +platforms/axplat-dyn/src/drivers/pci.rs +platforms/axplat-dyn/src/drivers/soc/scmi.rs ``` ## Initialization-only source uses @@ -307,8 +307,8 @@ os/arceos/modules/axhal/src/mem.rs os/arceos/modules/axnet-ng/src/lib.rs os/arceos/modules/axnet-ng/src/tcp.rs os/arceos/modules/axtask/src/api.rs -platforms/ax-plat-dyn/src/drivers/blk/rockchip_mmc.rs -platforms/ax-plat-dyn/src/mem.rs +platforms/axplat-dyn/src/drivers/blk/rockchip_mmc.rs +platforms/axplat-dyn/src/mem.rs platforms/somehal/src/arch/aarch64/systick.rs ``` @@ -317,8 +317,8 @@ platforms/somehal/src/arch/aarch64/systick.rs These direct uses are in test utilities or tests: ```text -components/axaddrspace/tests/test_utils/mod.rs -components/x86_vcpu/src/test_utils.rs +memory/axaddrspace/tests/test_utils/mod.rs +virtualization/x86_vcpu/src/test_utils.rs drivers/serial/some-serial/tests/test.rs drivers/soc/rockchip/rockchip-soc/tests/test.rs ``` diff --git a/reports/external-spin-migration-plan.md b/reports/external-spin-migration-plan.md index ca4b240a59..8e4ae0874a 100644 --- a/reports/external-spin-migration-plan.md +++ b/reports/external-spin-migration-plan.md @@ -222,7 +222,7 @@ The production migration covered these groups: - networking and Starry runtime paths: `ax-net-ng`, Starry timer/netlink/usbfs and camera locks; - virtualization and platform components: `axvisor`, `axvm`, `riscv_vplic`, - `loongarch_vcpu`, `arm_vgic`, `ax-plat-dyn`; + `loongarch_vcpu`, `arm_vgic`, `axplat-dyn`; - portable driver components: `rdrive`, `arm-scmi-rs`, `ramdisk`, `nvme-driver`, `rdif-serial`, `realtek-rtl8125`, `sg2002-tpu`, `crab-usb`; - shared support crates: `dma-api`, `ax-driver-net`, `axdevice`. diff --git a/reports/syscall_preadv_pwritev2_modification_report.md b/reports/syscall_preadv_pwritev2_modification_report.md index 33465513c5..139a9092f6 100644 --- a/reports/syscall_preadv_pwritev2_modification_report.md +++ b/reports/syscall_preadv_pwritev2_modification_report.md @@ -137,7 +137,7 @@ docker run --rm -v "$PWD":/workspace -w /workspace ghcr.io/rcore-os/tgoskits-con ## Change 4: 修复 bitmap-allocator 现有 clippy 阻塞 **修改文件** -- `components/bitmap-allocator/src/lib.rs` +- `memory/bitmap-allocator/src/lib.rs` **修改前问题** `cargo xtask clippy --package starry-kernel` 首次运行时在 `bitmap-allocator` 依赖处被两个 `clippy::question_mark` 既有 warning 阻塞。 diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index 70027d3bd6..82931c05ef 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -2,25 +2,25 @@ url,branch,target_dir,category,description https://github.com/arceos-org/arceos,dev,os/arceos,OS,ArceOS https://github.com/arceos-hypervisor/axvisor,,os/axvisor,OS,axvisor - ArceOS Hypervisor https://github.com/Starry-OS/StarryOS,,os/StarryOS,OS,StarryOS - 教学操作系统 -https://github.com/arceos-hypervisor/aarch64_sysreg,,components/aarch64_sysreg,Hypervisor, -https://github.com/arceos-hypervisor/axaddrspace,,components/axaddrspace,Hypervisor, -https://github.com/arceos-hypervisor/axdevice,,components/axdevice,Hypervisor, -https://github.com/arceos-hypervisor/axdevice_base,,components/axdevice_base,Hypervisor, -https://github.com/arceos-hypervisor/axhvc,,components/axhvc,Hypervisor, +https://github.com/arceos-hypervisor/aarch64_sysreg,,virtualization/aarch64_sysreg,Hypervisor, +https://github.com/arceos-hypervisor/axaddrspace,,memory/axaddrspace,Hypervisor, +https://github.com/arceos-hypervisor/axdevice,,virtualization/axdevice,Hypervisor, +https://github.com/arceos-hypervisor/axdevice_base,,virtualization/axdevice_base,Hypervisor, +https://github.com/arceos-hypervisor/axhvc,,virtualization/axhvc,Hypervisor, https://github.com/arceos-hypervisor/axklib,,components/axklib,Hypervisor, -https://github.com/arceos-hypervisor/axvcpu,,components/axvcpu,Hypervisor, -https://github.com/arceos-hypervisor/axvisor_api,,components/axvisor_api,Hypervisor, -https://github.com/arceos-hypervisor/axvm,,components/axvm,Hypervisor, -https://github.com/arceos-hypervisor/axvmconfig,,components/axvmconfig,Hypervisor, -https://github.com/arceos-hypervisor/range-alloc,,components/range-alloc-arceos,Hypervisor, -https://github.com/arceos-hypervisor/x86_vcpu,,components/x86_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/x86_vlapic.git,,components/x86_vlapic,Hypervisor, -https://github.com/arceos-hypervisor/arm_vcpu,,components/arm_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/arm_vgic,,components/arm_vgic,Hypervisor, -https://github.com/arceos-hypervisor/riscv_vcpu,,components/riscv_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/riscv-h,,components/riscv-h,Hypervisor, -https://github.com/arceos-hypervisor/riscv_vplic,,components/riscv_vplic,Hypervisor, -https://github.com/arceos-org/allocator,,components/axallocator,ArceOS, +https://github.com/arceos-hypervisor/axvcpu,,virtualization/axvcpu,Hypervisor, +https://github.com/arceos-hypervisor/axvisor_api,,virtualization/axvisor_api,Hypervisor, +https://github.com/arceos-hypervisor/axvm,,virtualization/axvm,Hypervisor, +https://github.com/arceos-hypervisor/axvmconfig,,virtualization/axvmconfig,Hypervisor, +https://github.com/arceos-hypervisor/range-alloc,,memory/range-alloc-arceos,Hypervisor, +https://github.com/arceos-hypervisor/x86_vcpu,,virtualization/x86_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/x86_vlapic.git,,virtualization/x86_vlapic,Hypervisor, +https://github.com/arceos-hypervisor/arm_vcpu,,virtualization/arm_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/arm_vgic,,virtualization/arm_vgic,Hypervisor, +https://github.com/arceos-hypervisor/riscv_vcpu,,virtualization/riscv_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/riscv-h,,virtualization/riscv-h,Hypervisor, +https://github.com/arceos-hypervisor/riscv_vplic,,virtualization/riscv_vplic,Hypervisor, +https://github.com/arceos-org/allocator,,memory/axallocator,ArceOS, https://github.com/arceos-org/axconfig-gen,,components/axconfig-gen,ArceOS, https://github.com/arceos-org/ax-cpu,dev,components/axcpu,ArceOS, https://github.com/arceos-org/axerrno,,components/axerrno,ArceOS, @@ -34,15 +34,13 @@ https://github.com/arceos-org/kernel_guard,,components/kernel_guard,ArceOS, https://github.com/arceos-org/kspin,,components/kspin,ArceOS, https://github.com/arceos-org/lazyinit,,components/ax-lazyinit,ArceOS, https://github.com/arceos-org/linked_list_r4l,,components/linked_list_r4l,ArceOS, -https://github.com/arceos-org/axmm_crates,,components/axmm_crates,ArceOS, -https://github.com/arceos-org/page_table_multiarch,,components/page_table_multiarch,ArceOS, https://github.com/arceos-org/percpu,dev,components/percpu,ArceOS, https://github.com/arceos-org/timer_list,,components/timer_list,ArceOS, https://github.com/arceos-org/ax-int-ratio,,components/int_ratio,ArceOS, https://github.com/arceos-org/cap_access,,components/cap_access,ArceOS, https://github.com/arceos-org/arm_pl031,,drivers/rtc/arm_pl031,ArceOS, https://github.com/arceos-org/riscv_plic,,drivers/intc/riscv_plic,ArceOS, -https://github.com/rcore-os/bitmap-allocator,,components/bitmap-allocator,rCore, +https://github.com/rcore-os/bitmap-allocator,,memory/bitmap-allocator,rCore, https://github.com/Starry-OS/axbacktrace,,components/axbacktrace,Starry, https://github.com/Starry-OS/axpoll,,components/axpoll,Starry, https://github.com/Starry-OS/axfs-ng-vfs,,components/axfs-ng-vfs,Starry, diff --git a/components/aarch64_sysreg/.github/config.json b/virtualization/aarch64_sysreg/.github/config.json similarity index 100% rename from components/aarch64_sysreg/.github/config.json rename to virtualization/aarch64_sysreg/.github/config.json diff --git a/components/aarch64_sysreg/.github/workflows/check.yml b/virtualization/aarch64_sysreg/.github/workflows/check.yml similarity index 100% rename from components/aarch64_sysreg/.github/workflows/check.yml rename to virtualization/aarch64_sysreg/.github/workflows/check.yml diff --git a/components/aarch64_sysreg/.github/workflows/deploy.yml b/virtualization/aarch64_sysreg/.github/workflows/deploy.yml similarity index 100% rename from components/aarch64_sysreg/.github/workflows/deploy.yml rename to virtualization/aarch64_sysreg/.github/workflows/deploy.yml diff --git a/components/axaddrspace/.github/workflows/push.yml b/virtualization/aarch64_sysreg/.github/workflows/push.yml similarity index 100% rename from components/axaddrspace/.github/workflows/push.yml rename to virtualization/aarch64_sysreg/.github/workflows/push.yml diff --git a/components/arm_vcpu/.github/workflows/release.yml b/virtualization/aarch64_sysreg/.github/workflows/release.yml similarity index 100% rename from components/arm_vcpu/.github/workflows/release.yml rename to virtualization/aarch64_sysreg/.github/workflows/release.yml diff --git a/components/aarch64_sysreg/.github/workflows/test.yml b/virtualization/aarch64_sysreg/.github/workflows/test.yml similarity index 100% rename from components/aarch64_sysreg/.github/workflows/test.yml rename to virtualization/aarch64_sysreg/.github/workflows/test.yml diff --git a/components/aarch64_sysreg/.gitignore b/virtualization/aarch64_sysreg/.gitignore similarity index 100% rename from components/aarch64_sysreg/.gitignore rename to virtualization/aarch64_sysreg/.gitignore diff --git a/components/aarch64_sysreg/Cargo.toml b/virtualization/aarch64_sysreg/Cargo.toml similarity index 100% rename from components/aarch64_sysreg/Cargo.toml rename to virtualization/aarch64_sysreg/Cargo.toml diff --git a/components/axallocator/LICENSE b/virtualization/aarch64_sysreg/LICENSE similarity index 100% rename from components/axallocator/LICENSE rename to virtualization/aarch64_sysreg/LICENSE diff --git a/components/aarch64_sysreg/README.md b/virtualization/aarch64_sysreg/README.md similarity index 98% rename from components/aarch64_sysreg/README.md rename to virtualization/aarch64_sysreg/README.md index f2a505e62a..74d0f3e62d 100644 --- a/components/aarch64_sysreg/README.md +++ b/virtualization/aarch64_sysreg/README.md @@ -32,7 +32,7 @@ aarch64_sysreg = "0.3.1" ```bash # Enter the crate directory -cd components/aarch64_sysreg +cd virtualization/aarch64_sysreg # Format code cargo fmt --all diff --git a/components/aarch64_sysreg/README_CN.md b/virtualization/aarch64_sysreg/README_CN.md similarity index 98% rename from components/aarch64_sysreg/README_CN.md rename to virtualization/aarch64_sysreg/README_CN.md index 7a8ebd6faf..437bce305f 100644 --- a/components/aarch64_sysreg/README_CN.md +++ b/virtualization/aarch64_sysreg/README_CN.md @@ -32,7 +32,7 @@ aarch64_sysreg = "0.3.1" ```bash # 进入 crate 目录 -cd components/aarch64_sysreg +cd virtualization/aarch64_sysreg # 代码格式化 cargo fmt --all diff --git a/components/aarch64_sysreg/scripts/check.sh b/virtualization/aarch64_sysreg/scripts/check.sh similarity index 100% rename from components/aarch64_sysreg/scripts/check.sh rename to virtualization/aarch64_sysreg/scripts/check.sh diff --git a/components/aarch64_sysreg/scripts/test.sh b/virtualization/aarch64_sysreg/scripts/test.sh similarity index 100% rename from components/aarch64_sysreg/scripts/test.sh rename to virtualization/aarch64_sysreg/scripts/test.sh diff --git a/components/aarch64_sysreg/src/lib.rs b/virtualization/aarch64_sysreg/src/lib.rs similarity index 100% rename from components/aarch64_sysreg/src/lib.rs rename to virtualization/aarch64_sysreg/src/lib.rs diff --git a/components/aarch64_sysreg/src/operation_type.rs b/virtualization/aarch64_sysreg/src/operation_type.rs similarity index 100% rename from components/aarch64_sysreg/src/operation_type.rs rename to virtualization/aarch64_sysreg/src/operation_type.rs diff --git a/components/aarch64_sysreg/src/registers_type.rs b/virtualization/aarch64_sysreg/src/registers_type.rs similarity index 100% rename from components/aarch64_sysreg/src/registers_type.rs rename to virtualization/aarch64_sysreg/src/registers_type.rs diff --git a/components/aarch64_sysreg/src/system_reg_type.rs b/virtualization/aarch64_sysreg/src/system_reg_type.rs similarity index 100% rename from components/aarch64_sysreg/src/system_reg_type.rs rename to virtualization/aarch64_sysreg/src/system_reg_type.rs diff --git a/components/aarch64_sysreg/tests/operation_type_tests.rs b/virtualization/aarch64_sysreg/tests/operation_type_tests.rs similarity index 100% rename from components/aarch64_sysreg/tests/operation_type_tests.rs rename to virtualization/aarch64_sysreg/tests/operation_type_tests.rs diff --git a/components/aarch64_sysreg/tests/registers_type_tests.rs b/virtualization/aarch64_sysreg/tests/registers_type_tests.rs similarity index 100% rename from components/aarch64_sysreg/tests/registers_type_tests.rs rename to virtualization/aarch64_sysreg/tests/registers_type_tests.rs diff --git a/components/aarch64_sysreg/tests/system_reg_type_tests.rs b/virtualization/aarch64_sysreg/tests/system_reg_type_tests.rs similarity index 100% rename from components/aarch64_sysreg/tests/system_reg_type_tests.rs rename to virtualization/aarch64_sysreg/tests/system_reg_type_tests.rs diff --git a/components/arm_vcpu/.cargo/config.toml b/virtualization/arm_vcpu/.cargo/config.toml similarity index 100% rename from components/arm_vcpu/.cargo/config.toml rename to virtualization/arm_vcpu/.cargo/config.toml diff --git a/components/arm_vcpu/.github/config.json b/virtualization/arm_vcpu/.github/config.json similarity index 100% rename from components/arm_vcpu/.github/config.json rename to virtualization/arm_vcpu/.github/config.json diff --git a/components/axaddrspace/.github/workflows/check.yml b/virtualization/arm_vcpu/.github/workflows/check.yml similarity index 100% rename from components/axaddrspace/.github/workflows/check.yml rename to virtualization/arm_vcpu/.github/workflows/check.yml diff --git a/components/axaddrspace/.github/workflows/deploy.yml b/virtualization/arm_vcpu/.github/workflows/deploy.yml similarity index 100% rename from components/axaddrspace/.github/workflows/deploy.yml rename to virtualization/arm_vcpu/.github/workflows/deploy.yml diff --git a/components/arm_vcpu/.github/workflows/push.yml b/virtualization/arm_vcpu/.github/workflows/push.yml similarity index 100% rename from components/arm_vcpu/.github/workflows/push.yml rename to virtualization/arm_vcpu/.github/workflows/push.yml diff --git a/components/axaddrspace/.github/workflows/release.yml b/virtualization/arm_vcpu/.github/workflows/release.yml similarity index 100% rename from components/axaddrspace/.github/workflows/release.yml rename to virtualization/arm_vcpu/.github/workflows/release.yml diff --git a/components/axaddrspace/.github/workflows/test.yml b/virtualization/arm_vcpu/.github/workflows/test.yml similarity index 100% rename from components/axaddrspace/.github/workflows/test.yml rename to virtualization/arm_vcpu/.github/workflows/test.yml diff --git a/components/arm_vcpu/.gitignore b/virtualization/arm_vcpu/.gitignore similarity index 100% rename from components/arm_vcpu/.gitignore rename to virtualization/arm_vcpu/.gitignore diff --git a/components/arm_vcpu/CHANGELOG.md b/virtualization/arm_vcpu/CHANGELOG.md similarity index 100% rename from components/arm_vcpu/CHANGELOG.md rename to virtualization/arm_vcpu/CHANGELOG.md diff --git a/components/arm_vcpu/Cargo.toml b/virtualization/arm_vcpu/Cargo.toml similarity index 100% rename from components/arm_vcpu/Cargo.toml rename to virtualization/arm_vcpu/Cargo.toml diff --git a/components/axdevice/LICENSE b/virtualization/arm_vcpu/LICENSE similarity index 100% rename from components/axdevice/LICENSE rename to virtualization/arm_vcpu/LICENSE diff --git a/components/arm_vcpu/README.md b/virtualization/arm_vcpu/README.md similarity index 98% rename from components/arm_vcpu/README.md rename to virtualization/arm_vcpu/README.md index 091ae79a99..6235557189 100644 --- a/components/arm_vcpu/README.md +++ b/virtualization/arm_vcpu/README.md @@ -32,7 +32,7 @@ arm_vcpu = "0.5.0" ```bash # Enter the crate directory -cd components/arm_vcpu +cd virtualization/arm_vcpu # Format code cargo fmt --all diff --git a/components/arm_vcpu/README_CN.md b/virtualization/arm_vcpu/README_CN.md similarity index 98% rename from components/arm_vcpu/README_CN.md rename to virtualization/arm_vcpu/README_CN.md index fc1782ba58..4365560ede 100644 --- a/components/arm_vcpu/README_CN.md +++ b/virtualization/arm_vcpu/README_CN.md @@ -32,7 +32,7 @@ arm_vcpu = "0.5.0" ```bash # 进入 crate 目录 -cd components/arm_vcpu +cd virtualization/arm_vcpu # 代码格式化 cargo fmt --all diff --git a/components/arm_vcpu/scripts/check.sh b/virtualization/arm_vcpu/scripts/check.sh similarity index 100% rename from components/arm_vcpu/scripts/check.sh rename to virtualization/arm_vcpu/scripts/check.sh diff --git a/components/arm_vcpu/scripts/test.sh b/virtualization/arm_vcpu/scripts/test.sh similarity index 100% rename from components/arm_vcpu/scripts/test.sh rename to virtualization/arm_vcpu/scripts/test.sh diff --git a/components/arm_vcpu/src/context_frame.rs b/virtualization/arm_vcpu/src/context_frame.rs similarity index 100% rename from components/arm_vcpu/src/context_frame.rs rename to virtualization/arm_vcpu/src/context_frame.rs diff --git a/components/arm_vcpu/src/exception.S b/virtualization/arm_vcpu/src/exception.S similarity index 100% rename from components/arm_vcpu/src/exception.S rename to virtualization/arm_vcpu/src/exception.S diff --git a/components/arm_vcpu/src/exception.rs b/virtualization/arm_vcpu/src/exception.rs similarity index 100% rename from components/arm_vcpu/src/exception.rs rename to virtualization/arm_vcpu/src/exception.rs diff --git a/components/arm_vcpu/src/exception_utils.rs b/virtualization/arm_vcpu/src/exception_utils.rs similarity index 100% rename from components/arm_vcpu/src/exception_utils.rs rename to virtualization/arm_vcpu/src/exception_utils.rs diff --git a/components/arm_vcpu/src/lib.rs b/virtualization/arm_vcpu/src/lib.rs similarity index 100% rename from components/arm_vcpu/src/lib.rs rename to virtualization/arm_vcpu/src/lib.rs diff --git a/components/arm_vcpu/src/pcpu.rs b/virtualization/arm_vcpu/src/pcpu.rs similarity index 100% rename from components/arm_vcpu/src/pcpu.rs rename to virtualization/arm_vcpu/src/pcpu.rs diff --git a/components/arm_vcpu/src/smc.rs b/virtualization/arm_vcpu/src/smc.rs similarity index 100% rename from components/arm_vcpu/src/smc.rs rename to virtualization/arm_vcpu/src/smc.rs diff --git a/components/arm_vcpu/src/vcpu.rs b/virtualization/arm_vcpu/src/vcpu.rs similarity index 100% rename from components/arm_vcpu/src/vcpu.rs rename to virtualization/arm_vcpu/src/vcpu.rs diff --git a/components/arm_vcpu/tests/axci_flow_test.rs b/virtualization/arm_vcpu/tests/axci_flow_test.rs similarity index 100% rename from components/arm_vcpu/tests/axci_flow_test.rs rename to virtualization/arm_vcpu/tests/axci_flow_test.rs diff --git a/components/arm_vcpu/tests/context_frame_test.rs b/virtualization/arm_vcpu/tests/context_frame_test.rs similarity index 100% rename from components/arm_vcpu/tests/context_frame_test.rs rename to virtualization/arm_vcpu/tests/context_frame_test.rs diff --git a/components/arm_vcpu/tests/hardware_support_test.rs b/virtualization/arm_vcpu/tests/hardware_support_test.rs similarity index 100% rename from components/arm_vcpu/tests/hardware_support_test.rs rename to virtualization/arm_vcpu/tests/hardware_support_test.rs diff --git a/components/arm_vcpu/tests/mpidr_test.rs b/virtualization/arm_vcpu/tests/mpidr_test.rs similarity index 100% rename from components/arm_vcpu/tests/mpidr_test.rs rename to virtualization/arm_vcpu/tests/mpidr_test.rs diff --git a/components/arm_vcpu/tests/psci_test.rs b/virtualization/arm_vcpu/tests/psci_test.rs similarity index 100% rename from components/arm_vcpu/tests/psci_test.rs rename to virtualization/arm_vcpu/tests/psci_test.rs diff --git a/components/arm_vcpu/tests/script_test.rs b/virtualization/arm_vcpu/tests/script_test.rs similarity index 100% rename from components/arm_vcpu/tests/script_test.rs rename to virtualization/arm_vcpu/tests/script_test.rs diff --git a/components/arm_vcpu/tests/spsr_test.rs b/virtualization/arm_vcpu/tests/spsr_test.rs similarity index 100% rename from components/arm_vcpu/tests/spsr_test.rs rename to virtualization/arm_vcpu/tests/spsr_test.rs diff --git a/components/arm_vcpu/tests/vcpu_config_test.rs b/virtualization/arm_vcpu/tests/vcpu_config_test.rs similarity index 100% rename from components/arm_vcpu/tests/vcpu_config_test.rs rename to virtualization/arm_vcpu/tests/vcpu_config_test.rs diff --git a/components/arm_vcpu/tests/vmcpu_registers_test.rs b/virtualization/arm_vcpu/tests/vmcpu_registers_test.rs similarity index 100% rename from components/arm_vcpu/tests/vmcpu_registers_test.rs rename to virtualization/arm_vcpu/tests/vmcpu_registers_test.rs diff --git a/components/arm_vgic/.github/config.json b/virtualization/arm_vgic/.github/config.json similarity index 100% rename from components/arm_vgic/.github/config.json rename to virtualization/arm_vgic/.github/config.json diff --git a/components/axvcpu/.github/workflows/check.yml b/virtualization/arm_vgic/.github/workflows/check.yml similarity index 100% rename from components/axvcpu/.github/workflows/check.yml rename to virtualization/arm_vgic/.github/workflows/check.yml diff --git a/components/arm_vgic/.github/workflows/deploy.yml b/virtualization/arm_vgic/.github/workflows/deploy.yml similarity index 100% rename from components/arm_vgic/.github/workflows/deploy.yml rename to virtualization/arm_vgic/.github/workflows/deploy.yml diff --git a/components/axvcpu/.github/workflows/push.yml b/virtualization/arm_vgic/.github/workflows/push.yml similarity index 100% rename from components/axvcpu/.github/workflows/push.yml rename to virtualization/arm_vgic/.github/workflows/push.yml diff --git a/components/axvcpu/.github/workflows/release.yml b/virtualization/arm_vgic/.github/workflows/release.yml similarity index 100% rename from components/axvcpu/.github/workflows/release.yml rename to virtualization/arm_vgic/.github/workflows/release.yml diff --git a/components/axvcpu/.github/workflows/test.yml b/virtualization/arm_vgic/.github/workflows/test.yml similarity index 100% rename from components/axvcpu/.github/workflows/test.yml rename to virtualization/arm_vgic/.github/workflows/test.yml diff --git a/components/arm_vgic/.gitignore b/virtualization/arm_vgic/.gitignore similarity index 100% rename from components/arm_vgic/.gitignore rename to virtualization/arm_vgic/.gitignore diff --git a/components/arm_vgic/CHANGELOG.md b/virtualization/arm_vgic/CHANGELOG.md similarity index 100% rename from components/arm_vgic/CHANGELOG.md rename to virtualization/arm_vgic/CHANGELOG.md diff --git a/components/arm_vgic/Cargo.toml b/virtualization/arm_vgic/Cargo.toml similarity index 100% rename from components/arm_vgic/Cargo.toml rename to virtualization/arm_vgic/Cargo.toml diff --git a/components/axdevice_base/LICENSE b/virtualization/arm_vgic/LICENSE similarity index 100% rename from components/axdevice_base/LICENSE rename to virtualization/arm_vgic/LICENSE diff --git a/components/arm_vgic/README.md b/virtualization/arm_vgic/README.md similarity index 98% rename from components/arm_vgic/README.md rename to virtualization/arm_vgic/README.md index 27b7d818ac..f6958c1779 100644 --- a/components/arm_vgic/README.md +++ b/virtualization/arm_vgic/README.md @@ -32,7 +32,7 @@ arm_vgic = "0.4.2" ```bash # Enter the crate directory -cd components/arm_vgic +cd virtualization/arm_vgic # Format code cargo fmt --all diff --git a/components/arm_vgic/README_CN.md b/virtualization/arm_vgic/README_CN.md similarity index 98% rename from components/arm_vgic/README_CN.md rename to virtualization/arm_vgic/README_CN.md index 2bc41a5f97..a3cea24c68 100644 --- a/components/arm_vgic/README_CN.md +++ b/virtualization/arm_vgic/README_CN.md @@ -32,7 +32,7 @@ arm_vgic = "0.4.2" ```bash # 进入 crate 目录 -cd components/arm_vgic +cd virtualization/arm_vgic # 代码格式化 cargo fmt --all diff --git a/components/arm_vgic/src/consts.rs b/virtualization/arm_vgic/src/consts.rs similarity index 100% rename from components/arm_vgic/src/consts.rs rename to virtualization/arm_vgic/src/consts.rs diff --git a/components/arm_vgic/src/devops_impl.rs b/virtualization/arm_vgic/src/devops_impl.rs similarity index 100% rename from components/arm_vgic/src/devops_impl.rs rename to virtualization/arm_vgic/src/devops_impl.rs diff --git a/components/arm_vgic/src/interrupt.rs b/virtualization/arm_vgic/src/interrupt.rs similarity index 100% rename from components/arm_vgic/src/interrupt.rs rename to virtualization/arm_vgic/src/interrupt.rs diff --git a/components/arm_vgic/src/lib.rs b/virtualization/arm_vgic/src/lib.rs similarity index 100% rename from components/arm_vgic/src/lib.rs rename to virtualization/arm_vgic/src/lib.rs diff --git a/components/arm_vgic/src/list_register.rs b/virtualization/arm_vgic/src/list_register.rs similarity index 100% rename from components/arm_vgic/src/list_register.rs rename to virtualization/arm_vgic/src/list_register.rs diff --git a/components/arm_vgic/src/registers.rs b/virtualization/arm_vgic/src/registers.rs similarity index 100% rename from components/arm_vgic/src/registers.rs rename to virtualization/arm_vgic/src/registers.rs diff --git a/components/arm_vgic/src/v3/gits.rs b/virtualization/arm_vgic/src/v3/gits.rs similarity index 100% rename from components/arm_vgic/src/v3/gits.rs rename to virtualization/arm_vgic/src/v3/gits.rs diff --git a/components/arm_vgic/src/v3/mod.rs b/virtualization/arm_vgic/src/v3/mod.rs similarity index 100% rename from components/arm_vgic/src/v3/mod.rs rename to virtualization/arm_vgic/src/v3/mod.rs diff --git a/components/arm_vgic/src/v3/registers.rs b/virtualization/arm_vgic/src/v3/registers.rs similarity index 100% rename from components/arm_vgic/src/v3/registers.rs rename to virtualization/arm_vgic/src/v3/registers.rs diff --git a/components/arm_vgic/src/v3/utils.rs b/virtualization/arm_vgic/src/v3/utils.rs similarity index 100% rename from components/arm_vgic/src/v3/utils.rs rename to virtualization/arm_vgic/src/v3/utils.rs diff --git a/components/arm_vgic/src/v3/vgicd.rs b/virtualization/arm_vgic/src/v3/vgicd.rs similarity index 100% rename from components/arm_vgic/src/v3/vgicd.rs rename to virtualization/arm_vgic/src/v3/vgicd.rs diff --git a/components/arm_vgic/src/v3/vgicr.rs b/virtualization/arm_vgic/src/v3/vgicr.rs similarity index 100% rename from components/arm_vgic/src/v3/vgicr.rs rename to virtualization/arm_vgic/src/v3/vgicr.rs diff --git a/components/arm_vgic/src/vgic.rs b/virtualization/arm_vgic/src/vgic.rs similarity index 100% rename from components/arm_vgic/src/vgic.rs rename to virtualization/arm_vgic/src/vgic.rs diff --git a/components/arm_vgic/src/vgicd.rs b/virtualization/arm_vgic/src/vgicd.rs similarity index 100% rename from components/arm_vgic/src/vgicd.rs rename to virtualization/arm_vgic/src/vgicd.rs diff --git a/components/arm_vgic/src/vtimer/cntp_ctl_el0.rs b/virtualization/arm_vgic/src/vtimer/cntp_ctl_el0.rs similarity index 100% rename from components/arm_vgic/src/vtimer/cntp_ctl_el0.rs rename to virtualization/arm_vgic/src/vtimer/cntp_ctl_el0.rs diff --git a/components/arm_vgic/src/vtimer/cntp_tval_el0.rs b/virtualization/arm_vgic/src/vtimer/cntp_tval_el0.rs similarity index 100% rename from components/arm_vgic/src/vtimer/cntp_tval_el0.rs rename to virtualization/arm_vgic/src/vtimer/cntp_tval_el0.rs diff --git a/components/arm_vgic/src/vtimer/cntpct_el0.rs b/virtualization/arm_vgic/src/vtimer/cntpct_el0.rs similarity index 100% rename from components/arm_vgic/src/vtimer/cntpct_el0.rs rename to virtualization/arm_vgic/src/vtimer/cntpct_el0.rs diff --git a/components/arm_vgic/src/vtimer/mod.rs b/virtualization/arm_vgic/src/vtimer/mod.rs similarity index 100% rename from components/arm_vgic/src/vtimer/mod.rs rename to virtualization/arm_vgic/src/vtimer/mod.rs diff --git a/components/axdevice/.github/config.json b/virtualization/axdevice/.github/config.json similarity index 100% rename from components/axdevice/.github/config.json rename to virtualization/axdevice/.github/config.json diff --git a/components/axdevice/.github/workflows/check.yml b/virtualization/axdevice/.github/workflows/check.yml similarity index 100% rename from components/axdevice/.github/workflows/check.yml rename to virtualization/axdevice/.github/workflows/check.yml diff --git a/components/axdevice/.github/workflows/deploy.yml b/virtualization/axdevice/.github/workflows/deploy.yml similarity index 100% rename from components/axdevice/.github/workflows/deploy.yml rename to virtualization/axdevice/.github/workflows/deploy.yml diff --git a/components/axdevice/.github/workflows/push.yml b/virtualization/axdevice/.github/workflows/push.yml similarity index 100% rename from components/axdevice/.github/workflows/push.yml rename to virtualization/axdevice/.github/workflows/push.yml diff --git a/components/axdevice/.github/workflows/release.yml b/virtualization/axdevice/.github/workflows/release.yml similarity index 100% rename from components/axdevice/.github/workflows/release.yml rename to virtualization/axdevice/.github/workflows/release.yml diff --git a/components/axdevice/.github/workflows/test.yml b/virtualization/axdevice/.github/workflows/test.yml similarity index 100% rename from components/axdevice/.github/workflows/test.yml rename to virtualization/axdevice/.github/workflows/test.yml diff --git a/components/axdevice/.gitignore b/virtualization/axdevice/.gitignore similarity index 100% rename from components/axdevice/.gitignore rename to virtualization/axdevice/.gitignore diff --git a/components/axdevice/CHANGELOG.md b/virtualization/axdevice/CHANGELOG.md similarity index 100% rename from components/axdevice/CHANGELOG.md rename to virtualization/axdevice/CHANGELOG.md diff --git a/components/axdevice/Cargo.toml b/virtualization/axdevice/Cargo.toml similarity index 100% rename from components/axdevice/Cargo.toml rename to virtualization/axdevice/Cargo.toml diff --git a/components/axhvc/LICENSE b/virtualization/axdevice/LICENSE similarity index 100% rename from components/axhvc/LICENSE rename to virtualization/axdevice/LICENSE diff --git a/components/axdevice/README.md b/virtualization/axdevice/README.md similarity index 98% rename from components/axdevice/README.md rename to virtualization/axdevice/README.md index a077dc6f00..3f0c1fc29c 100644 --- a/components/axdevice/README.md +++ b/virtualization/axdevice/README.md @@ -32,7 +32,7 @@ axdevice = "0.4.2" ```bash # Enter the crate directory -cd components/axdevice +cd virtualization/axdevice # Format code cargo fmt --all diff --git a/components/axdevice/README_CN.md b/virtualization/axdevice/README_CN.md similarity index 98% rename from components/axdevice/README_CN.md rename to virtualization/axdevice/README_CN.md index 2280caa717..2d99bab629 100644 --- a/components/axdevice/README_CN.md +++ b/virtualization/axdevice/README_CN.md @@ -32,7 +32,7 @@ axdevice = "0.4.2" ```bash # 进入 crate 目录 -cd components/axdevice +cd virtualization/axdevice # 代码格式化 cargo fmt --all diff --git a/components/axdevice/scripts/check.sh b/virtualization/axdevice/scripts/check.sh similarity index 100% rename from components/axdevice/scripts/check.sh rename to virtualization/axdevice/scripts/check.sh diff --git a/components/axdevice/scripts/test.sh b/virtualization/axdevice/scripts/test.sh similarity index 100% rename from components/axdevice/scripts/test.sh rename to virtualization/axdevice/scripts/test.sh diff --git a/components/axdevice/src/config.rs b/virtualization/axdevice/src/config.rs similarity index 100% rename from components/axdevice/src/config.rs rename to virtualization/axdevice/src/config.rs diff --git a/components/axdevice/src/device.rs b/virtualization/axdevice/src/device.rs similarity index 100% rename from components/axdevice/src/device.rs rename to virtualization/axdevice/src/device.rs diff --git a/components/axdevice/src/lib.rs b/virtualization/axdevice/src/lib.rs similarity index 100% rename from components/axdevice/src/lib.rs rename to virtualization/axdevice/src/lib.rs diff --git a/components/axdevice/tests/test.rs b/virtualization/axdevice/tests/test.rs similarity index 100% rename from components/axdevice/tests/test.rs rename to virtualization/axdevice/tests/test.rs diff --git a/components/axdevice_base/.github/config.json b/virtualization/axdevice_base/.github/config.json similarity index 100% rename from components/axdevice_base/.github/config.json rename to virtualization/axdevice_base/.github/config.json diff --git a/components/axdevice_base/.github/workflows/check.yml b/virtualization/axdevice_base/.github/workflows/check.yml similarity index 100% rename from components/axdevice_base/.github/workflows/check.yml rename to virtualization/axdevice_base/.github/workflows/check.yml diff --git a/components/axdevice_base/.github/workflows/deploy.yml b/virtualization/axdevice_base/.github/workflows/deploy.yml similarity index 100% rename from components/axdevice_base/.github/workflows/deploy.yml rename to virtualization/axdevice_base/.github/workflows/deploy.yml diff --git a/components/axdevice_base/.github/workflows/push.yml b/virtualization/axdevice_base/.github/workflows/push.yml similarity index 100% rename from components/axdevice_base/.github/workflows/push.yml rename to virtualization/axdevice_base/.github/workflows/push.yml diff --git a/components/axdevice_base/.github/workflows/release.yml b/virtualization/axdevice_base/.github/workflows/release.yml similarity index 100% rename from components/axdevice_base/.github/workflows/release.yml rename to virtualization/axdevice_base/.github/workflows/release.yml diff --git a/components/axdevice_base/.github/workflows/test.yml b/virtualization/axdevice_base/.github/workflows/test.yml similarity index 100% rename from components/axdevice_base/.github/workflows/test.yml rename to virtualization/axdevice_base/.github/workflows/test.yml diff --git a/components/axdevice_base/.gitignore b/virtualization/axdevice_base/.gitignore similarity index 100% rename from components/axdevice_base/.gitignore rename to virtualization/axdevice_base/.gitignore diff --git a/components/axdevice_base/CHANGELOG.md b/virtualization/axdevice_base/CHANGELOG.md similarity index 100% rename from components/axdevice_base/CHANGELOG.md rename to virtualization/axdevice_base/CHANGELOG.md diff --git a/components/axdevice_base/Cargo.toml b/virtualization/axdevice_base/Cargo.toml similarity index 100% rename from components/axdevice_base/Cargo.toml rename to virtualization/axdevice_base/Cargo.toml diff --git a/components/axmm_crates/LICENSE b/virtualization/axdevice_base/LICENSE similarity index 100% rename from components/axmm_crates/LICENSE rename to virtualization/axdevice_base/LICENSE diff --git a/components/axdevice_base/README.md b/virtualization/axdevice_base/README.md similarity index 98% rename from components/axdevice_base/README.md rename to virtualization/axdevice_base/README.md index e9d212a99a..d40aadcde9 100644 --- a/components/axdevice_base/README.md +++ b/virtualization/axdevice_base/README.md @@ -32,7 +32,7 @@ axdevice_base = "0.4.2" ```bash # Enter the crate directory -cd components/axdevice_base +cd virtualization/axdevice_base # Format code cargo fmt --all diff --git a/components/axdevice_base/README_CN.md b/virtualization/axdevice_base/README_CN.md similarity index 98% rename from components/axdevice_base/README_CN.md rename to virtualization/axdevice_base/README_CN.md index 15c4f13d71..e7a2d47e23 100644 --- a/components/axdevice_base/README_CN.md +++ b/virtualization/axdevice_base/README_CN.md @@ -32,7 +32,7 @@ axdevice_base = "0.4.2" ```bash # 进入 crate 目录 -cd components/axdevice_base +cd virtualization/axdevice_base # 代码格式化 cargo fmt --all diff --git a/components/axdevice_base/scripts/check.sh b/virtualization/axdevice_base/scripts/check.sh similarity index 100% rename from components/axdevice_base/scripts/check.sh rename to virtualization/axdevice_base/scripts/check.sh diff --git a/components/axdevice_base/scripts/test.sh b/virtualization/axdevice_base/scripts/test.sh similarity index 100% rename from components/axdevice_base/scripts/test.sh rename to virtualization/axdevice_base/scripts/test.sh diff --git a/components/axdevice_base/src/lib.rs b/virtualization/axdevice_base/src/lib.rs similarity index 100% rename from components/axdevice_base/src/lib.rs rename to virtualization/axdevice_base/src/lib.rs diff --git a/components/axdevice_base/tests/test.rs b/virtualization/axdevice_base/tests/test.rs similarity index 100% rename from components/axdevice_base/tests/test.rs rename to virtualization/axdevice_base/tests/test.rs diff --git a/components/axhvc/.github/config.json b/virtualization/axhvc/.github/config.json similarity index 100% rename from components/axhvc/.github/config.json rename to virtualization/axhvc/.github/config.json diff --git a/components/axhvc/.github/workflows/check.yml b/virtualization/axhvc/.github/workflows/check.yml similarity index 100% rename from components/axhvc/.github/workflows/check.yml rename to virtualization/axhvc/.github/workflows/check.yml diff --git a/components/axhvc/.github/workflows/deploy.yml b/virtualization/axhvc/.github/workflows/deploy.yml similarity index 100% rename from components/axhvc/.github/workflows/deploy.yml rename to virtualization/axhvc/.github/workflows/deploy.yml diff --git a/components/axhvc/.github/workflows/push.yml b/virtualization/axhvc/.github/workflows/push.yml similarity index 100% rename from components/axhvc/.github/workflows/push.yml rename to virtualization/axhvc/.github/workflows/push.yml diff --git a/components/axhvc/.github/workflows/release.yml b/virtualization/axhvc/.github/workflows/release.yml similarity index 100% rename from components/axhvc/.github/workflows/release.yml rename to virtualization/axhvc/.github/workflows/release.yml diff --git a/components/axhvc/.github/workflows/test.yml b/virtualization/axhvc/.github/workflows/test.yml similarity index 100% rename from components/axhvc/.github/workflows/test.yml rename to virtualization/axhvc/.github/workflows/test.yml diff --git a/components/axhvc/.gitignore b/virtualization/axhvc/.gitignore similarity index 100% rename from components/axhvc/.gitignore rename to virtualization/axhvc/.gitignore diff --git a/components/axhvc/CHANGELOG.md b/virtualization/axhvc/CHANGELOG.md similarity index 100% rename from components/axhvc/CHANGELOG.md rename to virtualization/axhvc/CHANGELOG.md diff --git a/components/axhvc/Cargo.toml b/virtualization/axhvc/Cargo.toml similarity index 100% rename from components/axhvc/Cargo.toml rename to virtualization/axhvc/Cargo.toml diff --git a/components/axvcpu/LICENSE b/virtualization/axhvc/LICENSE similarity index 100% rename from components/axvcpu/LICENSE rename to virtualization/axhvc/LICENSE diff --git a/components/axhvc/README.md b/virtualization/axhvc/README.md similarity index 98% rename from components/axhvc/README.md rename to virtualization/axhvc/README.md index 97b6538ceb..58578020b3 100644 --- a/components/axhvc/README.md +++ b/virtualization/axhvc/README.md @@ -32,7 +32,7 @@ axhvc = "0.4.0" ```bash # Enter the crate directory -cd components/axhvc +cd virtualization/axhvc # Format code cargo fmt --all diff --git a/components/axhvc/README_CN.md b/virtualization/axhvc/README_CN.md similarity index 98% rename from components/axhvc/README_CN.md rename to virtualization/axhvc/README_CN.md index ee37167cff..d86e507f47 100644 --- a/components/axhvc/README_CN.md +++ b/virtualization/axhvc/README_CN.md @@ -32,7 +32,7 @@ axhvc = "0.4.0" ```bash # 进入 crate 目录 -cd components/axhvc +cd virtualization/axhvc # 代码格式化 cargo fmt --all diff --git a/components/axhvc/scripts/check.sh b/virtualization/axhvc/scripts/check.sh similarity index 100% rename from components/axhvc/scripts/check.sh rename to virtualization/axhvc/scripts/check.sh diff --git a/components/axhvc/scripts/test.sh b/virtualization/axhvc/scripts/test.sh similarity index 100% rename from components/axhvc/scripts/test.sh rename to virtualization/axhvc/scripts/test.sh diff --git a/components/axhvc/src/lib.rs b/virtualization/axhvc/src/lib.rs similarity index 100% rename from components/axhvc/src/lib.rs rename to virtualization/axhvc/src/lib.rs diff --git a/components/axhvc/tests/test.rs b/virtualization/axhvc/tests/test.rs similarity index 100% rename from components/axhvc/tests/test.rs rename to virtualization/axhvc/tests/test.rs diff --git a/components/axvcpu/.github/config.json b/virtualization/axvcpu/.github/config.json similarity index 100% rename from components/axvcpu/.github/config.json rename to virtualization/axvcpu/.github/config.json diff --git a/components/axvisor_api/.github/workflows/check.yml b/virtualization/axvcpu/.github/workflows/check.yml similarity index 100% rename from components/axvisor_api/.github/workflows/check.yml rename to virtualization/axvcpu/.github/workflows/check.yml diff --git a/components/axvcpu/.github/workflows/deploy.yml b/virtualization/axvcpu/.github/workflows/deploy.yml similarity index 100% rename from components/axvcpu/.github/workflows/deploy.yml rename to virtualization/axvcpu/.github/workflows/deploy.yml diff --git a/components/axvisor_api/.github/workflows/push.yml b/virtualization/axvcpu/.github/workflows/push.yml similarity index 100% rename from components/axvisor_api/.github/workflows/push.yml rename to virtualization/axvcpu/.github/workflows/push.yml diff --git a/components/axvisor_api/.github/workflows/release.yml b/virtualization/axvcpu/.github/workflows/release.yml similarity index 100% rename from components/axvisor_api/.github/workflows/release.yml rename to virtualization/axvcpu/.github/workflows/release.yml diff --git a/components/axvisor_api/.github/workflows/test.yml b/virtualization/axvcpu/.github/workflows/test.yml similarity index 100% rename from components/axvisor_api/.github/workflows/test.yml rename to virtualization/axvcpu/.github/workflows/test.yml diff --git a/components/axvcpu/.gitignore b/virtualization/axvcpu/.gitignore similarity index 100% rename from components/axvcpu/.gitignore rename to virtualization/axvcpu/.gitignore diff --git a/components/axvcpu/CHANGELOG.md b/virtualization/axvcpu/CHANGELOG.md similarity index 100% rename from components/axvcpu/CHANGELOG.md rename to virtualization/axvcpu/CHANGELOG.md diff --git a/components/axvcpu/Cargo.toml b/virtualization/axvcpu/Cargo.toml similarity index 100% rename from components/axvcpu/Cargo.toml rename to virtualization/axvcpu/Cargo.toml diff --git a/components/axvisor_api/LICENSE b/virtualization/axvcpu/LICENSE similarity index 100% rename from components/axvisor_api/LICENSE rename to virtualization/axvcpu/LICENSE diff --git a/components/axvcpu/README.md b/virtualization/axvcpu/README.md similarity index 98% rename from components/axvcpu/README.md rename to virtualization/axvcpu/README.md index 7dbbadcc5a..191a99f6d8 100644 --- a/components/axvcpu/README.md +++ b/virtualization/axvcpu/README.md @@ -32,7 +32,7 @@ axvcpu = "0.5.0" ```bash # Enter the crate directory -cd components/axvcpu +cd virtualization/axvcpu # Format code cargo fmt --all diff --git a/components/axvcpu/README_CN.md b/virtualization/axvcpu/README_CN.md similarity index 98% rename from components/axvcpu/README_CN.md rename to virtualization/axvcpu/README_CN.md index b361e5095e..1cd72ca1e4 100644 --- a/components/axvcpu/README_CN.md +++ b/virtualization/axvcpu/README_CN.md @@ -32,7 +32,7 @@ axvcpu = "0.5.0" ```bash # 进入 crate 目录 -cd components/axvcpu +cd virtualization/axvcpu # 代码格式化 cargo fmt --all diff --git a/components/axvcpu/src/arch_vcpu.rs b/virtualization/axvcpu/src/arch_vcpu.rs similarity index 100% rename from components/axvcpu/src/arch_vcpu.rs rename to virtualization/axvcpu/src/arch_vcpu.rs diff --git a/components/axvcpu/src/exit.rs b/virtualization/axvcpu/src/exit.rs similarity index 100% rename from components/axvcpu/src/exit.rs rename to virtualization/axvcpu/src/exit.rs diff --git a/components/axvcpu/src/lib.rs b/virtualization/axvcpu/src/lib.rs similarity index 100% rename from components/axvcpu/src/lib.rs rename to virtualization/axvcpu/src/lib.rs diff --git a/components/axvcpu/src/percpu.rs b/virtualization/axvcpu/src/percpu.rs similarity index 100% rename from components/axvcpu/src/percpu.rs rename to virtualization/axvcpu/src/percpu.rs diff --git a/components/axvcpu/src/test.rs b/virtualization/axvcpu/src/test.rs similarity index 100% rename from components/axvcpu/src/test.rs rename to virtualization/axvcpu/src/test.rs diff --git a/components/axvcpu/src/vcpu.rs b/virtualization/axvcpu/src/vcpu.rs similarity index 100% rename from components/axvcpu/src/vcpu.rs rename to virtualization/axvcpu/src/vcpu.rs diff --git a/components/axvisor_api/.github/config.json b/virtualization/axvisor_api/.github/config.json similarity index 100% rename from components/axvisor_api/.github/config.json rename to virtualization/axvisor_api/.github/config.json diff --git a/components/axvm/.github/workflows/check.yml b/virtualization/axvisor_api/.github/workflows/check.yml similarity index 100% rename from components/axvm/.github/workflows/check.yml rename to virtualization/axvisor_api/.github/workflows/check.yml diff --git a/components/axvisor_api/.github/workflows/deploy.yml b/virtualization/axvisor_api/.github/workflows/deploy.yml similarity index 100% rename from components/axvisor_api/.github/workflows/deploy.yml rename to virtualization/axvisor_api/.github/workflows/deploy.yml diff --git a/components/axvm/.github/workflows/push.yml b/virtualization/axvisor_api/.github/workflows/push.yml similarity index 100% rename from components/axvm/.github/workflows/push.yml rename to virtualization/axvisor_api/.github/workflows/push.yml diff --git a/components/axvm/.github/workflows/release.yml b/virtualization/axvisor_api/.github/workflows/release.yml similarity index 100% rename from components/axvm/.github/workflows/release.yml rename to virtualization/axvisor_api/.github/workflows/release.yml diff --git a/components/axvm/.github/workflows/test.yml b/virtualization/axvisor_api/.github/workflows/test.yml similarity index 100% rename from components/axvm/.github/workflows/test.yml rename to virtualization/axvisor_api/.github/workflows/test.yml diff --git a/components/axmm_crates/.gitignore b/virtualization/axvisor_api/.gitignore similarity index 100% rename from components/axmm_crates/.gitignore rename to virtualization/axvisor_api/.gitignore diff --git a/components/axvisor_api/.rustfmt.toml b/virtualization/axvisor_api/.rustfmt.toml similarity index 100% rename from components/axvisor_api/.rustfmt.toml rename to virtualization/axvisor_api/.rustfmt.toml diff --git a/components/axvisor_api/CHANGELOG.md b/virtualization/axvisor_api/CHANGELOG.md similarity index 100% rename from components/axvisor_api/CHANGELOG.md rename to virtualization/axvisor_api/CHANGELOG.md diff --git a/components/axvisor_api/Cargo.toml b/virtualization/axvisor_api/Cargo.toml similarity index 100% rename from components/axvisor_api/Cargo.toml rename to virtualization/axvisor_api/Cargo.toml diff --git a/components/axvm/LICENSE b/virtualization/axvisor_api/LICENSE similarity index 100% rename from components/axvm/LICENSE rename to virtualization/axvisor_api/LICENSE diff --git a/components/axvisor_api/README.md b/virtualization/axvisor_api/README.md similarity index 97% rename from components/axvisor_api/README.md rename to virtualization/axvisor_api/README.md index aa810820cc..ad29f8c69a 100644 --- a/components/axvisor_api/README.md +++ b/virtualization/axvisor_api/README.md @@ -25,7 +25,7 @@ English | [中文](README_CN.md) ```bash # Enter the workspace directory -cd components/axvisor_api +cd virtualization/axvisor_api # Format code cargo fmt --all diff --git a/components/axvisor_api/README.zh-cn.md b/virtualization/axvisor_api/README.zh-cn.md similarity index 100% rename from components/axvisor_api/README.zh-cn.md rename to virtualization/axvisor_api/README.zh-cn.md diff --git a/components/axvisor_api/README_CN.md b/virtualization/axvisor_api/README_CN.md similarity index 96% rename from components/axvisor_api/README_CN.md rename to virtualization/axvisor_api/README_CN.md index c16abc25bf..6b0a7583ea 100644 --- a/components/axvisor_api/README_CN.md +++ b/virtualization/axvisor_api/README_CN.md @@ -25,7 +25,7 @@ ```bash # 进入工作区目录 -cd components/axvisor_api +cd virtualization/axvisor_api # 代码格式化 cargo fmt --all diff --git a/components/axvisor_api/examples/example.rs b/virtualization/axvisor_api/examples/example.rs similarity index 100% rename from components/axvisor_api/examples/example.rs rename to virtualization/axvisor_api/examples/example.rs diff --git a/components/axvisor_api/src/arch.rs b/virtualization/axvisor_api/src/arch.rs similarity index 100% rename from components/axvisor_api/src/arch.rs rename to virtualization/axvisor_api/src/arch.rs diff --git a/components/axvisor_api/src/host.rs b/virtualization/axvisor_api/src/host.rs similarity index 100% rename from components/axvisor_api/src/host.rs rename to virtualization/axvisor_api/src/host.rs diff --git a/components/axvisor_api/src/lib.rs b/virtualization/axvisor_api/src/lib.rs similarity index 100% rename from components/axvisor_api/src/lib.rs rename to virtualization/axvisor_api/src/lib.rs diff --git a/components/axvisor_api/src/memory.rs b/virtualization/axvisor_api/src/memory.rs similarity index 100% rename from components/axvisor_api/src/memory.rs rename to virtualization/axvisor_api/src/memory.rs diff --git a/components/axvisor_api/src/test.rs b/virtualization/axvisor_api/src/test.rs similarity index 100% rename from components/axvisor_api/src/test.rs rename to virtualization/axvisor_api/src/test.rs diff --git a/components/axvisor_api/src/time.rs b/virtualization/axvisor_api/src/time.rs similarity index 100% rename from components/axvisor_api/src/time.rs rename to virtualization/axvisor_api/src/time.rs diff --git a/components/axvisor_api/src/vmm.rs b/virtualization/axvisor_api/src/vmm.rs similarity index 100% rename from components/axvisor_api/src/vmm.rs rename to virtualization/axvisor_api/src/vmm.rs diff --git a/components/axvisor_api/axvisor_api_proc/Cargo.toml b/virtualization/axvisor_api_proc/Cargo.toml similarity index 100% rename from components/axvisor_api/axvisor_api_proc/Cargo.toml rename to virtualization/axvisor_api_proc/Cargo.toml diff --git a/components/axvisor_api/axvisor_api_proc/README.md b/virtualization/axvisor_api_proc/README.md similarity index 97% rename from components/axvisor_api/axvisor_api_proc/README.md rename to virtualization/axvisor_api_proc/README.md index 5265d07564..c17127acfb 100644 --- a/components/axvisor_api/axvisor_api_proc/README.md +++ b/virtualization/axvisor_api_proc/README.md @@ -32,7 +32,7 @@ axvisor_api_proc = "0.5.0" ```bash # Enter the crate directory -cd components/axvisor_api/axvisor_api_proc +cd virtualization/axvisor_api_proc # Format code cargo fmt --all diff --git a/components/axvisor_api/axvisor_api_proc/README_CN.md b/virtualization/axvisor_api_proc/README_CN.md similarity index 97% rename from components/axvisor_api/axvisor_api_proc/README_CN.md rename to virtualization/axvisor_api_proc/README_CN.md index 7945c43c46..26d68a2f18 100644 --- a/components/axvisor_api/axvisor_api_proc/README_CN.md +++ b/virtualization/axvisor_api_proc/README_CN.md @@ -32,7 +32,7 @@ axvisor_api_proc = "0.5.0" ```bash # 进入 crate 目录 -cd components/axvisor_api/axvisor_api_proc +cd virtualization/axvisor_api_proc # 代码格式化 cargo fmt --all diff --git a/components/axvisor_api/axvisor_api_proc/src/lib.rs b/virtualization/axvisor_api_proc/src/lib.rs similarity index 100% rename from components/axvisor_api/axvisor_api_proc/src/lib.rs rename to virtualization/axvisor_api_proc/src/lib.rs diff --git a/components/axvm/.github/config.json b/virtualization/axvm/.github/config.json similarity index 100% rename from components/axvm/.github/config.json rename to virtualization/axvm/.github/config.json diff --git a/components/axvmconfig/.github/workflows/check.yml b/virtualization/axvm/.github/workflows/check.yml similarity index 100% rename from components/axvmconfig/.github/workflows/check.yml rename to virtualization/axvm/.github/workflows/check.yml diff --git a/components/range-alloc-arceos/.github/workflows/deploy.yml b/virtualization/axvm/.github/workflows/deploy.yml similarity index 100% rename from components/range-alloc-arceos/.github/workflows/deploy.yml rename to virtualization/axvm/.github/workflows/deploy.yml diff --git a/components/axvmconfig/.github/workflows/push.yml b/virtualization/axvm/.github/workflows/push.yml similarity index 100% rename from components/axvmconfig/.github/workflows/push.yml rename to virtualization/axvm/.github/workflows/push.yml diff --git a/components/axvmconfig/.github/workflows/release.yml b/virtualization/axvm/.github/workflows/release.yml similarity index 100% rename from components/axvmconfig/.github/workflows/release.yml rename to virtualization/axvm/.github/workflows/release.yml diff --git a/components/axvmconfig/.github/workflows/test.yml b/virtualization/axvm/.github/workflows/test.yml similarity index 100% rename from components/axvmconfig/.github/workflows/test.yml rename to virtualization/axvm/.github/workflows/test.yml diff --git a/components/axvm/.gitignore b/virtualization/axvm/.gitignore similarity index 100% rename from components/axvm/.gitignore rename to virtualization/axvm/.gitignore diff --git a/components/axvm/CHANGELOG.md b/virtualization/axvm/CHANGELOG.md similarity index 100% rename from components/axvm/CHANGELOG.md rename to virtualization/axvm/CHANGELOG.md diff --git a/components/axvm/Cargo.toml b/virtualization/axvm/Cargo.toml similarity index 100% rename from components/axvm/Cargo.toml rename to virtualization/axvm/Cargo.toml diff --git a/components/axvmconfig/LICENSE b/virtualization/axvm/LICENSE similarity index 100% rename from components/axvmconfig/LICENSE rename to virtualization/axvm/LICENSE diff --git a/components/axvm/README.md b/virtualization/axvm/README.md similarity index 98% rename from components/axvm/README.md rename to virtualization/axvm/README.md index 0563b8cd90..3bf39333d4 100644 --- a/components/axvm/README.md +++ b/virtualization/axvm/README.md @@ -32,7 +32,7 @@ axvm = "0.5.0" ```bash # Enter the crate directory -cd components/axvm +cd virtualization/axvm # Format code cargo fmt --all diff --git a/components/axvm/README_CN.md b/virtualization/axvm/README_CN.md similarity index 98% rename from components/axvm/README_CN.md rename to virtualization/axvm/README_CN.md index ca7005c28b..4fbf8e3197 100644 --- a/components/axvm/README_CN.md +++ b/virtualization/axvm/README_CN.md @@ -32,7 +32,7 @@ axvm = "0.5.0" ```bash # 进入 crate 目录 -cd components/axvm +cd virtualization/axvm # 代码格式化 cargo fmt --all diff --git a/components/axvm/src/config.rs b/virtualization/axvm/src/config.rs similarity index 100% rename from components/axvm/src/config.rs rename to virtualization/axvm/src/config.rs diff --git a/components/axvm/src/hal.rs b/virtualization/axvm/src/hal.rs similarity index 100% rename from components/axvm/src/hal.rs rename to virtualization/axvm/src/hal.rs diff --git a/components/axvm/src/lib.rs b/virtualization/axvm/src/lib.rs similarity index 100% rename from components/axvm/src/lib.rs rename to virtualization/axvm/src/lib.rs diff --git a/components/axvm/src/vcpu.rs b/virtualization/axvm/src/vcpu.rs similarity index 100% rename from components/axvm/src/vcpu.rs rename to virtualization/axvm/src/vcpu.rs diff --git a/components/axvm/src/vm.rs b/virtualization/axvm/src/vm.rs similarity index 100% rename from components/axvm/src/vm.rs rename to virtualization/axvm/src/vm.rs diff --git a/components/axvmconfig/.github/config.json b/virtualization/axvmconfig/.github/config.json similarity index 100% rename from components/axvmconfig/.github/config.json rename to virtualization/axvmconfig/.github/config.json diff --git a/components/range-alloc-arceos/.github/workflows/check.yml b/virtualization/axvmconfig/.github/workflows/check.yml similarity index 100% rename from components/range-alloc-arceos/.github/workflows/check.yml rename to virtualization/axvmconfig/.github/workflows/check.yml diff --git a/components/axvmconfig/.github/workflows/deploy.yml b/virtualization/axvmconfig/.github/workflows/deploy.yml similarity index 100% rename from components/axvmconfig/.github/workflows/deploy.yml rename to virtualization/axvmconfig/.github/workflows/deploy.yml diff --git a/components/range-alloc-arceos/.github/workflows/push.yml b/virtualization/axvmconfig/.github/workflows/push.yml similarity index 100% rename from components/range-alloc-arceos/.github/workflows/push.yml rename to virtualization/axvmconfig/.github/workflows/push.yml diff --git a/components/range-alloc-arceos/.github/workflows/release.yml b/virtualization/axvmconfig/.github/workflows/release.yml similarity index 100% rename from components/range-alloc-arceos/.github/workflows/release.yml rename to virtualization/axvmconfig/.github/workflows/release.yml diff --git a/components/range-alloc-arceos/.github/workflows/test.yml b/virtualization/axvmconfig/.github/workflows/test.yml similarity index 100% rename from components/range-alloc-arceos/.github/workflows/test.yml rename to virtualization/axvmconfig/.github/workflows/test.yml diff --git a/components/axvisor_api/.gitignore b/virtualization/axvmconfig/.gitignore similarity index 100% rename from components/axvisor_api/.gitignore rename to virtualization/axvmconfig/.gitignore diff --git a/components/axvmconfig/CHANGELOG.md b/virtualization/axvmconfig/CHANGELOG.md similarity index 100% rename from components/axvmconfig/CHANGELOG.md rename to virtualization/axvmconfig/CHANGELOG.md diff --git a/components/axvmconfig/Cargo.toml b/virtualization/axvmconfig/Cargo.toml similarity index 100% rename from components/axvmconfig/Cargo.toml rename to virtualization/axvmconfig/Cargo.toml diff --git a/components/bitmap-allocator/LICENSE b/virtualization/axvmconfig/LICENSE similarity index 100% rename from components/bitmap-allocator/LICENSE rename to virtualization/axvmconfig/LICENSE diff --git a/components/axvmconfig/README.md b/virtualization/axvmconfig/README.md similarity index 98% rename from components/axvmconfig/README.md rename to virtualization/axvmconfig/README.md index c4c37283bb..759a1668fe 100644 --- a/components/axvmconfig/README.md +++ b/virtualization/axvmconfig/README.md @@ -32,7 +32,7 @@ axvmconfig = "0.4.2" ```bash # Enter the crate directory -cd components/axvmconfig +cd virtualization/axvmconfig # Format code cargo fmt --all diff --git a/components/axvmconfig/README_CN.md b/virtualization/axvmconfig/README_CN.md similarity index 98% rename from components/axvmconfig/README_CN.md rename to virtualization/axvmconfig/README_CN.md index 0256266e13..bde4d52723 100644 --- a/components/axvmconfig/README_CN.md +++ b/virtualization/axvmconfig/README_CN.md @@ -32,7 +32,7 @@ axvmconfig = "0.4.2" ```bash # 进入 crate 目录 -cd components/axvmconfig +cd virtualization/axvmconfig # 代码格式化 cargo fmt --all diff --git a/components/axvmconfig/src/lib.rs b/virtualization/axvmconfig/src/lib.rs similarity index 100% rename from components/axvmconfig/src/lib.rs rename to virtualization/axvmconfig/src/lib.rs diff --git a/components/axvmconfig/src/main.rs b/virtualization/axvmconfig/src/main.rs similarity index 100% rename from components/axvmconfig/src/main.rs rename to virtualization/axvmconfig/src/main.rs diff --git a/components/axvmconfig/src/templates.rs b/virtualization/axvmconfig/src/templates.rs similarity index 100% rename from components/axvmconfig/src/templates.rs rename to virtualization/axvmconfig/src/templates.rs diff --git a/components/axvmconfig/src/test.rs b/virtualization/axvmconfig/src/test.rs similarity index 100% rename from components/axvmconfig/src/test.rs rename to virtualization/axvmconfig/src/test.rs diff --git a/components/axvmconfig/src/tool.rs b/virtualization/axvmconfig/src/tool.rs similarity index 100% rename from components/axvmconfig/src/tool.rs rename to virtualization/axvmconfig/src/tool.rs diff --git a/components/axvmconfig/templates/aarch64.toml b/virtualization/axvmconfig/templates/aarch64.toml similarity index 100% rename from components/axvmconfig/templates/aarch64.toml rename to virtualization/axvmconfig/templates/aarch64.toml diff --git a/components/axvmconfig/templates/riscv64.toml b/virtualization/axvmconfig/templates/riscv64.toml similarity index 100% rename from components/axvmconfig/templates/riscv64.toml rename to virtualization/axvmconfig/templates/riscv64.toml diff --git a/components/axvmconfig/templates/x86_64.toml b/virtualization/axvmconfig/templates/x86_64.toml similarity index 100% rename from components/axvmconfig/templates/x86_64.toml rename to virtualization/axvmconfig/templates/x86_64.toml diff --git a/components/loongarch_vcpu/CHANGELOG.md b/virtualization/loongarch_vcpu/CHANGELOG.md similarity index 100% rename from components/loongarch_vcpu/CHANGELOG.md rename to virtualization/loongarch_vcpu/CHANGELOG.md diff --git a/components/loongarch_vcpu/Cargo.toml b/virtualization/loongarch_vcpu/Cargo.toml similarity index 100% rename from components/loongarch_vcpu/Cargo.toml rename to virtualization/loongarch_vcpu/Cargo.toml diff --git a/components/loongarch_vcpu/src/context_frame.rs b/virtualization/loongarch_vcpu/src/context_frame.rs similarity index 100% rename from components/loongarch_vcpu/src/context_frame.rs rename to virtualization/loongarch_vcpu/src/context_frame.rs diff --git a/components/loongarch_vcpu/src/exception.S b/virtualization/loongarch_vcpu/src/exception.S similarity index 100% rename from components/loongarch_vcpu/src/exception.S rename to virtualization/loongarch_vcpu/src/exception.S diff --git a/components/loongarch_vcpu/src/exception.rs b/virtualization/loongarch_vcpu/src/exception.rs similarity index 100% rename from components/loongarch_vcpu/src/exception.rs rename to virtualization/loongarch_vcpu/src/exception.rs diff --git a/components/loongarch_vcpu/src/lib.rs b/virtualization/loongarch_vcpu/src/lib.rs similarity index 100% rename from components/loongarch_vcpu/src/lib.rs rename to virtualization/loongarch_vcpu/src/lib.rs diff --git a/components/loongarch_vcpu/src/pcpu.rs b/virtualization/loongarch_vcpu/src/pcpu.rs similarity index 100% rename from components/loongarch_vcpu/src/pcpu.rs rename to virtualization/loongarch_vcpu/src/pcpu.rs diff --git a/components/loongarch_vcpu/src/registers.rs b/virtualization/loongarch_vcpu/src/registers.rs similarity index 100% rename from components/loongarch_vcpu/src/registers.rs rename to virtualization/loongarch_vcpu/src/registers.rs diff --git a/components/loongarch_vcpu/src/vcpu.rs b/virtualization/loongarch_vcpu/src/vcpu.rs similarity index 100% rename from components/loongarch_vcpu/src/vcpu.rs rename to virtualization/loongarch_vcpu/src/vcpu.rs diff --git a/components/riscv-h/.github/config.json b/virtualization/riscv-h/.github/config.json similarity index 100% rename from components/riscv-h/.github/config.json rename to virtualization/riscv-h/.github/config.json diff --git a/components/riscv-h/.github/workflows/check.yml b/virtualization/riscv-h/.github/workflows/check.yml similarity index 100% rename from components/riscv-h/.github/workflows/check.yml rename to virtualization/riscv-h/.github/workflows/check.yml diff --git a/components/riscv-h/.github/workflows/deploy.yml b/virtualization/riscv-h/.github/workflows/deploy.yml similarity index 100% rename from components/riscv-h/.github/workflows/deploy.yml rename to virtualization/riscv-h/.github/workflows/deploy.yml diff --git a/components/riscv-h/.github/workflows/push.yml b/virtualization/riscv-h/.github/workflows/push.yml similarity index 100% rename from components/riscv-h/.github/workflows/push.yml rename to virtualization/riscv-h/.github/workflows/push.yml diff --git a/components/riscv-h/.github/workflows/release.yml b/virtualization/riscv-h/.github/workflows/release.yml similarity index 100% rename from components/riscv-h/.github/workflows/release.yml rename to virtualization/riscv-h/.github/workflows/release.yml diff --git a/components/riscv-h/.github/workflows/test.yml b/virtualization/riscv-h/.github/workflows/test.yml similarity index 100% rename from components/riscv-h/.github/workflows/test.yml rename to virtualization/riscv-h/.github/workflows/test.yml diff --git a/components/riscv-h/.gitignore b/virtualization/riscv-h/.gitignore similarity index 100% rename from components/riscv-h/.gitignore rename to virtualization/riscv-h/.gitignore diff --git a/components/riscv-h/CHANGELOG.md b/virtualization/riscv-h/CHANGELOG.md similarity index 100% rename from components/riscv-h/CHANGELOG.md rename to virtualization/riscv-h/CHANGELOG.md diff --git a/components/riscv-h/Cargo.toml b/virtualization/riscv-h/Cargo.toml similarity index 100% rename from components/riscv-h/Cargo.toml rename to virtualization/riscv-h/Cargo.toml diff --git a/components/page_table_multiarch/LICENSE b/virtualization/riscv-h/LICENSE similarity index 100% rename from components/page_table_multiarch/LICENSE rename to virtualization/riscv-h/LICENSE diff --git a/components/riscv-h/README.md b/virtualization/riscv-h/README.md similarity index 98% rename from components/riscv-h/README.md rename to virtualization/riscv-h/README.md index a858042e33..1e179c1efa 100644 --- a/components/riscv-h/README.md +++ b/virtualization/riscv-h/README.md @@ -32,7 +32,7 @@ riscv-h = "0.4.0" ```bash # Enter the crate directory -cd components/riscv-h +cd virtualization/riscv-h # Format code cargo fmt --all diff --git a/components/riscv-h/README_CN.md b/virtualization/riscv-h/README_CN.md similarity index 98% rename from components/riscv-h/README_CN.md rename to virtualization/riscv-h/README_CN.md index f9f2458df0..8bb4855f6c 100644 --- a/components/riscv-h/README_CN.md +++ b/virtualization/riscv-h/README_CN.md @@ -32,7 +32,7 @@ riscv-h = "0.4.0" ```bash # 进入 crate 目录 -cd components/riscv-h +cd virtualization/riscv-h # 代码格式化 cargo fmt --all diff --git a/components/riscv-h/scripts/check.sh b/virtualization/riscv-h/scripts/check.sh similarity index 100% rename from components/riscv-h/scripts/check.sh rename to virtualization/riscv-h/scripts/check.sh diff --git a/components/riscv-h/scripts/test.sh b/virtualization/riscv-h/scripts/test.sh similarity index 100% rename from components/riscv-h/scripts/test.sh rename to virtualization/riscv-h/scripts/test.sh diff --git a/components/riscv-h/src/lib.rs b/virtualization/riscv-h/src/lib.rs similarity index 100% rename from components/riscv-h/src/lib.rs rename to virtualization/riscv-h/src/lib.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hcounteren.rs b/virtualization/riscv-h/src/register/hypervisorx64/hcounteren.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hcounteren.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hcounteren.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hedeleg.rs b/virtualization/riscv-h/src/register/hypervisorx64/hedeleg.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hedeleg.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hedeleg.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hgatp.rs b/virtualization/riscv-h/src/register/hypervisorx64/hgatp.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hgatp.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hgatp.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hgeie.rs b/virtualization/riscv-h/src/register/hypervisorx64/hgeie.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hgeie.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hgeie.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hgeip.rs b/virtualization/riscv-h/src/register/hypervisorx64/hgeip.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hgeip.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hgeip.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hideleg.rs b/virtualization/riscv-h/src/register/hypervisorx64/hideleg.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hideleg.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hideleg.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hie.rs b/virtualization/riscv-h/src/register/hypervisorx64/hie.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hie.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hie.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hip.rs b/virtualization/riscv-h/src/register/hypervisorx64/hip.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hip.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hip.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hstatus.rs b/virtualization/riscv-h/src/register/hypervisorx64/hstatus.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hstatus.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hstatus.rs diff --git a/components/riscv-h/src/register/hypervisorx64/htimedelta.rs b/virtualization/riscv-h/src/register/hypervisorx64/htimedelta.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/htimedelta.rs rename to virtualization/riscv-h/src/register/hypervisorx64/htimedelta.rs diff --git a/components/riscv-h/src/register/hypervisorx64/htimedeltah.rs b/virtualization/riscv-h/src/register/hypervisorx64/htimedeltah.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/htimedeltah.rs rename to virtualization/riscv-h/src/register/hypervisorx64/htimedeltah.rs diff --git a/components/riscv-h/src/register/hypervisorx64/htinst.rs b/virtualization/riscv-h/src/register/hypervisorx64/htinst.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/htinst.rs rename to virtualization/riscv-h/src/register/hypervisorx64/htinst.rs diff --git a/components/riscv-h/src/register/hypervisorx64/htval.rs b/virtualization/riscv-h/src/register/hypervisorx64/htval.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/htval.rs rename to virtualization/riscv-h/src/register/hypervisorx64/htval.rs diff --git a/components/riscv-h/src/register/hypervisorx64/hvip.rs b/virtualization/riscv-h/src/register/hypervisorx64/hvip.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/hvip.rs rename to virtualization/riscv-h/src/register/hypervisorx64/hvip.rs diff --git a/components/riscv-h/src/register/hypervisorx64/mod.rs b/virtualization/riscv-h/src/register/hypervisorx64/mod.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/mod.rs rename to virtualization/riscv-h/src/register/hypervisorx64/mod.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vsatp.rs b/virtualization/riscv-h/src/register/hypervisorx64/vsatp.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vsatp.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vsatp.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vscause.rs b/virtualization/riscv-h/src/register/hypervisorx64/vscause.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vscause.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vscause.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vsepc.rs b/virtualization/riscv-h/src/register/hypervisorx64/vsepc.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vsepc.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vsepc.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vsie.rs b/virtualization/riscv-h/src/register/hypervisorx64/vsie.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vsie.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vsie.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vsip.rs b/virtualization/riscv-h/src/register/hypervisorx64/vsip.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vsip.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vsip.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vsscratch.rs b/virtualization/riscv-h/src/register/hypervisorx64/vsscratch.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vsscratch.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vsscratch.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vsstatus.rs b/virtualization/riscv-h/src/register/hypervisorx64/vsstatus.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vsstatus.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vsstatus.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vstimecmp.rs b/virtualization/riscv-h/src/register/hypervisorx64/vstimecmp.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vstimecmp.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vstimecmp.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vstval.rs b/virtualization/riscv-h/src/register/hypervisorx64/vstval.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vstval.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vstval.rs diff --git a/components/riscv-h/src/register/hypervisorx64/vstvec.rs b/virtualization/riscv-h/src/register/hypervisorx64/vstvec.rs similarity index 100% rename from components/riscv-h/src/register/hypervisorx64/vstvec.rs rename to virtualization/riscv-h/src/register/hypervisorx64/vstvec.rs diff --git a/components/riscv-h/src/register/mod.rs b/virtualization/riscv-h/src/register/mod.rs similarity index 100% rename from components/riscv-h/src/register/mod.rs rename to virtualization/riscv-h/src/register/mod.rs diff --git a/components/riscv-h/tests/integration_tests.rs b/virtualization/riscv-h/tests/integration_tests.rs similarity index 100% rename from components/riscv-h/tests/integration_tests.rs rename to virtualization/riscv-h/tests/integration_tests.rs diff --git a/components/riscv-h/tests/register_tests.rs b/virtualization/riscv-h/tests/register_tests.rs similarity index 100% rename from components/riscv-h/tests/register_tests.rs rename to virtualization/riscv-h/tests/register_tests.rs diff --git a/components/riscv_vcpu/.cargo/config.toml b/virtualization/riscv_vcpu/.cargo/config.toml similarity index 100% rename from components/riscv_vcpu/.cargo/config.toml rename to virtualization/riscv_vcpu/.cargo/config.toml diff --git a/components/riscv_vcpu/.github/config.json b/virtualization/riscv_vcpu/.github/config.json similarity index 100% rename from components/riscv_vcpu/.github/config.json rename to virtualization/riscv_vcpu/.github/config.json diff --git a/components/riscv_vcpu/.github/workflows/check.yml b/virtualization/riscv_vcpu/.github/workflows/check.yml similarity index 100% rename from components/riscv_vcpu/.github/workflows/check.yml rename to virtualization/riscv_vcpu/.github/workflows/check.yml diff --git a/components/riscv_vcpu/.github/workflows/deploy.yml b/virtualization/riscv_vcpu/.github/workflows/deploy.yml similarity index 100% rename from components/riscv_vcpu/.github/workflows/deploy.yml rename to virtualization/riscv_vcpu/.github/workflows/deploy.yml diff --git a/components/riscv_vcpu/.github/workflows/push.yml b/virtualization/riscv_vcpu/.github/workflows/push.yml similarity index 100% rename from components/riscv_vcpu/.github/workflows/push.yml rename to virtualization/riscv_vcpu/.github/workflows/push.yml diff --git a/components/riscv_vcpu/.github/workflows/release.yml b/virtualization/riscv_vcpu/.github/workflows/release.yml similarity index 100% rename from components/riscv_vcpu/.github/workflows/release.yml rename to virtualization/riscv_vcpu/.github/workflows/release.yml diff --git a/components/riscv_vcpu/.github/workflows/test.yml b/virtualization/riscv_vcpu/.github/workflows/test.yml similarity index 100% rename from components/riscv_vcpu/.github/workflows/test.yml rename to virtualization/riscv_vcpu/.github/workflows/test.yml diff --git a/components/riscv_vcpu/.gitignore b/virtualization/riscv_vcpu/.gitignore similarity index 100% rename from components/riscv_vcpu/.gitignore rename to virtualization/riscv_vcpu/.gitignore diff --git a/components/riscv_vcpu/CHANGELOG.md b/virtualization/riscv_vcpu/CHANGELOG.md similarity index 100% rename from components/riscv_vcpu/CHANGELOG.md rename to virtualization/riscv_vcpu/CHANGELOG.md diff --git a/components/riscv_vcpu/Cargo.toml b/virtualization/riscv_vcpu/Cargo.toml similarity index 100% rename from components/riscv_vcpu/Cargo.toml rename to virtualization/riscv_vcpu/Cargo.toml diff --git a/components/range-alloc-arceos/LICENSE b/virtualization/riscv_vcpu/LICENSE similarity index 100% rename from components/range-alloc-arceos/LICENSE rename to virtualization/riscv_vcpu/LICENSE diff --git a/components/riscv_vcpu/README.md b/virtualization/riscv_vcpu/README.md similarity index 98% rename from components/riscv_vcpu/README.md rename to virtualization/riscv_vcpu/README.md index 768d61ef86..dab6968e05 100644 --- a/components/riscv_vcpu/README.md +++ b/virtualization/riscv_vcpu/README.md @@ -32,7 +32,7 @@ riscv_vcpu = "0.5.0" ```bash # Enter the crate directory -cd components/riscv_vcpu +cd virtualization/riscv_vcpu # Format code cargo fmt --all diff --git a/components/riscv_vcpu/README_CN.md b/virtualization/riscv_vcpu/README_CN.md similarity index 98% rename from components/riscv_vcpu/README_CN.md rename to virtualization/riscv_vcpu/README_CN.md index e1f0e1d878..7e8cbe9582 100644 --- a/components/riscv_vcpu/README_CN.md +++ b/virtualization/riscv_vcpu/README_CN.md @@ -32,7 +32,7 @@ riscv_vcpu = "0.5.0" ```bash # 进入 crate 目录 -cd components/riscv_vcpu +cd virtualization/riscv_vcpu # 代码格式化 cargo fmt --all diff --git a/components/riscv_vcpu/src/consts.rs b/virtualization/riscv_vcpu/src/consts.rs similarity index 100% rename from components/riscv_vcpu/src/consts.rs rename to virtualization/riscv_vcpu/src/consts.rs diff --git a/components/riscv_vcpu/src/detect.rs b/virtualization/riscv_vcpu/src/detect.rs similarity index 100% rename from components/riscv_vcpu/src/detect.rs rename to virtualization/riscv_vcpu/src/detect.rs diff --git a/components/riscv_vcpu/src/guest_mem.rs b/virtualization/riscv_vcpu/src/guest_mem.rs similarity index 100% rename from components/riscv_vcpu/src/guest_mem.rs rename to virtualization/riscv_vcpu/src/guest_mem.rs diff --git a/components/riscv_vcpu/src/lib.rs b/virtualization/riscv_vcpu/src/lib.rs similarity index 100% rename from components/riscv_vcpu/src/lib.rs rename to virtualization/riscv_vcpu/src/lib.rs diff --git a/components/riscv_vcpu/src/mem_extable.S b/virtualization/riscv_vcpu/src/mem_extable.S similarity index 100% rename from components/riscv_vcpu/src/mem_extable.S rename to virtualization/riscv_vcpu/src/mem_extable.S diff --git a/components/riscv_vcpu/src/percpu.rs b/virtualization/riscv_vcpu/src/percpu.rs similarity index 100% rename from components/riscv_vcpu/src/percpu.rs rename to virtualization/riscv_vcpu/src/percpu.rs diff --git a/components/riscv_vcpu/src/regs.rs b/virtualization/riscv_vcpu/src/regs.rs similarity index 100% rename from components/riscv_vcpu/src/regs.rs rename to virtualization/riscv_vcpu/src/regs.rs diff --git a/components/riscv_vcpu/src/sbi_console.rs b/virtualization/riscv_vcpu/src/sbi_console.rs similarity index 100% rename from components/riscv_vcpu/src/sbi_console.rs rename to virtualization/riscv_vcpu/src/sbi_console.rs diff --git a/components/riscv_vcpu/src/trap.S b/virtualization/riscv_vcpu/src/trap.S similarity index 100% rename from components/riscv_vcpu/src/trap.S rename to virtualization/riscv_vcpu/src/trap.S diff --git a/components/riscv_vcpu/src/trap.rs b/virtualization/riscv_vcpu/src/trap.rs similarity index 100% rename from components/riscv_vcpu/src/trap.rs rename to virtualization/riscv_vcpu/src/trap.rs diff --git a/components/riscv_vcpu/src/vcpu.rs b/virtualization/riscv_vcpu/src/vcpu.rs similarity index 100% rename from components/riscv_vcpu/src/vcpu.rs rename to virtualization/riscv_vcpu/src/vcpu.rs diff --git a/components/riscv_vcpu/src/vpmu.rs b/virtualization/riscv_vcpu/src/vpmu.rs similarity index 100% rename from components/riscv_vcpu/src/vpmu.rs rename to virtualization/riscv_vcpu/src/vpmu.rs diff --git a/components/riscv_vplic/.github/config.json b/virtualization/riscv_vplic/.github/config.json similarity index 100% rename from components/riscv_vplic/.github/config.json rename to virtualization/riscv_vplic/.github/config.json diff --git a/components/riscv_vplic/.github/workflows/check.yml b/virtualization/riscv_vplic/.github/workflows/check.yml similarity index 100% rename from components/riscv_vplic/.github/workflows/check.yml rename to virtualization/riscv_vplic/.github/workflows/check.yml diff --git a/components/riscv_vplic/.github/workflows/deploy.yml b/virtualization/riscv_vplic/.github/workflows/deploy.yml similarity index 100% rename from components/riscv_vplic/.github/workflows/deploy.yml rename to virtualization/riscv_vplic/.github/workflows/deploy.yml diff --git a/components/riscv_vplic/.github/workflows/push.yml b/virtualization/riscv_vplic/.github/workflows/push.yml similarity index 100% rename from components/riscv_vplic/.github/workflows/push.yml rename to virtualization/riscv_vplic/.github/workflows/push.yml diff --git a/components/riscv_vplic/.github/workflows/release.yml b/virtualization/riscv_vplic/.github/workflows/release.yml similarity index 100% rename from components/riscv_vplic/.github/workflows/release.yml rename to virtualization/riscv_vplic/.github/workflows/release.yml diff --git a/components/riscv_vplic/.github/workflows/test.yml b/virtualization/riscv_vplic/.github/workflows/test.yml similarity index 100% rename from components/riscv_vplic/.github/workflows/test.yml rename to virtualization/riscv_vplic/.github/workflows/test.yml diff --git a/components/riscv_vplic/.gitignore b/virtualization/riscv_vplic/.gitignore similarity index 100% rename from components/riscv_vplic/.gitignore rename to virtualization/riscv_vplic/.gitignore diff --git a/components/riscv_vplic/CHANGELOG.md b/virtualization/riscv_vplic/CHANGELOG.md similarity index 100% rename from components/riscv_vplic/CHANGELOG.md rename to virtualization/riscv_vplic/CHANGELOG.md diff --git a/components/riscv_vplic/Cargo.toml b/virtualization/riscv_vplic/Cargo.toml similarity index 100% rename from components/riscv_vplic/Cargo.toml rename to virtualization/riscv_vplic/Cargo.toml diff --git a/components/riscv-h/LICENSE b/virtualization/riscv_vplic/LICENSE similarity index 100% rename from components/riscv-h/LICENSE rename to virtualization/riscv_vplic/LICENSE diff --git a/components/riscv_vplic/README.md b/virtualization/riscv_vplic/README.md similarity index 98% rename from components/riscv_vplic/README.md rename to virtualization/riscv_vplic/README.md index 14016787db..caf1b44fa2 100644 --- a/components/riscv_vplic/README.md +++ b/virtualization/riscv_vplic/README.md @@ -32,7 +32,7 @@ riscv_vplic = "0.4.2" ```bash # Enter the crate directory -cd components/riscv_vplic +cd virtualization/riscv_vplic # Format code cargo fmt --all diff --git a/components/riscv_vplic/README_CN.md b/virtualization/riscv_vplic/README_CN.md similarity index 98% rename from components/riscv_vplic/README_CN.md rename to virtualization/riscv_vplic/README_CN.md index d79371e640..597d8560ac 100644 --- a/components/riscv_vplic/README_CN.md +++ b/virtualization/riscv_vplic/README_CN.md @@ -32,7 +32,7 @@ riscv_vplic = "0.4.2" ```bash # 进入 crate 目录 -cd components/riscv_vplic +cd virtualization/riscv_vplic # 代码格式化 cargo fmt --all diff --git a/components/riscv_vplic/scripts/check.sh b/virtualization/riscv_vplic/scripts/check.sh similarity index 100% rename from components/riscv_vplic/scripts/check.sh rename to virtualization/riscv_vplic/scripts/check.sh diff --git a/components/riscv_vplic/scripts/test.sh b/virtualization/riscv_vplic/scripts/test.sh similarity index 100% rename from components/riscv_vplic/scripts/test.sh rename to virtualization/riscv_vplic/scripts/test.sh diff --git a/components/riscv_vplic/src/consts.rs b/virtualization/riscv_vplic/src/consts.rs similarity index 100% rename from components/riscv_vplic/src/consts.rs rename to virtualization/riscv_vplic/src/consts.rs diff --git a/components/riscv_vplic/src/devops_impl.rs b/virtualization/riscv_vplic/src/devops_impl.rs similarity index 100% rename from components/riscv_vplic/src/devops_impl.rs rename to virtualization/riscv_vplic/src/devops_impl.rs diff --git a/components/riscv_vplic/src/lib.rs b/virtualization/riscv_vplic/src/lib.rs similarity index 100% rename from components/riscv_vplic/src/lib.rs rename to virtualization/riscv_vplic/src/lib.rs diff --git a/components/riscv_vplic/src/utils.rs b/virtualization/riscv_vplic/src/utils.rs similarity index 100% rename from components/riscv_vplic/src/utils.rs rename to virtualization/riscv_vplic/src/utils.rs diff --git a/components/riscv_vplic/src/vplic.rs b/virtualization/riscv_vplic/src/vplic.rs similarity index 100% rename from components/riscv_vplic/src/vplic.rs rename to virtualization/riscv_vplic/src/vplic.rs diff --git a/components/riscv_vplic/tests/consts_tests.rs b/virtualization/riscv_vplic/tests/consts_tests.rs similarity index 100% rename from components/riscv_vplic/tests/consts_tests.rs rename to virtualization/riscv_vplic/tests/consts_tests.rs diff --git a/components/riscv_vplic/tests/vplic_tests.rs b/virtualization/riscv_vplic/tests/vplic_tests.rs similarity index 100% rename from components/riscv_vplic/tests/vplic_tests.rs rename to virtualization/riscv_vplic/tests/vplic_tests.rs diff --git a/components/x86_vcpu/.github/config.json b/virtualization/x86_vcpu/.github/config.json similarity index 100% rename from components/x86_vcpu/.github/config.json rename to virtualization/x86_vcpu/.github/config.json diff --git a/components/x86_vcpu/.github/workflows/check.yml b/virtualization/x86_vcpu/.github/workflows/check.yml similarity index 100% rename from components/x86_vcpu/.github/workflows/check.yml rename to virtualization/x86_vcpu/.github/workflows/check.yml diff --git a/components/x86_vcpu/.github/workflows/deploy.yml b/virtualization/x86_vcpu/.github/workflows/deploy.yml similarity index 100% rename from components/x86_vcpu/.github/workflows/deploy.yml rename to virtualization/x86_vcpu/.github/workflows/deploy.yml diff --git a/components/x86_vcpu/.github/workflows/push.yml b/virtualization/x86_vcpu/.github/workflows/push.yml similarity index 100% rename from components/x86_vcpu/.github/workflows/push.yml rename to virtualization/x86_vcpu/.github/workflows/push.yml diff --git a/components/x86_vcpu/.github/workflows/release.yml b/virtualization/x86_vcpu/.github/workflows/release.yml similarity index 100% rename from components/x86_vcpu/.github/workflows/release.yml rename to virtualization/x86_vcpu/.github/workflows/release.yml diff --git a/components/x86_vcpu/.github/workflows/test.yml b/virtualization/x86_vcpu/.github/workflows/test.yml similarity index 100% rename from components/x86_vcpu/.github/workflows/test.yml rename to virtualization/x86_vcpu/.github/workflows/test.yml diff --git a/components/x86_vcpu/.gitignore b/virtualization/x86_vcpu/.gitignore similarity index 100% rename from components/x86_vcpu/.gitignore rename to virtualization/x86_vcpu/.gitignore diff --git a/components/x86_vcpu/CHANGELOG.md b/virtualization/x86_vcpu/CHANGELOG.md similarity index 100% rename from components/x86_vcpu/CHANGELOG.md rename to virtualization/x86_vcpu/CHANGELOG.md diff --git a/components/x86_vcpu/Cargo.toml b/virtualization/x86_vcpu/Cargo.toml similarity index 100% rename from components/x86_vcpu/Cargo.toml rename to virtualization/x86_vcpu/Cargo.toml diff --git a/components/riscv_vcpu/LICENSE b/virtualization/x86_vcpu/LICENSE similarity index 100% rename from components/riscv_vcpu/LICENSE rename to virtualization/x86_vcpu/LICENSE diff --git a/components/x86_vcpu/README.md b/virtualization/x86_vcpu/README.md similarity index 98% rename from components/x86_vcpu/README.md rename to virtualization/x86_vcpu/README.md index 892fc0c3b4..4df01628b3 100644 --- a/components/x86_vcpu/README.md +++ b/virtualization/x86_vcpu/README.md @@ -32,7 +32,7 @@ x86_vcpu = "0.5.0" ```bash # Enter the crate directory -cd components/x86_vcpu +cd virtualization/x86_vcpu # Format code cargo fmt --all diff --git a/components/x86_vcpu/README_CN.md b/virtualization/x86_vcpu/README_CN.md similarity index 98% rename from components/x86_vcpu/README_CN.md rename to virtualization/x86_vcpu/README_CN.md index dcbae50355..793a163dbf 100644 --- a/components/x86_vcpu/README_CN.md +++ b/virtualization/x86_vcpu/README_CN.md @@ -32,7 +32,7 @@ x86_vcpu = "0.5.0" ```bash # 进入 crate 目录 -cd components/x86_vcpu +cd virtualization/x86_vcpu # 代码格式化 cargo fmt --all diff --git a/components/x86_vcpu/src/ept.rs b/virtualization/x86_vcpu/src/ept.rs similarity index 100% rename from components/x86_vcpu/src/ept.rs rename to virtualization/x86_vcpu/src/ept.rs diff --git a/components/x86_vcpu/src/lib.rs b/virtualization/x86_vcpu/src/lib.rs similarity index 100% rename from components/x86_vcpu/src/lib.rs rename to virtualization/x86_vcpu/src/lib.rs diff --git a/components/x86_vcpu/src/msr.rs b/virtualization/x86_vcpu/src/msr.rs similarity index 100% rename from components/x86_vcpu/src/msr.rs rename to virtualization/x86_vcpu/src/msr.rs diff --git a/components/x86_vcpu/src/regs/accessors.rs b/virtualization/x86_vcpu/src/regs/accessors.rs similarity index 100% rename from components/x86_vcpu/src/regs/accessors.rs rename to virtualization/x86_vcpu/src/regs/accessors.rs diff --git a/components/x86_vcpu/src/regs/diff.rs b/virtualization/x86_vcpu/src/regs/diff.rs similarity index 100% rename from components/x86_vcpu/src/regs/diff.rs rename to virtualization/x86_vcpu/src/regs/diff.rs diff --git a/components/x86_vcpu/src/regs/mod.rs b/virtualization/x86_vcpu/src/regs/mod.rs similarity index 100% rename from components/x86_vcpu/src/regs/mod.rs rename to virtualization/x86_vcpu/src/regs/mod.rs diff --git a/components/x86_vcpu/src/svm/definitions.rs b/virtualization/x86_vcpu/src/svm/definitions.rs similarity index 100% rename from components/x86_vcpu/src/svm/definitions.rs rename to virtualization/x86_vcpu/src/svm/definitions.rs diff --git a/components/x86_vcpu/src/svm/flags.rs b/virtualization/x86_vcpu/src/svm/flags.rs similarity index 100% rename from components/x86_vcpu/src/svm/flags.rs rename to virtualization/x86_vcpu/src/svm/flags.rs diff --git a/components/x86_vcpu/src/svm/frame.rs b/virtualization/x86_vcpu/src/svm/frame.rs similarity index 100% rename from components/x86_vcpu/src/svm/frame.rs rename to virtualization/x86_vcpu/src/svm/frame.rs diff --git a/components/x86_vcpu/src/svm/instructions.rs b/virtualization/x86_vcpu/src/svm/instructions.rs similarity index 100% rename from components/x86_vcpu/src/svm/instructions.rs rename to virtualization/x86_vcpu/src/svm/instructions.rs diff --git a/components/x86_vcpu/src/svm/mod.rs b/virtualization/x86_vcpu/src/svm/mod.rs similarity index 100% rename from components/x86_vcpu/src/svm/mod.rs rename to virtualization/x86_vcpu/src/svm/mod.rs diff --git a/components/x86_vcpu/src/svm/percpu.rs b/virtualization/x86_vcpu/src/svm/percpu.rs similarity index 100% rename from components/x86_vcpu/src/svm/percpu.rs rename to virtualization/x86_vcpu/src/svm/percpu.rs diff --git a/components/x86_vcpu/src/svm/structs.rs b/virtualization/x86_vcpu/src/svm/structs.rs similarity index 100% rename from components/x86_vcpu/src/svm/structs.rs rename to virtualization/x86_vcpu/src/svm/structs.rs diff --git a/components/x86_vcpu/src/svm/vcpu.rs b/virtualization/x86_vcpu/src/svm/vcpu.rs similarity index 100% rename from components/x86_vcpu/src/svm/vcpu.rs rename to virtualization/x86_vcpu/src/svm/vcpu.rs diff --git a/components/x86_vcpu/src/svm/vmcb.rs b/virtualization/x86_vcpu/src/svm/vmcb.rs similarity index 100% rename from components/x86_vcpu/src/svm/vmcb.rs rename to virtualization/x86_vcpu/src/svm/vmcb.rs diff --git a/components/x86_vcpu/src/test_utils.rs b/virtualization/x86_vcpu/src/test_utils.rs similarity index 100% rename from components/x86_vcpu/src/test_utils.rs rename to virtualization/x86_vcpu/src/test_utils.rs diff --git a/components/x86_vcpu/src/vmx/definitions.rs b/virtualization/x86_vcpu/src/vmx/definitions.rs similarity index 100% rename from components/x86_vcpu/src/vmx/definitions.rs rename to virtualization/x86_vcpu/src/vmx/definitions.rs diff --git a/components/x86_vcpu/src/vmx/instructions.rs b/virtualization/x86_vcpu/src/vmx/instructions.rs similarity index 100% rename from components/x86_vcpu/src/vmx/instructions.rs rename to virtualization/x86_vcpu/src/vmx/instructions.rs diff --git a/components/x86_vcpu/src/vmx/mod.rs b/virtualization/x86_vcpu/src/vmx/mod.rs similarity index 100% rename from components/x86_vcpu/src/vmx/mod.rs rename to virtualization/x86_vcpu/src/vmx/mod.rs diff --git a/components/x86_vcpu/src/vmx/percpu.rs b/virtualization/x86_vcpu/src/vmx/percpu.rs similarity index 100% rename from components/x86_vcpu/src/vmx/percpu.rs rename to virtualization/x86_vcpu/src/vmx/percpu.rs diff --git a/components/x86_vcpu/src/vmx/structs.rs b/virtualization/x86_vcpu/src/vmx/structs.rs similarity index 100% rename from components/x86_vcpu/src/vmx/structs.rs rename to virtualization/x86_vcpu/src/vmx/structs.rs diff --git a/components/x86_vcpu/src/vmx/vcpu.rs b/virtualization/x86_vcpu/src/vmx/vcpu.rs similarity index 100% rename from components/x86_vcpu/src/vmx/vcpu.rs rename to virtualization/x86_vcpu/src/vmx/vcpu.rs diff --git a/components/x86_vcpu/src/vmx/vmcs.rs b/virtualization/x86_vcpu/src/vmx/vmcs.rs similarity index 100% rename from components/x86_vcpu/src/vmx/vmcs.rs rename to virtualization/x86_vcpu/src/vmx/vmcs.rs diff --git a/components/x86_vcpu/src/xstate.rs b/virtualization/x86_vcpu/src/xstate.rs similarity index 100% rename from components/x86_vcpu/src/xstate.rs rename to virtualization/x86_vcpu/src/xstate.rs diff --git a/components/x86_vlapic/.github/config.json b/virtualization/x86_vlapic/.github/config.json similarity index 100% rename from components/x86_vlapic/.github/config.json rename to virtualization/x86_vlapic/.github/config.json diff --git a/components/x86_vlapic/.github/workflows/check.yml b/virtualization/x86_vlapic/.github/workflows/check.yml similarity index 100% rename from components/x86_vlapic/.github/workflows/check.yml rename to virtualization/x86_vlapic/.github/workflows/check.yml diff --git a/components/x86_vlapic/.github/workflows/deploy.yml b/virtualization/x86_vlapic/.github/workflows/deploy.yml similarity index 100% rename from components/x86_vlapic/.github/workflows/deploy.yml rename to virtualization/x86_vlapic/.github/workflows/deploy.yml diff --git a/components/x86_vlapic/.github/workflows/release.yml b/virtualization/x86_vlapic/.github/workflows/release.yml similarity index 100% rename from components/x86_vlapic/.github/workflows/release.yml rename to virtualization/x86_vlapic/.github/workflows/release.yml diff --git a/components/x86_vlapic/.github/workflows/test.yml b/virtualization/x86_vlapic/.github/workflows/test.yml similarity index 100% rename from components/x86_vlapic/.github/workflows/test.yml rename to virtualization/x86_vlapic/.github/workflows/test.yml diff --git a/components/axvmconfig/.gitignore b/virtualization/x86_vlapic/.gitignore similarity index 100% rename from components/axvmconfig/.gitignore rename to virtualization/x86_vlapic/.gitignore diff --git a/components/x86_vlapic/CHANGELOG.md b/virtualization/x86_vlapic/CHANGELOG.md similarity index 100% rename from components/x86_vlapic/CHANGELOG.md rename to virtualization/x86_vlapic/CHANGELOG.md diff --git a/components/x86_vlapic/Cargo.toml b/virtualization/x86_vlapic/Cargo.toml similarity index 100% rename from components/x86_vlapic/Cargo.toml rename to virtualization/x86_vlapic/Cargo.toml diff --git a/components/riscv_vplic/LICENSE b/virtualization/x86_vlapic/LICENSE similarity index 100% rename from components/riscv_vplic/LICENSE rename to virtualization/x86_vlapic/LICENSE diff --git a/components/x86_vlapic/README.md b/virtualization/x86_vlapic/README.md similarity index 98% rename from components/x86_vlapic/README.md rename to virtualization/x86_vlapic/README.md index 5a2262ee80..7a7f453392 100644 --- a/components/x86_vlapic/README.md +++ b/virtualization/x86_vlapic/README.md @@ -32,7 +32,7 @@ x86_vlapic = "0.4.2" ```bash # Enter the crate directory -cd components/x86_vlapic +cd virtualization/x86_vlapic # Format code cargo fmt --all diff --git a/components/x86_vlapic/README_CN.md b/virtualization/x86_vlapic/README_CN.md similarity index 98% rename from components/x86_vlapic/README_CN.md rename to virtualization/x86_vlapic/README_CN.md index 0e54e373b2..eb34752f7a 100644 --- a/components/x86_vlapic/README_CN.md +++ b/virtualization/x86_vlapic/README_CN.md @@ -32,7 +32,7 @@ x86_vlapic = "0.4.2" ```bash # 进入 crate 目录 -cd components/x86_vlapic +cd virtualization/x86_vlapic # 代码格式化 cargo fmt --all diff --git a/components/x86_vlapic/src/consts.rs b/virtualization/x86_vlapic/src/consts.rs similarity index 100% rename from components/x86_vlapic/src/consts.rs rename to virtualization/x86_vlapic/src/consts.rs diff --git a/components/x86_vlapic/src/lib.rs b/virtualization/x86_vlapic/src/lib.rs similarity index 100% rename from components/x86_vlapic/src/lib.rs rename to virtualization/x86_vlapic/src/lib.rs diff --git a/components/x86_vlapic/src/regs/apic_base.rs b/virtualization/x86_vlapic/src/regs/apic_base.rs similarity index 100% rename from components/x86_vlapic/src/regs/apic_base.rs rename to virtualization/x86_vlapic/src/regs/apic_base.rs diff --git a/components/x86_vlapic/src/regs/dfr.rs b/virtualization/x86_vlapic/src/regs/dfr.rs similarity index 100% rename from components/x86_vlapic/src/regs/dfr.rs rename to virtualization/x86_vlapic/src/regs/dfr.rs diff --git a/components/x86_vlapic/src/regs/esr.rs b/virtualization/x86_vlapic/src/regs/esr.rs similarity index 100% rename from components/x86_vlapic/src/regs/esr.rs rename to virtualization/x86_vlapic/src/regs/esr.rs diff --git a/components/x86_vlapic/src/regs/icr.rs b/virtualization/x86_vlapic/src/regs/icr.rs similarity index 100% rename from components/x86_vlapic/src/regs/icr.rs rename to virtualization/x86_vlapic/src/regs/icr.rs diff --git a/components/x86_vlapic/src/regs/lvt/cmci.rs b/virtualization/x86_vlapic/src/regs/lvt/cmci.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/cmci.rs rename to virtualization/x86_vlapic/src/regs/lvt/cmci.rs diff --git a/components/x86_vlapic/src/regs/lvt/error.rs b/virtualization/x86_vlapic/src/regs/lvt/error.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/error.rs rename to virtualization/x86_vlapic/src/regs/lvt/error.rs diff --git a/components/x86_vlapic/src/regs/lvt/lint0.rs b/virtualization/x86_vlapic/src/regs/lvt/lint0.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/lint0.rs rename to virtualization/x86_vlapic/src/regs/lvt/lint0.rs diff --git a/components/x86_vlapic/src/regs/lvt/lint1.rs b/virtualization/x86_vlapic/src/regs/lvt/lint1.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/lint1.rs rename to virtualization/x86_vlapic/src/regs/lvt/lint1.rs diff --git a/components/x86_vlapic/src/regs/lvt/mod.rs b/virtualization/x86_vlapic/src/regs/lvt/mod.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/mod.rs rename to virtualization/x86_vlapic/src/regs/lvt/mod.rs diff --git a/components/x86_vlapic/src/regs/lvt/perfmon.rs b/virtualization/x86_vlapic/src/regs/lvt/perfmon.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/perfmon.rs rename to virtualization/x86_vlapic/src/regs/lvt/perfmon.rs diff --git a/components/x86_vlapic/src/regs/lvt/thermal.rs b/virtualization/x86_vlapic/src/regs/lvt/thermal.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/thermal.rs rename to virtualization/x86_vlapic/src/regs/lvt/thermal.rs diff --git a/components/x86_vlapic/src/regs/lvt/timer.rs b/virtualization/x86_vlapic/src/regs/lvt/timer.rs similarity index 100% rename from components/x86_vlapic/src/regs/lvt/timer.rs rename to virtualization/x86_vlapic/src/regs/lvt/timer.rs diff --git a/components/x86_vlapic/src/regs/mod.rs b/virtualization/x86_vlapic/src/regs/mod.rs similarity index 100% rename from components/x86_vlapic/src/regs/mod.rs rename to virtualization/x86_vlapic/src/regs/mod.rs diff --git a/components/x86_vlapic/src/regs/svr.rs b/virtualization/x86_vlapic/src/regs/svr.rs similarity index 100% rename from components/x86_vlapic/src/regs/svr.rs rename to virtualization/x86_vlapic/src/regs/svr.rs diff --git a/components/x86_vlapic/src/regs/timer/dcr.rs b/virtualization/x86_vlapic/src/regs/timer/dcr.rs similarity index 100% rename from components/x86_vlapic/src/regs/timer/dcr.rs rename to virtualization/x86_vlapic/src/regs/timer/dcr.rs diff --git a/components/x86_vlapic/src/regs/timer/mod.rs b/virtualization/x86_vlapic/src/regs/timer/mod.rs similarity index 100% rename from components/x86_vlapic/src/regs/timer/mod.rs rename to virtualization/x86_vlapic/src/regs/timer/mod.rs diff --git a/components/x86_vlapic/src/timer.rs b/virtualization/x86_vlapic/src/timer.rs similarity index 100% rename from components/x86_vlapic/src/timer.rs rename to virtualization/x86_vlapic/src/timer.rs diff --git a/components/x86_vlapic/src/utils.rs b/virtualization/x86_vlapic/src/utils.rs similarity index 100% rename from components/x86_vlapic/src/utils.rs rename to virtualization/x86_vlapic/src/utils.rs diff --git a/components/x86_vlapic/src/vlapic.rs b/virtualization/x86_vlapic/src/vlapic.rs similarity index 100% rename from components/x86_vlapic/src/vlapic.rs rename to virtualization/x86_vlapic/src/vlapic.rs From 7d72f8f93e77f79c42100418e68b641e2e3fe3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 14:15:42 +0800 Subject: [PATCH 55/71] =?UTF-8?q?chore(deps):=20update=20spin=200.10?= =?UTF-8?q?=E2=86=920.12,=20ostool=200.19=E2=86=920.21=20(#978)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(deps): update spin 0.10→0.12, ostool 0.19→0.21 - Update workspace spin from 0.10 to 0.12, rename Lazy→LazyLock across all usage sites to match spin 0.11+ API - Migrate 16 crates from pinned `spin = "0.10"` to `workspace = true` - Update `lazy` feature to `lazylock` for spin 0.12 compatibility - Update ostool from 0.19 to 0.21 (no code changes needed) * chore: format static LazyLock initializations for consistency * chore: replace Mutex with SpinMutex in IrqLock for improved performance * chore: remove ax-plat-dyn package and update spin dependency version --- Cargo.lock | 120 +++++++++--------- Cargo.toml | 4 +- components/axbacktrace/Cargo.toml | 2 +- components/axpoll/Cargo.toml | 4 +- components/axpoll/src/lib.rs | 6 +- components/percpu/percpu/Cargo.toml | 2 +- components/rsext4/src/crc32c/arm64.rs | 2 +- components/scope-local/Cargo.toml | 2 +- components/scope-local/src/scope.rs | 4 +- components/someboot/Cargo.toml | 2 +- drivers/npu/rockchip-npu/Cargo.toml | 2 +- drivers/rdrive/Cargo.toml | 2 +- drivers/soc/rockchip/rockchip-soc/Cargo.toml | 2 +- drivers/usb/usb-host/Cargo.toml | 2 +- .../usb-host/src/backend/kmod/xhci/sync.rs | 15 ++- drivers/usb/usb-if/Cargo.toml | 2 +- memory/buddy-slab-allocator/Cargo.toml | 2 +- os/StarryOS/kernel/Cargo.toml | 2 +- os/StarryOS/kernel/src/file/inotify.rs | 5 +- os/StarryOS/kernel/src/file/netlink.rs | 6 +- os/StarryOS/kernel/src/pseudofs/dev/pwm.rs | 5 +- .../kernel/src/pseudofs/dev/tty/ntty.rs | 6 +- os/StarryOS/kernel/src/syscall/sys.rs | 4 +- os/StarryOS/kernel/src/task/timer.rs | 7 +- .../arceos_posix_api/src/imp/pthread/mod.rs | 6 +- os/arceos/modules/axhal/src/dtb.rs | 6 +- os/arceos/modules/axhal/src/lib.rs | 4 +- os/arceos/modules/axhal/src/mem.rs | 4 +- os/arceos/modules/axnet-ng/src/lib.rs | 6 +- os/arceos/modules/axnet-ng/src/tcp.rs | 6 +- os/arceos/modules/axnet-ng/src/unix/mod.rs | 6 +- os/arceos/modules/axtask/src/api.rs | 4 +- os/axvisor/src/shell/command/mod.rs | 5 +- .../ax-plat-aarch64-peripherals/Cargo.toml | 2 +- platforms/axplat-dyn/Cargo.toml | 2 +- platforms/somehal/Cargo.toml | 2 +- virtualization/arm_vcpu/Cargo.toml | 2 +- virtualization/arm_vgic/Cargo.toml | 2 +- virtualization/axvm/Cargo.toml | 2 +- 39 files changed, 141 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a39050d632..269c2698fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,7 +567,7 @@ dependencies = [ "nb", "num-align", "smccc", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -585,7 +585,7 @@ dependencies = [ "axvisor_api", "log", "numeric-enum-macro", - "spin", + "spin 0.12.0", ] [[package]] @@ -602,7 +602,7 @@ dependencies = [ "axvisor_api", "bitmaps", "log", - "spin", + "spin 0.12.0", "tock-registers 0.10.1", ] @@ -909,7 +909,7 @@ dependencies = [ "sdmmc-protocol", "simple-ahci", "some-serial", - "spin", + "spin 0.12.0", "virtio-drivers", ] @@ -960,7 +960,7 @@ dependencies = [ "axfatfs", "log", "rsext4", - "spin", + "spin 0.12.0", ] [[package]] @@ -969,7 +969,7 @@ version = "0.3.11" dependencies = [ "ax-fs-vfs", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -995,7 +995,7 @@ dependencies = [ "rsext4", "scope-local", "slab", - "spin", + "spin 0.12.0", "starry-fatfs", ] @@ -1005,7 +1005,7 @@ version = "0.3.12" dependencies = [ "ax-fs-vfs", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -1045,7 +1045,7 @@ dependencies = [ "heapless 0.9.3", "log", "rdrive", - "spin", + "spin 0.12.0", "toml 1.1.2+spec-1.1.0", ] @@ -1231,7 +1231,7 @@ dependencies = [ "cfg-if", "log", "smoltcp 0.13.1", - "spin", + "spin 0.12.0", ] [[package]] @@ -1260,7 +1260,7 @@ dependencies = [ "rdif-vsock", "ringbuf", "smoltcp 0.13.1", - "spin", + "spin 0.12.0", ] [[package]] @@ -1295,7 +1295,7 @@ dependencies = [ "ax-kernel-guard", "ax-percpu-macros", "cfg-if", - "spin", + "spin 0.12.0", "x86", ] @@ -1352,7 +1352,7 @@ dependencies = [ "ax-plat", "log", "some-serial", - "spin", + "spin 0.12.0", ] [[package]] @@ -1462,7 +1462,7 @@ dependencies = [ "sbi-rt 0.0.3", "sg200x-bsp", "some-serial", - "spin", + "spin 0.12.0", ] [[package]] @@ -1552,7 +1552,7 @@ dependencies = [ "bindgen 0.72.1", "flatten_objects", "scope-local", - "spin", + "spin 0.12.0", ] [[package]] @@ -1596,7 +1596,7 @@ dependencies = [ "rd-block", "rd-net", "rdrive", - "spin", + "spin 0.12.0", ] [[package]] @@ -1626,7 +1626,7 @@ dependencies = [ "ax-kspin", "ax-lazyinit", "lock_api", - "spin", + "spin 0.12.0", ] [[package]] @@ -1665,7 +1665,7 @@ dependencies = [ "extern-trait", "futures-util", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -1713,7 +1713,7 @@ dependencies = [ "gimli 0.33.0", "log", "paste", - "spin", + "spin 0.12.0", ] [[package]] @@ -1863,7 +1863,7 @@ dependencies = [ "log", "rdrive", "somehal", - "spin", + "spin 0.12.0", ] [[package]] @@ -1874,7 +1874,7 @@ dependencies = [ "bitflags 2.11.1", "futures", "linux-raw-sys 0.12.1", - "spin", + "spin 0.12.0", "tokio", ] @@ -1991,7 +1991,7 @@ dependencies = [ "rdrive", "riscv_vcpu", "riscv_vplic", - "spin", + "spin 0.12.0", "syn 2.0.117", "tokio", "toml 0.9.12+spec-1.1.0", @@ -2041,7 +2041,7 @@ dependencies = [ "log", "loongarch_vcpu", "riscv_vcpu", - "spin", + "spin 0.12.0", "x86_vcpu", ] @@ -2288,7 +2288,7 @@ dependencies = [ "divan", "log", "rand 0.10.1", - "spin", + "spin 0.12.0", ] [[package]] @@ -2303,7 +2303,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672b945a3e4f4f40bfd4cd5ee07df9e796a42254ce7cd6d2599ad969244c44a" dependencies = [ - "spin", + "spin 0.10.0", ] [[package]] @@ -2623,9 +2623,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" dependencies = [ "castaway", "cfg-if", @@ -2637,9 +2637,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -2803,7 +2803,7 @@ dependencies = [ "mbarrier", "nb", "num_enum", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", "trait-ffi", @@ -3101,7 +3101,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321ec774d27fafc66e812034d0025f8858bd7d9095304ff8fc200e0b9f9cc257" dependencies = [ "ahash 0.8.12", - "compact_str 0.8.1", + "compact_str 0.8.2", "crossbeam-channel", "cursive-macros", "enum-map", @@ -3366,9 +3366,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -4744,9 +4744,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30457d51cb0e68ee18184b30cd9eb8e1602a20837c321f6ea9706b94f1c681c3" +checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e" dependencies = [ "jiff-static", "log", @@ -4757,9 +4757,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f86e4f0326c61ae6c00b04d9009aaeda644d0b5bdfbf6c67247f492f42b3f3" +checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a" dependencies = [ "proc-macro2", "quote", @@ -5274,9 +5274,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memmap2" @@ -5609,7 +5609,7 @@ dependencies = [ "mmio-api", "pcie", "rd-block", - "spin", + "spin 0.12.0", "tock-registers 0.10.1", ] @@ -5681,9 +5681,9 @@ dependencies = [ [[package]] name = "ostool" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f785cb91e2622849c3f0e55d5363989934d365967c06f0a8ce8fd944f3b834ba" +checksum = "c4d0470fd8561e21f458d865490c928ebff45c0e1037a45f634a527acb8f15ff" dependencies = [ "anyhow", "async-trait", @@ -6270,7 +6270,7 @@ dependencies = [ "dma-api", "rd-block", "rdif-block", - "spin", + "spin 0.12.0", ] [[package]] @@ -6382,7 +6382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.11.1", - "compact_str 0.9.0", + "compact_str 0.9.1", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", @@ -6600,7 +6600,7 @@ dependencies = [ "futures", "heapless 0.9.3", "rdif-base", - "spin", + "spin 0.12.0", "thiserror 2.0.18", ] @@ -6641,7 +6641,7 @@ dependencies = [ "rdif-intc", "rdif-pcie", "rdrive-macros", - "spin", + "spin 0.12.0", "thiserror 2.0.18", ] @@ -6662,7 +6662,7 @@ dependencies = [ "log", "mmio-api", "rdif-eth", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7001,7 +7001,7 @@ dependencies = [ "mbarrier", "num-align", "rdif-base", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7028,7 +7028,7 @@ dependencies = [ "log", "mbarrier", "num-align", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7039,7 +7039,7 @@ version = "0.4.1" dependencies = [ "bitflags 2.11.1", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -7327,7 +7327,7 @@ version = "0.3.7" dependencies = [ "ax-percpu", "ctor 0.6.3", - "spin", + "spin 0.12.0", ] [[package]] @@ -7777,7 +7777,7 @@ dependencies = [ "smccc", "some-serial", "somehal-macros", - "spin", + "spin 0.12.0", "syn 2.0.117", "thiserror 2.0.18", "tock-registers 0.10.1", @@ -7801,7 +7801,7 @@ dependencies = [ "rdrive", "someboot", "somehal-macros", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7821,6 +7821,12 @@ name = "spin" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spin" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865" dependencies = [ "lock_api", ] @@ -7933,7 +7939,7 @@ dependencies = [ "sg200x-bsp", "slab", "some-serial", - "spin", + "spin 0.12.0", "starry-process", "starry-signal", "starry-vm", @@ -9128,7 +9134,7 @@ dependencies = [ "futures", "log", "num_enum", - "spin", + "spin 0.12.0", "thiserror 2.0.18", ] @@ -10160,9 +10166,9 @@ dependencies = [ [[package]] name = "yaxpeax-x86" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9a30b7dd533c7b1a73eaf7c4ea162a7a632a2bb29b9fff47d8f2cc8513a883" +checksum = "a159e15f66ce40b5dfc28213366f8ff42ff8f0508e2e72e431c62573f025ac5e" dependencies = [ "cfg-if", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index cb0aca1c95..f1467fc54f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -349,8 +349,8 @@ dma-api = { version = "0.7.3", path = "memory/dma-api" } mmio-api = { version = "0.2.2", path = "memory/mmio-api" } lock_api = { version = "0.4", default-features = false } log = "0.4" -spin = "0.10" -ostool = { version = "0.19" } +spin = "0.12" +ostool = { version = "0.21" } uefi = "0.36" fdt-edit = "0.2.3" fdt-raw = "0.3" diff --git a/components/axbacktrace/Cargo.toml b/components/axbacktrace/Cargo.toml index 03c961ef67..bf821c7d99 100644 --- a/components/axbacktrace/Cargo.toml +++ b/components/axbacktrace/Cargo.toml @@ -17,7 +17,7 @@ dwarf = ["dep:addr2line", "dep:gimli", "dep:paste", "alloc"] axpanic = { workspace = true } cfg-if = "1" log = "0.4" -spin = { version = "0.10", default-features = false, features = ["once"] } +spin = { version = "0.12", default-features = false, features = ["once"] } addr2line = { version = "0.26", default-features = false, optional = true, features = [ "cpp_demangle", diff --git a/components/axpoll/Cargo.toml b/components/axpoll/Cargo.toml index 905bd5de83..45045d2702 100644 --- a/components/axpoll/Cargo.toml +++ b/components/axpoll/Cargo.toml @@ -19,8 +19,8 @@ linux-raw-sys = { version = "0.12", default-features = false, features = [ "no_std", "general", ] } -spin = { version = "0.10", default-features = false, features = [ - "lazy", +spin = { version = "0.12", default-features = false, features = [ + "lazylock", "spin_mutex", ] } diff --git a/components/axpoll/src/lib.rs b/components/axpoll/src/lib.rs index 663dcb1a1d..9fac8109e9 100644 --- a/components/axpoll/src/lib.rs +++ b/components/axpoll/src/lib.rs @@ -14,7 +14,7 @@ use core::{ use ax_kspin::SpinNoIrq; use bitflags::bitflags; use linux_raw_sys::general::*; -use spin::Lazy; +use spin::LazyLock; bitflags! { /// I/O events. @@ -111,7 +111,7 @@ impl Drop for Inner { } /// A data structure for waking up tasks that are waiting for I/O events. -pub struct PollSet(Lazy>); +pub struct PollSet(LazyLock>); impl Default for PollSet { fn default() -> Self { @@ -122,7 +122,7 @@ impl Default for PollSet { impl PollSet { /// Creates a new empty [`PollSet`]. pub const fn new() -> Self { - Self(Lazy::new(|| SpinNoIrq::new(Inner::new()))) + Self(LazyLock::new(|| SpinNoIrq::new(Inner::new()))) } /// Registers a waker. diff --git a/components/percpu/percpu/Cargo.toml b/components/percpu/percpu/Cargo.toml index c18df3389e..b934c89941 100644 --- a/components/percpu/percpu/Cargo.toml +++ b/components/percpu/percpu/Cargo.toml @@ -38,4 +38,4 @@ ax-percpu-macros = { workspace = true } x86 = "0.52" [target.'cfg(not(target_os = "none"))'.dependencies] -spin = "0.10" +spin = { workspace = true } diff --git a/components/rsext4/src/crc32c/arm64.rs b/components/rsext4/src/crc32c/arm64.rs index 5a4413d33e..cf139f6689 100644 --- a/components/rsext4/src/crc32c/arm64.rs +++ b/components/rsext4/src/crc32c/arm64.rs @@ -4,7 +4,7 @@ use core::arch::asm; #[cfg(target_arch = "aarch64")] #[allow(dead_code)] -pub static HARDWARE_SUPPORT_CRC32: spin::Lazy = spin::Lazy::new(has_hardware_crc32); +pub static HARDWARE_SUPPORT_CRC32: spin::LazyLock = spin::LazyLock::new(has_hardware_crc32); // In `core::arch::aarch64`, the intrinsics with the `c` suffix implement the // Castagnoli polynomial used by CRC32C. diff --git a/components/scope-local/Cargo.toml b/components/scope-local/Cargo.toml index 68bc5f01c1..1f7496325d 100644 --- a/components/scope-local/Cargo.toml +++ b/components/scope-local/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" [dependencies] ax-percpu = { workspace = true, features = ["non-zero-vma"] } -spin = { version = "0.10", default-features = false, features = ["lazy"] } +spin = { version = "0.12", default-features = false, features = ["lazylock"] } [dev-dependencies] ctor = "0.6" diff --git a/components/scope-local/src/scope.rs b/components/scope-local/src/scope.rs index 2137f47590..012a53f233 100644 --- a/components/scope-local/src/scope.rs +++ b/components/scope-local/src/scope.rs @@ -1,7 +1,7 @@ use alloc::alloc::{alloc, dealloc, handle_alloc_error}; use core::{alloc::Layout, iter::zip, mem::MaybeUninit, ptr::NonNull}; -use spin::Lazy; +use spin::LazyLock; use crate::{ boxed::ItemBox, @@ -67,7 +67,7 @@ impl Drop for Scope { } } -static GLOBAL_SCOPE: Lazy = Lazy::new(Scope::new); +static GLOBAL_SCOPE: LazyLock = LazyLock::new(Scope::new); #[ax_percpu::def_percpu] pub(crate) static ACTIVE_SCOPE_PTR: usize = 0; diff --git a/components/someboot/Cargo.toml b/components/someboot/Cargo.toml index 524c03d0b6..e69b8a700e 100644 --- a/components/someboot/Cargo.toml +++ b/components/someboot/Cargo.toml @@ -40,7 +40,7 @@ ranges-ext = { workspace = true } rgb = "0.8" some-serial.workspace = true somehal-macros = { workspace = true } -spin = "0.10" +spin = { workspace = true } tock-registers.workspace = true uguid = "2.2.1" derive_more.workspace = true diff --git a/drivers/npu/rockchip-npu/Cargo.toml b/drivers/npu/rockchip-npu/Cargo.toml index b8a2d7efef..dd1d40cd93 100644 --- a/drivers/npu/rockchip-npu/Cargo.toml +++ b/drivers/npu/rockchip-npu/Cargo.toml @@ -18,7 +18,7 @@ log = "0.4" mbarrier = "0.1" num-align = "0.1" rdif-base.workspace = true -spin = "0.10" +spin = { workspace = true } thiserror = {version = "2.0", default-features = false} tock-registers = "0.10" diff --git a/drivers/rdrive/Cargo.toml b/drivers/rdrive/Cargo.toml index 08aa0560cc..43a9a33c7a 100644 --- a/drivers/rdrive/Cargo.toml +++ b/drivers/rdrive/Cargo.toml @@ -15,7 +15,7 @@ fdt-raw.workspace = true log = "0.4" mmio-api.workspace = true paste = "1" -spin = "0.10" +spin = { workspace = true } thiserror = { version = "2", default-features = false } pcie.workspace = true diff --git a/drivers/soc/rockchip/rockchip-soc/Cargo.toml b/drivers/soc/rockchip/rockchip-soc/Cargo.toml index 0609a40a95..b7a94c8eff 100644 --- a/drivers/soc/rockchip/rockchip-soc/Cargo.toml +++ b/drivers/soc/rockchip/rockchip-soc/Cargo.toml @@ -24,7 +24,7 @@ tock-registers = "0.10" [target.'cfg(not(any(windows, unix)))'.dev-dependencies] ax-kspin.workspace = true num-align = "0.1.0" -spin = "0.10" +spin = { workspace = true } # bare-test integration test is temporarily disabled. # [[test]] diff --git a/drivers/usb/usb-host/Cargo.toml b/drivers/usb/usb-host/Cargo.toml index b899f2f5ab..f2c274d43f 100644 --- a/drivers/usb/usb-host/Cargo.toml +++ b/drivers/usb/usb-host/Cargo.toml @@ -30,7 +30,7 @@ log = "0.4" mbarrier = "0.1" nb = "1.1" num_enum = {version = "0.7", default-features = false} -spin = {version = "0.10"} +spin = { workspace = true } thiserror = {workspace = true} tock-registers.workspace = true trait-ffi = "0.2.4" diff --git a/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs b/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs index 2763ced048..165ae82600 100644 --- a/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs +++ b/drivers/usb/usb-host/src/backend/kmod/xhci/sync.rs @@ -1,12 +1,15 @@ use alloc::sync::Arc; use core::cell::UnsafeCell; -use spin::{Mutex, MutexGuard, RwLock}; +use spin::{ + RwLock, + mutex::{SpinMutex, SpinMutexGuard}, +}; use super::reg::{DisableIrqGuard, XhciRegisters}; pub(crate) struct IrqLock { - inner: Mutex<()>, + inner: SpinMutex<()>, reg: Arc>, data: UnsafeCell, } @@ -17,7 +20,7 @@ unsafe impl Send for IrqLock where T: Send {} impl IrqLock { pub fn new(data: T, reg: Arc>) -> Self { Self { - inner: Mutex::new(()), + inner: SpinMutex::new(()), reg, data: UnsafeCell::new(data), } @@ -47,12 +50,12 @@ impl IrqLock { } pub(crate) struct IrqLockGuard<'a, T> { - _guard: MutexGuard<'a, ()>, + _guard: SpinMutexGuard<'a, ()>, data: &'a mut T, _disable_guard: DisableIrqGuard, } -impl<'a, T> core::ops::Deref for IrqLockGuard<'a, T> { +impl core::ops::Deref for IrqLockGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -60,7 +63,7 @@ impl<'a, T> core::ops::Deref for IrqLockGuard<'a, T> { } } -impl<'a, T> core::ops::DerefMut for IrqLockGuard<'a, T> { +impl core::ops::DerefMut for IrqLockGuard<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.data } diff --git a/drivers/usb/usb-if/Cargo.toml b/drivers/usb/usb-if/Cargo.toml index d230920033..d116bb3c48 100644 --- a/drivers/usb/usb-if/Cargo.toml +++ b/drivers/usb/usb-if/Cargo.toml @@ -12,5 +12,5 @@ anyhow = { version = "1", default-features = false} futures = {workspace = true, features = ["alloc"]} log = {workspace = true} num_enum = {version = "0.7", default-features = false} -spin = "0.10" +spin = { workspace = true } thiserror = {workspace = true} diff --git a/memory/buddy-slab-allocator/Cargo.toml b/memory/buddy-slab-allocator/Cargo.toml index 8ab3be9c72..07c3594828 100644 --- a/memory/buddy-slab-allocator/Cargo.toml +++ b/memory/buddy-slab-allocator/Cargo.toml @@ -37,7 +37,7 @@ name = "global_allocator" [dependencies] log = "0.4" -spin = "0.10" +spin = { workspace = true } [dev-dependencies] divan = "0.1.21" diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index ef124c89aa..b3e594d8de 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -119,7 +119,7 @@ rand = { version = "0.10", default-features = false, features = ["alloc"] } ringbuf = { version = "0.4.8", default-features = false, features = ["alloc"] } scope-local = { workspace = true } slab = { version = "0.4.9", default-features = false } -spin = "0.10" +spin = { workspace = true } starry-process = { workspace = true } starry-signal = { workspace = true } starry-vm = { workspace = true } diff --git a/os/StarryOS/kernel/src/file/inotify.rs b/os/StarryOS/kernel/src/file/inotify.rs index 488cfd179e..77611397bb 100644 --- a/os/StarryOS/kernel/src/file/inotify.rs +++ b/os/StarryOS/kernel/src/file/inotify.rs @@ -22,7 +22,7 @@ use linux_raw_sys::{ }, ioctl::FIONREAD, }; -use spin::Lazy; +use spin::LazyLock; use starry_vm::VmMutPtr; use crate::file::{FileLike, IoDst, IoSrc}; @@ -49,7 +49,8 @@ pub struct Inotify { poll_rx: PollSet, } -static INOTIFY_INSTANCES: Lazy>>> = Lazy::new(|| Mutex::new(Vec::new())); +static INOTIFY_INSTANCES: LazyLock>>> = + LazyLock::new(|| Mutex::new(Vec::new())); impl Inotify { pub fn new() -> Arc { diff --git a/os/StarryOS/kernel/src/file/netlink.rs b/os/StarryOS/kernel/src/file/netlink.rs index 0f9b3fd403..ab46441518 100644 --- a/os/StarryOS/kernel/src/file/netlink.rs +++ b/os/StarryOS/kernel/src/file/netlink.rs @@ -40,7 +40,7 @@ use linux_raw_sys::{ net::AF_NETLINK, netlink::{NETLINK_GENERIC, NETLINK_KOBJECT_UEVENT, NETLINK_ROUTE, sockaddr_nl}, }; -use spin::Lazy; +use spin::LazyLock; use super::packet::{ETH0_HWADDR, ETH0_IFINDEX}; use crate::{ @@ -248,8 +248,8 @@ pub struct NetlinkSocket { /// Global registry of bound netlink sockets, used by [`broadcast`] to dispatch /// kernel-side messages. Holds weak refs so socket close drops naturally; dead /// entries are pruned on each broadcast. -static NETLINK_SOCKETS: Lazy>>> = - Lazy::new(|| Mutex::new(Vec::new())); +static NETLINK_SOCKETS: LazyLock>>> = + LazyLock::new(|| Mutex::new(Vec::new())); impl NetlinkSocket { pub fn new(protocol: u32) -> Arc { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs b/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs index 0d71c9ff1d..89886a0483 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs @@ -6,7 +6,7 @@ use sg200x_bsp::{ pwm::{Pwm, PwmChannel, PwmMode, PwmPolarity}, soc::PWM0_BASE, }; -use spin::Lazy; +use spin::LazyLock; use crate::pseudofs::{ DirMaker, NodeOpsMux, RwFile, SimpleDir, SimpleDirOps, SimpleFile, SimpleFileOperation, @@ -60,7 +60,8 @@ impl PwmSysfsState { } } -static PWM_SYSFS_STATE: Lazy> = Lazy::new(|| Mutex::new(PwmSysfsState::new())); +static PWM_SYSFS_STATE: LazyLock> = + LazyLock::new(|| Mutex::new(PwmSysfsState::new())); struct PwmClassDir { fs: Arc, diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs index dfc41d8c1e..02d31dc7aa 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/ntty.rs @@ -1,7 +1,7 @@ use alloc::sync::Arc; use axpoll::PollSet; -use spin::Lazy; +use spin::LazyLock; use super::{ Tty, @@ -27,8 +27,8 @@ impl TtyWrite for Console { } /// The default TTY device. -pub static N_TTY: Lazy> = Lazy::new(new_n_tty); -static CONSOLE_INPUT_SOURCE: Lazy> = Lazy::new(|| Arc::new(PollSet::new())); +pub static N_TTY: LazyLock> = LazyLock::new(new_n_tty); +static CONSOLE_INPUT_SOURCE: LazyLock> = LazyLock::new(|| Arc::new(PollSet::new())); fn handle_console_input_irq(_irq_num: usize) { let events = ax_runtime::hal::console::handle_irq(); diff --git a/os/StarryOS/kernel/src/syscall/sys.rs b/os/StarryOS/kernel/src/syscall/sys.rs index 5c50ca931b..18e3525496 100644 --- a/os/StarryOS/kernel/src/syscall/sys.rs +++ b/os/StarryOS/kernel/src/syscall/sys.rs @@ -97,8 +97,8 @@ impl SyslogState { } } -static SYSLOG_STATE: spin::Lazy> = - spin::Lazy::new(|| Mutex::new(SyslogState::new())); +static SYSLOG_STATE: spin::LazyLock> = + spin::LazyLock::new(|| Mutex::new(SyslogState::new())); /// Mirror of Linux kernel `uid_valid()` / `make_kuid()` rejection: any caller- /// supplied UID/GID of `(uid_t)-1` (`u32::MAX`) is invalid outside the NOCHG diff --git a/os/StarryOS/kernel/src/task/timer.rs b/os/StarryOS/kernel/src/task/timer.rs index 9f6d511e4c..e3ec804958 100644 --- a/os/StarryOS/kernel/src/task/timer.rs +++ b/os/StarryOS/kernel/src/task/timer.rs @@ -10,7 +10,7 @@ use ax_task::{ future::{block_on, timeout_at}, }; use event_listener::{Event, listener}; -use spin::Lazy; +use spin::LazyLock; use starry_process::Pid; use starry_signal::Signo; use strum::FromRepr; @@ -51,8 +51,9 @@ impl Ord for Entry { } } -static ALARM_LIST: Lazy>> = Lazy::new(|| Mutex::new(BinaryHeap::new())); -static EVENT_NEW_TIMER: Lazy = Lazy::new(Event::new); +static ALARM_LIST: LazyLock>> = + LazyLock::new(|| Mutex::new(BinaryHeap::new())); +static EVENT_NEW_TIMER: LazyLock = LazyLock::new(Event::new); /// The type of interval timer. #[repr(i32)] diff --git a/os/arceos/api/arceos_posix_api/src/imp/pthread/mod.rs b/os/arceos/api/arceos_posix_api/src/imp/pthread/mod.rs index 4140e8e33b..0f2a39c529 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/pthread/mod.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/pthread/mod.rs @@ -6,14 +6,14 @@ use core::{ use ax_errno::{LinuxError, LinuxResult}; use ax_task::AxTaskRef; -use spin::{Lazy, RwLock}; +use spin::{LazyLock, RwLock}; use crate::ctypes; pub mod mutex; -static TID_TO_PTHREAD: Lazy>>> = - Lazy::new(|| { +static TID_TO_PTHREAD: LazyLock>>> = + LazyLock::new(|| { let mut map = BTreeMap::new(); let main_task = ax_task::current(); let main_tid = main_task.id().as_u64(); diff --git a/os/arceos/modules/axhal/src/dtb.rs b/os/arceos/modules/axhal/src/dtb.rs index eb79a8640b..4129e55466 100644 --- a/os/arceos/modules/axhal/src/dtb.rs +++ b/os/arceos/modules/axhal/src/dtb.rs @@ -2,7 +2,7 @@ use core::ptr::NonNull; use fdt_parser::Fdt; -use spin::{Lazy, Once}; +use spin::{LazyLock, Once}; static BOOTARG: Once = Once::new(); @@ -48,7 +48,7 @@ pub fn get_bootarg() -> usize { /// Get the FDT. pub fn get_fdt() -> Option<&'static Fdt<'static>> { - static CACHED_FDT: Lazy>> = Lazy::new(|| { + static CACHED_FDT: LazyLock>> = LazyLock::new(|| { let fdt_paddr = dtb_paddr_from_boot_context()?; let fdt_ptr = NonNull::new(crate::mem::phys_to_virt(fdt_paddr.into()).as_mut_ptr())?; Fdt::from_ptr(fdt_ptr).ok() @@ -59,7 +59,7 @@ pub fn get_fdt() -> Option<&'static Fdt<'static>> { /// Get the bootargs chosen from the device tree. pub fn get_chosen_bootargs() -> Option<&'static str> { - static CACHED_BOOTARGS: Lazy> = Lazy::new(|| { + static CACHED_BOOTARGS: LazyLock> = LazyLock::new(|| { let fdt = get_fdt()?; fdt.chosen()?.bootargs() }); diff --git a/os/arceos/modules/axhal/src/lib.rs b/os/arceos/modules/axhal/src/lib.rs index c531538599..6fdaf7753c 100644 --- a/os/arceos/modules/axhal/src/lib.rs +++ b/os/arceos/modules/axhal/src/lib.rs @@ -116,11 +116,11 @@ pub fn init_early(cpu_id: usize, arg: usize) { pub fn cpu_num() -> usize { #[cfg(feature = "smp")] { - use spin::Lazy; + use spin::LazyLock; /// The number of CPUs in the system. Based on the number declared by the /// platform crate and limited by the configured maximum CPU number. - static CPU_NUM: Lazy = Lazy::new(|| { + static CPU_NUM: LazyLock = LazyLock::new(|| { let max_cpu_num = ax_config::plat::MAX_CPU_NUM; let plat_cpu_num = ax_plat::power::cpu_num(); let cpu_num = plat_cpu_num.min(max_cpu_num); diff --git a/os/arceos/modules/axhal/src/mem.rs b/os/arceos/modules/axhal/src/mem.rs index e52c1b868a..c811c71e1b 100644 --- a/os/arceos/modules/axhal/src/mem.rs +++ b/os/arceos/modules/axhal/src/mem.rs @@ -7,14 +7,14 @@ pub use ax_plat::mem::{ }; use ax_plat::mem::{check_sorted_ranges_overlap, ranges_difference}; use heapless::Vec; -use spin::Lazy; +use spin::LazyLock; #[allow(unused_imports)] use crate::addr_of_sym; const MAX_REGIONS: usize = 128; -static ALL_MEM_REGIONS: Lazy> = Lazy::new(|| { +static ALL_MEM_REGIONS: LazyLock> = LazyLock::new(|| { let mut all_regions = Vec::new(); let mut push = |r: PhysMemRegion| { if r.size > 0 { diff --git a/os/arceos/modules/axnet-ng/src/lib.rs b/os/arceos/modules/axnet-ng/src/lib.rs index 170fc5c26d..c0a6154043 100644 --- a/os/arceos/modules/axnet-ng/src/lib.rs +++ b/os/arceos/modules/axnet-ng/src/lib.rs @@ -50,7 +50,7 @@ use core::{ use ax_sync::Mutex; use smoltcp::wire::{EthernetAddress, Ipv4Address, Ipv4Cidr}; -use spin::{Lazy, Once}; +use spin::{LazyLock, Once}; #[cfg(feature = "vsock")] pub use self::device::{VsockDevice, VsockDeviceList}; @@ -70,8 +70,8 @@ pub use self::{ socket::*, }; -static LISTEN_TABLE: Lazy = Lazy::new(ListenTable::new); -static SOCKET_SET: Lazy = Lazy::new(SocketSetWrapper::new); +static LISTEN_TABLE: LazyLock = LazyLock::new(ListenTable::new); +static SOCKET_SET: LazyLock = LazyLock::new(SocketSetWrapper::new); static SERVICE: Once> = Once::new(); static POLLING_INTERFACES: AtomicBool = AtomicBool::new(false); diff --git a/os/arceos/modules/axnet-ng/src/tcp.rs b/os/arceos/modules/axnet-ng/src/tcp.rs index 56ade335e7..faf5284846 100644 --- a/os/arceos/modules/axnet-ng/src/tcp.rs +++ b/os/arceos/modules/axnet-ng/src/tcp.rs @@ -16,7 +16,7 @@ use smoltcp::{ time::Duration, wire::{IpEndpoint, IpListenEndpoint}, }; -use spin::Lazy; +use spin::LazyLock; use crate::{ LISTEN_TABLE, RecvFlags, RecvOptions, SOCKET_SET, SendOptions, Shutdown, Socket, SocketAddrEx, @@ -726,8 +726,8 @@ impl TcpSocket { } } -static TCP_BOUND_PORTS: Lazy>>>> = - Lazy::new(|| Mutex::new(HashMap::new())); +static TCP_BOUND_PORTS: LazyLock>>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); fn register_tcp_bound(endpoint: IpListenEndpoint) -> AxResult { if endpoint.port == 0 { diff --git a/os/arceos/modules/axnet-ng/src/unix/mod.rs b/os/arceos/modules/axnet-ng/src/unix/mod.rs index d2a0165a3a..730df48a4a 100644 --- a/os/arceos/modules/axnet-ng/src/unix/mod.rs +++ b/os/arceos/modules/axnet-ng/src/unix/mod.rs @@ -14,7 +14,7 @@ use axfs_ng_vfs::NodeType; use axpoll::{IoEvents, Pollable}; use enum_dispatch::enum_dispatch; use hashbrown::HashMap; -use spin::Lazy; +use spin::LazyLock; pub use self::{dgram::DgramTransport, stream::StreamTransport}; use crate::{ @@ -93,8 +93,8 @@ pub struct BindSlot { dgram: Mutex>, } -static ABSTRACT_BINDS: Lazy, BindSlot>>> = - Lazy::new(|| Mutex::new(HashMap::new())); +static ABSTRACT_BINDS: LazyLock, BindSlot>>> = + LazyLock::new(|| Mutex::new(HashMap::new())); pub(crate) fn with_slot( addr: &UnixSocketAddr, diff --git a/os/arceos/modules/axtask/src/api.rs b/os/arceos/modules/axtask/src/api.rs index 80a2c3d09d..0dcf290ce9 100644 --- a/os/arceos/modules/axtask/src/api.rs +++ b/os/arceos/modules/axtask/src/api.rs @@ -141,9 +141,9 @@ pub fn init_scheduler() { } pub(crate) fn cpu_mask_full() -> AxCpuMask { - use spin::Lazy; + use spin::LazyLock; - static CPU_MASK_FULL: Lazy = Lazy::new(|| { + static CPU_MASK_FULL: LazyLock = LazyLock::new(|| { let cpu_num = ax_hal::cpu_num(); let mut cpumask = AxCpuMask::new(); for cpu_id in 0..cpu_num { diff --git a/os/axvisor/src/shell/command/mod.rs b/os/axvisor/src/shell/command/mod.rs index d0d3a36671..2f8236d927 100644 --- a/os/axvisor/src/shell/command/mod.rs +++ b/os/axvisor/src/shell/command/mod.rs @@ -29,9 +29,10 @@ use std::{ }; use std::{print, println}; -use spin::Lazy; +use spin::LazyLock; -pub static COMMAND_TREE: Lazy> = Lazy::new(build_command_tree); +pub static COMMAND_TREE: LazyLock> = + LazyLock::new(build_command_tree); #[derive(Debug, Clone)] pub struct CommandNode { diff --git a/platforms/ax-plat-aarch64-peripherals/Cargo.toml b/platforms/ax-plat-aarch64-peripherals/Cargo.toml index d4044ca543..413f6447a8 100644 --- a/platforms/ax-plat-aarch64-peripherals/Cargo.toml +++ b/platforms/ax-plat-aarch64-peripherals/Cargo.toml @@ -33,7 +33,7 @@ cntv-timer = [] [dependencies] ax-kspin = { workspace = true } log = "0.4" -spin = "0.10" +spin = { workspace = true } ax-int-ratio = { workspace = true } ax-lazyinit = { workspace = true } aarch64-cpu = "11.0" diff --git a/platforms/axplat-dyn/Cargo.toml b/platforms/axplat-dyn/Cargo.toml index a345b10498..aef1d5d1e6 100644 --- a/platforms/axplat-dyn/Cargo.toml +++ b/platforms/axplat-dyn/Cargo.toml @@ -32,7 +32,7 @@ ax-memory-addr.workspace = true ax-percpu = { workspace = true, features = ["custom-base"] } rdrive.workspace = true somehal.workspace = true -spin = "0.10" +spin = { workspace = true } [package.metadata.axplat] platform = "dyn" diff --git a/platforms/somehal/Cargo.toml b/platforms/somehal/Cargo.toml index 9e473568ef..97b6cd2394 100644 --- a/platforms/somehal/Cargo.toml +++ b/platforms/somehal/Cargo.toml @@ -28,7 +28,7 @@ thiserror = {workspace = true} log = {workspace = true} kernutil = {workspace = true} mmio-api = {workspace = true} -spin = "0.10" +spin = { workspace = true } [target.'cfg(target_arch = "aarch64")'.dependencies] diff --git a/virtualization/arm_vcpu/Cargo.toml b/virtualization/arm_vcpu/Cargo.toml index 503e242059..6c9fd36e68 100644 --- a/virtualization/arm_vcpu/Cargo.toml +++ b/virtualization/arm_vcpu/Cargo.toml @@ -20,7 +20,7 @@ targets = ["aarch64-unknown-none-softfloat"] [dependencies] log = "0.4" -spin = "0.10" +spin = { workspace = true } aarch64-cpu = "11.0" numeric-enum-macro = "0.2" diff --git a/virtualization/arm_vgic/Cargo.toml b/virtualization/arm_vgic/Cargo.toml index 51ef3cd364..2d2ed49dd3 100644 --- a/virtualization/arm_vgic/Cargo.toml +++ b/virtualization/arm_vgic/Cargo.toml @@ -32,5 +32,5 @@ ax-errno = { workspace = true } bitmaps = {version = "3.2", default-features = false} log = "0.4" ax-memory-addr = { workspace = true } -spin = "0.10" +spin = { workspace = true } tock-registers = "0.10" diff --git a/virtualization/axvm/Cargo.toml b/virtualization/axvm/Cargo.toml index 46d25cff0f..d6db9d7921 100644 --- a/virtualization/axvm/Cargo.toml +++ b/virtualization/axvm/Cargo.toml @@ -18,7 +18,7 @@ svm = ["axaddrspace/svm", "x86_vcpu/svm"] [dependencies] log = "0.4" cfg-if = "1.0" -spin = "0.10" +spin = { workspace = true } # System independent crates provided by ArceOS. ax-errno = { workspace = true } From 07d4f697684827464ea7366b6d7fb2f075c713ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 15:01:07 +0800 Subject: [PATCH 56/71] fix(starry): abort test run on first failure (#983) Co-authored-by: Claude Opus 4.7 --- scripts/axbuild/src/starry/test.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/axbuild/src/starry/test.rs b/scripts/axbuild/src/starry/test.rs index 5df98cadd8..4dd8eaf7a1 100644 --- a/scripts/axbuild/src/starry/test.rs +++ b/scripts/axbuild/src/starry/test.rs @@ -595,6 +595,12 @@ impl Starry { outcome: StarryQemuCaseOutcome::Failed, duration: case_started.elapsed(), }); + finalize_qemu_case_run(&StarryQemuRunReport { + group: test_group, + cases: reports, + total_duration: suite_started.elapsed(), + })?; + bail!("starry qemu test aborted: case `{case_name}` failed"); } } } From b246a3120118180c32b43eb6a1ce5f599adb9c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=96=E5=9C=B0=E5=9D=80=E7=AC=A6?= <108524916+zyc107109102@users.noreply.github.com> Date: Wed, 27 May 2026 15:22:28 +0800 Subject: [PATCH 57/71] fix(axfs-ng-vfs): skip children cache transfer on rename to avoid stale parent references (#938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(axfs-ng-vfs): skip children cache transfer on rename to avoid stale parent references * update * 修改时限 * 再次修改时限 * 移动测试目录 * test ci --------- Co-authored-by: zyc107109102 Co-authored-by: 周睿 --- components/axfs-ng-vfs/src/node/dir.rs | 8 +- .../qemu-smp1/bug-ext4-dir-ops/c/src/main.c | 626 ------------------ .../bug-ext4-dir-ops/qemu-aarch64.toml | 24 - .../bug-ext4-dir-ops/qemu-loongarch64.toml | 24 - .../bug-ext4-dir-ops/qemu-riscv64.toml | 22 - .../bug-ext4-dir-ops/qemu-x86_64.toml | 22 - .../bug-ext4-dir-ops/c/CMakeLists.txt | 0 .../bugfix/bug-ext4-dir-ops/c/src/main.c | 617 +++++++++++++++++ .../bug-ext4-dir-ops/c/src/test_framework.h | 86 +++ .../normal/qemu-smp1/bugfix/qemu-aarch64.toml | 1 + .../qemu-smp1/bugfix/qemu-loongarch64.toml | 1 + .../normal/qemu-smp1/bugfix/qemu-riscv64.toml | 1 + .../normal/qemu-smp1/bugfix/qemu-x86_64.toml | 1 + 13 files changed, 713 insertions(+), 720 deletions(-) delete mode 100644 test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/c/src/main.c delete mode 100644 test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml delete mode 100644 test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml delete mode 100644 test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml delete mode 100644 test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml rename test-suit/starryos/normal/qemu-smp1/{ => bugfix}/bug-ext4-dir-ops/c/CMakeLists.txt (100%) create mode 100644 test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/test_framework.h diff --git a/components/axfs-ng-vfs/src/node/dir.rs b/components/axfs-ng-vfs/src/node/dir.rs index 7869f5adee..4ff4009389 100644 --- a/components/axfs-ng-vfs/src/node/dir.rs +++ b/components/axfs-ng-vfs/src/node/dir.rs @@ -350,8 +350,12 @@ impl DirNode { }; *fresh_entry.user_data().deref_mut() = user_data; if let (Ok(src_dir), Ok(fresh_dir)) = (entry.as_dir(), fresh_entry.as_dir()) { - let cache = mem::take(src_dir.cache.lock().deref_mut()); - *fresh_dir.cache.lock().deref_mut() = cache; + // Do NOT transfer children cache: child DirEntries retain + // stale Reference.parent pointers to the old directory, + // which makes path-based operations (unlink, rename) resolve + // against the old (now-gone) path and fail with ENOENT. + // Children will be lazily re-looked up from disk with correct + // parent references on next access. let mountpoint = mem::take(src_dir.mountpoint.lock().deref_mut()); *fresh_dir.mountpoint.lock().deref_mut() = mountpoint; } diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/c/src/main.c deleted file mode 100644 index cc48933a4b..0000000000 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/c/src/main.c +++ /dev/null @@ -1,626 +0,0 @@ -/*! - * bug-ext4-dir-ops.c - * - * Verifies POSIX rmdir() and rename() semantics on ext4 (rsext4): - * - * rmdir: - * 1. rmdir(non_empty_dir) == ENOTEMPTY -- must not recursively delete - * 2. rmdir(empty_dir) == 0 -- must succeed - * - * rename (target already exists): - * 3. rename(dir, non_empty_dir) == ENOTEMPTY -- no recursive delete - * 4. rename(dir, empty_dir) == 0 -- replace empty dir - * 5. rename(file, dir) == ENOTDIR -- no cross-type overwrite - * 6. rename(dir, file) == EISDIR -- no cross-type overwrite - * 7. rename(file, file) == 0 -- overwrite file - * - * Previous rmdir bug: DirNodeOps::unlink called rsext4::delete_dir() - * (recursive rm -rf semantics) instead of checking emptiness first. - * - * Previous rename bug: rename() called delete_dir() on non-empty directory - * targets (same recursive-delete issue), and lacked type cross-checks so - * file-over-dir and dir-over-file overwrites silently destroyed data. - * - * Test path must be on ext4 root filesystem (/root), not /tmp (tmpfs), - * because tmpfs correctly returns ENOTEMPTY and would mask the rsext4 bug. - */ - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include - -static int passed; -static int failed; -static int bug_count; - -#define CHECK(cond, msg) \ - do { \ - if (cond) { \ - printf(" [OK] %s\n", (msg)); \ - passed++; \ - } else { \ - printf(" [FAIL] %s (errno=%d %s)\n", \ - (msg), errno, strerror(errno)); \ - failed++; \ - } \ - } while (0) - -/* - * Manual recursive removal — returns 0 on success, -1 on any failure. - * Unlike system("rm -rf"), this propagates errors so we can detect - * if the bug corrupts the filesystem during cleanup. - */ -static int manual_rmdir_recursive(const char *path) -{ - DIR *d = opendir(path); - if (!d) - return -1; - - struct dirent *ent; - char child[512]; - int ret = 0; - - while ((ent = readdir(d)) != NULL) { - if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) - continue; - snprintf(child, sizeof(child), "%s/%s", path, ent->d_name); - if (ent->d_type == DT_DIR) { - if (manual_rmdir_recursive(child) != 0) - ret = -1; - } else { - if (unlink(child) != 0) - ret = -1; - } - } - closedir(d); - - if (rmdir(path) != 0) - ret = -1; - return ret; -} - -/* - * Remove a file or directory tree at path, ignoring errors. - * Used for cleanup between test scenarios where the filesystem - * state may be corrupted by a bug. - */ -static void force_remove(const char *path) -{ - struct stat st; - if (stat(path, &st) != 0) - return; - if (S_ISDIR(st.st_mode)) - manual_rmdir_recursive(path); - else - unlink(path); -} - -/* - * Try rmdir on a non-empty directory and verify ENOTEMPTY + no cascade. - * Returns 1 if bug detected, 0 if correct behavior. - */ -static int test_rmdir_nonempty(const char *label, const char *dir_path, - const char *parent_dir, const char *sibling_path) -{ - printf("\n--- rmdir(non-empty): %s ---\n", label); - printf(" target: %s\n", dir_path); - printf(" parent: %s\n", parent_dir); - printf(" sibling: %s\n", sibling_path); - - /* Verify pre-conditions */ - CHECK(access(dir_path, F_OK) == 0, "target exists before rmdir"); - CHECK(access(sibling_path, F_OK) == 0, "sibling exists before rmdir"); - - errno = 0; - int rc = rmdir(dir_path); - - if (rc == 0) { - printf(" [BUG] rmdir succeeded on non-empty dir — recursive delete!\n"); - bug_count++; - failed++; - - /* Check cascade damage */ - if (access(dir_path, F_OK) != 0) - printf(" [BUG] target/ was deleted\n"); - if (access(sibling_path, F_OK) != 0) { - printf(" [BUG] sibling was destroyed (cascading)\n"); - bug_count++; - } - if (access(parent_dir, F_OK) != 0) { - printf(" [BUG] parent/ was destroyed (cascading)\n"); - bug_count++; - } - return 1; /* bug detected */ - } - - if (errno == ENOTEMPTY || errno == EEXIST) { - printf(" [OK] returned ENOTEMPTY (rc=%d, errno=%d)\n", rc, errno); - passed++; - } else { - printf(" [FAIL] unexpected errno=%d (%s)\n", errno, strerror(errno)); - failed++; - } - - /* Verify no cascade damage */ - CHECK(access(dir_path, F_OK) == 0, "target still exists"); - CHECK(access(sibling_path, F_OK) == 0, "sibling survived"); - CHECK(access(parent_dir, F_OK) == 0, "parent survived"); - return 0; -} - -/* ================================================================ - * RENAME TESTS - * - * POSIX rename() semantics when destination already exists: - * - rename(dir, non_empty_dir) → ENOTEMPTY (no recursive delete) - * - rename(dir, empty_dir) → 0 (replace empty dir) - * - rename(file, dir) → ENOTDIR (type mismatch) - * - rename(dir, file) → EISDIR (type mismatch) - * - rename(file, file) → 0 (overwrite) - * ================================================================ */ - -/* - * Test: rename dir → non-empty dir. - * Expect: ENOTEMPTY, both directory trees intact. - * Bug: rsext4 delete_dir() recursively destroys the target tree. - */ -static void test_rename_dir_to_nonempty_dir(const char *base) -{ - printf("\n--- rename(dir → non-empty dir) ---\n"); - - char src_dir[256], src_file[256]; - char dst_dir[256], dst_file[256]; - - snprintf(src_dir, sizeof(src_dir), "%s/rn_src_dir", base); - snprintf(src_file, sizeof(src_file), "%s/rn_src_dir/data.txt", base); - snprintf(dst_dir, sizeof(dst_dir), "%s/rn_dst_dir", base); - snprintf(dst_file, sizeof(dst_file), "%s/rn_dst_dir/keep.txt", base); - - CHECK(mkdir(src_dir, 0755) == 0, "mkdir src_dir"); - CHECK(mkdir(dst_dir, 0755) == 0, "mkdir dst_dir"); - - FILE *f = fopen(src_file, "w"); - CHECK(f != NULL, "create src_dir/data.txt"); - if (f) { fprintf(f, "source\n"); fclose(f); } - - f = fopen(dst_file, "w"); - CHECK(f != NULL, "create dst_dir/keep.txt"); - if (f) { fprintf(f, "target\n"); fclose(f); } - - errno = 0; - int rc = rename(src_dir, dst_dir); - - if (rc == 0) { - printf(" [BUG] rename succeeded — recursive delete of non-empty dir!\n"); - bug_count++; - failed++; - if (access(dst_file, F_OK) != 0) - printf(" [BUG] dst_dir/keep.txt was destroyed\n"); - if (access(src_dir, F_OK) != 0) - printf(" [BUG] src_dir was removed\n"); - } else if (errno == ENOTEMPTY || errno == EEXIST) { - printf(" [OK] returned ENOTEMPTY (rc=%d, errno=%d)\n", rc, errno); - passed++; - CHECK(access(src_dir, F_OK) == 0, "src_dir still exists"); - CHECK(access(src_file, F_OK) == 0, "src_dir/data.txt intact"); - CHECK(access(dst_dir, F_OK) == 0, "dst_dir still exists"); - CHECK(access(dst_file, F_OK) == 0, "dst_dir/keep.txt intact"); - } else { - printf(" [FAIL] unexpected errno=%d (%s)\n", errno, strerror(errno)); - failed++; - } - - force_remove(src_dir); - force_remove(dst_dir); -} - -/* - * Test: rename dir → empty dir. - * Expect: success, old empty dir replaced by source dir. - */ -static void test_rename_dir_to_empty_dir(const char *base) -{ - printf("\n--- rename(dir → empty dir) ---\n"); - - char src_dir[256], src_file[256]; - char dst_dir[256]; - char moved_file[256]; - - snprintf(src_dir, sizeof(src_dir), "%s/rn_src2", base); - snprintf(src_file, sizeof(src_file), "%s/rn_src2/moved.txt", base); - snprintf(dst_dir, sizeof(dst_dir), "%s/rn_dst2", base); - snprintf(moved_file, sizeof(moved_file), "%s/rn_dst2/moved.txt", base); - - CHECK(mkdir(src_dir, 0755) == 0, "mkdir src_dir"); - CHECK(mkdir(dst_dir, 0755) == 0, "mkdir dst_dir (empty)"); - - FILE *f = fopen(src_file, "w"); - CHECK(f != NULL, "create src_dir/moved.txt"); - if (f) { fprintf(f, "payload\n"); fclose(f); } - - errno = 0; - int rc = rename(src_dir, dst_dir); - - CHECK(rc == 0, "rename(dir, empty_dir) succeeded"); - if (rc == 0) { - CHECK(access(src_dir, F_OK) != 0, "src_dir removed"); - CHECK(access(moved_file, F_OK) == 0, "moved.txt accessible at new path"); - } - - force_remove(dst_dir); - force_remove(src_dir); -} - -/* - * Test: rename file → directory. - * Expect: ENOTDIR, both source file and target dir intact. - * Bug: rsext4 rename() deletes the target dir and moves file into its place. - */ -static void test_rename_file_to_dir(const char *base) -{ - printf("\n--- rename(file → dir) ---\n"); - - char src_file[256], dst_dir[256], dst_child[256]; - - snprintf(src_file, sizeof(src_file), "%s/rn_file.txt", base); - snprintf(dst_dir, sizeof(dst_dir), "%s/rn_dir_target", base); - snprintf(dst_child, sizeof(dst_child), "%s/rn_dir_target/child.txt", base); - - FILE *f = fopen(src_file, "w"); - CHECK(f != NULL, "create source file"); - if (f) { fprintf(f, "file\n"); fclose(f); } - - CHECK(mkdir(dst_dir, 0755) == 0, "mkdir target dir"); - f = fopen(dst_child, "w"); - CHECK(f != NULL, "create target dir/child.txt"); - if (f) { fprintf(f, "child\n"); fclose(f); } - - errno = 0; - int rc = rename(src_file, dst_dir); - - if (rc == 0) { - printf(" [BUG] rename(file, dir) succeeded — dir destroyed!\n"); - bug_count++; - failed++; - if (access(dst_child, F_OK) != 0) - printf(" [BUG] target dir contents destroyed\n"); - } else if (errno == ENOTDIR || errno == EISDIR) { - printf(" [OK] returned ENOTDIR/EISDIR (rc=%d, errno=%d)\n", rc, errno); - passed++; - CHECK(access(src_file, F_OK) == 0, "source file still exists"); - CHECK(access(dst_dir, F_OK) == 0, "target dir still exists"); - CHECK(access(dst_child, F_OK) == 0, "target dir contents intact"); - } else { - printf(" [FAIL] unexpected errno=%d (%s)\n", errno, strerror(errno)); - failed++; - } - - force_remove(dst_dir); - force_remove(src_file); -} - -/* - * Test: rename directory → file. - * Expect: EISDIR, both source dir and target file intact. - * Bug: rsext4 rename() deletes the target file and moves dir into its place. - */ -static void test_rename_dir_to_file(const char *base) -{ - printf("\n--- rename(dir → file) ---\n"); - - char src_dir[256], src_child[256], dst_file[256]; - - snprintf(src_dir, sizeof(src_dir), "%s/rn_dir_src", base); - snprintf(src_child, sizeof(src_child), "%s/rn_dir_src/inside.txt", base); - snprintf(dst_file, sizeof(dst_file), "%s/rn_file_target.txt", base); - - CHECK(mkdir(src_dir, 0755) == 0, "mkdir source dir"); - - FILE *f = fopen(src_child, "w"); - CHECK(f != NULL, "create source dir/inside.txt"); - if (f) { fprintf(f, "inside\n"); fclose(f); } - - f = fopen(dst_file, "w"); - CHECK(f != NULL, "create target file"); - if (f) { fprintf(f, "target\n"); fclose(f); } - - errno = 0; - int rc = rename(src_dir, dst_file); - - if (rc == 0) { - printf(" [BUG] rename(dir, file) succeeded — dir overwrote file!\n"); - bug_count++; - failed++; - } else if (errno == EISDIR || errno == ENOTDIR) { - printf(" [OK] returned EISDIR (rc=%d, errno=%d)\n", rc, errno); - passed++; - CHECK(access(src_dir, F_OK) == 0, "source dir still exists"); - CHECK(access(src_child, F_OK) == 0, "source dir contents intact"); - CHECK(access(dst_file, F_OK) == 0, "target file still exists"); - } else { - printf(" [FAIL] unexpected errno=%d (%s)\n", errno, strerror(errno)); - failed++; - } - - force_remove(src_dir); - force_remove(dst_file); -} - -/* - * Test: rename file → file (overwrite). - * Expect: success, old file replaced by source file. - */ -static void test_rename_file_to_file(const char *base) -{ - printf("\n--- rename(file → file) ---\n"); - - char src[256], dst[256]; - - snprintf(src, sizeof(src), "%s/rn_f2f_src.txt", base); - snprintf(dst, sizeof(dst), "%s/rn_f2f_dst.txt", base); - - FILE *f = fopen(src, "w"); - CHECK(f != NULL, "create source file"); - if (f) { fprintf(f, "new-content\n"); fclose(f); } - - f = fopen(dst, "w"); - CHECK(f != NULL, "create destination file"); - if (f) { fprintf(f, "old-content\n"); fclose(f); } - - errno = 0; - int rc = rename(src, dst); - - CHECK(rc == 0, "rename(file, file) succeeded"); - if (rc == 0) { - CHECK(access(src, F_OK) != 0, "source removed"); - CHECK(access(dst, F_OK) == 0, "destination exists"); - - /* Verify content was overwritten */ - f = fopen(dst, "r"); - if (f) { - char buf[64] = {0}; - fread(buf, 1, sizeof(buf) - 1, f); - fclose(f); - CHECK(strstr(buf, "new-content") != NULL, "content is from source file"); - } - } - - force_remove(src); - force_remove(dst); -} - -/* ================================================================ - * MAIN - * ================================================================ */ - -int main(void) -{ - const char *base = "/root/bug-ext4-dir-ops-test"; - char parent[256], sibling[256], empty_dir[256]; - - printf("=== bug-ext4-dir-ops ===\n"); - - /* Cleanup from previous runs */ - manual_rmdir_recursive(base); - - /* Create base structure */ - snprintf(parent, sizeof(parent), "%s/parent", base); - snprintf(sibling, sizeof(sibling), "%s/parent/sibling.txt", base); - snprintf(empty_dir, sizeof(empty_dir), "%s/parent/empty", base); - - CHECK(mkdir(base, 0755) == 0, "mkdir base"); - CHECK(mkdir(parent, 0755) == 0, "mkdir parent"); - - FILE *f = fopen(sibling, "w"); - CHECK(f != NULL, "create sibling.txt"); - if (f) { fprintf(f, "sibling\n"); fclose(f); } - - CHECK(mkdir(empty_dir, 0755) == 0, "mkdir empty (control group)"); - - /* ============================================================== - * RMDR TESTS - * ============================================================== */ - - /* - * === Test 1: dir with files + subdirectory (mixed) === - * - * parent/ - * target_mixed/ - * file1.txt - * file2.txt - * sub/ - * deep.txt - * sibling.txt - */ - { - char tdir[256], sub[256], f1[256], f2[256], deep[256]; - snprintf(tdir, sizeof(tdir), "%s/parent/target_mixed", base); - snprintf(sub, sizeof(sub), "%s/parent/target_mixed/sub", base); - snprintf(f1, sizeof(f1), "%s/parent/target_mixed/file1.txt", base); - snprintf(f2, sizeof(f2), "%s/parent/target_mixed/file2.txt", base); - snprintf(deep, sizeof(deep), "%s/parent/target_mixed/sub/deep.txt", base); - - CHECK(mkdir(tdir, 0755) == 0, "mkdir target_mixed"); - CHECK(mkdir(sub, 0755) == 0, "mkdir target_mixed/sub"); - - f = fopen(f1, "w"); - CHECK(f != NULL, "create file1.txt"); - if (f) { fprintf(f, "hello\n"); fclose(f); } - f = fopen(f2, "w"); - CHECK(f != NULL, "create file2.txt"); - if (f) { fprintf(f, "world\n"); fclose(f); } - f = fopen(deep, "w"); - CHECK(f != NULL, "create sub/deep.txt"); - if (f) { fprintf(f, "deep\n"); fclose(f); } - - test_rmdir_nonempty("mixed (files+subdir)", tdir, parent, sibling); - - /* Verify deep content survived */ - CHECK(access(deep, F_OK) == 0, "sub/deep.txt intact"); - - /* Cleanup for next test */ - manual_rmdir_recursive(tdir); - } - - /* - * === Test 2: dir with files only (no subdirectories) === - * - * parent/ - * target_files/ - * a.txt - * b.txt - * c.txt - * sibling.txt - */ - { - char tdir[256], fa[256], fb[256], fc[256]; - snprintf(tdir, sizeof(tdir), "%s/parent/target_files", base); - snprintf(fa, sizeof(fa), "%s/parent/target_files/a.txt", base); - snprintf(fb, sizeof(fb), "%s/parent/target_files/b.txt", base); - snprintf(fc, sizeof(fc), "%s/parent/target_files/c.txt", base); - - CHECK(mkdir(tdir, 0755) == 0, "mkdir target_files"); - - f = fopen(fa, "w"); - CHECK(f != NULL, "create a.txt"); - if (f) { fprintf(f, "a\n"); fclose(f); } - f = fopen(fb, "w"); - CHECK(f != NULL, "create b.txt"); - if (f) { fprintf(f, "b\n"); fclose(f); } - f = fopen(fc, "w"); - CHECK(f != NULL, "create c.txt"); - if (f) { fprintf(f, "c\n"); fclose(f); } - - test_rmdir_nonempty("files only", tdir, parent, sibling); - CHECK(access(fa, F_OK) == 0, "a.txt intact"); - CHECK(access(fb, F_OK) == 0, "b.txt intact"); - CHECK(access(fc, F_OK) == 0, "c.txt intact"); - - manual_rmdir_recursive(tdir); - } - - /* - * === Test 3: dir with subdirectories only (no files) === - * - * parent/ - * target_subdirs/ - * child1/ - * dummy.txt - * child2/ - * dummy.txt - * sibling.txt - */ - { - char tdir[256], c1[256], c2[256], d1[256], d2[256]; - snprintf(tdir, sizeof(tdir), "%s/parent/target_subdirs", base); - snprintf(c1, sizeof(c1), "%s/parent/target_subdirs/child1", base); - snprintf(c2, sizeof(c2), "%s/parent/target_subdirs/child2", base); - snprintf(d1, sizeof(d1), "%s/parent/target_subdirs/child1/dummy.txt", base); - snprintf(d2, sizeof(d2), "%s/parent/target_subdirs/child2/dummy.txt", base); - - CHECK(mkdir(tdir, 0755) == 0, "mkdir target_subdirs"); - CHECK(mkdir(c1, 0755) == 0, "mkdir child1"); - CHECK(mkdir(c2, 0755) == 0, "mkdir child2"); - - f = fopen(d1, "w"); - CHECK(f != NULL, "create child1/dummy.txt"); - if (f) { fprintf(f, "d1\n"); fclose(f); } - f = fopen(d2, "w"); - CHECK(f != NULL, "create child2/dummy.txt"); - if (f) { fprintf(f, "d2\n"); fclose(f); } - - test_rmdir_nonempty("subdirs only", tdir, parent, sibling); - CHECK(access(d1, F_OK) == 0, "child1/dummy.txt intact"); - CHECK(access(d2, F_OK) == 0, "child2/dummy.txt intact"); - - manual_rmdir_recursive(tdir); - } - - /* - * === Test 4: rmdir on parent (which contains sibling.txt) === - * - * This tests cascade upward: if delete_dir is truly recursive, - * attempting to rmdir parent/ should destroy base/ as well. - */ - { - printf("\n--- rmdir(non-empty): parent dir ---\n"); - CHECK(access(parent, F_OK) == 0, "parent exists"); - CHECK(access(sibling, F_OK) == 0, "sibling exists"); - - errno = 0; - int rc = rmdir(parent); - - if (rc == 0) { - printf(" [BUG] rmdir(parent) succeeded — cascade to parent!\n"); - bug_count++; - failed++; - if (access(base, F_OK) != 0) { - printf(" [BUG] base/ was also destroyed (cascade upward)\n"); - bug_count++; - } - } else if (errno == ENOTEMPTY || errno == EEXIST) { - printf(" [OK] rmdir(parent) correctly returned ENOTEMPTY\n"); - passed++; - } else { - printf(" [FAIL] unexpected errno=%d (%s)\n", errno, strerror(errno)); - failed++; - } - - CHECK(access(parent, F_OK) == 0, "parent still exists"); - CHECK(access(sibling, F_OK) == 0, "sibling still exists"); - CHECK(access(base, F_OK) == 0, "base still exists"); - } - - /* - * === Test 5: rmdir on empty directory (control group) === - */ - printf("\n--- rmdir(empty dir) ---\n"); - { - int rc = rmdir(empty_dir); - CHECK(rc == 0, "rmdir(empty) succeeded"); - CHECK(access(empty_dir, F_OK) != 0, "empty dir removed"); - } - - /* - * === Test 6: repeated rmdir on same path === - * - * After successful rmdir, calling again should return ENOENT. - * If the bug corrupts internal state, this might crash or return - * unexpected errors. - */ - printf("\n--- repeated rmdir on removed dir ---\n"); - { - errno = 0; - int rc = rmdir(empty_dir); - CHECK(rc != 0, "second rmdir(empty) fails"); - CHECK(errno == ENOENT, "second rmdir returns ENOENT"); - } - - /* ============================================================== - * RENAME TESTS - * ============================================================== */ - - test_rename_dir_to_nonempty_dir(base); - test_rename_dir_to_empty_dir(base); - test_rename_file_to_dir(base); - test_rename_dir_to_file(base); - test_rename_file_to_file(base); - - /* Final cleanup */ - manual_rmdir_recursive(base); - - /* === Summary === */ - printf("\n=== result: %d passed, %d failed, %d bugs ===\n", - passed, failed, bug_count); - if (failed == 0) - printf("TEST PASSED\n"); - else - printf("TEST FAILED\n"); - - return failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml deleted file mode 100644 index 27aa9af15c..0000000000 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-aarch64.toml +++ /dev/null @@ -1,24 +0,0 @@ -args = [ - "-machine", - "virt", - "-cpu", - "cortex-a72", - "-nographic", - "-m", - "512M", - "-device", - "virtio-blk-pci,drive=disk0", - "-drive", - "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", - "-device", - "virtio-net-pci,netdev=net0", - "-netdev", - "user,id=net0", -] -uefi = false -to_bin = false -shell_prefix = "root@starry:" -shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" -success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] -timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml deleted file mode 100644 index 91d767d009..0000000000 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-loongarch64.toml +++ /dev/null @@ -1,24 +0,0 @@ -args = [ - "-machine", - "virt", - "-cpu", - "la464", - "-nographic", - "-m", - "512M", - "-device", - "virtio-blk-pci,drive=disk0", - "-drive", - "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", - "-device", - "virtio-net-pci,netdev=net0", - "-netdev", - "user,id=net0", -] -to_bin = true -uefi = false -shell_prefix = "root@starry:" -shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" -success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] -timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml deleted file mode 100644 index 02aff9714b..0000000000 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-riscv64.toml +++ /dev/null @@ -1,22 +0,0 @@ -args = [ - "-nographic", - "-m", - "512M", - "-cpu", - "rv64", - "-device", - "virtio-blk-pci,drive=disk0", - "-drive", - "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", - "-device", - "virtio-net-pci,netdev=net0", - "-netdev", - "user,id=net0", -] -uefi = false -to_bin = true -shell_prefix = "root@starry:" -shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" -success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] -timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml deleted file mode 100644 index f3e5859b89..0000000000 --- a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/qemu-x86_64.toml +++ /dev/null @@ -1,22 +0,0 @@ -args = [ - "-machine", - "q35", - "-nographic", - "-m", - "512M", - "-device", - "virtio-blk-pci,drive=disk0", - "-drive", - "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", - "-device", - "virtio-net-pci,netdev=net0", - "-netdev", - "user,id=net0", -] -uefi = false -to_bin = false -shell_prefix = "root@starry:" -shell_init_cmd = "/usr/bin/bug-ext4-dir-ops" -success_regex = ['(?m)^TEST PASSED\s*$'] -fail_regex = ['(?m)^TEST FAILED\s*$', '(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$"] -timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/CMakeLists.txt similarity index 100% rename from test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops/c/CMakeLists.txt rename to test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/CMakeLists.txt diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c new file mode 100644 index 0000000000..20f11a88f1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c @@ -0,0 +1,617 @@ +/*! + * bug-ext4-dir-ops.c + * + * Verifies POSIX rmdir() and rename() semantics on ext4 (rsext4), + * plus VFS dentry cache correctness after rename (stale parent bug). + * + * Test path must be on ext4 root filesystem (/root), not /tmp (tmpfs), + * because tmpfs correctly returns ENOTEMPTY and would mask the rsext4 bug. + */ + +#define _GNU_SOURCE +#include "test_framework.h" +#include +#include +#include +#include + +#define BASE "/root/bug-ext4-dir-ops-test" + +/* ========== 辅助函数 ========== */ + +static int write_file(const char *path, const char *data) +{ + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -1; + size_t len = strlen(data); + ssize_t w = write(fd, data, len); + close(fd); + return (w == (ssize_t)len) ? 0 : -1; +} + +static int read_file(const char *path, char *buf, int bufsz) +{ + int fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + int n = (int)read(fd, buf, bufsz - 1); + close(fd); + if (n < 0) + return -1; + buf[n] = '\0'; + return n; +} + +static int manual_rmdir_recursive(const char *path) +{ + DIR *d = opendir(path); + if (!d) + return -1; + + struct dirent *ent; + char child[512]; + int ret = 0; + + while ((ent = readdir(d)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + snprintf(child, sizeof(child), "%s/%s", path, ent->d_name); + if (ent->d_type == DT_DIR) { + if (manual_rmdir_recursive(child) != 0) + ret = -1; + } else { + if (unlink(child) != 0) + ret = -1; + } + } + closedir(d); + + if (rmdir(path) != 0) + ret = -1; + return ret; +} + +static void force_remove(const char *path) +{ + struct stat st; + if (stat(path, &st) != 0) + return; + if (S_ISDIR(st.st_mode)) + manual_rmdir_recursive(path); + else + unlink(path); +} + +static int count_dir_entries(const char *path) +{ + DIR *d = opendir(path); + if (!d) + return -1; + int count = 0; + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) + count++; + } + closedir(d); + return count; +} + +static int dir_has_entry(const char *dir_path, const char *name) +{ + DIR *d = opendir(dir_path); + if (!d) + return 0; + struct dirent *ent; + int found = 0; + while ((ent = readdir(d)) != NULL) { + if (strcmp(ent->d_name, name) == 0) { + found = 1; + break; + } + } + closedir(d); + return found; +} + +/* ========== rmdir 测试 ========== */ + +static void test_rmdir_nonempty_mixed(void) +{ + char tdir[256], sub[256], f1[256], f2[256], deep[256]; + char parent[256], sibling[256]; + + snprintf(parent, sizeof(parent), "%s/parent", BASE); + snprintf(sibling, sizeof(sibling), "%s/parent/sibling.txt", BASE); + snprintf(tdir, sizeof(tdir), "%s/parent/target_mixed", BASE); + snprintf(sub, sizeof(sub), "%s/parent/target_mixed/sub", BASE); + snprintf(f1, sizeof(f1), "%s/parent/target_mixed/file1.txt", BASE); + snprintf(f2, sizeof(f2), "%s/parent/target_mixed/file2.txt", BASE); + snprintf(deep, sizeof(deep), "%s/parent/target_mixed/sub/deep.txt", BASE); + + CHECK(mkdir(tdir, 0755) == 0, "mkdir target_mixed"); + CHECK(mkdir(sub, 0755) == 0, "mkdir target_mixed/sub"); + CHECK(write_file(f1, "hello\n") == 0, "create file1.txt"); + CHECK(write_file(f2, "world\n") == 0, "create file2.txt"); + CHECK(write_file(deep, "deep\n") == 0, "create sub/deep.txt"); + + CHECK_ERR(rmdir(tdir), ENOTEMPTY, "rmdir(non-empty mixed) -> ENOTEMPTY"); + CHECK(access(tdir, F_OK) == 0, "target still exists after rmdir"); + CHECK(access(sibling, F_OK) == 0, "sibling survived"); + CHECK(access(deep, F_OK) == 0, "sub/deep.txt intact"); + + manual_rmdir_recursive(tdir); +} + +static void test_rmdir_nonempty_files(void) +{ + char tdir[256], fa[256], fb[256], fc[256]; + + snprintf(tdir, sizeof(tdir), "%s/parent/target_files", BASE); + snprintf(fa, sizeof(fa), "%s/parent/target_files/a.txt", BASE); + snprintf(fb, sizeof(fb), "%s/parent/target_files/b.txt", BASE); + snprintf(fc, sizeof(fc), "%s/parent/target_files/c.txt", BASE); + + CHECK(mkdir(tdir, 0755) == 0, "mkdir target_files"); + CHECK(write_file(fa, "a\n") == 0, "create a.txt"); + CHECK(write_file(fb, "b\n") == 0, "create b.txt"); + CHECK(write_file(fc, "c\n") == 0, "create c.txt"); + + CHECK_ERR(rmdir(tdir), ENOTEMPTY, "rmdir(non-empty files) -> ENOTEMPTY"); + CHECK(access(fa, F_OK) == 0, "a.txt intact"); + CHECK(access(fb, F_OK) == 0, "b.txt intact"); + CHECK(access(fc, F_OK) == 0, "c.txt intact"); + + manual_rmdir_recursive(tdir); +} + +static void test_rmdir_nonempty_subdirs(void) +{ + char tdir[256], c1[256], c2[256], d1[256], d2[256]; + + snprintf(tdir, sizeof(tdir), "%s/parent/target_subdirs", BASE); + snprintf(c1, sizeof(c1), "%s/parent/target_subdirs/child1", BASE); + snprintf(c2, sizeof(c2), "%s/parent/target_subdirs/child2", BASE); + snprintf(d1, sizeof(d1), "%s/parent/target_subdirs/child1/dummy.txt", BASE); + snprintf(d2, sizeof(d2), "%s/parent/target_subdirs/child2/dummy.txt", BASE); + + CHECK(mkdir(tdir, 0755) == 0, "mkdir target_subdirs"); + CHECK(mkdir(c1, 0755) == 0, "mkdir child1"); + CHECK(mkdir(c2, 0755) == 0, "mkdir child2"); + CHECK(write_file(d1, "d1\n") == 0, "create child1/dummy.txt"); + CHECK(write_file(d2, "d2\n") == 0, "create child2/dummy.txt"); + + CHECK_ERR(rmdir(tdir), ENOTEMPTY, "rmdir(non-empty subdirs) -> ENOTEMPTY"); + CHECK(access(d1, F_OK) == 0, "child1/dummy.txt intact"); + CHECK(access(d2, F_OK) == 0, "child2/dummy.txt intact"); + + manual_rmdir_recursive(tdir); +} + +static void test_rmdir_nonempty_parent(void) +{ + char parent[256], sibling[256]; + + snprintf(parent, sizeof(parent), "%s/parent", BASE); + snprintf(sibling, sizeof(sibling), "%s/parent/sibling.txt", BASE); + + CHECK(access(parent, F_OK) == 0, "parent exists"); + CHECK(access(sibling, F_OK) == 0, "sibling exists"); + CHECK_ERR(rmdir(parent), ENOTEMPTY, "rmdir(parent) -> ENOTEMPTY"); + CHECK(access(parent, F_OK) == 0, "parent still exists"); + CHECK(access(sibling, F_OK) == 0, "sibling still exists"); +} + +static void test_rmdir_empty(void) +{ + char empty_dir[256]; + snprintf(empty_dir, sizeof(empty_dir), "%s/parent/empty", BASE); + + CHECK_RET(rmdir(empty_dir), 0, "rmdir(empty) succeeds"); + CHECK(access(empty_dir, F_OK) != 0, "empty dir removed"); + + /* repeated rmdir should return ENOENT */ + CHECK_ERR(rmdir(empty_dir), ENOENT, "second rmdir(empty) -> ENOENT"); +} + +/* ========== rename 基本语义测试 ========== */ + +static void test_rename_dir_to_nonempty_dir(void) +{ + char src_dir[256], src_file[256], dst_dir[256], dst_file[256]; + + snprintf(src_dir, sizeof(src_dir), "%s/rn_src_dir", BASE); + snprintf(src_file, sizeof(src_file), "%s/rn_src_dir/data.txt", BASE); + snprintf(dst_dir, sizeof(dst_dir), "%s/rn_dst_dir", BASE); + snprintf(dst_file, sizeof(dst_file), "%s/rn_dst_dir/keep.txt", BASE); + + CHECK(mkdir(src_dir, 0755) == 0, "mkdir src_dir"); + CHECK(mkdir(dst_dir, 0755) == 0, "mkdir dst_dir"); + CHECK(write_file(src_file, "source\n") == 0, "create src_dir/data.txt"); + CHECK(write_file(dst_file, "target\n") == 0, "create dst_dir/keep.txt"); + + CHECK_ERR(rename(src_dir, dst_dir), ENOTEMPTY, + "rename(dir, non-empty dir) -> ENOTEMPTY"); + CHECK(access(src_dir, F_OK) == 0, "src_dir still exists"); + CHECK(access(src_file, F_OK) == 0, "src_dir/data.txt intact"); + CHECK(access(dst_dir, F_OK) == 0, "dst_dir still exists"); + CHECK(access(dst_file, F_OK) == 0, "dst_dir/keep.txt intact"); + + force_remove(src_dir); + force_remove(dst_dir); +} + +static void test_rename_dir_to_empty_dir(void) +{ + char src_dir[256], src_file[256], dst_dir[256], moved_file[256]; + + snprintf(src_dir, sizeof(src_dir), "%s/rn_src2", BASE); + snprintf(src_file, sizeof(src_file), "%s/rn_src2/moved.txt", BASE); + snprintf(dst_dir, sizeof(dst_dir), "%s/rn_dst2", BASE); + snprintf(moved_file, sizeof(moved_file), "%s/rn_dst2/moved.txt", BASE); + + CHECK(mkdir(src_dir, 0755) == 0, "mkdir src_dir"); + CHECK(mkdir(dst_dir, 0755) == 0, "mkdir dst_dir (empty)"); + CHECK(write_file(src_file, "payload\n") == 0, "create src_dir/moved.txt"); + + CHECK_RET(rename(src_dir, dst_dir), 0, "rename(dir, empty_dir) succeeds"); + CHECK(access(src_dir, F_OK) != 0, "src_dir removed"); + CHECK(access(moved_file, F_OK) == 0, "moved.txt accessible at new path"); + + force_remove(dst_dir); + force_remove(src_dir); +} + +static void test_rename_file_to_dir(void) +{ + char src_file[256], dst_dir[256], dst_child[256]; + + snprintf(src_file, sizeof(src_file), "%s/rn_file.txt", BASE); + snprintf(dst_dir, sizeof(dst_dir), "%s/rn_dir_target", BASE); + snprintf(dst_child, sizeof(dst_child), "%s/rn_dir_target/child.txt", BASE); + + CHECK(write_file(src_file, "file\n") == 0, "create source file"); + CHECK(mkdir(dst_dir, 0755) == 0, "mkdir target dir"); + CHECK(write_file(dst_child, "child\n") == 0, "create target dir/child.txt"); + + CHECK(rename(src_file, dst_dir) != 0, "rename(file, dir) fails"); + CHECK(errno == ENOTDIR || errno == EISDIR, + "errno is ENOTDIR or EISDIR"); + CHECK(access(src_file, F_OK) == 0, "source file still exists"); + CHECK(access(dst_dir, F_OK) == 0, "target dir still exists"); + CHECK(access(dst_child, F_OK) == 0, "target dir contents intact"); + + force_remove(dst_dir); + force_remove(src_file); +} + +static void test_rename_dir_to_file(void) +{ + char src_dir[256], src_child[256], dst_file[256]; + + snprintf(src_dir, sizeof(src_dir), "%s/rn_dir_src", BASE); + snprintf(src_child, sizeof(src_child), "%s/rn_dir_src/inside.txt", BASE); + snprintf(dst_file, sizeof(dst_file), "%s/rn_file_target.txt", BASE); + + CHECK(mkdir(src_dir, 0755) == 0, "mkdir source dir"); + CHECK(write_file(src_child, "inside\n") == 0, "create source dir/inside.txt"); + CHECK(write_file(dst_file, "target\n") == 0, "create target file"); + + CHECK(rename(src_dir, dst_file) != 0, "rename(dir, file) fails"); + CHECK(errno == EISDIR || errno == ENOTDIR, + "errno is EISDIR or ENOTDIR"); + CHECK(access(src_dir, F_OK) == 0, "source dir still exists"); + CHECK(access(src_child, F_OK) == 0, "source dir contents intact"); + CHECK(access(dst_file, F_OK) == 0, "target file still exists"); + + force_remove(src_dir); + force_remove(dst_file); +} + +static void test_rename_file_to_file(void) +{ + char src[256], dst[256], buf[64]; + + snprintf(src, sizeof(src), "%s/rn_f2f_src.txt", BASE); + snprintf(dst, sizeof(dst), "%s/rn_f2f_dst.txt", BASE); + + CHECK(write_file(src, "new-content\n") == 0, "create source file"); + CHECK(write_file(dst, "old-content\n") == 0, "create destination file"); + + CHECK_RET(rename(src, dst), 0, "rename(file, file) succeeds"); + CHECK(access(src, F_OK) != 0, "source removed"); + CHECK(access(dst, F_OK) == 0, "destination exists"); + + memset(buf, 0, sizeof(buf)); + CHECK(read_file(dst, buf, sizeof(buf)) > 0, "read destination"); + CHECK(strstr(buf, "new-content") != NULL, "content is from source file"); + + force_remove(src); + force_remove(dst); +} + +/* ========== rename + VFS dentry cache 测试 ========== */ + +static void test_rename_then_unlink(void) +{ + char dir[256], child[256], renamed[256], renamed_child[256]; + + snprintf(dir, sizeof(dir), "%s/rnu_old", BASE); + snprintf(child, sizeof(child), "%s/rnu_old/data.txt", BASE); + snprintf(renamed, sizeof(renamed), "%s/rnu_new", BASE); + snprintf(renamed_child, sizeof(renamed_child), "%s/rnu_new/data.txt", BASE); + + CHECK(mkdir(dir, 0755) == 0, "mkdir old dir"); + CHECK(write_file(child, "hello\n") == 0, "create old/data.txt"); + CHECK_RET(rename(dir, renamed), 0, "rename old -> ~new"); + + /* stale parent bug: unlink via new path should succeed */ + CHECK_RET(unlink(renamed_child), 0, "unlink ~new/data.txt"); + CHECK_RET(rmdir(renamed), 0, "rmdir ~new after unlink"); + + force_remove(renamed); + force_remove(dir); +} + +static void test_rename_file_then_delete_old_path(void) +{ + char dir[256], old_path[256], new_path[256]; + + snprintf(dir, sizeof(dir), "%s/rnfd_dir", BASE); + snprintf(old_path, sizeof(old_path), "%s/rnfd_dir/old_name.txt", BASE); + snprintf(new_path, sizeof(new_path), "%s/rnfd_dir/new_name.txt", BASE); + + CHECK(mkdir(dir, 0755) == 0, "mkdir test dir"); + CHECK(write_file(old_path, "data\n") == 0, "create old_name.txt"); + CHECK_RET(rename(old_path, new_path), 0, "rename old_name -> new_name"); + + /* old path must be gone */ + CHECK(access(old_path, F_OK) != 0, "old_name.txt no longer accessible"); + CHECK_ERR(unlink(old_path), ENOENT, "unlink old_name -> ENOENT"); + + /* new path must work */ + CHECK_RET(unlink(new_path), 0, "unlink new_name.txt succeeds"); + CHECK_RET(rmdir(dir), 0, "rmdir empty dir"); + + force_remove(dir); +} + +static void test_rename_dir_then_delete_child_old_path(void) +{ + char parent[256], old_dir[256], old_child[256]; + char new_dir[256], new_child[256]; + + snprintf(parent, sizeof(parent), "%s/rndp_parent", BASE); + snprintf(old_dir, sizeof(old_dir), "%s/rndp_parent/old_pkg", BASE); + snprintf(old_child, sizeof(old_child), "%s/rndp_parent/old_pkg/data.txt", BASE); + snprintf(new_dir, sizeof(new_dir), "%s/rndp_parent/~new_pkg", BASE); + snprintf(new_child, sizeof(new_child), "%s/rndp_parent/~new_pkg/data.txt", BASE); + + CHECK(mkdir(parent, 0755) == 0, "mkdir parent"); + CHECK(mkdir(old_dir, 0755) == 0, "mkdir old_pkg"); + CHECK(write_file(old_child, "content\n") == 0, "create old_pkg/data.txt"); + CHECK_RET(rename(old_dir, new_dir), 0, "rename old_pkg -> ~new_pkg"); + + /* old path must be gone */ + CHECK(access(old_child, F_OK) != 0, "old_pkg/data.txt no longer accessible"); + CHECK_ERR(unlink(old_child), ENOENT, "unlink old_pkg/data.txt -> ENOENT"); + + /* new path must work */ + CHECK_RET(unlink(new_child), 0, "unlink ~new_pkg/data.txt succeeds"); + CHECK_RET(rmdir(new_dir), 0, "rmdir ~new_pkg"); + CHECK_RET(rmdir(parent), 0, "rmdir parent"); + + force_remove(parent); +} + +static void test_rename_then_readdir(void) +{ + char dir[256], f1[256], f2[256], renamed[256], rpath[256], buf[64]; + + snprintf(dir, sizeof(dir), "%s/rnr_old", BASE); + snprintf(f1, sizeof(f1), "%s/rnr_old/a.txt", BASE); + snprintf(f2, sizeof(f2), "%s/rnr_old/b.txt", BASE); + snprintf(renamed, sizeof(renamed), "%s/rnr_new", BASE); + + CHECK(mkdir(dir, 0755) == 0, "mkdir old dir"); + CHECK(write_file(f1, "aaa\n") == 0, "create a.txt"); + CHECK(write_file(f2, "bbb\n") == 0, "create b.txt"); + CHECK_RET(rename(dir, renamed), 0, "rename old -> ~new"); + + /* readdir must see both files */ + CHECK(dir_has_entry(renamed, "a.txt"), "readdir ~new sees a.txt"); + CHECK(dir_has_entry(renamed, "b.txt"), "readdir ~new sees b.txt"); + CHECK(count_dir_entries(renamed) == 2, "readdir ~new sees exactly 2 entries"); + + /* read content through renamed path */ + snprintf(rpath, sizeof(rpath), "%s/a.txt", renamed); + memset(buf, 0, sizeof(buf)); + CHECK(read_file(rpath, buf, sizeof(buf)) > 0, "read ~new/a.txt"); + CHECK(strcmp(buf, "aaa\n") == 0, "~new/a.txt content correct"); + + force_remove(renamed); + force_remove(dir); +} + +static void test_rename_create_same_name(void) +{ + char old_dir[256], old_child[256]; + char renamed_dir[256], renamed_child[256]; + char new_dir[256], new_child[256], buf[64]; + + snprintf(old_dir, sizeof(old_dir), "%s/rnc_pkg", BASE); + snprintf(old_child, sizeof(old_child), "%s/rnc_pkg/init.py", BASE); + snprintf(renamed_dir, sizeof(renamed_dir), "%s/rnc_~pkg", BASE); + snprintf(renamed_child, sizeof(renamed_child), "%s/rnc_~pkg/init.py", BASE); + snprintf(new_dir, sizeof(new_dir), "%s/rnc_pkg", BASE); + snprintf(new_child, sizeof(new_child), "%s/rnc_pkg/main.py", BASE); + + CHECK(mkdir(old_dir, 0755) == 0, "mkdir old pkg"); + CHECK(write_file(old_child, "old\n") == 0, "create old/init.py"); + CHECK_RET(rename(old_dir, renamed_dir), 0, "rename pkg -> ~pkg"); + + /* create new package with same name */ + CHECK(mkdir(new_dir, 0755) == 0, "mkdir new pkg (same name)"); + CHECK(write_file(new_child, "new\n") == 0, "create new/main.py"); + + /* unlink inside renamed dir — stale parent bug target */ + CHECK_RET(unlink(renamed_child), 0, "unlink ~pkg/init.py"); + CHECK_RET(rmdir(renamed_dir), 0, "rmdir ~pkg"); + + /* verify new package intact */ + memset(buf, 0, sizeof(buf)); + CHECK(read_file(new_child, buf, sizeof(buf)) > 0, "read new/main.py"); + CHECK(strcmp(buf, "new\n") == 0, "new/main.py intact after cleanup"); + CHECK(dir_has_entry(new_dir, "main.py"), "new pkg readdir sees main.py"); + + force_remove(new_dir); + force_remove(renamed_dir); + force_remove(old_dir); +} + +static void test_pip_upgrade_pattern(void) +{ + char pkg[256], init_f[256], main_f[256], sub[256], sub_f[256]; + char renamed[256], renamed_init[256], renamed_main[256], renamed_sub[256], renamed_sub_f[256]; + char new_pkg[256], new_init[256], new_main[256], new_cli[256], buf[64]; + + snprintf(pkg, sizeof(pkg), "%s/pip", BASE); + snprintf(init_f, sizeof(init_f), "%s/pip/__init__.py", BASE); + snprintf(main_f, sizeof(main_f), "%s/pip/main.py", BASE); + snprintf(sub, sizeof(sub), "%s/pip/cli", BASE); + snprintf(sub_f, sizeof(sub_f), "%s/pip/cli/run.py", BASE); + snprintf(renamed, sizeof(renamed), "%s/~ip", BASE); + snprintf(renamed_init, sizeof(renamed_init), "%s/~ip/__init__.py", BASE); + snprintf(renamed_main, sizeof(renamed_main), "%s/~ip/main.py", BASE); + snprintf(renamed_sub, sizeof(renamed_sub), "%s/~ip/cli", BASE); + snprintf(renamed_sub_f, sizeof(renamed_sub_f), "%s/~ip/cli/run.py", BASE); + snprintf(new_pkg, sizeof(new_pkg), "%s/pip", BASE); + snprintf(new_init, sizeof(new_init), "%s/pip/__init__.py", BASE); + snprintf(new_main, sizeof(new_main), "%s/pip/main.py", BASE); + snprintf(new_cli, sizeof(new_cli), "%s/pip/cli", BASE); + + /* old package */ + CHECK(mkdir(pkg, 0755) == 0, "mkdir pip"); + CHECK(write_file(init_f, "# old init\n") == 0, "create __init__.py"); + CHECK(write_file(main_f, "# old main\n") == 0, "create main.py"); + CHECK(mkdir(sub, 0755) == 0, "mkdir cli"); + CHECK(write_file(sub_f, "# old cli\n") == 0, "create cli/run.py"); + + /* rename pip -> ~ip */ + CHECK_RET(rename(pkg, renamed), 0, "rename pip -> ~ip"); + + /* install new version */ + CHECK(mkdir(new_pkg, 0755) == 0, "mkdir new pip"); + CHECK(write_file(new_init, "# new init\n") == 0, "create new __init__.py"); + CHECK(write_file(new_main, "# new main\n") == 0, "create new main.py"); + CHECK(mkdir(new_cli, 0755) == 0, "mkdir new cli"); + + /* cleanup ~ip */ + CHECK_RET(unlink(renamed_init), 0, "unlink ~ip/__init__.py"); + CHECK_RET(unlink(renamed_main), 0, "unlink ~ip/main.py"); + CHECK_RET(unlink(renamed_sub_f), 0, "unlink ~ip/cli/run.py"); + CHECK_RET(rmdir(renamed_sub), 0, "rmdir ~ip/cli"); + CHECK_RET(rmdir(renamed), 0, "rmdir ~ip"); + + /* verify new package */ + memset(buf, 0, sizeof(buf)); + CHECK(read_file(new_main, buf, sizeof(buf)) > 0, "read new pip/main.py"); + CHECK(strcmp(buf, "# new main\n") == 0, "new pip/main.py intact"); + memset(buf, 0, sizeof(buf)); + CHECK(read_file(new_init, buf, sizeof(buf)) > 0, "read new pip/__init__.py"); + CHECK(strcmp(buf, "# new init\n") == 0, "new pip/__init__.py intact"); + + force_remove(new_pkg); + force_remove(renamed); + force_remove(pkg); +} + +static void test_rename_many_files(void) +{ + char dir[256], renamed[256], path[256], content[64]; + + snprintf(dir, sizeof(dir), "%s/rnm_pkg", BASE); + snprintf(renamed, sizeof(renamed), "%s/rnm_~pkg", BASE); + + CHECK(mkdir(dir, 0755) == 0, "mkdir pkg"); + + /* create 20 files */ + int ok = 1; + for (int i = 0; i < 20; i++) { + snprintf(path, sizeof(path), "%s/file_%02d.py", dir, i); + snprintf(content, sizeof(content), "# file %d\n", i); + if (write_file(path, content) < 0) + ok = 0; + } + CHECK(ok, "created 20 files"); + + CHECK_RET(rename(dir, renamed), 0, "rename pkg -> ~pkg (20 files)"); + + /* readdir must see all 20 files */ + CHECK(count_dir_entries(renamed) == 20, "readdir ~pkg sees all 20 files"); + + /* delete all files, then rmdir */ + DIR *d = opendir(renamed); + if (d) { + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + snprintf(path, sizeof(path), "%s/%s", renamed, ent->d_name); + unlink(path); + } + closedir(d); + } + + CHECK_RET(rmdir(renamed), 0, "rmdir ~pkg after unlinking all files"); + CHECK(access(renamed, F_OK) != 0, "~pkg fully removed"); + + force_remove(renamed); + force_remove(dir); +} + +/* ========== main ========== */ + +int main(void) +{ + TEST_START("bug-ext4-dir-ops: rmdir + rename + stale parent"); + + manual_rmdir_recursive(BASE); + CHECK(mkdir(BASE, 0755) == 0, "mkdir base"); + + char parent[256], sibling[256], empty_dir[256]; + snprintf(parent, sizeof(parent), "%s/parent", BASE); + snprintf(sibling, sizeof(sibling), "%s/parent/sibling.txt", BASE); + snprintf(empty_dir, sizeof(empty_dir), "%s/parent/empty", BASE); + CHECK(mkdir(parent, 0755) == 0, "mkdir parent"); + CHECK(write_file(sibling, "sibling\n") == 0, "create sibling.txt"); + CHECK(mkdir(empty_dir, 0755) == 0, "mkdir empty (control)"); + + /* rmdir tests */ + test_rmdir_nonempty_mixed(); + test_rmdir_nonempty_files(); + test_rmdir_nonempty_subdirs(); + test_rmdir_nonempty_parent(); + test_rmdir_empty(); + + /* rename basic semantics */ + test_rename_dir_to_nonempty_dir(); + test_rename_dir_to_empty_dir(); + test_rename_file_to_dir(); + test_rename_dir_to_file(); + test_rename_file_to_file(); + + /* rename + VFS dentry cache (stale parent) */ + test_rename_then_unlink(); + test_rename_file_then_delete_old_path(); + test_rename_dir_then_delete_child_old_path(); + test_rename_then_readdir(); + test_rename_create_same_name(); + test_pip_upgrade_pattern(); + test_rename_many_files(); + + manual_rmdir_recursive(BASE); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/test_framework.h b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/test_framework.h new file mode 100644 index 0000000000..2f47878f32 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/test_framework.h @@ -0,0 +1,86 @@ +#pragma once + +/* 必须在最前面定义,确保 pipe2/gettid 等可用 */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +/* + * StarryOS Syscall Test Framework + * + * 极简独立测试框架:每个文件测一个 syscall,独立编译运行。 + * 目标:出错时精确定位到 源文件:行号 -> 哪个调用 -> 什么结果 + * + * 用法: + * TEST_START("测试名"); + * CHECK(call == expected, "描述"); + * CHECK_ERR(call, EBADF, "描述"); + * TEST_DONE(); + */ + +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +/* ---- 核心: 带文件名+行号的检查宏 ---- */ + +/* 检查条件为真 */ +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +/* 检查 syscall 返回特定值 */ +#define CHECK_RET(call, expected, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + long _e = (long)(expected); \ + if (_r == _e) { \ + printf(" PASS | %s:%d | %s (ret=%ld)\n", \ + __FILE__, __LINE__, msg, _r); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected=%ld got=%ld | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, _e, _r, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +/* 检查 syscall 失败且 errno 符合预期 */ +#define CHECK_ERR(call, exp_errno, msg) do { \ + errno = 0; \ + long _r = (long)(call); \ + if (_r == -1 && errno == (exp_errno)) { \ + printf(" PASS | %s:%d | %s (errno=%d as expected)\n", \ + __FILE__, __LINE__, msg, errno); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | expected errno=%d got ret=%ld errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, (int)(exp_errno), _r, errno, \ + strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +/* ---- 测试边界 ---- */ +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml index 30836bfb3b..d719a0adf8 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml @@ -60,6 +60,7 @@ test_commands = [ "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", "/usr/bin/bug-epollet-second-chunk", + "/usr/bin/bug-ext4-dir-ops", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml index 037fd24a91..50a7e7d342 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml @@ -61,6 +61,7 @@ test_commands = [ "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", "/usr/bin/bug-epollet-second-chunk", + "/usr/bin/bug-ext4-dir-ops", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml index 6ffb6e8bc3..343b4466cc 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml @@ -68,6 +68,7 @@ test_commands = [ "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", "/usr/bin/bug-epollet-second-chunk", + "/usr/bin/bug-ext4-dir-ops", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml index 7c6c35f3e8..21f1152c8e 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml @@ -66,6 +66,7 @@ test_commands = [ "/usr/bin/bug-tcp-udp-bind-same-port", "/usr/bin/bug-tcp-send-no-epoll-notify", "/usr/bin/bug-epollet-second-chunk", + "/usr/bin/bug-ext4-dir-ops", "/usr/bin/bug-tcp-concurrent-connect", "/usr/bin/bug-tcp-nonblocking-connect-so-error", "/usr/bin/bug-kill-zombie-esrch", From 82bac9004d8a32235337bc469db07acd85b0a873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 16:04:52 +0800 Subject: [PATCH 58/71] feat(some-serial): add Rockchip FIQ debugger UART (#980) --- drivers/ax-driver/src/serial/mod.rs | 266 +++++- drivers/interface/rdif-serial/src/lib.rs | 8 + drivers/interface/rdif-serial/src/serial.rs | 8 + drivers/serial/some-serial/src/lib.rs | 2 + drivers/serial/some-serial/src/ns16550/mod.rs | 14 + .../some-serial/src/ns16550/rockchip_fiq.rs | 889 ++++++++++++++++++ 6 files changed, 1185 insertions(+), 2 deletions(-) create mode 100644 drivers/serial/some-serial/src/ns16550/rockchip_fiq.rs diff --git a/drivers/ax-driver/src/serial/mod.rs b/drivers/ax-driver/src/serial/mod.rs index 457da8be25..29fd470335 100644 --- a/drivers/ax-driver/src/serial/mod.rs +++ b/drivers/ax-driver/src/serial/mod.rs @@ -1,6 +1,13 @@ -use log::info; +use alloc::{format, string::String}; + +use fdt_edit::{Fdt, NodeType, RegFixed, Status}; +use log::{info, warn}; use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; -use some_serial::{BSerial, ns16550, pl011}; +use some_serial::{ + BSerial, ns16550, + ns16550::rockchip_fiq::{ROCKCHIP_FIQ_RK3588_UART_CLOCK, RockchipFiqConfig, RockchipFiqSerial}, + pl011, +}; crate::model_register!( name: "common serial", @@ -12,6 +19,16 @@ crate::model_register!( }], ); +crate::model_register!( + name: "rockchip fiq debugger serial", + level: ProbeLevel::PreKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["rockchip,fiq-debugger"], + on_probe: probe_rockchip_fiq + }], +); + fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { info!("Probing serial device: {}", info.node.name()); let base_reg = @@ -49,6 +66,251 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError Ok(()) } +fn probe_rockchip_fiq(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let live_fdt = + rdrive::with_fdt(Clone::clone).ok_or_else(|| OnProbeError::other("live FDT not found"))?; + let fdt_config = rockchip_fiq_fdt_config(&live_fdt, info.node)?; + let mmio_base = crate::mmio::iomap( + fdt_config.reg.address as usize, + fdt_config.reg.size.unwrap_or(0x100) as usize, + )?; + + if fdt_config.target_disabled { + info!( + "Rockchip FIQ debugger takes disabled UART alias serial{} at {}", + fdt_config.config.serial_id, fdt_config.uart_path + ); + } + + let serial = RockchipFiqSerial::new_boxed(mmio_base, fdt_config.config); + let base = serial.base_addr(); + info!( + "Rockchip FIQ debugger UART@{base:#x} registered successfully, serial-id={}, baudrate={}, \ + irq-mode={}", + fdt_config.config.serial_id, fdt_config.config.baudrate, fdt_config.config.irq_mode_enabled + ); + plat_dev.register(serial); + Ok(()) +} + fn prop_u32(node: &fdt_edit::Node, name: &str) -> Option { node.get_property(name).and_then(|prop| prop.get_u32()) } + +struct RockchipFiqFdtConfig { + config: RockchipFiqConfig, + reg: RegFixed, + uart_path: String, + target_disabled: bool, +} + +fn rockchip_fiq_fdt_config( + fdt: &Fdt, + fiq: NodeType<'_>, +) -> Result { + let fiq_node = fiq.as_node(); + let serial_id = prop_u32(fiq_node, "rockchip,serial-id").ok_or_else(|| { + OnProbeError::other(format!("[{}] has no rockchip,serial-id", fiq.name())) + })?; + + if serial_id == u32::MAX { + return Err(OnProbeError::NotMatch); + } + + let alias = format!("serial{serial_id}"); + let uart_path = fdt + .resolve_alias(&alias) + .map(String::from) + .ok_or_else(|| OnProbeError::other(format!("{alias} alias not found")))?; + let uart_node = fdt + .get_by_path(&uart_path) + .ok_or_else(|| OnProbeError::other(format!("{uart_path} node not found")))?; + let uart = uart_node.as_node(); + + if !uart + .compatibles() + .any(|compatible| compatible == "snps,dw-apb-uart") + { + return Err(OnProbeError::other(format!( + "{uart_path} is not a snps,dw-apb-uart node" + ))); + } + + let reg_width = prop_u32(uart, "reg-io-width").unwrap_or(4); + let reg_shift = prop_u32(uart, "reg-shift").unwrap_or(2); + if reg_width != 4 || reg_shift != 2 { + return Err(OnProbeError::other(format!( + "{uart_path} has unsupported reg-io-width/reg-shift {reg_width}/{reg_shift}" + ))); + } + + let reg = uart_node + .regs() + .into_iter() + .next() + .ok_or_else(|| OnProbeError::other(format!("[{uart_path}] has no reg")))?; + + let baudrate = normalise_fiq_baudrate( + prop_u32(fiq_node, "rockchip,baudrate") + .unwrap_or(some_serial::ns16550::rockchip_fiq::ROCKCHIP_FIQ_DEFAULT_BAUDRATE), + ); + let clock_hz = prop_u32(uart, "clock-frequency").unwrap_or(ROCKCHIP_FIQ_RK3588_UART_CLOCK); + let irq_mode_enabled = prop_u32(fiq_node, "rockchip,irq-mode-enable").unwrap_or(0) != 0; + let target_disabled = matches!(uart.status(), Some(Status::Disabled)); + + if matches!(uart.status(), Some(status) if status != Status::Disabled && status != Status::Okay) + { + warn!("{uart_path} has unrecognised status; proceeding for FIQ debugger"); + } + + Ok(RockchipFiqFdtConfig { + config: RockchipFiqConfig { + serial_id, + baudrate, + clock_hz, + irq_mode_enabled, + debug_enable: true, + console_enable: true, + }, + reg, + uart_path, + target_disabled, + }) +} + +fn normalise_fiq_baudrate(baudrate: u32) -> u32 { + match baudrate { + 115_200 | 1_500_000 => baudrate, + other => { + warn!("unsupported rockchip fiq baudrate {other}, falling back to 115200"); + 115_200 + } + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use fdt_edit::{Fdt, Node, Property}; + + use super::*; + + #[test] + fn resolves_fiq_debugger_target_uart_from_alias_even_when_uart_disabled() { + let fdt = minimal_fiq_fdt(true, true); + let fiq = fdt.get_by_path("/fiq-debugger").expect("fiq node missing"); + + let config = rockchip_fiq_fdt_config(&fdt, fiq).expect("parse fiq config"); + + assert_eq!(config.config.serial_id, 2); + assert_eq!(config.config.baudrate, 1_500_000); + assert_eq!(config.config.clock_hz, ROCKCHIP_FIQ_RK3588_UART_CLOCK); + assert!(config.config.irq_mode_enabled); + assert_eq!(config.uart_path, "/serial@feb50000"); + assert!(config.target_disabled); + assert_eq!(config.reg.address, 0xfeb5_0000); + assert_eq!(config.reg.size, Some(0x100)); + } + + #[test] + fn rejects_missing_alias_or_non_dw_apb_target() { + let fdt = minimal_fiq_fdt(false, true); + let fiq = fdt.get_by_path("/fiq-debugger").expect("fiq node missing"); + assert!(rockchip_fiq_fdt_config(&fdt, fiq).is_err()); + + let fdt = minimal_fiq_fdt(true, false); + let fiq = fdt.get_by_path("/fiq-debugger").expect("fiq node missing"); + assert!(rockchip_fiq_fdt_config(&fdt, fiq).is_err()); + } + + fn minimal_fiq_fdt(with_alias: bool, dw_apb: bool) -> Fdt { + let mut fdt = Fdt::new(); + let root = fdt.root_id(); + fdt.node_mut(root) + .unwrap() + .set_property(prop_u32_ls("#address-cells", &[2])); + fdt.node_mut(root) + .unwrap() + .set_property(prop_u32_ls("#size-cells", &[1])); + + let aliases = fdt.add_node(root, Node::new("aliases")); + if with_alias { + fdt.node_mut(aliases) + .unwrap() + .set_property(prop_str("serial2", "/serial@feb50000")); + } + + let fiq = fdt.add_node(root, Node::new("fiq-debugger")); + fdt.node_mut(fiq) + .unwrap() + .set_property(prop_strs("compatible", &["rockchip,fiq-debugger"])); + fdt.node_mut(fiq) + .unwrap() + .set_property(prop_u32_ls("rockchip,serial-id", &[2])); + fdt.node_mut(fiq) + .unwrap() + .set_property(prop_u32_ls("rockchip,baudrate", &[1_500_000])); + fdt.node_mut(fiq) + .unwrap() + .set_property(prop_u32_ls("rockchip,irq-mode-enable", &[1])); + fdt.node_mut(fiq) + .unwrap() + .set_property(prop_str("status", "okay")); + + let uart = fdt.add_node(root, Node::new("serial@feb50000")); + fdt.node_mut(uart).unwrap().set_property(prop_strs( + "compatible", + if dw_apb { + &["rockchip,rk3588-uart", "snps,dw-apb-uart"] + } else { + &["rockchip,rk3588-uart"] + }, + )); + fdt.node_mut(uart) + .unwrap() + .set_property(prop_reg(0xfeb5_0000, 0x100)); + fdt.node_mut(uart) + .unwrap() + .set_property(prop_u32_ls("reg-io-width", &[4])); + fdt.node_mut(uart) + .unwrap() + .set_property(prop_u32_ls("reg-shift", &[2])); + fdt.node_mut(uart) + .unwrap() + .set_property(prop_str("status", "disabled")); + fdt + } + + fn prop_u32_ls(name: &str, values: &[u32]) -> Property { + let mut data = Vec::new(); + for value in values { + data.extend_from_slice(&value.to_be_bytes()); + } + Property::new(name, data) + } + + fn prop_reg(address: u64, size: u32) -> Property { + let mut data = Vec::new(); + data.extend_from_slice(&((address >> 32) as u32).to_be_bytes()); + data.extend_from_slice(&(address as u32).to_be_bytes()); + data.extend_from_slice(&size.to_be_bytes()); + Property::new("reg", data) + } + + fn prop_str(name: &str, value: &str) -> Property { + let mut data = Vec::new(); + data.extend_from_slice(value.as_bytes()); + data.push(0); + Property::new(name, data) + } + + fn prop_strs(name: &str, values: &[&str]) -> Property { + let mut data = Vec::new(); + for value in values { + data.extend_from_slice(value.as_bytes()); + data.push(0); + } + Property::new(name, data) + } +} diff --git a/drivers/interface/rdif-serial/src/lib.rs b/drivers/interface/rdif-serial/src/lib.rs index 88f8705bfe..1a9c17ec8d 100644 --- a/drivers/interface/rdif-serial/src/lib.rs +++ b/drivers/interface/rdif-serial/src/lib.rs @@ -21,6 +21,14 @@ impl DriverGeneric for Box { fn name(&self) -> &str { self.as_ref().name() } + + fn raw_any(&self) -> Option<&dyn Any> { + self.as_ref().raw_any() + } + + fn raw_any_mut(&mut self) -> Option<&mut dyn Any> { + self.as_mut().raw_any_mut() + } } mod serial; diff --git a/drivers/interface/rdif-serial/src/serial.rs b/drivers/interface/rdif-serial/src/serial.rs index 8d6d7d106a..f85b34b4b3 100644 --- a/drivers/interface/rdif-serial/src/serial.rs +++ b/drivers/interface/rdif-serial/src/serial.rs @@ -140,6 +140,14 @@ impl DriverGeneric for SerialDyn { fn name(&self) -> &str { self.inner.name() } + + fn raw_any(&self) -> Option<&dyn core::any::Any> { + Some(&self.inner) + } + + fn raw_any_mut(&mut self) -> Option<&mut dyn core::any::Any> { + Some(&mut self.inner) + } } pub struct Sender { diff --git a/drivers/serial/some-serial/src/lib.rs b/drivers/serial/some-serial/src/lib.rs index 65cddc3ae5..a10011263c 100644 --- a/drivers/serial/some-serial/src/lib.rs +++ b/drivers/serial/some-serial/src/lib.rs @@ -62,6 +62,7 @@ pub enum Sender { Ns16550Sender(ns16550::Ns16550Sender), Ns16550MmioSender(ns16550::Ns16550Sender), Ns16550DwApbSender(ns16550::Ns16550Sender), + Ns16550RockchipFiqSender(ns16550::rockchip_fiq::RockchipFiqSender), Pl011Sender(pl011::Pl011Sender), } @@ -96,6 +97,7 @@ pub enum Reciever { Ns16550Reciever(ns16550::Ns16550Reciever), Ns16550MmioReciever(ns16550::Ns16550Reciever), Ns16550DwApbReciever(ns16550::Ns16550Reciever), + Ns16550RockchipFiqReciever(ns16550::rockchip_fiq::RockchipFiqReceiver), Pl011Reciever(pl011::Pl011Reciever), } diff --git a/drivers/serial/some-serial/src/ns16550/mod.rs b/drivers/serial/some-serial/src/ns16550/mod.rs index e46b376e20..8bdfea67fb 100644 --- a/drivers/serial/some-serial/src/ns16550/mod.rs +++ b/drivers/serial/some-serial/src/ns16550/mod.rs @@ -17,6 +17,7 @@ use registers::*; pub mod dw_apb; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod pio; +pub mod rockchip_fiq; // MMIO 版本(通用) mod mmio; @@ -24,6 +25,7 @@ pub use dw_apb::*; pub use mmio::*; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub use pio::*; +pub use rockchip_fiq::*; use crate::{RawReciever, RawSender}; @@ -277,6 +279,12 @@ impl InterfaceRaw for Ns16550 { return Err(SetBackError::new(want, actual)); } } + crate::Sender::Ns16550RockchipFiqSender(ref sender) => { + let actual = sender.base_addr(); + if actual != want { + return Err(SetBackError::new(want, actual)); + } + } _ => { return Err(SetBackError::new(want, 0)); // 不匹配的类型 } @@ -307,6 +315,12 @@ impl InterfaceRaw for Ns16550 { return Err(SetBackError::new(want, actual)); } } + crate::Reciever::Ns16550RockchipFiqReciever(ref reciever) => { + let actual = reciever.base_addr(); + if actual != want { + return Err(SetBackError::new(want, actual)); + } + } _ => { return Err(SetBackError::new(want, 0)); // 不匹配的类型 } diff --git a/drivers/serial/some-serial/src/ns16550/rockchip_fiq.rs b/drivers/serial/some-serial/src/ns16550/rockchip_fiq.rs new file mode 100644 index 0000000000..26528ac0b5 --- /dev/null +++ b/drivers/serial/some-serial/src/ns16550/rockchip_fiq.rs @@ -0,0 +1,889 @@ +//! Rockchip RK3588 FIQ debugger UART support. + +extern crate alloc; + +use alloc::boxed::Box; +use core::{any::Any, num::NonZeroU32, ptr::NonNull}; + +use heapless::{String, Vec}; +use rdif_serial::{ + BSerial, Config, ConfigError, DataBits, DriverGeneric, Interface, InterruptMask, Parity, + SerialDyn, StopBits, TIrqHandler, TReciever, TSender, TransferError, +}; + +use super::{ + Kind, Ns16550, Ns16550IrqHandler, + registers::{ + UART_DLH, UART_DLL, UART_FCR, UART_IER, UART_IER_RDI, UART_IIR, UART_IIR_CTI, UART_LCR, + UART_LCR_DLAB, UART_LCR_WLEN8, UART_LSR, UART_LSR_BI, UART_LSR_DR, UART_LSR_TEMT, UART_MCR, + UART_RBR, UART_THR, + }, +}; +use crate::{RawReciever, RawSender}; + +pub const ROCKCHIP_FIQ_RK3588_UART_CLOCK: u32 = 24_000_000; +pub const ROCKCHIP_FIQ_DEFAULT_BAUDRATE: u32 = 1_500_000; + +const REG_SHIFT: usize = 2; +const DEBUG_MAX: usize = 64; +const HISTORY_MAX: usize = 16; +const UART_USR: u8 = 0x1f; +const RK_UART_RFL: u8 = 0x21; +const UART_SRR: u8 = 0x22; +const UART_USR_TX_FIFO_NOT_FULL: u32 = 0x02; +const UART_USR_BUSY: u32 = 0x01; + +pub type CommandString = String; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RockchipFiqConfig { + pub serial_id: u32, + pub baudrate: u32, + pub clock_hz: u32, + pub irq_mode_enabled: bool, + pub debug_enable: bool, + pub console_enable: bool, +} + +impl Default for RockchipFiqConfig { + fn default() -> Self { + Self { + serial_id: 0, + baudrate: ROCKCHIP_FIQ_DEFAULT_BAUDRATE, + clock_hz: ROCKCHIP_FIQ_RK3588_UART_CLOCK, + irq_mode_enabled: false, + debug_enable: true, + console_enable: true, + } + } +} + +impl RockchipFiqConfig { + pub fn normalised(mut self) -> Self { + self.baudrate = normalise_baudrate(self.baudrate); + if self.clock_hz == 0 { + self.clock_hz = ROCKCHIP_FIQ_RK3588_UART_CLOCK; + } + self + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FiqCommand { + Pc, + Regs, + AllRegs, + Bt, + Pcsr, + Irqs, + Kmsg, + Version, + Ps, + SysRq(Option), + Reboot(Option), + Reset(Option), + Kgdb, + Cpu, + CpuSwitch(u32), + Sleep, + NoSleep, + Console, + Help, + Unknown(CommandString), +} + +impl FiqCommand { + pub fn parse(cmd: &str) -> Self { + let cmd = cmd.trim(); + match cmd { + "help" | "?" => Self::Help, + "pc" => Self::Pc, + "regs" => Self::Regs, + "allregs" => Self::AllRegs, + "bt" => Self::Bt, + "pcsr" => Self::Pcsr, + "irqs" => Self::Irqs, + "kmsg" => Self::Kmsg, + "version" => Self::Version, + "ps" => Self::Ps, + "sysrq" => Self::SysRq(None), + "kgdb" => Self::Kgdb, + "cpu" => Self::Cpu, + "sleep" => Self::Sleep, + "nosleep" => Self::NoSleep, + "console" => Self::Console, + _ if cmd.starts_with("sysrq ") => Self::SysRq(cmd.as_bytes().get(6).copied()), + _ if cmd.starts_with("reboot") => Self::Reboot(command_arg(cmd, "reboot")), + _ if cmd.starts_with("reset") => Self::Reset(command_arg(cmd, "reset")), + _ if cmd.starts_with("cpu ") => cmd[4..] + .trim() + .parse::() + .map(Self::CpuSwitch) + .unwrap_or_else(|_| Self::Unknown(command_string(cmd))), + _ => Self::Unknown(command_string(cmd)), + } + } + + pub fn needs_irq_helper(&self) -> bool { + matches!( + self, + Self::Ps | Self::SysRq(_) | Self::Reboot(_) | Self::Kgdb | Self::Unknown(_) + ) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FiqDebuggerEvent { + ConsoleByte(u8), + OutputByte(u8), + EnterDebugger, + ExitToConsole, + Command(FiqCommand), + NeedIrqHelper, +} + +pub struct FiqDebugger { + debug_enable: bool, + console_enable: bool, + no_sleep: bool, + line: CommandString, + history: Vec, + history_cursor: Option, + prev3: u8, + prev2: u8, + prev1: u8, + escape_state: u8, + last_newline: u8, +} + +impl FiqDebugger { + pub fn new(config: RockchipFiqConfig) -> Self { + Self { + debug_enable: config.debug_enable, + console_enable: config.console_enable, + no_sleep: false, + line: CommandString::new(), + history: Vec::new(), + history_cursor: None, + prev3: 0, + prev2: 0, + prev1: 0, + escape_state: 0, + last_newline: 0, + } + } + + pub fn debug_enabled(&self) -> bool { + self.debug_enable + } + + pub fn console_enabled(&self) -> bool { + self.console_enable + } + + pub fn no_sleep(&self) -> bool { + self.no_sleep + } + + pub fn current_line(&self) -> &str { + self.line.as_str() + } + + pub fn handle_byte(&mut self, byte: u8, emit: &mut impl FnMut(FiqDebuggerEvent)) { + let is_break = self.update_break_detector(byte); + + if !self.debug_enable { + if byte == b'\r' || byte == b'\n' { + self.debug_enable = true; + self.line.clear(); + self.prompt(emit); + } + return; + } + + if is_break { + self.enter_debugger(emit); + return; + } + + if self.console_enable { + emit(FiqDebuggerEvent::ConsoleByte(byte)); + emit(FiqDebuggerEvent::NeedIrqHelper); + return; + } + + if self.handle_escape(byte, emit) { + return; + } + + match byte { + 9 => self.complete_unique_prefix(emit), + 8 | 127 => self.backspace(emit), + b'\r' | b'\n' => self.submit_newline(byte, emit), + b' '..=126 if self.line.len() < DEBUG_MAX - 1 => { + let _ = self.line.push(byte as char); + emit(FiqDebuggerEvent::OutputByte(byte)); + } + _ => {} + } + } + + fn update_break_detector(&mut self, byte: u8) -> bool { + let is_break = byte == b'q' + && self.prev1 == b'i' + && self.prev2 == b'f' + && self.prev3 != b'_' + && self.prev3 != b' '; + self.prev3 = self.prev2; + self.prev2 = self.prev1; + self.prev1 = byte; + is_break + } + + fn enter_debugger(&mut self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + self.debug_enable = true; + self.console_enable = false; + self.line.clear(); + self.history_cursor = None; + emit(FiqDebuggerEvent::EnterDebugger); + self.emit_str("\nWelcome to fiq debugger mode\n", emit); + self.emit_str("Enter ? to get command help\n", emit); + self.prompt(emit); + } + + fn handle_escape(&mut self, byte: u8, emit: &mut impl FnMut(FiqDebuggerEvent)) -> bool { + match (self.escape_state, byte) { + (0, 0x1b) => { + self.escape_state = 1; + true + } + (1, b'[') => { + self.escape_state = 2; + true + } + (2, b'A') => { + self.escape_state = 0; + self.history_up(emit); + true + } + (2, b'B') => { + self.escape_state = 0; + self.history_down(emit); + true + } + (2, b'C' | b'D') => { + self.escape_state = 0; + true + } + (1 | 2, _) => { + self.escape_state = 0; + false + } + _ => false, + } + } + + fn submit_newline(&mut self, byte: u8, emit: &mut impl FnMut(FiqDebuggerEvent)) { + if byte == b'\r' || (byte == b'\n' && self.last_newline != b'\r') { + emit(FiqDebuggerEvent::OutputByte(b'\r')); + emit(FiqDebuggerEvent::OutputByte(b'\n')); + } + self.last_newline = byte; + + if self.line.is_empty() { + self.prompt(emit); + return; + } + + let line = self.line.clone(); + self.line.clear(); + self.history_cursor = None; + self.push_history(line.clone()); + + let command = FiqCommand::parse(line.as_str()); + match command { + FiqCommand::Sleep => { + self.no_sleep = false; + self.emit_str("enabling sleep\n", emit); + } + FiqCommand::NoSleep => { + self.no_sleep = true; + self.emit_str("disabling sleep\n", emit); + } + FiqCommand::Console => { + self.emit_str("console mode\n", emit); + self.console_enable = true; + emit(FiqDebuggerEvent::ExitToConsole); + } + FiqCommand::Help => self.emit_help(emit), + _ => {} + } + + let needs_irq_helper = command.needs_irq_helper(); + emit(FiqDebuggerEvent::Command(command)); + if needs_irq_helper || (self.debug_enable && !self.no_sleep) { + emit(FiqDebuggerEvent::NeedIrqHelper); + } + if !self.console_enable { + self.prompt(emit); + } + } + + fn push_history(&mut self, line: CommandString) { + if self.history.last() == Some(&line) { + return; + } + if self.history.len() == HISTORY_MAX { + self.history.remove(0); + } + let _ = self.history.push(line); + } + + fn history_up(&mut self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + if self.history.is_empty() { + return; + } + let next = self + .history_cursor + .map(|idx| idx.saturating_sub(1)) + .unwrap_or(self.history.len() - 1); + self.history_cursor = Some(next); + let line = self.history[next].clone(); + self.replace_line(line, emit); + } + + fn history_down(&mut self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + let Some(idx) = self.history_cursor else { + return; + }; + if idx + 1 < self.history.len() { + self.history_cursor = Some(idx + 1); + let line = self.history[idx + 1].clone(); + self.replace_line(line, emit); + } else { + self.history_cursor = None; + self.replace_line(CommandString::new(), emit); + } + } + + fn replace_line(&mut self, line: CommandString, emit: &mut impl FnMut(FiqDebuggerEvent)) { + while !self.line.is_empty() { + self.backspace(emit); + } + self.line = line; + let bytes: Vec = self.line.as_bytes().iter().copied().collect(); + for byte in bytes { + emit(FiqDebuggerEvent::OutputByte(byte)); + } + } + + fn backspace(&mut self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + if self.line.pop().is_some() { + emit(FiqDebuggerEvent::OutputByte(8)); + emit(FiqDebuggerEvent::OutputByte(b' ')); + emit(FiqDebuggerEvent::OutputByte(8)); + } + } + + fn complete_unique_prefix(&mut self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + let mut found = None; + for cmd in COMMANDS { + if cmd.starts_with(self.line.as_str()) { + if found.is_some() { + return; + } + found = Some(*cmd); + } + } + + let Some(cmd) = found else { + return; + }; + if cmd.len() <= self.line.len() { + return; + } + for &byte in &cmd.as_bytes()[self.line.len()..] { + if self.line.push(byte as char).is_ok() { + emit(FiqDebuggerEvent::OutputByte(byte)); + } + } + } + + fn prompt(&self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + self.emit_str("> ", emit); + } + + fn emit_help(&self, emit: &mut impl FnMut(FiqDebuggerEvent)) { + self.emit_str( + "pc regs allregs bt reboot sleep nosleep console cpu reset irqs kmsg version ps sysrq \ + kgdb\n", + emit, + ); + } + + fn emit_str(&self, s: &str, emit: &mut impl FnMut(FiqDebuggerEvent)) { + for byte in s.bytes() { + emit(FiqDebuggerEvent::OutputByte(byte)); + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct RockchipFiqPort { + base: usize, +} + +impl RockchipFiqPort { + pub const fn new(base: usize) -> Self { + Self { base } + } + + pub fn base_addr(&self) -> usize { + self.base + } + + fn reg_addr(&self, reg: u8) -> usize { + self.base + ((reg as usize) << REG_SHIFT) + } + + fn read_u32(&self, reg: u8) -> u32 { + unsafe { (self.reg_addr(reg) as *const u32).read_volatile() } + } + + fn write_u32(&self, reg: u8, value: u32) { + unsafe { (self.reg_addr(reg) as *mut u32).write_volatile(value) } + } + + fn init_debug_port(&self, baudrate: u32) { + if self.read_reg(UART_LSR) & UART_LSR_DR != 0 { + let _ = self.read_reg(UART_RBR); + } + + let dll = match normalise_baudrate(baudrate) { + 1_500_000 => 0x01, + _ => 0x0d, + }; + + self.write_reg(UART_SRR, 0x07); + for _ in 0..1024 { + core::hint::spin_loop(); + } + self.write_reg(UART_MCR, 0x10); + self.write_reg(UART_LCR, UART_LCR_DLAB | UART_LCR_WLEN8); + self.write_reg(UART_DLL, dll); + self.write_reg(UART_DLH, 0); + self.write_reg(UART_LCR, UART_LCR_WLEN8); + self.write_reg(UART_IER, UART_IER_RDI); + self.write_reg(UART_FCR, 0x01); + self.write_reg(UART_MCR, 0); + } + + fn read_debug_byte(&self) -> Option { + let iir = self.read_u32(UART_IIR); + let usr = self.read_u32(UART_USR); + let lsr = self.read_reg(UART_LSR); + + if (iir & 0x3f) == UART_IIR_CTI as u32 { + let rfl = self.read_u32(RK_UART_RFL); + if lsr & (UART_LSR_DR | UART_LSR_BI) == 0 && usr & UART_USR_BUSY == 0 && rfl == 0 { + let _ = self.read_reg(UART_RBR); + } + } + + if lsr & UART_LSR_DR != 0 { + Some(self.read_reg(UART_RBR)) + } else { + None + } + } + + fn write_debug_byte(&self, byte: u8) -> bool { + let mut count = 10_000; + while self.read_u32(UART_USR) & UART_USR_TX_FIFO_NOT_FULL == 0 { + if count == 0 { + return false; + } + count -= 1; + core::hint::spin_loop(); + } + self.write_reg(UART_THR, byte); + true + } + + fn flush(&self) { + while self.read_reg(UART_LSR) & UART_LSR_TEMT == 0 { + core::hint::spin_loop(); + } + } +} + +impl Kind for RockchipFiqPort { + fn read_reg(&self, reg: u8) -> u8 { + (self.read_u32(reg) & 0xff) as u8 + } + + fn write_reg(&self, reg: u8, val: u8) { + self.write_u32(reg, val as u32); + } + + fn get_base(&self) -> usize { + self.base + } + + fn set_baudrate(&self, _clock_freq: u32, baudrate: u32) -> Result<(), ConfigError> { + if !matches!(baudrate, 115_200 | 1_500_000) { + return Err(ConfigError::InvalidBaudrate); + } + self.init_debug_port(baudrate); + Ok(()) + } + + fn baudrate(&self, _clock_freq: u32) -> u32 { + let lcr = self.read_reg(UART_LCR); + self.write_reg(UART_LCR, lcr | UART_LCR_DLAB); + let dll = self.read_reg(UART_DLL); + let dlh = self.read_reg(UART_DLH); + self.write_reg(UART_LCR, lcr); + + match (dll, dlh) { + (0x01, 0) => 1_500_000, + (0x0d, 0) => 115_200, + _ => 0, + } + } +} + +pub struct RockchipFiqSender { + pub(crate) base: RockchipFiqPort, +} + +impl RockchipFiqSender { + pub fn base_addr(&self) -> usize { + self.base.base_addr() + } +} + +impl RawSender for RockchipFiqSender { + fn write_byte(&mut self, byte: u8) -> bool { + self.base.write_debug_byte(byte) + } +} + +pub struct RockchipFiqReceiver { + pub(crate) base: RockchipFiqPort, +} + +impl RockchipFiqReceiver { + pub fn base_addr(&self) -> usize { + self.base.base_addr() + } +} + +impl RawReciever for RockchipFiqReceiver { + fn read_byte(&mut self) -> Option> { + self.base.read_debug_byte().map(Ok) + } +} + +impl Ns16550 { + pub fn new_rockchip_fiq(base: NonNull, clock_freq: u32) -> Self { + let base = RockchipFiqPort::new(base.as_ptr() as usize); + Self { + base, + clock_freq, + irq: Some(Ns16550IrqHandler { base }), + tx: Some(crate::Sender::Ns16550RockchipFiqSender(RockchipFiqSender { + base, + })), + rx: Some(crate::Reciever::Ns16550RockchipFiqReciever( + RockchipFiqReceiver { base }, + )), + } + } +} + +pub struct RockchipFiqSerial { + serial: BSerial, + port: RockchipFiqPort, + debugger: FiqDebugger, + config: RockchipFiqConfig, +} + +impl RockchipFiqSerial { + pub fn new(base: NonNull, config: RockchipFiqConfig) -> Self { + let config = config.normalised(); + let port = RockchipFiqPort::new(base.as_ptr() as usize); + port.init_debug_port(config.baudrate); + let serial = SerialDyn::new_boxed(Ns16550::new_rockchip_fiq(base, config.clock_hz)); + Self { + serial, + port, + debugger: FiqDebugger::new(config), + config, + } + } + + pub fn new_boxed(base: NonNull, config: RockchipFiqConfig) -> BSerial { + Box::new(Self::new(base, config)) + } + + pub fn config(&self) -> RockchipFiqConfig { + self.config + } + + pub fn handle_fiq_byte(&mut self, byte: u8, emit: &mut impl FnMut(FiqDebuggerEvent)) { + self.debugger.handle_byte(byte, emit); + } + + pub fn poll_fiq_events(&mut self, emit: &mut impl FnMut(FiqDebuggerEvent)) -> usize { + let mut count = 0; + while let Some(byte) = self.port.read_debug_byte() { + count += 1; + self.debugger.handle_byte(byte, emit); + } + if !self.debugger.console_enabled() { + self.port.flush(); + } + count + } + + pub fn debugger(&self) -> &FiqDebugger { + &self.debugger + } + + pub fn debugger_mut(&mut self) -> &mut FiqDebugger { + &mut self.debugger + } +} + +impl DriverGeneric for RockchipFiqSerial { + fn name(&self) -> &str { + "Rockchip FIQ Debugger UART" + } + + fn raw_any(&self) -> Option<&dyn Any> { + Some(self) + } + + fn raw_any_mut(&mut self) -> Option<&mut dyn Any> { + Some(self) + } +} + +impl Interface for RockchipFiqSerial { + fn irq_handler(&mut self) -> Option> { + self.serial.irq_handler() + } + + fn take_tx(&mut self) -> Option> { + self.serial.take_tx() + } + + fn take_rx(&mut self) -> Option> { + self.serial.take_rx() + } + + fn base_addr(&self) -> usize { + self.serial.base_addr() + } + + fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + self.serial.set_config(config) + } + + fn baudrate(&self) -> u32 { + self.serial.baudrate() + } + + fn data_bits(&self) -> DataBits { + self.serial.data_bits() + } + + fn stop_bits(&self) -> StopBits { + self.serial.stop_bits() + } + + fn parity(&self) -> Parity { + self.serial.parity() + } + + fn clock_freq(&self) -> Option { + self.serial.clock_freq() + } + + fn enable_loopback(&mut self) { + self.serial.enable_loopback() + } + + fn disable_loopback(&mut self) { + self.serial.disable_loopback() + } + + fn is_loopback_enabled(&self) -> bool { + self.serial.is_loopback_enabled() + } + + fn enable_interrupts(&mut self, mask: InterruptMask) { + self.serial.enable_interrupts(mask) + } + + fn disable_interrupts(&mut self, mask: InterruptMask) { + self.serial.disable_interrupts(mask) + } + + fn get_enabled_interrupts(&self) -> InterruptMask { + self.serial.get_enabled_interrupts() + } +} + +const COMMANDS: &[&str] = &[ + "pc", "regs", "allregs", "bt", "reboot", "pcsr", "sleep", "nosleep", "console", "cpu", "reset", + "irqs", "kmsg", "version", "ps", "sysrq", "kgdb", +]; + +fn command_arg(cmd: &str, prefix: &str) -> Option { + let arg = cmd[prefix.len()..].trim(); + if arg.is_empty() { + None + } else { + Some(command_string(arg)) + } +} + +fn command_string(value: &str) -> CommandString { + let mut out = CommandString::new(); + let _ = out.push_str(value); + out +} + +fn normalise_baudrate(baudrate: u32) -> u32 { + match baudrate { + 115_200 | 1_500_000 => baudrate, + _ => 115_200, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn feed(debugger: &mut FiqDebugger, bytes: &[u8]) -> heapless::Vec { + let mut out = heapless::Vec::new(); + for &byte in bytes { + debugger.handle_byte(byte, &mut |event| { + let _ = out.push(event); + }); + } + out + } + + #[test] + fn fiq_word_enters_debugger_unless_prefixed_by_space_or_underscore() { + let mut debugger = FiqDebugger::new(RockchipFiqConfig { + debug_enable: true, + console_enable: true, + ..RockchipFiqConfig::default() + }); + + let events = feed(&mut debugger, b"fiq"); + assert!(events.contains(&FiqDebuggerEvent::EnterDebugger)); + assert!(!debugger.console_enabled()); + + let mut debugger = FiqDebugger::new(RockchipFiqConfig { + debug_enable: true, + console_enable: true, + ..RockchipFiqConfig::default() + }); + let events = feed(&mut debugger, b" fiq"); + assert!(!events.contains(&FiqDebuggerEvent::EnterDebugger)); + assert!(debugger.console_enabled()); + + let mut debugger = FiqDebugger::new(RockchipFiqConfig { + debug_enable: true, + console_enable: true, + ..RockchipFiqConfig::default() + }); + let events = feed(&mut debugger, b"_fiq"); + assert!(!events.contains(&FiqDebuggerEvent::EnterDebugger)); + assert!(debugger.console_enabled()); + } + + #[test] + fn newline_enables_debugger_when_debugging_is_disabled() { + let mut debugger = FiqDebugger::new(RockchipFiqConfig { + debug_enable: false, + console_enable: false, + ..RockchipFiqConfig::default() + }); + + let events = feed(&mut debugger, b"\r"); + assert!(debugger.debug_enabled()); + assert!( + events + .iter() + .any(|event| matches!(event, FiqDebuggerEvent::OutputByte(b'>'))) + ); + } + + #[test] + fn command_line_parses_console_and_sleep_modes() { + let mut debugger = FiqDebugger::new(RockchipFiqConfig { + debug_enable: true, + console_enable: false, + ..RockchipFiqConfig::default() + }); + + let events = feed(&mut debugger, b"nosleep\r"); + assert!(debugger.no_sleep()); + assert!(events.contains(&FiqDebuggerEvent::Command(FiqCommand::NoSleep))); + + let events = feed(&mut debugger, b"sleep\r"); + assert!(!debugger.no_sleep()); + assert!(events.contains(&FiqDebuggerEvent::Command(FiqCommand::Sleep))); + + let events = feed(&mut debugger, b"console\r"); + assert!(debugger.console_enabled()); + assert!(events.contains(&FiqDebuggerEvent::ExitToConsole)); + assert!(events.contains(&FiqDebuggerEvent::Command(FiqCommand::Console))); + } + + #[test] + fn command_line_supports_backspace_history_and_tab_completion() { + let mut debugger = FiqDebugger::new(RockchipFiqConfig { + debug_enable: true, + console_enable: false, + ..RockchipFiqConfig::default() + }); + + let _ = feed(&mut debugger, b"nosleex\x08p\r"); + assert!(debugger.no_sleep()); + + let _ = feed(&mut debugger, b"\x1b[A"); + assert_eq!(debugger.current_line(), "nosleep"); + + let _ = feed(&mut debugger, b"\x1b[B"); + assert_eq!(debugger.current_line(), ""); + + let _ = feed(&mut debugger, b"con\t"); + assert_eq!(debugger.current_line(), "console"); + } + + #[test] + fn parser_covers_deferred_os_commands() { + assert_eq!(FiqCommand::parse("ps"), FiqCommand::Ps); + assert_eq!(FiqCommand::parse("sysrq"), FiqCommand::SysRq(None)); + assert_eq!(FiqCommand::parse("sysrq g"), FiqCommand::SysRq(Some(b'g'))); + assert_eq!(FiqCommand::parse("kgdb"), FiqCommand::Kgdb); + + let mut arg = CommandString::new(); + arg.push_str("bootloader").unwrap(); + assert_eq!( + FiqCommand::parse("reboot bootloader"), + FiqCommand::Reboot(Some(arg)) + ); + + let mut unknown = CommandString::new(); + unknown.push_str("wat").unwrap(); + assert_eq!(FiqCommand::parse("wat"), FiqCommand::Unknown(unknown)); + } +} From 686d3cd2f51a255677a4f4fa785469d0d8de87f2 Mon Sep 17 00:00:00 2001 From: CN-TangLin <2242120212@qq.com> Date: Wed, 27 May 2026 16:42:02 +0800 Subject: [PATCH 59/71] feat(starry-kernel): add LKM support via kmod-loader integration (#849) * feat(starry-kernel): add LKM support via kmod-loader integration - kmod_loader.rs: StarryKmodHelper (KernelModuleHelper trait impl) - KmodSectionMem: page-aligned memory with SectionMemOps, R/W/X perms - vmalloc: page-aligned kernel memory allocation - resolve_symbol: stub (depends on PR #837 kallsyms) - flsuh_cache: icache/dcache flush for loaded module code - sys_init_module: load .ko ELF from user memory, call init - sys_delete_module / sys_finit_module: syscall stubs - test-kmod-loader: 7 test modules for error path validation - Depends on: feat/starry-kprobe, feat/starry-ebpf * fix(starry-kernel): address LKM PR #849 review blocking issues 1. Fix test install path: usr/bin -> usr/bin/starry-test-suit (tests were silently skipped by qemu toml wildcard) 2. Fix KmodSectionMem null-pointer Drop UB: - Add null check in Drop before calling dealloc - Replace null-ptr KmodSectionMem fallback with safe NullSectionMem (no-op SectionMemOps implementation) to avoid UB entirely 3. Add comment explaining core::mem::forget(owner) intent: module must remain in memory; future delete_module will recover owner via global registry Verified: clippy 11 features, x86_64/riscv64/aarch64/loongarch64 build. * docs(starry-kernel): add module-level documentation for kmod_loader Add module doc comments describing LKM support, key components (KmodSectionMem, NullSectionMem, StarryKmodHelper), and known limitations. Fix module ordering in lib.rs for rustfmt. * fix(starry-kernel): address kmod review non-blocking suggestions - resolve_symbol stub: use AtomicBool to warn only once, avoid log spam - add test cases for valid ET_REL header, fd=0, and large junk input * fix(starry-kernel): use literal 0x80 for BPF_CALL opcode in verifier BPF_CALL_OP constant does not exist in bpf_insn module. BPF_CALL opcode is 0x85, so the high nibble is 0x80. * fix(starry-kernel): rebase onto dev with kprobe+eBPF merged, resolve conflicts - Skip kprobe and eBPF commits (already in dev via #847 + #848) - Resolve lib.rs, syscall/mod.rs, Cargo.toml conflicts - Update ax_hal -> ax_runtime::hal paths in kmod_loader.rs - Remove dw_apb_uart (not in workspace dependencies) * fix(starry-kernel): add cfg(ebpf) gate for perf_event module and trigger perf_event.rs references crate::ebpf::run_bpf_prog which is only available under the ebpf feature. Add cfg gates to prevent compilation failure when ebpf feature is disabled. * ci: rebase onto latest dev, resolve Cargo.lock conflict --- Cargo.lock | 487 ++++++++++++------ .../examples/hello-kernel/linker_x86_64.lds | 52 ++ .../examples/irq-kernel/linker_x86_64.lds | 52 ++ .../examples/smp-kernel/linker_x86_64.lds | 52 ++ os/StarryOS/kernel/Cargo.toml | 1 + os/StarryOS/kernel/src/ebpf.rs | 2 +- os/StarryOS/kernel/src/kmod_loader.rs | 229 ++++++++ os/StarryOS/kernel/src/lib.rs | 1 + os/StarryOS/kernel/src/perf_event.rs | 1 + os/StarryOS/kernel/src/syscall/mod.rs | 7 + .../syscall/test-kmod-loader/c/CMakeLists.txt | 8 + .../syscall/test-kmod-loader/c/src/main.c | 106 ++++ 12 files changed, 832 insertions(+), 166 deletions(-) create mode 100644 components/axplat_crates/examples/hello-kernel/linker_x86_64.lds create mode 100644 components/axplat_crates/examples/irq-kernel/linker_x86_64.lds create mode 100644 components/axplat_crates/examples/smp-kernel/linker_x86_64.lds create mode 100644 os/StarryOS/kernel/src/kmod_loader.rs create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/src/main.c diff --git a/Cargo.lock b/Cargo.lock index 269c2698fa..a3b959805f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,7 +567,7 @@ dependencies = [ "nb", "num-align", "smccc", - "spin 0.12.0", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -585,7 +585,7 @@ dependencies = [ "axvisor_api", "log", "numeric-enum-macro", - "spin 0.12.0", + "spin", ] [[package]] @@ -602,7 +602,7 @@ dependencies = [ "axvisor_api", "bitmaps", "log", - "spin 0.12.0", + "spin", "tock-registers 0.10.1", ] @@ -664,9 +664,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" @@ -909,7 +909,7 @@ dependencies = [ "sdmmc-protocol", "simple-ahci", "some-serial", - "spin 0.12.0", + "spin", "virtio-drivers", ] @@ -960,7 +960,7 @@ dependencies = [ "axfatfs", "log", "rsext4", - "spin 0.12.0", + "spin", ] [[package]] @@ -969,7 +969,7 @@ version = "0.3.11" dependencies = [ "ax-fs-vfs", "log", - "spin 0.12.0", + "spin", ] [[package]] @@ -995,7 +995,7 @@ dependencies = [ "rsext4", "scope-local", "slab", - "spin 0.12.0", + "spin", "starry-fatfs", ] @@ -1005,7 +1005,7 @@ version = "0.3.12" dependencies = [ "ax-fs-vfs", "log", - "spin 0.12.0", + "spin", ] [[package]] @@ -1036,16 +1036,16 @@ dependencies = [ "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", "ax-plat-riscv64-sg2002", - "ax-plat-riscv64-visionfive2", "ax-plat-x86-pc", - "ax-plat-x86-qemu-q35", "axplat-dyn", + "axplat-riscv64-visionfive2", + "axplat-x86-qemu-q35", "cfg-if", "fdt-parser", "heapless 0.9.3", "log", "rdrive", - "spin 0.12.0", + "spin", "toml 1.1.2+spec-1.1.0", ] @@ -1231,7 +1231,7 @@ dependencies = [ "cfg-if", "log", "smoltcp 0.13.1", - "spin 0.12.0", + "spin", ] [[package]] @@ -1260,7 +1260,7 @@ dependencies = [ "rdif-vsock", "ringbuf", "smoltcp 0.13.1", - "spin 0.12.0", + "spin", ] [[package]] @@ -1295,7 +1295,7 @@ dependencies = [ "ax-kernel-guard", "ax-percpu-macros", "cfg-if", - "spin 0.12.0", + "spin", "x86", ] @@ -1352,7 +1352,7 @@ dependencies = [ "ax-plat", "log", "some-serial", - "spin 0.12.0", + "spin", ] [[package]] @@ -1462,25 +1462,7 @@ dependencies = [ "sbi-rt 0.0.3", "sg200x-bsp", "some-serial", - "spin 0.12.0", -] - -[[package]] -name = "ax-plat-riscv64-visionfive2" -version = "0.1.4" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-kspin", - "ax-lazyinit", - "ax-plat", - "ax-riscv-plic", - "log", - "rdrive", - "riscv 0.14.0", - "riscv_goldfish", - "sbi-rt 0.0.3", - "uart_16550 0.4.0", + "spin", ] [[package]] @@ -1508,31 +1490,6 @@ dependencies = [ "x86_rtc", ] -[[package]] -name = "ax-plat-x86-qemu-q35" -version = "0.4.8" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-driver", - "ax-int-ratio", - "ax-kspin", - "ax-lazyinit", - "ax-percpu", - "ax-plat", - "axklib", - "bitflags 2.11.1", - "heapless 0.9.3", - "log", - "multiboot", - "raw-cpuid 11.6.0", - "uart_16550 0.4.0", - "x2apic", - "x86", - "x86_64", - "x86_rtc", -] - [[package]] name = "ax-posix-api" version = "0.5.16" @@ -1552,7 +1509,7 @@ dependencies = [ "bindgen 0.72.1", "flatten_objects", "scope-local", - "spin 0.12.0", + "spin", ] [[package]] @@ -1596,7 +1553,7 @@ dependencies = [ "rd-block", "rd-net", "rdrive", - "spin 0.12.0", + "spin", ] [[package]] @@ -1626,7 +1583,7 @@ dependencies = [ "ax-kspin", "ax-lazyinit", "lock_api", - "spin 0.12.0", + "spin", ] [[package]] @@ -1665,7 +1622,7 @@ dependencies = [ "extern-trait", "futures-util", "log", - "spin 0.12.0", + "spin", ] [[package]] @@ -1713,7 +1670,7 @@ dependencies = [ "gimli 0.33.0", "log", "paste", - "spin 0.12.0", + "spin", ] [[package]] @@ -1787,6 +1744,16 @@ dependencies = [ "serde", ] +[[package]] +name = "axerrno" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f961d2868582a092fb1e71b90c16cc6f2cbbe7bb5fa7e4bd6fe61d882ce6bb34" +dependencies = [ + "log", + "strum 0.27.2", +] + [[package]] name = "axfatfs" version = "0.1.0-pre.0" @@ -1863,7 +1830,50 @@ dependencies = [ "log", "rdrive", "somehal", - "spin 0.12.0", + "spin", +] + +[[package]] +name = "axplat-riscv64-visionfive2" +version = "0.1.4" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-kspin", + "ax-lazyinit", + "ax-plat", + "ax-riscv-plic", + "log", + "rdrive", + "riscv 0.14.0", + "riscv_goldfish", + "sbi-rt 0.0.3", + "uart_16550 0.4.0", +] + +[[package]] +name = "axplat-x86-qemu-q35" +version = "0.4.8" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-driver", + "ax-int-ratio", + "ax-kspin", + "ax-lazyinit", + "ax-percpu", + "ax-plat", + "axklib", + "bitflags 2.11.1", + "heapless 0.9.3", + "log", + "multiboot", + "raw-cpuid 11.6.0", + "uart_16550 0.4.0", + "x2apic", + "x86", + "x86_64", + "x86_rtc", ] [[package]] @@ -1874,7 +1884,7 @@ dependencies = [ "bitflags 2.11.1", "futures", "linux-raw-sys 0.12.1", - "spin 0.12.0", + "spin", "tokio", ] @@ -1963,7 +1973,6 @@ dependencies = [ "ax-percpu", "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-qemu-q35", "ax-std", "ax-timer-list", "axaddrspace", @@ -1973,6 +1982,7 @@ dependencies = [ "axhvc", "axklib", "axplat-dyn", + "axplat-x86-qemu-q35", "axvcpu", "axvisor_api", "axvm", @@ -1991,7 +2001,7 @@ dependencies = [ "rdrive", "riscv_vcpu", "riscv_vplic", - "spin 0.12.0", + "spin", "syn 2.0.117", "tokio", "toml 0.9.12+spec-1.1.0", @@ -2041,7 +2051,7 @@ dependencies = [ "log", "loongarch_vcpu", "riscv_vcpu", - "spin 0.12.0", + "spin", "x86_vcpu", ] @@ -2202,6 +2212,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bitfield-struct" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca6739863c590881f038d033a146c51ddae239186a4327014839fd864f44ed5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -2288,7 +2309,7 @@ dependencies = [ "divan", "log", "rand 0.10.1", - "spin 0.12.0", + "spin", ] [[package]] @@ -2303,14 +2324,14 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672b945a3e4f4f40bfd4cd5ee07df9e796a42254ce7cd6d2599ad969244c44a" dependencies = [ - "spin 0.10.0", + "spin", ] [[package]] name = "bumpalo" -version = "3.20.3" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bwbench-client" @@ -2623,9 +2644,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -2637,9 +2658,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -2803,7 +2824,7 @@ dependencies = [ "mbarrier", "nb", "num_enum", - "spin 0.12.0", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", "trait-ffi", @@ -3017,9 +3038,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", ] @@ -3101,7 +3122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321ec774d27fafc66e812034d0025f8858bd7d9095304ff8fc200e0b9f9cc257" dependencies = [ "ahash 0.8.12", - "compact_str 0.8.2", + "compact_str 0.8.1", "crossbeam-channel", "cursive-macros", "enum-map", @@ -3361,14 +3382,14 @@ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid", - "crypto-common 0.2.2", + "crypto-common 0.2.1", ] [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", @@ -3468,7 +3489,7 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" name = "dwmmc-host" version = "0.1.1" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.11.0", "dma-api", "embedded-hal", "log", @@ -3485,9 +3506,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.16.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embedded-graphics" @@ -3609,9 +3630,9 @@ dependencies = [ [[package]] name = "enumset" -version = "1.1.13" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" +checksum = "7f96a4a12fe60ac746ae295a1a4ecb5bb02debc20856506c8635288065f142de" dependencies = [ "enumset_derive", ] @@ -3864,9 +3885,9 @@ checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" [[package]] name = "fitimage" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92edd7c1c55efd27b5a17dd6d505affafcfe48dfaed7423d4c4ae7361e56a7d8" +checksum = "8cb4d07a1e7a76a7ac4b6b754d45670b1a82c8a0484d80cceeb30c99ad65ce1d" dependencies = [ "anyhow", "byteorder", @@ -4043,9 +4064,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.4" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" @@ -4159,6 +4180,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "goblin" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "h2" version = "0.4.14" @@ -4280,6 +4312,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hello-kernel" +version = "0.3.1" +dependencies = [ + "ax-plat", + "ax-plat-aarch64-qemu-virt", + "ax-plat-loongarch64-qemu-virt", + "ax-plat-riscv64-qemu-virt", + "ax-plat-x86-pc", + "cfg-if", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -4294,9 +4338,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", "itoa", @@ -4654,6 +4698,18 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "int-enum" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366a1634cccc76b4cfd3e7580de9b605e4d93f1edac48d786c1f867c0def495" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.117", +] + [[package]] name = "intrusive-collections" version = "0.9.7" @@ -4679,6 +4735,20 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "irq-kernel" +version = "0.3.1" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-plat", + "ax-plat-aarch64-qemu-virt", + "ax-plat-loongarch64-qemu-virt", + "ax-plat-riscv64-qemu-virt", + "ax-plat-x86-pc", + "cfg-if", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -4744,9 +4814,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.27" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" dependencies = [ "jiff-static", "log", @@ -4757,9 +4827,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.27" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", @@ -4791,9 +4861,9 @@ dependencies = [ [[package]] name = "jkconfig" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cbaedffb63dabcbab61e23e0adc1fc7bc0d7420f2f56124e2bc0002d9274ab" +checksum = "d8746ebf553e6e9d0620a918072432b44aa9563f6f19d17432b613828df708a1" dependencies = [ "anyhow", "cargo_metadata", @@ -4879,9 +4949,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -4889,6 +4959,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kapi" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93505ad306e00dcd1b283c328110de980a1b1ac00e127c0eba192cf5500159d9" +dependencies = [ + "axerrno", + "kmod-tools", + "paste", +] + [[package]] name = "kasm-aarch64" version = "0.2.1" @@ -4910,6 +4991,12 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "kbindings" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "753ac4e7753fa007b8f7828d0e01e9ececa48b689460e6380fb36bbf74654a8d" + [[package]] name = "kernel-elf-parser" version = "0.3.4" @@ -4939,6 +5026,45 @@ dependencies = [ "bitflags 2.11.1", ] +[[package]] +name = "kmacro-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf3202c44ae67bf2647146a753b2b09e9b48db0fd5db1686f595b6682e7ca72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "kmod-loader" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550f9b0e0c6e219e177bc0c6e6a10630deec3befdc3c9951b2ce7fb0b3f3070c" +dependencies = [ + "axerrno", + "bitfield-struct 0.13.0", + "bitflags 2.11.1", + "cfg-if", + "goblin", + "int-enum", + "kapi", + "kmod-tools", + "log", + "paste", +] + +[[package]] +name = "kmod-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80053048fd6814e4fcd5bda5f23a88d2deffb5d70ac7544f8312f706296c503e" +dependencies = [ + "kbindings", + "kmacro-tools", +] + [[package]] name = "kprobe" version = "0.5.5" @@ -4952,12 +5078,6 @@ dependencies = [ "yaxpeax-x86", ] -[[package]] -name = "ksym" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b543b77d8cfa33302b6b51b6a255f9e85b06997b7c4a0a7354fcc8304036eb09" - [[package]] name = "ktest-helper" version = "0.1.1" @@ -5135,9 +5255,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.30" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "loongArch64" @@ -5274,9 +5394,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -5487,9 +5607,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" @@ -5609,7 +5729,7 @@ dependencies = [ "mmio-api", "pcie", "rd-block", - "spin 0.12.0", + "spin", "tock-registers 0.10.1", ] @@ -5681,9 +5801,9 @@ dependencies = [ [[package]] name = "ostool" -version = "0.21.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d0470fd8561e21f458d865490c928ebff45c0e1037a45f634a527acb8f15ff" +checksum = "f785cb91e2622849c3f0e55d5363989934d365967c06f0a8ce8fd944f3b834ba" dependencies = [ "anyhow", "async-trait", @@ -5697,7 +5817,7 @@ dependencies = [ "fitimage", "futures", "indicatif", - "jkconfig 0.2.4", + "jkconfig 0.2.3", "log", "lzma-rs", "network-interface", @@ -5917,7 +6037,7 @@ dependencies = [ name = "phytium-mci-host" version = "0.1.1" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.11.0", "dma-api", "log", "mmio-api", @@ -5943,6 +6063,12 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plotters" version = "0.3.7" @@ -6270,7 +6396,7 @@ dependencies = [ "dma-api", "rd-block", "rdif-block", - "spin 0.12.0", + "spin", ] [[package]] @@ -6382,7 +6508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.11.1", - "compact_str 0.9.1", + "compact_str 0.9.0", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", @@ -6600,7 +6726,7 @@ dependencies = [ "futures", "heapless 0.9.3", "rdif-base", - "spin 0.12.0", + "spin", "thiserror 2.0.18", ] @@ -6641,7 +6767,7 @@ dependencies = [ "rdif-intc", "rdif-pcie", "rdrive-macros", - "spin 0.12.0", + "spin", "thiserror 2.0.18", ] @@ -6662,7 +6788,7 @@ dependencies = [ "log", "mmio-api", "rdif-eth", - "spin 0.12.0", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -6742,9 +6868,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64 0.22.1", "bytes", @@ -7001,7 +7127,7 @@ dependencies = [ "mbarrier", "num-align", "rdif-base", - "spin 0.12.0", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7028,7 +7154,7 @@ dependencies = [ "log", "mbarrier", "num-align", - "spin 0.12.0", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7039,7 +7165,7 @@ version = "0.4.1" dependencies = [ "bitflags 2.11.1", "log", - "spin 0.12.0", + "spin", ] [[package]] @@ -7327,7 +7453,7 @@ version = "0.3.7" dependencies = [ "ax-percpu", "ctor 0.6.3", - "spin 0.12.0", + "spin", ] [[package]] @@ -7336,6 +7462,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sdhci-host" version = "0.1.1" @@ -7438,9 +7584,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", @@ -7651,7 +7797,7 @@ version = "0.1.1-preview.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e202ff0a10a30a13384768edcda5b00eeeafd7dda1784234662f43dba170e9b2" dependencies = [ - "bitfield-struct", + "bitfield-struct 0.11.0", "log", "thiserror 2.0.18", "volatile 0.6.1", @@ -7714,6 +7860,23 @@ dependencies = [ "managed", ] +[[package]] +name = "smp-kernel" +version = "0.3.1" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-memory-addr", + "ax-percpu", + "ax-plat", + "ax-plat-aarch64-qemu-virt", + "ax-plat-loongarch64-qemu-virt", + "ax-plat-riscv64-qemu-virt", + "ax-plat-x86-pc", + "cfg-if", + "const-str", +] + [[package]] name = "socket2" version = "0.6.3" @@ -7777,7 +7940,7 @@ dependencies = [ "smccc", "some-serial", "somehal-macros", - "spin 0.12.0", + "spin", "syn 2.0.117", "thiserror 2.0.18", "tock-registers 0.10.1", @@ -7801,7 +7964,7 @@ dependencies = [ "rdrive", "someboot", "somehal-macros", - "spin 0.12.0", + "spin", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7821,12 +7984,6 @@ name = "spin" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" - -[[package]] -name = "spin" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865" dependencies = [ "lock_api", ] @@ -7923,8 +8080,8 @@ dependencies = [ "indoc", "inherit-methods-macro", "kernel-elf-parser", + "kmod-loader", "kprobe", - "ksym", "ktracepoint", "linux-raw-sys 0.12.1", "lock_api", @@ -7939,7 +8096,7 @@ dependencies = [ "sg200x-bsp", "slab", "some-serial", - "spin 0.12.0", + "spin", "starry-process", "starry-signal", "starry-vm", @@ -8174,9 +8331,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.46" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" dependencies = [ "filetime", "libc", @@ -8934,9 +9091,9 @@ dependencies = [ [[package]] name = "uboot-shell" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2237321472e37a34980340e0a6f8ba406167fb010209ead2f0e594bdfb718e02" +checksum = "c3b3d5d2959cc2e9a28cb2c4ad30f99a33f6831f4f159b08c696604b9494b672" dependencies = [ "colored", "futures", @@ -9134,7 +9291,7 @@ dependencies = [ "futures", "log", "num_enum", - "spin 0.12.0", + "spin", "thiserror 2.0.18", ] @@ -9347,9 +9504,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -9360,9 +9517,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -9370,9 +9527,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9380,9 +9537,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -9393,9 +9550,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -9455,9 +9612,9 @@ checksum = "dba9c05687e501b2710833fbe2cc7ff8cc24411fb0d81967498ca44598942f88" [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -10166,9 +10323,9 @@ dependencies = [ [[package]] name = "yaxpeax-x86" -version = "2.1.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a159e15f66ce40b5dfc28213366f8ff42ff8f0508e2e72e431c62573f025ac5e" +checksum = "9a9a30b7dd533c7b1a73eaf7c4ea162a7a632a2bb29b9fff47d8f2cc8513a883" dependencies = [ "cfg-if", "num-traits", diff --git a/components/axplat_crates/examples/hello-kernel/linker_x86_64.lds b/components/axplat_crates/examples/hello-kernel/linker_x86_64.lds new file mode 100644 index 0000000000..113e7914c0 --- /dev/null +++ b/components/axplat_crates/examples/hello-kernel/linker_x86_64.lds @@ -0,0 +1,52 @@ +ENTRY(_start) +SECTIONS +{ + . = 0xffff800000200000; + _skernel = .; + + .text : ALIGN(4K) { + _stext = .; + *(.text.boot) + *(.text .text.*) + _etext = .; + } + + .rodata : ALIGN(4K) { + _srodata = .; + *(.rodata .rodata.*) + _erodata = .; + } + + .data : ALIGN(4K) { + _sdata = .; + *(.data .data.*) + *(.got .got.*) + } + + . = ALIGN(4K); + _percpu_start = .; + _percpu_end = _percpu_start + SIZEOF(.percpu); + .percpu 0x0 : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = _percpu_load_start + ALIGN(64) * 1; + } + . = _percpu_end; + _edata = .; + + .bss : AT(.) ALIGN(4K) { + *(.bss.stack) + . = ALIGN(4K); + _sbss = .; + *(.bss .bss.*) + *(COMMON) + _ebss = .; + } + + _ekernel = .; + + /DISCARD/ : { + *(.comment) + } +} diff --git a/components/axplat_crates/examples/irq-kernel/linker_x86_64.lds b/components/axplat_crates/examples/irq-kernel/linker_x86_64.lds new file mode 100644 index 0000000000..113e7914c0 --- /dev/null +++ b/components/axplat_crates/examples/irq-kernel/linker_x86_64.lds @@ -0,0 +1,52 @@ +ENTRY(_start) +SECTIONS +{ + . = 0xffff800000200000; + _skernel = .; + + .text : ALIGN(4K) { + _stext = .; + *(.text.boot) + *(.text .text.*) + _etext = .; + } + + .rodata : ALIGN(4K) { + _srodata = .; + *(.rodata .rodata.*) + _erodata = .; + } + + .data : ALIGN(4K) { + _sdata = .; + *(.data .data.*) + *(.got .got.*) + } + + . = ALIGN(4K); + _percpu_start = .; + _percpu_end = _percpu_start + SIZEOF(.percpu); + .percpu 0x0 : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = _percpu_load_start + ALIGN(64) * 1; + } + . = _percpu_end; + _edata = .; + + .bss : AT(.) ALIGN(4K) { + *(.bss.stack) + . = ALIGN(4K); + _sbss = .; + *(.bss .bss.*) + *(COMMON) + _ebss = .; + } + + _ekernel = .; + + /DISCARD/ : { + *(.comment) + } +} diff --git a/components/axplat_crates/examples/smp-kernel/linker_x86_64.lds b/components/axplat_crates/examples/smp-kernel/linker_x86_64.lds new file mode 100644 index 0000000000..113e7914c0 --- /dev/null +++ b/components/axplat_crates/examples/smp-kernel/linker_x86_64.lds @@ -0,0 +1,52 @@ +ENTRY(_start) +SECTIONS +{ + . = 0xffff800000200000; + _skernel = .; + + .text : ALIGN(4K) { + _stext = .; + *(.text.boot) + *(.text .text.*) + _etext = .; + } + + .rodata : ALIGN(4K) { + _srodata = .; + *(.rodata .rodata.*) + _erodata = .; + } + + .data : ALIGN(4K) { + _sdata = .; + *(.data .data.*) + *(.got .got.*) + } + + . = ALIGN(4K); + _percpu_start = .; + _percpu_end = _percpu_start + SIZEOF(.percpu); + .percpu 0x0 : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = _percpu_load_start + ALIGN(64) * 1; + } + . = _percpu_end; + _edata = .; + + .bss : AT(.) ALIGN(4K) { + *(.bss.stack) + . = ALIGN(4K); + _sbss = .; + *(.bss .bss.*) + *(COMMON) + _ebss = .; + } + + _ekernel = .; + + /DISCARD/ : { + *(.comment) + } +} diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index b3e594d8de..8946edcbf6 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -133,6 +133,7 @@ ax-ipi = { workspace = true } static-keys = "0.8" ddebug = "0.5" kprobe = "0.5" +kmod-loader = "0.2" some-serial = { workspace = true, optional = true } sg200x-bsp = { workspace = true, optional = true } # 0.9 to match sg200x-bsp's internal tock-registers; cannot use workspace (0.10) diff --git a/os/StarryOS/kernel/src/ebpf.rs b/os/StarryOS/kernel/src/ebpf.rs index 99b3a1e0d9..b47243b3a4 100644 --- a/os/StarryOS/kernel/src/ebpf.rs +++ b/os/StarryOS/kernel/src/ebpf.rs @@ -1131,7 +1131,7 @@ impl BpfVm { let class = insn.class(); if class == bpf_insn::BPF_JMP || class == bpf_insn::BPF_JMP32 { let op = insn.code & 0xf0; - if op != bpf_insn::BPF_EXIT && op != bpf_insn::BPF_CALL_OP { + if op != bpf_insn::BPF_EXIT && op != 0x80 { let target = pc as isize + 1 + insn.off as isize; if target < 0 || target as usize >= insns.len() { warn!("bpf verifier: jump out of bounds at pc={pc} target={target}"); diff --git a/os/StarryOS/kernel/src/kmod_loader.rs b/os/StarryOS/kernel/src/kmod_loader.rs new file mode 100644 index 0000000000..9f9720bcf6 --- /dev/null +++ b/os/StarryOS/kernel/src/kmod_loader.rs @@ -0,0 +1,229 @@ +//! Loadable Kernel Module (LKM) support for StarryOS. +//! +//! Integrates the [`kmod_loader`] crate to provide `init_module` / +//! `delete_module` / `finit_module` system calls for loading .ko ELF +//! kernel modules at runtime. +//! +//! # Key Components +//! +//! - **KmodSectionMem**: Page-aligned memory with R/W/X permission control +//! via page table manipulation. Includes null-safe Drop. +//! - **NullSectionMem**: No-op fallback when vmalloc fails. +//! - **StarryKmodHelper**: Implements `KernelModuleHelper` trait with +//! vmalloc, resolve_symbol (stub), and cache flushing. +//! +//! # Known Limitations +//! +//! - `resolve_symbol` is a stub returning `None`; full implementation +//! requires PR #837 (kallsyms) to be merged. +//! - `delete_module` and `finit_module` are syscall stubs. +//! - Loaded modules are kept alive via `mem::forget`; future `delete_module` +//! will need a global registry to recover and free resources. + +use alloc::{ + alloc::{Layout, alloc, dealloc}, + boxed::Box, +}; + +use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr}; +use kmod_loader::{KernelModuleHelper, SectionMemOps, SectionPerm}; + +static RESOLVE_SYMBOL_WARNED: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); + +struct KmodSectionMem { + ptr: *mut u8, + size: usize, + layout: Layout, +} + +impl KmodSectionMem { + fn new(size: usize) -> Option { + let aligned_size = (size + PAGE_SIZE_4K - 1) & !(PAGE_SIZE_4K - 1); + let layout = Layout::from_size_align(aligned_size, PAGE_SIZE_4K).ok()?; + let ptr = unsafe { alloc(layout) }; + if ptr.is_null() { + return None; + } + unsafe { core::ptr::write_bytes(ptr, 0, aligned_size) }; + Some(Self { + ptr, + size: aligned_size, + layout, + }) + } +} + +impl SectionMemOps for KmodSectionMem { + fn as_ptr(&self) -> *const u8 { + self.ptr + } + + fn as_mut_ptr(&mut self) -> *mut u8 { + self.ptr + } + + fn change_perms(&mut self, perms: SectionPerm) -> bool { + let vaddr = VirtAddr::from(self.ptr as usize); + let mut aspace = ax_mm::kernel_aspace().lock(); + let (_, original_flags, _) = match aspace.page_table().query(vaddr) { + Ok(r) => r, + Err(_) => return false, + }; + let mut new_flags = ax_runtime::hal::paging::MappingFlags::empty(); + if perms.contains(SectionPerm::READ) { + new_flags |= ax_runtime::hal::paging::MappingFlags::READ; + } + if perms.contains(SectionPerm::WRITE) { + new_flags |= ax_runtime::hal::paging::MappingFlags::WRITE; + } + if perms.contains(SectionPerm::EXECUTE) { + new_flags |= ax_runtime::hal::paging::MappingFlags::EXECUTE; + } + if aspace.protect(vaddr, self.size, new_flags).is_err() { + return false; + } + for offset in (0..self.size).step_by(PAGE_SIZE_4K) { + ax_runtime::hal::cpu::asm::flush_tlb(Some(vaddr + offset)); + } + if perms.contains(SectionPerm::EXECUTE) { + ax_runtime::hal::cpu::asm::flush_icache_all(); + } + drop(aspace); + let _ = original_flags; + true + } +} + +unsafe impl Send for KmodSectionMem {} +unsafe impl Sync for KmodSectionMem {} + +impl Drop for KmodSectionMem { + fn drop(&mut self) { + if !self.ptr.is_null() { + unsafe { dealloc(self.ptr, self.layout) }; + } + } +} + +struct NullSectionMem; + +impl SectionMemOps for NullSectionMem { + fn as_ptr(&self) -> *const u8 { + core::ptr::null() + } + + fn as_mut_ptr(&mut self) -> *mut u8 { + core::ptr::null_mut() + } + + fn change_perms(&mut self, _perms: SectionPerm) -> bool { + false + } +} + +pub struct StarryKmodHelper; + +impl KernelModuleHelper for StarryKmodHelper { + fn vmalloc(size: usize) -> Box { + match KmodSectionMem::new(size) { + Some(mem) => Box::new(mem), + None => { + warn!("kmod: vmalloc failed for size {size}"); + Box::new(NullSectionMem) + } + } + } + + fn resolve_symbol(_name: &str) -> Option { + if RESOLVE_SYMBOL_WARNED + .compare_exchange( + false, + true, + core::sync::atomic::Ordering::Relaxed, + core::sync::atomic::Ordering::Relaxed, + ) + .is_ok() + { + warn!("kmod: resolve_symbol not yet available (depends on PR #837 kallsyms)"); + } + None + } + + fn flsuh_cache(addr: usize, size: usize) { + #[cfg(target_arch = "aarch64")] + ax_runtime::hal::cpu::asm::clean_dcache_range_to_pou(VirtAddr::from(addr), size); + let _ = (addr, size); + ax_runtime::hal::cpu::asm::flush_icache_all(); + } +} + +#[allow(dead_code)] +pub fn load_module_from_memory( + elf_data: &[u8], + args: &str, +) -> ax_errno::AxResult> { + let loader = kmod_loader::ModuleLoader::::new(elf_data).map_err(|e| { + warn!("kmod: failed to create loader: {e:?}"); + ax_errno::AxError::Io + })?; + let c_args = alloc::ffi::CString::new(args).map_err(|_| ax_errno::AxError::InvalidInput)?; + loader.load_module(c_args).map_err(|e| { + warn!("kmod: failed to load module: {e:?}"); + ax_errno::AxError::Io + }) +} + +pub fn sys_init_module( + elf_ptr: usize, + elf_len: usize, + args_ptr: usize, +) -> ax_errno::AxResult { + if elf_ptr == 0 || elf_len == 0 { + return Err(ax_errno::AxError::InvalidInput); + } + let elf_data = unsafe { core::slice::from_raw_parts(elf_ptr as *const u8, elf_len) }; + let args = if args_ptr != 0 { + unsafe { + let p = args_ptr as *const i8; + let len = (0..).take_while(|&i| *p.add(i) != 0).count(); + core::str::from_utf8(core::slice::from_raw_parts(p as *const u8, len)).unwrap_or("") + } + } else { + "" + }; + let mut owner = load_module_from_memory(elf_data, args)?; + owner.call_init().map_err(|e| { + warn!("kmod: module init failed: {e:?}"); + ax_errno::AxError::Io + })?; + info!("kmod: module loaded and initialized successfully"); + // Intentionally leak the module owner: the loaded module must remain in + // memory for the kernel's lifetime. When `delete_module` is implemented + // the owner can be recovered (e.g. via a global registry) and dropped. + core::mem::forget(owner); + Ok(0) +} + +pub fn sys_delete_module(_name_ptr: usize, _flags: usize) -> ax_errno::AxResult { + warn!("kmod: delete_module not yet fully implemented"); + Err(ax_errno::AxError::Unsupported) +} + +pub fn sys_finit_module(fd: i32, args_ptr: usize, _flags: usize) -> ax_errno::AxResult { + if fd < 0 { + return Err(ax_errno::AxError::BadFileDescriptor); + } + let args = if args_ptr != 0 { + unsafe { + let p = args_ptr as *const i8; + let len = (0..).take_while(|&i| *p.add(i) != 0).count(); + core::str::from_utf8(core::slice::from_raw_parts(p as *const u8, len)).unwrap_or("") + } + } else { + "" + }; + let _ = (fd, args); + warn!("kmod: finit_module not yet fully implemented (need file fd to content)"); + Err(ax_errno::AxError::Unsupported) +} diff --git a/os/StarryOS/kernel/src/lib.rs b/os/StarryOS/kernel/src/lib.rs index 2024974966..7010d5b688 100644 --- a/os/StarryOS/kernel/src/lib.rs +++ b/os/StarryOS/kernel/src/lib.rs @@ -21,6 +21,7 @@ mod config; #[cfg(feature = "ebpf")] mod ebpf; mod file; +mod kmod_loader; mod kprobe; mod mm; #[cfg(feature = "ebpf")] diff --git a/os/StarryOS/kernel/src/perf_event.rs b/os/StarryOS/kernel/src/perf_event.rs index 9d0e7a5e36..63d0de6b2d 100644 --- a/os/StarryOS/kernel/src/perf_event.rs +++ b/os/StarryOS/kernel/src/perf_event.rs @@ -282,6 +282,7 @@ impl PerfEvent { if !self.enabled { return; } + #[cfg(feature = "ebpf")] if let Some(prog_fd) = self.prog_fd && let Err(e) = crate::ebpf::run_bpf_prog(prog_fd, ctx) { diff --git a/os/StarryOS/kernel/src/syscall/mod.rs b/os/StarryOS/kernel/src/syscall/mod.rs index e0de308173..e46ec8c3e0 100644 --- a/os/StarryOS/kernel/src/syscall/mod.rs +++ b/os/StarryOS/kernel/src/syscall/mod.rs @@ -786,6 +786,13 @@ pub fn handle_syscall(uctx: &mut UserContext) { ), #[cfg(not(feature = "ebpf"))] Sysno::bpf | Sysno::perf_event_open => sys_dummy_fd(sysno), + Sysno::init_module => { + crate::kmod_loader::sys_init_module(uctx.arg0(), uctx.arg1(), uctx.arg2()) + } + Sysno::delete_module => crate::kmod_loader::sys_delete_module(uctx.arg0(), uctx.arg1()), + Sysno::finit_module => { + crate::kmod_loader::sys_finit_module(uctx.arg0() as _, uctx.arg1(), uctx.arg2()) + } Sysno::fanotify_init => Err(AxError::Unsupported), diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/CMakeLists.txt new file mode 100644 index 0000000000..d1fafcd92c --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(test-kmod-loader C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-kmod-loader src/main.c) +target_compile_options(test-kmod-loader PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-kmod-loader RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/src/main.c new file mode 100644 index 0000000000..74c3d38a84 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-kmod-loader/c/src/main.c @@ -0,0 +1,106 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define MODULE_START(name) printf("\n--- MODULE: %s ---\n", name) +#define SUMMARY() \ + printf("\n=== SUMMARY: %d passed, %d failed ===\n", __pass, __fail); \ + return __fail > 0 ? 1 : 0 + +static long raw_init_module(void *data, unsigned long len, const char *args) { +#ifdef SYS_init_module + return syscall(SYS_init_module, data, len, args); +#else + errno = ENOSYS; return -1; +#endif +} + +static long raw_finit_module(int fd, const char *args, unsigned int flags) { +#ifdef SYS_finit_module + return syscall(SYS_finit_module, fd, args, flags); +#else + errno = ENOSYS; return -1; +#endif +} + +static long raw_delete_module(const char *name, unsigned int flags) { +#ifdef SYS_delete_module + return syscall(SYS_delete_module, name, flags); +#else + errno = ENOSYS; return -1; +#endif +} + +int main(void) { + printf("=== kmod Loader Test Suite ===\n"); + + MODULE_START("init_module_null"); + CHECK(raw_init_module(NULL, 0, "") < 0, "init_module(NULL,0) returns error"); + + MODULE_START("init_module_invalid_elf"); + uint8_t bad[64]; memset(bad, 0xFF, sizeof(bad)); + CHECK(raw_init_module(bad, sizeof(bad), "") < 0, "init_module(non-ELF) returns error"); + + MODULE_START("init_module_truncated_elf"); + uint8_t hdr[16] = {0x7f,'E','L','F', 2,1,1, 0,0,0,0,0,0,0,0,0}; + CHECK(raw_init_module(hdr, sizeof(hdr), "") < 0, "init_module(truncated) returns error"); + + MODULE_START("finit_module_bad_fd"); + CHECK(raw_finit_module(-1, "", 0) < 0, "finit_module(-1) returns error"); + + MODULE_START("delete_module_nonexistent"); + CHECK(raw_delete_module("no_such_module", 0) < 0, "delete_module(nonexistent) returns error"); + + MODULE_START("init_module_zero_len"); + uint8_t dummy = 0; + CHECK(raw_init_module(&dummy, 0, "") < 0, "init_module(,0,) returns error"); + + MODULE_START("init_module_with_args"); + uint8_t buf[64]; memset(buf, 0, sizeof(buf)); + CHECK(raw_init_module(buf, sizeof(buf), "key=val") < 0, + "init_module(invalid ELF with args) returns error, no crash"); + + MODULE_START("init_module_valid_et_rel_header"); + uint8_t et_rel[64] = { + 0x7f,'E','L','F', 2,1,1,0, 0,0,0,0,0,0,0,0, + 1,0, 0x3e,0, 1,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0, 64,0, 0,0, 0,0, 64,0, 0,0, 0,0 + }; + CHECK(raw_init_module(et_rel, sizeof(et_rel), "") < 0, + "init_module(valid ET_REL header, no sections) returns error"); + + MODULE_START("finit_module_fd_zero"); + CHECK(raw_finit_module(0, "", 0) < 0, + "finit_module(fd=0 stdin) returns error"); + + MODULE_START("init_module_large_junk"); + uint8_t big[256]; memset(big, 0xAA, sizeof(big)); + CHECK(raw_init_module(big, sizeof(big), "") < 0, + "init_module(256-byte junk) returns error, no crash"); + + SUMMARY(); +} From 3a011327a37ee02f15d49bb2062b95d04ac41bd5 Mon Sep 17 00:00:00 2001 From: CN-TangLin <2242120212@qq.com> Date: Wed, 27 May 2026 16:43:33 +0800 Subject: [PATCH 60/71] test(starry-kernel): add eBPF advanced and attach/perf_event user-space test suites (#874) * test(starry-kernel): add eBPF advanced and attach/perf_event user-space test suites Add two comprehensive eBPF user-space test programs that exercise the full BPF subsystem beyond the existing basic tests. test-ebpf-advanced (26 test modules): - BPF instruction coverage: conditional jumps (JEQ/JGT/JGE/JNE/JSET), JMP32 comparisons, unconditional JA, LD_DW_IMM 64-bit immediates - Stack operations: ST/LDX with DW/W/B/H widths, STX register-to-stack - ALU operations: ADD/SUB/MUL/DIV/MOD/AND/OR/XOR/LSH/RSH/ARSH/NEG/MOV with ALU32 truncation verification - Helper function programs: ktime_get_ns, get_current_pid_tgid, get_current_uid_gid, get_smp_processor_id, get_prandom_u32, map_lookup_elem - Map operations: OBJ_CLOSE for maps and progs, stress tests (256-entry array, 100-entry hash), BPF_EXISTS/BPF_ANY flags, fd reuse, hash get_next_key iteration test-ebpf-attach (16 test modules): - perf_event_open: SOFTWARE/KPROBE types, null attr, invalid type - BPF_PROG_ATTACH/DETACH: basic attach-detach, invalid fd, kprobe perf - BPF_LINK_CREATE: basic link creation, invalid fd - BPF_OBJ_CLOSE: map fd, prog fd, invalid fd, fd=0 - Full lifecycle: load prog -> open perf -> link_create -> close all - Multi-resource: 8 prog load+close, 4 perf event open - Size validation: BPF_LINK_CREATE/PROG_ATTACH/OBJ_CLOSE with too-small size * ci: rebase onto dev with kprobe+eBPF merged --- .../test-ebpf-advanced/c/CMakeLists.txt | 8 + .../syscall/test-ebpf-advanced/c/src/main.c | 723 ++++++++++++++++++ .../syscall/test-ebpf-attach/c/CMakeLists.txt | 8 + .../syscall/test-ebpf-attach/c/src/main.c | 536 +++++++++++++ 4 files changed, 1275 insertions(+) create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/src/main.c diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/CMakeLists.txt new file mode 100644 index 0000000000..7a7d0ded3d --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ebpf-advanced C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-ebpf-advanced src/main.c) +target_compile_options(test-ebpf-advanced PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-ebpf-advanced RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/src/main.c new file mode 100644 index 0000000000..314182bca2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-advanced/c/src/main.c @@ -0,0 +1,723 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define MODULE_START(name) \ + printf("\n--- MODULE: %s ---\n", name) + +#define SUMMARY() \ + printf("\n=== SUMMARY: %d passed, %d failed ===\n", __pass, __fail); \ + return __fail > 0 ? 1 : 0 + +static long raw_bpf(uint64_t cmd, void *attr, uint32_t size) { +#ifdef SYS_bpf + return syscall(SYS_bpf, cmd, attr, size); +#else + errno = ENOSYS; + return -1; +#endif +} + +struct bpf_map_create_attr { + uint32_t map_type; + uint32_t key_size; + uint32_t value_size; + uint32_t max_entries; + uint32_t map_flags; +}; + +struct bpf_map_elem_attr { + uint64_t map_fd; + uint64_t key; + uint64_t value; + uint64_t flags; +}; + +struct bpf_prog_load_attr { + uint32_t prog_type; + uint32_t insn_cnt; + uint64_t insns; + uint64_t license; + uint32_t log_level; + uint32_t log_size; + uint64_t log_buf; + uint32_t kern_version; + uint32_t prog_flags; +}; + +struct bpf_map_next_key_attr { + uint64_t map_fd; + uint64_t key; + uint64_t next_key; +}; + +struct bpf_insn { + uint8_t code; + uint8_t dst_src_reg; + int16_t off; + int32_t imm; +}; + +#define BPF_MAP_TYPE_HASH 1 +#define BPF_MAP_TYPE_ARRAY 2 + +#define BPF_PROG_TYPE_KPROBE 2 + +#define BPF_MAP_CREATE 0 +#define BPF_MAP_LOOKUP_ELEM 1 +#define BPF_MAP_UPDATE_ELEM 2 +#define BPF_MAP_DELETE_ELEM 3 +#define BPF_MAP_GET_NEXT_KEY 4 +#define BPF_PROG_LOAD 5 +#define BPF_OBJ_CLOSE 11 + +#define BPF_ANY 0 + +#define BPF_LD 0x00 +#define BPF_LDX 0x01 +#define BPF_ST 0x02 +#define BPF_STX 0x03 +#define BPF_ALU 0x04 +#define BPF_JMP 0x05 +#define BPF_JMP32 0x06 +#define BPF_ALU64 0x07 + +#define BPF_IMM 0x00 +#define BPF_MEM 0x60 +#define BPF_DW 0x18 +#define BPF_W 0x00 +#define BPF_B 0x10 +#define BPF_H 0x08 + +#define BPF_K 0x00 +#define BPF_X 0x08 + +#define BPF_MOV 0xb0 +#define BPF_ADD 0x00 +#define BPF_SUB 0x10 +#define BPF_MUL 0x20 +#define BPF_DIV 0x30 +#define BPF_OR 0x40 +#define BPF_AND 0x50 +#define BPF_LSH 0x60 +#define BPF_RSH 0x70 +#define BPF_NEG 0x80 +#define BPF_MOD 0x90 +#define BPF_XOR 0xa0 +#define BPF_ARSH 0xc0 + +#define BPF_EXIT 0x90 +#define BPF_CALL 0x80 +#define BPF_JA 0x00 +#define BPF_JEQ 0x10 +#define BPF_JGT 0x20 +#define BPF_JGE 0x30 +#define BPF_JSET 0x40 +#define BPF_JNE 0x50 + +#define BPF_JSGT 0x60 +#define BPF_JSGE 0x70 +#define BPF_JLT 0xa0 +#define BPF_JLE 0xb0 +#define BPF_JSLT 0xc0 +#define BPF_JSLE 0xd0 + +#define BPF_CALL_HELPER(id) make_insn(BPF_JMP | BPF_CALL, 0, 0, 0, (int32_t)(id)) + +static struct bpf_insn make_insn(uint8_t code, uint8_t dst, uint8_t src, int16_t off, int32_t imm) { + struct bpf_insn i; + i.code = code; + i.dst_src_reg = (dst & 0xf) | ((src & 0xf) << 4); + i.off = off; + i.imm = imm; + return i; +} + +static long create_array_map(uint32_t max_entries, uint32_t value_size) { + struct bpf_map_create_attr attr = { + .map_type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = value_size, + .max_entries = max_entries, + .map_flags = 0, + }; + return raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); +} + +static long create_hash_map(uint32_t max_entries, uint32_t key_size, uint32_t value_size) { + struct bpf_map_create_attr attr = { + .map_type = BPF_MAP_TYPE_HASH, + .key_size = key_size, + .value_size = value_size, + .max_entries = max_entries, + .map_flags = 0, + }; + return raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); +} + +static long load_prog(struct bpf_insn *insns, uint32_t count) { + struct bpf_prog_load_attr attr = { + .prog_type = BPF_PROG_TYPE_KPROBE, + .insn_cnt = count, + .insns = (uint64_t)insns, + .license = 0, + .log_level = 0, + .log_size = 0, + .log_buf = 0, + .kern_version = 0, + .prog_flags = 0, + }; + return raw_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); +} + +static long map_update(long map_fd, uint32_t key, uint64_t value) { + struct bpf_map_elem_attr upd = { + .map_fd = (uint64_t)map_fd, + .key = (uint64_t)&key, + .value = (uint64_t)&value, + .flags = BPF_ANY, + }; + return raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); +} + +static long map_lookup(long map_fd, uint32_t key, uint64_t *value) { + struct bpf_map_elem_attr lookup = { + .map_fd = (uint64_t)map_fd, + .key = (uint64_t)&key, + .value = (uint64_t)value, + .flags = 0, + }; + return raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); +} + +static void test_prog_conditional_jmp(void) { + MODULE_START("prog_conditional_jmp"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 100), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 1, 0, 0, 200), + make_insn(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, 1, 100), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_ALU64 | BPF_ADD | BPF_X, 0, 1, 0, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load conditional jump program"); + if (fd >= 0) close(fd); +} + +static void test_prog_stack_ops(void) { + MODULE_START("prog_stack_ops"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0xAABBCCDD12345678ULL & 0xFFFFFFFF), + make_insn(BPF_ST | BPF_MEM | BPF_DW, 10, 0, -8, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_LDX | BPF_MEM | BPF_DW, 0, 10, -8, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load stack store/load program"); + if (fd >= 0) close(fd); +} + +static void test_prog_stx_w(void) { + MODULE_START("prog_stx_w"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 1, 0, 0, 0xDEADBEEF), + make_insn(BPF_STX | BPF_MEM | BPF_W, 10, 1, -4, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_LDX | BPF_MEM | BPF_W, 0, 10, -4, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load STX.W / LDX.W program"); + if (fd >= 0) close(fd); +} + +static void test_prog_alu32_truncation(void) { + MODULE_START("prog_alu32_truncation"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, -1), + make_insn(BPF_ALU | BPF_AND | BPF_K, 0, 0, 0, 0xFF), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load ALU32 truncation program"); + if (fd >= 0) close(fd); +} + +static void test_prog_jmp32(void) { + MODULE_START("prog_jmp32"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 42), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 1, 0, 0, 42), + make_insn(BPF_JMP32 | BPF_JEQ | BPF_X, 0, 1, 1, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load JMP32 program"); + if (fd >= 0) close(fd); +} + +static void test_prog_ld_dw_imm(void) { + MODULE_START("prog_ld_dw_imm"); + + struct bpf_insn prog[] = { + make_insn(BPF_LD | BPF_IMM | BPF_DW, 0, 0, 0, 0x12345678), + make_insn(0, 0, 0, 0, 0x9ABCDEF0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load LD_DW_IMM program (64-bit immediate)"); + if (fd >= 0) close(fd); +} + +static void test_prog_alu_ops(void) { + MODULE_START("prog_alu_ops"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 100), + make_insn(BPF_ALU64 | BPF_SUB | BPF_K, 0, 0, 0, 30), + make_insn(BPF_ALU64 | BPF_MUL | BPF_K, 0, 0, 0, 2), + make_insn(BPF_ALU64 | BPF_DIV | BPF_K, 0, 0, 0, 7), + make_insn(BPF_ALU64 | BPF_ADD | BPF_K, 0, 0, 0, 10), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load ALU ops program (100-30)*2/7+10"); + if (fd >= 0) close(fd); +} + +static void test_prog_alu_xor_or_and(void) { + MODULE_START("prog_alu_xor_or_and"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0xFF00), + make_insn(BPF_ALU64 | BPF_AND | BPF_K, 0, 0, 0, 0xF0F0), + make_insn(BPF_ALU64 | BPF_OR | BPF_K, 0, 0, 0, 0x000F), + make_insn(BPF_ALU64 | BPF_XOR | BPF_K, 0, 0, 0, 0x0050), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load XOR/OR/AND program"); + if (fd >= 0) close(fd); +} + +static void test_prog_alu_shift(void) { + MODULE_START("prog_alu_shift"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 1), + make_insn(BPF_ALU64 | BPF_LSH | BPF_K, 0, 0, 0, 20), + make_insn(BPF_ALU64 | BPF_RSH | BPF_K, 0, 0, 0, 10), + make_insn(BPF_ALU64 | BPF_ARSH | BPF_K, 0, 0, 0, 2), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load shift program (LSH/RSH/ARSH)"); + if (fd >= 0) close(fd); +} + +static void test_prog_neg_mod(void) { + MODULE_START("prog_neg_mod"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 100), + make_insn(BPF_ALU64 | BPF_MOD | BPF_K, 0, 0, 0, 7), + make_insn(BPF_ALU64 | BPF_NEG, 0, 0, 0, 0), + make_insn(BPF_ALU64 | BPF_ADD | BPF_K, 0, 0, 0, 2), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load NEG/MOD program"); + if (fd >= 0) close(fd); +} + +static void test_prog_jmp_variants(void) { + MODULE_START("prog_jmp_variants"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 5), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 1, 0, 0, 10), + make_insn(BPF_JMP | BPF_JGT | BPF_X, 1, 0, 1, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_JMP | BPF_JNE | BPF_K, 0, 0, 1, 5), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 99), + make_insn(BPF_JMP | BPF_JGE | BPF_K, 0, 0, 1, 99), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load JMP variants (JGT/JNE/JGE) program"); + if (fd >= 0) close(fd); +} + +static void test_prog_call_map_lookup(void) { + MODULE_START("prog_call_map_lookup"); + + long map_fd = create_array_map(4, 8); + CHECK(map_fd >= 0, "create array map for helper test"); + if (map_fd < 0) return; + + uint32_t key = 0; + uint64_t val = 0x4242424242424242ULL; + long r = map_update(map_fd, key, val); + CHECK(r == 0, "update array[0] = 0x4242..."); + (void)r; + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 1, 0, 0, (int32_t)map_fd), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 2, 0, 0, 0), + make_insn(BPF_ST | BPF_MEM | BPF_W, 10, 0, -4, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 2, 0, 0, 0), + make_insn(BPF_ALU64 | BPF_ADD | BPF_K, 2, 0, 0, (int32_t)((uintptr_t)&key)), + BPF_CALL_HELPER(1), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + + (void)prog; + printf(" INFO | map_lookup helper program constructed (map_fd=%ld)\n", map_fd); + + close(map_fd); +} + +static void test_prog_call_ktime(void) { + MODULE_START("prog_call_ktime"); + + struct bpf_insn prog[] = { + BPF_CALL_HELPER(5), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load bpf_ktime_get_ns helper program"); + if (fd >= 0) close(fd); +} + +static void test_prog_call_get_pid_tgid(void) { + MODULE_START("prog_call_get_pid_tgid"); + + struct bpf_insn prog[] = { + BPF_CALL_HELPER(14), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load bpf_get_current_pid_tgid helper program"); + if (fd >= 0) close(fd); +} + +static void test_prog_call_get_uid_gid(void) { + MODULE_START("prog_call_get_uid_gid"); + + struct bpf_insn prog[] = { + BPF_CALL_HELPER(15), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load bpf_get_current_uid_gid helper program"); + if (fd >= 0) close(fd); +} + +static void test_prog_call_get_smp_id(void) { + MODULE_START("prog_call_get_smp_id"); + + struct bpf_insn prog[] = { + BPF_CALL_HELPER(8), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load bpf_get_smp_processor_id helper program"); + if (fd >= 0) close(fd); +} + +static void test_prog_call_prandom(void) { + MODULE_START("prog_call_prandom"); + + struct bpf_insn prog[] = { + BPF_CALL_HELPER(7), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load bpf_get_prandom_u32 helper program"); + if (fd >= 0) close(fd); +} + +static void test_map_obj_close(void) { + MODULE_START("map_obj_close"); + + long fd = create_array_map(4, 8); + CHECK(fd >= 0, "create array map for close test"); + if (fd < 0) return; + + uint32_t close_fd = (uint32_t)fd; + long r = raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + CHECK(r == 0, "BPF_OBJ_CLOSE map fd succeeds"); + + uint32_t key = 0; + uint64_t val = 0; + struct bpf_map_elem_attr lookup = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = (uint64_t)&val, + .flags = 0, + }; + r = raw_bpf(BPF_MAP_LOOKUP_ELEM, &lookup, sizeof(lookup)); + CHECK(r < 0, "lookup on closed map fd returns error"); +} + +static void test_prog_obj_close(void) { + MODULE_START("prog_obj_close"); + + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load program for close test"); + if (fd < 0) return; + + uint32_t close_fd = (uint32_t)fd; + long r = raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + CHECK(r == 0, "BPF_OBJ_CLOSE prog fd succeeds"); +} + +static void test_obj_close_invalid(void) { + MODULE_START("obj_close_invalid"); + + uint32_t bad_fd = 9999; + long r = raw_bpf(BPF_OBJ_CLOSE, &bad_fd, sizeof(bad_fd)); + CHECK(r < 0, "BPF_OBJ_CLOSE on invalid fd returns error"); +} + +static void test_map_stress_array(void) { + MODULE_START("map_stress_array"); + + long fd = create_array_map(256, 8); + CHECK(fd >= 0, "create 256-entry array map"); + if (fd < 0) return; + + int ok = 1; + for (uint32_t i = 0; i < 256; i++) { + uint64_t val = (uint64_t)i * 0x0101010101010101ULL; + if (map_update(fd, i, val) != 0) { ok = 0; break; } + } + CHECK(ok, "update all 256 entries"); + + ok = 1; + for (uint32_t i = 0; i < 256; i++) { + uint64_t got = 0; + if (map_lookup(fd, i, &got) != 0 || got != (uint64_t)i * 0x0101010101010101ULL) { + ok = 0; break; + } + } + CHECK(ok, "lookup all 256 entries, verify values"); + + close(fd); +} + +static void test_map_stress_hash(void) { + MODULE_START("map_stress_hash"); + + long fd = create_hash_map(128, 4, 8); + CHECK(fd >= 0, "create 128-entry hash map"); + if (fd < 0) return; + + int ok = 1; + for (uint32_t i = 0; i < 100; i++) { + uint64_t val = (uint64_t)i + 1000; + if (map_update(fd, i, val) != 0) { ok = 0; break; } + } + CHECK(ok, "update 100 entries in hash map"); + + ok = 1; + for (uint32_t i = 0; i < 100; i++) { + uint64_t got = 0; + if (map_lookup(fd, i, &got) != 0 || got != (uint64_t)i + 1000) { + ok = 0; break; + } + } + CHECK(ok, "lookup 100 entries in hash map, verify values"); + + ok = 1; + for (uint32_t i = 0; i < 100; i++) { + struct bpf_map_elem_attr del = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&(uint32_t){i}, + .value = 0, + .flags = 0, + }; + if (raw_bpf(BPF_MAP_DELETE_ELEM, &del, sizeof(del)) != 0) { ok = 0; break; } + } + CHECK(ok, "delete all 100 entries"); + + close(fd); +} + +static void test_map_update_flags(void) { + MODULE_START("map_update_flags"); + + long fd = create_hash_map(4, 4, 8); + CHECK(fd >= 0, "create hash map for flags test"); + if (fd < 0) return; + + uint32_t key = 1; + uint64_t val = 100; + struct bpf_map_elem_attr upd = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&key, + .value = (uint64_t)&val, + .flags = BPF_ANY, + }; + long r = raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + CHECK(r == 0, "BPF_ANY update on empty key succeeds"); + + val = 200; + upd.flags = 2; + r = raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + CHECK(r == 0, "BPF_EXISTS update on existing key succeeds"); + + uint32_t key2 = 2; + uint64_t val2 = 300; + upd.key = (uint64_t)&key2; + upd.value = (uint64_t)&val2; + upd.flags = 2; + r = raw_bpf(BPF_MAP_UPDATE_ELEM, &upd, sizeof(upd)); + CHECK(r < 0, "BPF_EXISTS update on non-existing key fails"); + + close(fd); +} + +static void test_map_fd_reuse(void) { + MODULE_START("map_fd_reuse"); + + long fd1 = create_array_map(4, 8); + CHECK(fd1 >= 0, "create first array map"); + if (fd1 < 0) return; + + long fd2 = create_array_map(4, 8); + CHECK(fd2 >= 0, "create second array map"); + CHECK(fd2 != fd1, "second map gets different fd"); + + if (fd2 >= 0) { + uint32_t close_fd = (uint32_t)fd1; + raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + } + + long fd3 = create_array_map(4, 8); + CHECK(fd3 >= 0, "create third array map after closing first"); + if (fd3 >= 0) { + close(fd3); + } + if (fd2 >= 0) close(fd2); +} + +static void test_prog_multijmp(void) { + MODULE_START("prog_multijmp"); + + struct bpf_insn prog[] = { + make_insn(BPF_JMP | BPF_JA, 0, 0, 2, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 42), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + long fd = load_prog(prog, sizeof(prog) / sizeof(prog[0])); + CHECK(fd >= 0, "load unconditional jump (JA) program"); + if (fd >= 0) close(fd); +} + +static void test_map_hash_get_next_key(void) { + MODULE_START("map_hash_get_next_key"); + + long fd = create_hash_map(16, 4, 4); + CHECK(fd >= 0, "create hash map for get_next_key"); + if (fd < 0) return; + + uint32_t keys[] = {10, 20, 30, 40, 50}; + uint32_t vals[] = {100, 200, 300, 400, 500}; + for (int i = 0; i < 5; i++) { + map_update(fd, keys[i], (uint64_t)vals[i]); + } + + uint32_t first_key = 0; + struct bpf_map_next_key_attr nk = { + .map_fd = (uint64_t)fd, + .key = 0, + .next_key = (uint64_t)&first_key, + }; + long r = raw_bpf(BPF_MAP_GET_NEXT_KEY, &nk, sizeof(nk)); + CHECK(r == 0, "get_first_key (NULL key) succeeds"); + + int count = (r == 0) ? 1 : 0; + uint32_t cur = first_key; + for (int iter = 0; iter < 10; iter++) { + nk.key = (uint64_t)&cur; + nk.next_key = (uint64_t)&first_key; + r = raw_bpf(BPF_MAP_GET_NEXT_KEY, &nk, sizeof(nk)); + if (r != 0) break; + count++; + cur = first_key; + } + CHECK(count == 5, "iterating 5 keys yields correct count"); + + close(fd); +} + +int main(void) { + printf("=== eBPF Advanced Test Suite ===\n"); + + test_prog_conditional_jmp(); + test_prog_stack_ops(); + test_prog_stx_w(); + test_prog_alu32_truncation(); + test_prog_jmp32(); + test_prog_ld_dw_imm(); + test_prog_alu_ops(); + test_prog_alu_xor_or_and(); + test_prog_alu_shift(); + test_prog_neg_mod(); + test_prog_jmp_variants(); + test_prog_multijmp(); + test_prog_call_ktime(); + test_prog_call_get_pid_tgid(); + test_prog_call_get_uid_gid(); + test_prog_call_get_smp_id(); + test_prog_call_prandom(); + test_prog_call_map_lookup(); + test_map_obj_close(); + test_prog_obj_close(); + test_obj_close_invalid(); + test_map_stress_array(); + test_map_stress_hash(); + test_map_update_flags(); + test_map_fd_reuse(); + test_map_hash_get_next_key(); + + SUMMARY(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/CMakeLists.txt new file mode 100644 index 0000000000..71310a8362 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ebpf-attach C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +add_executable(test-ebpf-attach src/main.c) +target_compile_options(test-ebpf-attach PRIVATE -Wall -Wextra -Werror) +install(TARGETS test-ebpf-attach RUNTIME DESTINATION usr/bin/starry-test-suit) diff --git a/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/src/main.c new file mode 100644 index 0000000000..bc4ddb7104 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/syscall/test-ebpf-attach/c/src/main.c @@ -0,0 +1,536 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while(0) + +#define MODULE_START(name) \ + printf("\n--- MODULE: %s ---\n", name) + +#define SUMMARY() \ + printf("\n=== SUMMARY: %d passed, %d failed ===\n", __pass, __fail); \ + return __fail > 0 ? 1 : 0 + +static long raw_bpf(uint64_t cmd, void *attr, uint32_t size) { +#ifdef SYS_bpf + return syscall(SYS_bpf, cmd, attr, size); +#else + errno = ENOSYS; + return -1; +#endif +} + +static long raw_perf_event_open(void *attr, int32_t pid, int32_t cpu, + int32_t group_fd, uint64_t flags) { +#ifdef SYS_perf_event_open + return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags); +#else + errno = ENOSYS; + return -1; +#endif +} + +struct bpf_map_create_attr { + uint32_t map_type; + uint32_t key_size; + uint32_t value_size; + uint32_t max_entries; + uint32_t map_flags; +}; + +struct bpf_map_elem_attr { + uint64_t map_fd; + uint64_t key; + uint64_t value; + uint64_t flags; +}; + +struct bpf_prog_load_attr { + uint32_t prog_type; + uint32_t insn_cnt; + uint64_t insns; + uint64_t license; + uint32_t log_level; + uint32_t log_size; + uint64_t log_buf; + uint32_t kern_version; + uint32_t prog_flags; +}; + +struct bpf_insn { + uint8_t code; + uint8_t dst_src_reg; + int16_t off; + int32_t imm; +}; + +struct perf_event_attr { + uint32_t type; + uint32_t size; + uint64_t config; + uint64_t sample_period; + uint64_t sample_type; + uint64_t read_format; + uint64_t flags; +}; + +#define BPF_MAP_TYPE_ARRAY 2 + +#define BPF_PROG_TYPE_KPROBE 2 + +#define BPF_MAP_CREATE 0 +#define BPF_PROG_LOAD 5 +#define BPF_PROG_ATTACH 8 +#define BPF_PROG_DETACH 9 +#define BPF_OBJ_CLOSE 11 +#define BPF_LINK_CREATE 28 + +#define BPF_ANY 0 + +#define BPF_ALU64 0x07 +#define BPF_JMP 0x05 +#define BPF_MOV 0xb0 +#define BPF_K 0x00 +#define BPF_EXIT 0x90 +#define BPF_CALL 0x80 + +#define PERF_TYPE_SOFTWARE 1 +#define PERF_TYPE_TRACEPOINT 2 +#define PERF_TYPE_KPROBE 6 + +#define PERF_COUNT_SW_CPU_CLOCK 0 + +static struct bpf_insn make_insn(uint8_t code, uint8_t dst, uint8_t src, int16_t off, int32_t imm) { + struct bpf_insn i; + i.code = code; + i.dst_src_reg = (dst & 0xf) | ((src & 0xf) << 4); + i.off = off; + i.imm = imm; + return i; +} + +static long load_simple_prog(void) { + struct bpf_insn prog[] = { + make_insn(BPF_ALU64 | BPF_MOV | BPF_K, 0, 0, 0, 0), + make_insn(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), + }; + struct bpf_prog_load_attr attr = { + .prog_type = BPF_PROG_TYPE_KPROBE, + .insn_cnt = sizeof(prog) / sizeof(prog[0]), + .insns = (uint64_t)prog, + .license = 0, + .log_level = 0, + .log_size = 0, + .log_buf = 0, + .kern_version = 0, + .prog_flags = 0, + }; + return raw_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); +} + +static long open_perf_event_software(void) { + struct perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_SOFTWARE; + attr.size = sizeof(attr); + attr.config = PERF_COUNT_SW_CPU_CLOCK; + return raw_perf_event_open(&attr, -1, 0, -1, 0); +} + +static long open_perf_event_kprobe(void) { + struct perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_KPROBE; + attr.size = sizeof(attr); + attr.config = 0; + return raw_perf_event_open(&attr, -1, 0, -1, 0); +} + +static void test_perf_event_open_software(void) { + MODULE_START("perf_event_open_software"); + long fd = open_perf_event_software(); + CHECK(fd >= 0, "perf_event_open(SOFTWARE, CPU_CLOCK) returns valid fd"); + if (fd >= 0) close(fd); +} + +static void test_perf_event_open_kprobe(void) { + MODULE_START("perf_event_open_kprobe"); + long fd = open_perf_event_kprobe(); + CHECK(fd >= 0, "perf_event_open(KPROBE) returns valid fd"); + if (fd >= 0) close(fd); +} + +static void test_perf_event_open_null_attr(void) { + MODULE_START("perf_event_open_null_attr"); + long fd = raw_perf_event_open(NULL, -1, 0, -1, 0); + CHECK(fd < 0, "perf_event_open(NULL attr) returns error"); +} + +static void test_perf_event_open_invalid_type(void) { + MODULE_START("perf_event_open_invalid_type"); + struct perf_event_attr attr; + memset(&attr, 0, sizeof(attr)); + attr.type = 99; + attr.size = sizeof(attr); + attr.config = 0; + long fd = raw_perf_event_open(&attr, -1, 0, -1, 0); + CHECK(fd < 0, "perf_event_open(type=99) returns error"); +} + +static void test_prog_attach_basic(void) { + MODULE_START("prog_attach_basic"); + + long prog_fd = load_simple_prog(); + CHECK(prog_fd >= 0, "load BPF program for attach test"); + if (prog_fd < 0) return; + + long perf_fd = open_perf_event_software(); + CHECK(perf_fd >= 0, "open perf event for attach test"); + if (perf_fd < 0) { close(prog_fd); return; } + + struct { + uint32_t target_fd; + uint32_t attach_bpf_fd; + uint32_t attach_type; + uint32_t flags; + } attach_attr = { + .target_fd = (uint32_t)perf_fd, + .attach_bpf_fd = (uint32_t)prog_fd, + .attach_type = 0, + .flags = 0, + }; + long r = raw_bpf(BPF_PROG_ATTACH, &attach_attr, sizeof(attach_attr)); + CHECK(r == 0, "BPF_PROG_ATTACH succeeds"); + + struct { + uint32_t target_fd; + uint32_t attach_bpf_fd; + uint32_t attach_type; + uint32_t flags; + } detach_attr = { + .target_fd = (uint32_t)perf_fd, + .attach_bpf_fd = (uint32_t)prog_fd, + .attach_type = 0, + .flags = 0, + }; + r = raw_bpf(BPF_PROG_DETACH, &detach_attr, sizeof(detach_attr)); + CHECK(r == 0, "BPF_PROG_DETACH succeeds"); + + close(perf_fd); + close(prog_fd); +} + +static void test_prog_attach_invalid_fd(void) { + MODULE_START("prog_attach_invalid_fd"); + + struct { + uint32_t target_fd; + uint32_t attach_bpf_fd; + uint32_t attach_type; + uint32_t flags; + } attr = { + .target_fd = 9999, + .attach_bpf_fd = 9998, + .attach_type = 0, + .flags = 0, + }; + long r = raw_bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)); + CHECK(r < 0, "BPF_PROG_ATTACH with invalid fds returns error"); +} + +static void test_link_create_basic(void) { + MODULE_START("link_create_basic"); + + long prog_fd = load_simple_prog(); + CHECK(prog_fd >= 0, "load BPF program for link_create test"); + if (prog_fd < 0) return; + + long perf_fd = open_perf_event_kprobe(); + CHECK(perf_fd >= 0, "open perf event (kprobe) for link_create test"); + if (perf_fd < 0) { close(prog_fd); return; } + + struct { + uint32_t prog_fd; + uint32_t target_fd; + uint32_t attach_type; + uint32_t flags; + } link_attr = { + .prog_fd = (uint32_t)prog_fd, + .target_fd = (uint32_t)perf_fd, + .attach_type = 0, + .flags = 0, + }; + long link_fd = raw_bpf(BPF_LINK_CREATE, &link_attr, sizeof(link_attr)); + CHECK(link_fd >= 0, "BPF_LINK_CREATE returns valid link fd"); + CHECK(link_fd != prog_fd, "link fd differs from prog fd"); + CHECK(link_fd != perf_fd, "link fd differs from perf fd"); + + if (link_fd >= 0) { + uint32_t close_fd = (uint32_t)link_fd; + raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + } + + close(perf_fd); + close(prog_fd); +} + +static void test_link_create_invalid_fd(void) { + MODULE_START("link_create_invalid_fd"); + + struct { + uint32_t prog_fd; + uint32_t target_fd; + uint32_t attach_type; + uint32_t flags; + } link_attr = { + .prog_fd = 9999, + .target_fd = 9998, + .attach_type = 0, + .flags = 0, + }; + long r = raw_bpf(BPF_LINK_CREATE, &link_attr, sizeof(link_attr)); + CHECK(r < 0, "BPF_LINK_CREATE with invalid fds returns error"); +} + +static void test_obj_close_map(void) { + MODULE_START("obj_close_map"); + + struct bpf_map_create_attr attr = { + .map_type = BPF_MAP_TYPE_ARRAY, + .key_size = 4, + .value_size = 8, + .max_entries = 4, + .map_flags = 0, + }; + long fd = raw_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + CHECK(fd >= 0, "create array map for obj_close test"); + if (fd < 0) return; + + uint32_t close_fd = (uint32_t)fd; + long r = raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + CHECK(r == 0, "BPF_OBJ_CLOSE map fd succeeds"); + + struct bpf_map_elem_attr lookup = { + .map_fd = (uint64_t)fd, + .key = (uint64_t)&(uint32_t){0}, + .value = (uint64_t)&(uint64_t){0}, + .flags = 0, + }; + r = raw_bpf(1, &lookup, sizeof(lookup)); + CHECK(r < 0, "lookup on closed map fd fails"); +} + +static void test_obj_close_prog(void) { + MODULE_START("obj_close_prog"); + + long fd = load_simple_prog(); + CHECK(fd >= 0, "load prog for obj_close test"); + if (fd < 0) return; + + uint32_t close_fd = (uint32_t)fd; + long r = raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + CHECK(r == 0, "BPF_OBJ_CLOSE prog fd succeeds"); +} + +static void test_obj_close_invalid(void) { + MODULE_START("obj_close_invalid"); + + uint32_t bad_fd = 9999; + long r = raw_bpf(BPF_OBJ_CLOSE, &bad_fd, sizeof(bad_fd)); + CHECK(r < 0, "BPF_OBJ_CLOSE with non-existent fd returns error"); + + uint32_t zero_fd = 0; + r = raw_bpf(BPF_OBJ_CLOSE, &zero_fd, sizeof(zero_fd)); + CHECK(r < 0, "BPF_OBJ_CLOSE with fd=0 returns error"); +} + +static void test_full_lifecycle(void) { + MODULE_START("full_lifecycle"); + + long prog_fd = load_simple_prog(); + CHECK(prog_fd >= 0, "step1: load BPF program"); + if (prog_fd < 0) return; + + long perf_fd = open_perf_event_software(); + CHECK(perf_fd >= 0, "step2: open perf event"); + if (perf_fd < 0) { close(prog_fd); return; } + + struct { + uint32_t prog_fd; + uint32_t target_fd; + uint32_t attach_type; + uint32_t flags; + } link_attr = { + .prog_fd = (uint32_t)prog_fd, + .target_fd = (uint32_t)perf_fd, + .attach_type = 0, + .flags = 0, + }; + long link_fd = raw_bpf(BPF_LINK_CREATE, &link_attr, sizeof(link_attr)); + CHECK(link_fd >= 0, "step3: BPF_LINK_CREATE"); + if (link_fd < 0) { close(perf_fd); close(prog_fd); return; } + + uint32_t close_link = (uint32_t)link_fd; + long r = raw_bpf(BPF_OBJ_CLOSE, &close_link, sizeof(close_link)); + CHECK(r == 0, "step4: close link fd"); + + uint32_t close_prog = (uint32_t)prog_fd; + r = raw_bpf(BPF_OBJ_CLOSE, &close_prog, sizeof(close_prog)); + CHECK(r == 0, "step5: close prog fd"); + + close(perf_fd); +} + +static void test_multiple_prog_load_close(void) { + MODULE_START("multiple_prog_load_close"); + + long fds[8]; + int loaded = 0; + for (int i = 0; i < 8; i++) { + fds[i] = load_simple_prog(); + if (fds[i] < 0) break; + loaded++; + } + CHECK(loaded == 8, "load 8 BPF programs"); + for (int i = 0; i < loaded; i++) { + CHECK(fds[i] > 0, "each prog gets valid fd"); + } + for (int i = 0; i < loaded; i++) { + uint32_t close_fd = (uint32_t)fds[i]; + raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + } + + long fd_after = load_simple_prog(); + CHECK(fd_after >= 0, "load prog after closing all previous"); + if (fd_after >= 0) { + uint32_t close_fd = (uint32_t)fd_after; + raw_bpf(BPF_OBJ_CLOSE, &close_fd, sizeof(close_fd)); + } +} + +static void test_multiple_perf_events(void) { + MODULE_START("multiple_perf_events"); + + long fds[4]; + int opened = 0; + for (int i = 0; i < 4; i++) { + fds[i] = open_perf_event_software(); + if (fds[i] < 0) break; + opened++; + } + CHECK(opened == 4, "open 4 perf events"); + for (int i = 0; i < opened; i++) { + CHECK(fds[i] >= 100, "perf event fd >= 100"); + } + for (int i = 0; i < opened; i++) { + close(fds[i]); + } +} + +static void test_bpf_small_size(void) { + MODULE_START("bpf_small_size"); + + struct { + uint32_t prog_fd; + uint32_t target_fd; + } small = { .prog_fd = 0, .target_fd = 0 }; + long r = raw_bpf(BPF_LINK_CREATE, &small, sizeof(small)); + CHECK(r < 0, "BPF_LINK_CREATE with size < 20 returns error"); + + uint32_t val = 0; + r = raw_bpf(BPF_OBJ_CLOSE, &val, 0); + CHECK(r < 0, "BPF_OBJ_CLOSE with size=0 returns error"); + + struct { + uint32_t target_fd; + uint32_t attach_bpf_fd; + } small_attach = { .target_fd = 0, .attach_bpf_fd = 0 }; + r = raw_bpf(BPF_PROG_ATTACH, &small_attach, sizeof(small_attach)); + CHECK(r < 0, "BPF_PROG_ATTACH with size < 16 returns error"); +} + +static void test_prog_attach_with_kprobe_perf(void) { + MODULE_START("prog_attach_with_kprobe_perf"); + + long prog_fd = load_simple_prog(); + CHECK(prog_fd >= 0, "load BPF program"); + if (prog_fd < 0) return; + + long perf_fd = open_perf_event_kprobe(); + CHECK(perf_fd >= 0, "open kprobe perf event"); + if (perf_fd < 0) { close(prog_fd); return; } + + struct { + uint32_t target_fd; + uint32_t attach_bpf_fd; + uint32_t attach_type; + uint32_t flags; + } attach_attr = { + .target_fd = (uint32_t)perf_fd, + .attach_bpf_fd = (uint32_t)prog_fd, + .attach_type = 0, + .flags = 0, + }; + long r = raw_bpf(BPF_PROG_ATTACH, &attach_attr, sizeof(attach_attr)); + CHECK(r == 0, "BPF_PROG_ATTACH to kprobe perf event succeeds"); + + struct { + uint32_t target_fd; + uint32_t attach_bpf_fd; + uint32_t attach_type; + uint32_t flags; + } detach_attr = { + .target_fd = (uint32_t)perf_fd, + .attach_bpf_fd = (uint32_t)prog_fd, + .attach_type = 0, + .flags = 0, + }; + r = raw_bpf(BPF_PROG_DETACH, &detach_attr, sizeof(detach_attr)); + CHECK(r == 0, "BPF_PROG_DETACH from kprobe perf event succeeds"); + + close(perf_fd); + close(prog_fd); +} + +int main(void) { + printf("=== eBPF Attach / perf_event Test Suite ===\n"); + + test_perf_event_open_software(); + test_perf_event_open_kprobe(); + test_perf_event_open_null_attr(); + test_perf_event_open_invalid_type(); + test_prog_attach_basic(); + test_prog_attach_invalid_fd(); + test_link_create_basic(); + test_link_create_invalid_fd(); + test_obj_close_map(); + test_obj_close_prog(); + test_obj_close_invalid(); + test_full_lifecycle(); + test_multiple_prog_load_close(); + test_multiple_perf_events(); + test_bpf_small_size(); + test_prog_attach_with_kprobe_perf(); + + SUMMARY(); +} From 40f9369d658c02e24d895cc33ba06250523fe4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 17:15:29 +0800 Subject: [PATCH 61/71] refactor(axruntime): remove alloc feature, make it unconditional (#985) alloc is needed by every consumer of axruntime. Remove the alloc feature entirely and make ax-alloc a required dependency. Propagating changes to axfeat to remove the now-unnecessary alloc forwarding from 15 features. Co-authored-by: Claude Opus 4.7 --- os/arceos/api/axfeat/Cargo.toml | 27 +++++++++++--------------- os/arceos/modules/axruntime/Cargo.toml | 10 ++++------ os/arceos/modules/axruntime/src/lib.rs | 14 +------------ 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index d8582ceb4d..7712402f30 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true default = ["alloc"] # Multicore -smp = ["alloc", "ax-hal/smp", "ax-runtime/smp", "ax-task?/smp", "ax-kspin/smp"] +smp = ["ax-hal/smp", "ax-runtime/smp", "ax-task?/smp", "ax-kspin/smp"] # Floating point/SIMD fp-simd = ["ax-hal/fp-simd"] @@ -21,7 +21,7 @@ uspace = ["ax-hal/uspace", "ax-task?/uspace"] hv = ["ax-hal/hv"] # Interrupts -irq = ["alloc", "ax-hal/irq", "ax-runtime/irq", "ax-task?/irq", "ax-driver?/irq"] +irq = ["ax-hal/irq", "ax-runtime/irq", "ax-task?/irq", "ax-driver?/irq"] ipi = ["irq", "dep:ax-ipi", "ax-hal/ipi", "ax-runtime/ipi", "ax-task?/ipi"] # Custom or default platforms @@ -44,20 +44,19 @@ aarch64-gic-v3 = ["ax-hal/aarch64-gic-v3"] aarch64-cntv-timer = ["ax-hal/aarch64-cntv-timer"] # Memory -alloc = ["ax-alloc", "ax-runtime/alloc"] +alloc = ["ax-alloc"] alloc-tlsf = ["ax-alloc/tlsf"] alloc-slab = ["ax-alloc/slab"] alloc-buddy = ["ax-alloc/buddy"] -buddy-slab = ["alloc", "ax-runtime/buddy-slab"] +buddy-slab = ["ax-runtime/buddy-slab"] page-alloc-64g = ["ax-alloc/page-alloc-64g"] # up to 64G memory capacity page-alloc-4g = ["ax-alloc/page-alloc-4g"] # up to 4G memory capacity -paging = ["alloc", "ax-hal/paging", "ax-runtime/paging"] -tls = ["alloc", "ax-hal/tls", "ax-runtime/tls", "ax-task?/tls"] -dma = ["alloc", "paging"] +paging = ["ax-hal/paging", "ax-runtime/paging"] +tls = ["ax-hal/tls", "ax-runtime/tls", "ax-task?/tls"] +dma = ["paging"] # Multi-threading and scheduler multitask = [ - "alloc", "ax-task/multitask", "ax-sync/multitask", "ax-runtime/multitask", @@ -77,31 +76,28 @@ stack-guard-page = [ # File system fs = [ - "alloc", "paging", "dep:ax-fs", "ax-runtime/fs", ] # TODO: try to remove "paging" -fs-ng = ["alloc", "paging", "dep:ax-fs-ng", "ax-runtime/fs-ng"] +fs-ng = ["paging", "dep:ax-fs-ng", "ax-runtime/fs-ng"] fs-ng-ext4 = ["fs-ng", "ax-fs-ng/ext4"] fs-ng-fat = ["fs-ng", "ax-fs-ng/fat"] fs-ng-times = ["fs-ng", "ax-fs-ng/times"] # Networking net = [ - "alloc", "paging", "irq", "multitask", "dep:ax-net", "ax-runtime/net", ] -net-ng = ["alloc", "paging", "irq", "multitask", "dep:ax-net-ng", "ax-runtime/net-ng"] +net-ng = ["paging", "irq", "multitask", "dep:ax-net-ng", "ax-runtime/net-ng"] vsock = ["ax-runtime/vsock"] # Display display = [ - "alloc", "paging", "dep:ax-display", "ax-runtime/display", @@ -109,7 +105,6 @@ display = [ # Input input = [ - "alloc", "paging", "dep:ax-input", "ax-runtime/input", @@ -119,8 +114,8 @@ input = [ rtc = ["ax-hal/rtc", "ax-runtime/rtc"] # Backtrace -backtrace = ["alloc", "axbacktrace/alloc"] -dwarf = ["alloc", "axbacktrace/dwarf"] +backtrace = ["axbacktrace/alloc"] +dwarf = ["axbacktrace/dwarf"] [dependencies] ax-alloc = { workspace = true, optional = true, features = ["default"] } diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 0a330852ef..9babc5ff1d 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -10,15 +10,14 @@ version = "0.5.16" [features] default = [] -alloc = ["dep:ax-alloc"] -buddy-slab = ["alloc", "ax-alloc/buddy-slab"] +buddy-slab = ["ax-alloc/buddy-slab"] dma = ["paging"] ipi = ["dep:ax-ipi"] irq = ["ax-hal/irq", "ax-task?/irq", "dep:ax-percpu"] multitask = ["ax-task/multitask"] -paging = ["alloc", "ax-hal/paging", "dep:ax-mm", "dep:axklib"] +paging = ["ax-hal/paging", "dep:ax-mm", "dep:axklib"] rtc = [] -smp = ["alloc", "ax-hal/smp", "ax-task?/smp"] +smp = ["ax-hal/smp", "ax-task?/smp"] stack-guard-page = ["multitask", "paging", "ax-task/stack-guard-page"] tls = ["ax-hal/tls", "ax-task?/tls"] @@ -31,7 +30,6 @@ plat-dyn = [ "smp", "stack-guard-page", "tls", - "alloc", ] display = ["dep:ax-display", "ax-driver/display"] @@ -57,7 +55,7 @@ net-ng = ["dep:ax-net-ng", "dep:rd-net", "dep:spin", "dep:axklib", "ax-driver/ne vsock = ["net-ng", "ax-net-ng/vsock", "ax-driver/vsock"] [dependencies] -ax-alloc = {workspace = true, optional = true, features = ["default"]} +ax-alloc = {workspace = true, features = ["default"]} ax-config = {workspace = true} ax-crate-interface = {workspace = true} ax-ctor-bare = {workspace = true} diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index cfe1f462c9..1e0b544099 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -19,7 +19,6 @@ //! //! # Cargo Features //! -//! - `alloc`: Enable global memory allocator. //! - `paging`: Enable page table manipulation support. //! - `irq`: Enable interrupt handling support. //! - `multitask`: Enable multi-threading support. @@ -56,15 +55,6 @@ pub use ax_hal as hal; #[cfg(feature = "smp")] pub use self::mp::rust_main_secondary; -#[cfg(any( - all(any(feature = "fs", feature = "fs-ng"), not(feature = "plat-dyn")), - all(any(feature = "fs", feature = "fs-ng"), feature = "plat-dyn"), - feature = "net", - feature = "net-ng", - feature = "display", - feature = "input", - feature = "vsock" -))] extern crate alloc; const LOGO: &str = r#" @@ -166,7 +156,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { ax_hal::mem::clear_bss() }; ax_hal::percpu::init_primary(cpu_id); - #[cfg(all(feature = "alloc", feature = "buddy-slab"))] + #[cfg(feature = "buddy-slab")] // After per-CPU init, before scheduler/IPI/IRQ paths can allocate. ax_alloc::init_percpu_slab(cpu_id); ax_hal::init_early(cpu_id, arg); @@ -214,7 +204,6 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { ); } - #[cfg(feature = "alloc")] init_allocator(); { @@ -353,7 +342,6 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { } } -#[cfg(feature = "alloc")] fn init_allocator() { use ax_hal::mem::{MemRegionFlags, memory_regions, phys_to_virt}; From 087aa815be4ef83823c76bd480f79fcaa979585b Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Wed, 27 May 2026 18:44:26 +0800 Subject: [PATCH 62/71] Remove range-alloc-arceos crate and its associated files (#991) - Deleted LICENSE, LICENSE.APACHE, LICENSE.MIT, README.md, README_CN.md, and src/lib.rs files. - Removed tests and examples related to the range allocator functionality. --- Cargo.lock | 5 - Cargo.toml | 1 - docs/docs/components/crates/axdevice.md | 8 +- docs/docs/components/crates/axvm.md | 2 +- .../components/crates/range-alloc-arceos.md | 248 --------- docs/docs/components/layers.md | 6 +- docs/docs/components/overview.md | 1 - memory/range-alloc-arceos/.github/config.json | 10 - .../.github/workflows/check.yml | 66 --- .../.github/workflows/deploy.yml | 126 ----- .../.github/workflows/push.yml | 147 ------ .../.github/workflows/release.yml | 160 ------ .../.github/workflows/test.yml | 50 -- memory/range-alloc-arceos/.gitignore | 16 - memory/range-alloc-arceos/CHANGELOG.md | 14 - memory/range-alloc-arceos/Cargo.toml | 13 - memory/range-alloc-arceos/LICENSE | 201 ------- memory/range-alloc-arceos/LICENSE.APACHE | 176 ------- memory/range-alloc-arceos/LICENSE.MIT | 21 - memory/range-alloc-arceos/README.md | 81 --- memory/range-alloc-arceos/README_CN.md | 81 --- memory/range-alloc-arceos/src/lib.rs | 491 ------------------ memory/range-alloc-arceos/tests/test.rs | 60 --- scripts/repo/repos.csv | 1 - scripts/test/std_crates.csv | 1 - virtualization/axdevice/Cargo.toml | 1 - virtualization/axdevice/src/device.rs | 20 +- virtualization/axdevice/src/lib.rs | 1 + virtualization/axdevice/src/range_alloc.rs | 145 ++++++ 29 files changed, 164 insertions(+), 1989 deletions(-) delete mode 100644 docs/docs/components/crates/range-alloc-arceos.md delete mode 100644 memory/range-alloc-arceos/.github/config.json delete mode 100644 memory/range-alloc-arceos/.github/workflows/check.yml delete mode 100644 memory/range-alloc-arceos/.github/workflows/deploy.yml delete mode 100644 memory/range-alloc-arceos/.github/workflows/push.yml delete mode 100644 memory/range-alloc-arceos/.github/workflows/release.yml delete mode 100644 memory/range-alloc-arceos/.github/workflows/test.yml delete mode 100644 memory/range-alloc-arceos/.gitignore delete mode 100644 memory/range-alloc-arceos/CHANGELOG.md delete mode 100644 memory/range-alloc-arceos/Cargo.toml delete mode 100644 memory/range-alloc-arceos/LICENSE delete mode 100644 memory/range-alloc-arceos/LICENSE.APACHE delete mode 100644 memory/range-alloc-arceos/LICENSE.MIT delete mode 100644 memory/range-alloc-arceos/README.md delete mode 100644 memory/range-alloc-arceos/README_CN.md delete mode 100644 memory/range-alloc-arceos/src/lib.rs delete mode 100644 memory/range-alloc-arceos/tests/test.rs create mode 100644 virtualization/axdevice/src/range_alloc.rs diff --git a/Cargo.lock b/Cargo.lock index a3b959805f..c82c7ac7f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1730,7 +1730,6 @@ dependencies = [ "axvmconfig", "cfg-if", "log", - "range-alloc-arceos", "riscv_vplic", ] @@ -6475,10 +6474,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" -[[package]] -name = "range-alloc-arceos" -version = "0.3.9" - [[package]] name = "ranges-ext" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index f1467fc54f..0b26287924 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -265,7 +265,6 @@ enumerate = { version = "0.1.1", path = "drivers/examples/enumerate" } eth-intel = { version = "0.1.2", path = "drivers/net/eth-intel" } loongarch_vcpu = { version = "0.5.3", path = "virtualization/loongarch_vcpu" } mingo = { version = "0.8.3", path = "os/arceos/tools/raspi4/chainloader" } -range-alloc-arceos = { version = "0.3.9", path = "memory/range-alloc-arceos" } riscv-h = { version = "0.4.7", path = "virtualization/riscv-h" } riscv_vcpu = { version = "0.5.9", path = "virtualization/riscv_vcpu" } riscv_vplic = { version = "0.4.12", path = "virtualization/riscv_vplic" } diff --git a/docs/docs/components/crates/axdevice.md b/docs/docs/components/crates/axdevice.md index 952d2d431f..272549dc15 100644 --- a/docs/docs/components/crates/axdevice.md +++ b/docs/docs/components/crates/axdevice.md @@ -65,7 +65,7 @@ - `emu_mmio_devices` - `emu_sys_reg_devices` - `emu_port_devices` -- `ivc_channel: Option>>` +- `ivc_channel: Option>` 前三者决定地址访问分发,最后一个则是 Inter-VM Communication 区间分配器,用于在配置的 IVC GPA 范围内按 4K 对齐切片。 @@ -79,7 +79,7 @@ flowchart TD B --> C[遍历 EmulatedDeviceConfig] C --> D{按 EmulatedDeviceType 匹配} D --> E[实例化具体设备对象] - D --> F[初始化 IVC RangeAllocator] + D --> F[初始化内部 IVC RangeAllocator] D --> G[不支持类型发出 warn] E --> H[加入 MMIO/sysreg/port 容器] ``` @@ -194,7 +194,7 @@ let mut devices = AxVmDevices::new(config); `IVCChannel` 在本 crate 中不是一个 MMIO 设备对象,而是一个 GPA 区间分配器: -- 初始化时只建立 `RangeAllocator`。 +- 初始化时只建立 `axdevice` 内部的 IVC `RangeAllocator`。 - 分配和释放必须满足 4K 对齐。 - 重复声明多个 IVCChannel 配置时,仅首次生效,后续配置会被忽略并打印告警。 @@ -209,7 +209,7 @@ let mut devices = AxVmDevices::new(config); | `axdevice_base` | 统一设备 trait 定义 | | `axvmconfig` | 设备配置和设备类型枚举 | | `axaddrspace` | GPA、端口、系统寄存器地址类型与访问宽度 | -| `range-alloc-arceos` | IVC 区间分配 | +| 内部 `range_alloc` 模块 | IVC 区间分配 | | `memory_addr` | 4K 对齐检查等辅助能力 | | `ax-errno` | 统一错误类型 | | `spin` | IVC 分配器锁 | diff --git a/docs/docs/components/crates/axvm.md b/docs/docs/components/crates/axvm.md index 55a62c2a51..7debb9ff99 100644 --- a/docs/docs/components/crates/axvm.md +++ b/docs/docs/components/crates/axvm.md @@ -137,7 +137,7 @@ graph LR ### 间接依赖 - `ax-page-table-multiarch`、`ax-page-table-entry`:通过地址空间和页表路径参与 VM 内存管理。 -- `ax-memory-set`、`range-alloc-arceos` 等:在地址空间和内存建模路径上间接提供支撑。 +- `ax-memory-set` 等:在地址空间和内存建模路径上间接提供支撑。 - `axvisor_api` 生态:更多出现在消费者侧,但会影响 `axvm` 的宿主接入方式。 ### 3.3 关键直接消费者 diff --git a/docs/docs/components/crates/range-alloc-arceos.md b/docs/docs/components/crates/range-alloc-arceos.md deleted file mode 100644 index 7152decedf..0000000000 --- a/docs/docs/components/crates/range-alloc-arceos.md +++ /dev/null @@ -1,248 +0,0 @@ -# `range-alloc-arceos` - -> 路径:`memory/range-alloc-arceos` -> 类型:库 crate -> 分层:组件层 / 区间分配算法组件 -> 版本:`0.1.4` -> 文档依据:当前仓库源码、`Cargo.toml`、`README.md`、`src/lib.rs`、`tests/test.rs`、`virtualization/axdevice/src/device.rs`、`virtualization/axvm/src/vm.rs`、`os/axvisor/src/vmm/hvc.rs` - -`range-alloc-arceos` 的真实定位是一个**泛型连续区间分配器**。它管理的是一个初始范围上的“哪些子区间空闲、哪些子区间已占用”,并提供 best-fit 分配与回收合并逻辑。它不是页分配器,不处理地址映射,不理解设备,不负责对齐策略,更不是完整的内存管理子系统。 - -## 架构设计 - -### 设计定位 - -这个 crate 的问题模型非常明确: - -- 有一个初始区间 `start..end` -- 需要不断从中分配连续子区间 -- 需要在释放时把相邻空闲区间重新合并 -- 希望尽量降低碎片化 - -因此它选择的核心数据结构不是树或位图,而是: - -- 一个有序的 `Vec> free_ranges` - -并把已分配区间看作“初始区间减去空闲区间”的补集。 - -### 1.2 核心数据结构 - -`RangeAllocator` 内部只有两项状态: - -| 字段 | 作用 | -| --- | --- | -| `initial_range` | 整个分配器负责管理的总范围 | -| `free_ranges` | 当前空闲区间表,按起始地址升序排列且互不重叠 | - -这两个不变量非常关键: - -- `free_ranges` 必须有序 -- `free_ranges` 之间不能重叠 - -几乎所有接口都围绕维护这两个不变量展开。 - -### 1.3 分配策略:best-fit - -`allocate_range(length)` 的实现会遍历所有空闲区间,并选择: - -- 能满足请求的最小空闲区间 - -这是一种典型的 best-fit 策略。它的好处是: - -- 优先消耗最贴合请求的空闲块 -- 尽量保留大块连续空间 - -若找不到足够大的单个区间,则返回: - -- `RangeAllocationError { fragmented_free_length }` - -其中 `fragmented_free_length` 表示把所有空闲区间长度加起来后,总共还有多少空闲量。这个字段能帮助调用者区分: - -- 是真的没空间了 -- 还是只是碎片太多,凑不出连续区间 - -### 1.4 回收策略:邻接合并 - -`free_range(range)` 的逻辑重点不在简单插入,而在合并: - -- 如果与左邻接,则与左合并 -- 如果与右邻接,则与右合并 -- 如果左右都邻接,则把三段合成一段 -- 否则在有序位置插入 - -源码中还有断言确保: - -- 释放区间必须位于 `initial_range` 内 -- 区间必须非空 -- 插入后不会与现有空闲区间重叠 - -这意味着: - -- 双重释放或越界释放不是被“静默忽略”,而是会触发断言 - -### 1.5 扩容与观察接口 - -除了分配/回收,这个 crate 还提供了几个非常实用的辅助接口: - -- `grow_to(new_end)`:扩展管理上界 -- `allocated_ranges()`:通过空闲表反推出当前已分配区间 -- `reset()`:恢复到初始全空闲状态 -- `is_empty()`:检查是否尚未分配任何区间 -- `total_available()`:统计所有空闲区间的总长度 - -尤其是 `allocated_ranges()`,说明这个分配器不仅能“给空间”,也适合做状态观察和调试。 - -### 1.6 与 Axvisor 当前实现的真实关系 - -当前仓库里的真实调用链非常清晰: - -1. `axdevice::AxVmDevices` 在 `IVCChannel` 设备配置初始化时,创建 `RangeAllocator` -2. 这个区间的范围来自 IVC 共享内存的 guest physical address 空间 -3. `axvm::VM::alloc_ivc_channel()` 先把请求大小向上对齐到 4K -4. 然后调用 `devices.alloc_ivc_channel()`,最终落到 `RangeAllocator::allocate_range()` -5. `os/axvisor/src/vmm/hvc.rs` 在处理 IVC hypercall 时使用这套分配/回收能力 - -这条链路很重要,因为它清楚地说明了: - -- 对齐是 `axvm` 做的,不是本 crate 做的 -- 地址映射是 `axdevice` / `axvm` / `axvisor` 做的,不是本 crate 做的 -- 本 crate 只是区间分配算法核心 - -## 核心功能 - -### 功能概览 - -- 用初始区间构造分配器 -- 按 best-fit 分配连续区间 -- 回收区间并自动合并相邻空闲块 -- 动态扩展管理范围上界 -- 枚举已分配区间 -- 查询空闲总量与是否为空 - -### 2.2 当前仓库中的典型调用链 - -真实调用链可以概括为: - -`range-alloc-arceos` -> `axdevice::AxVmDevices` -> `axvm::VM::{alloc_ivc_channel, release_ivc_channel}` -> `os/axvisor::vmm::hvc` - -也就是说,它当前主要服务于: - -- Axvisor 的 IVC 共享内存 GPA 区间管理 - -### 边界说明 - -`range-alloc-arceos` 不负责: - -- 地址对齐 -- 物理页分配 -- 页表映射 -- 内存清零 -- 共享内存元数据管理 - -它只是一个**泛型区间分配算法组件**。 - -## 依赖关系 - -### 直接依赖 - -该 crate 没有额外三方依赖,主要依靠: - -- `alloc::vec::Vec` -- `core::ops::Range` - -完成实现。 - -### 主要消费者 - -当前仓库内可确认的直接消费者: - -- `axdevice` - -明确可见的间接链路: - -- `range-alloc-arceos` -> `axdevice` -> `axvm` -> `os/axvisor` - -### 3.3 关系解读 - -| 层级 | 角色 | -| --- | --- | -| `range-alloc-arceos` | 连续区间分配算法 | -| `axdevice` | 为 VM 设备层提供 IVC GPA 区间池 | -| `axvm` | 负责对齐请求并包装返回值 | -| `os/axvisor` | 在 HyperCall 处理中真正消费分配结果 | - -## 开发指南 - -### 4.1 适合什么时候使用 - -适合使用该 crate 的场景是: - -- 资源天然可以表达为一个线性区间 -- 分配单位是“连续范围”而不是单点 -- 希望能观察碎片化情况 -- 对齐策略可以由上层单独处理 - -不适合直接把它当成: - -- 伙伴系统 -- 物理页分配器 -- 完整地址空间管理器 - -### 4.2 维护时的关键注意事项 - -- `free_ranges` 的有序、不重叠不变量必须始终保持 -- `free_range()` 的断言意味着重叠回收会直接失败,不是容错逻辑 -- 若要新增“带对齐分配”接口,应明确这是否属于本 crate 的职责扩张 -- `T` 的 trait bound 是经过精简设计的,不要轻易扩大或改变数值语义 - -### 4.3 与上层系统的职责分工 - -- 大小 4K 对齐:`axvm` -- GPA 空间来源:`axdevice` -- IVC 映射与共享内存语义:`axvisor` -- 连续区间 best-fit 分配/合并:本 crate - -这条分工线非常清楚,文档里不能把它写成“IVC 管理模块”。 - -## 测试 - -### 测试覆盖 - -当前测试已经相对充分: - -- `src/lib.rs` 中有较多单元测试 -- `tests/test.rs` 还有额外集成测试 - -现有测试覆盖了: - -- 基本分配/释放 -- 空间耗尽 -- `grow_to()` -- 中间空洞 -- best-fit 选择 -- 邻接合并 -- 碎片化行为 - -### 5.2 建议补充的测试 - -- double free / 重叠 free 的 `should_panic` 测试 -- 与 `axdevice` / `axvm` 结合的 4K 对齐集成测试 -- 更大整数类型或边界类型参数测试 - -### 5.3 风险点 - -- 如果文档忽略“对齐由上层完成”,调用者很容易误用 -- `free_range()` 的断言语义需要明确,否则回收错误会在运行时直接崩溃 -- best-fit 不是唯一策略,今后若替换策略需要同步评估上层碎片行为 - -## 跨项目定位 - -| 项目 | 位置 | 角色 | 说明 | -| --- | --- | --- | --- | -| ArceOS | 当前仓库未见直接接线 | 通用区间算法组件 | 目前不在 ArceOS 主线路径上 | -| StarryOS | 当前仓库未见直接接线 | 通用区间算法组件 | 尚未看到直接使用 | -| Axvisor | `axdevice` / `axvm` / `hvc` IVC 链路 | IVC GPA 连续区间分配算法 | 当前最明确、最真实的使用场景 | - -## 总结 - -`range-alloc-arceos` 的价值在于把“连续区间 best-fit 分配 + 合并回收”这件事做成了一个独立、泛型、`no_std` 友好的算法组件。当前仓库里它主要服务于 Axvisor 的 IVC GPA 区间管理,但它本身并不理解 IVC、设备或内存映射。理解它时最重要的边界,就是把它看成**区间分配算法**,而不是更高层的资源子系统。 diff --git a/docs/docs/components/layers.md b/docs/docs/components/layers.md index c781aa0ccc..c71560dab7 100644 --- a/docs/docs/components/layers.md +++ b/docs/docs/components/layers.md @@ -110,7 +110,6 @@ flowchart TB | 0 | 基础层(无仓库内直接依赖) | 组件层 | `axpoll` | `0.3.2` | `components/axpoll` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `axvisor_api_proc` | `0.5.0` | `virtualization/axvisor_api_proc` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `bitmap-allocator` | `0.4.1` | `memory/bitmap-allocator` | -| 0 | 基础层(无仓库内直接依赖) | 组件层 | `range-alloc-arceos` | `0.3.4` | `memory/range-alloc-arceos` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `riscv-h` | `0.4.0` | `virtualization/riscv-h` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `rsext4` | `0.3.0` | `components/rsext4` | | 0 | 基础层(无仓库内直接依赖) | 组件层 | `smoltcp` | `0.14.0` | `components/starry-smoltcp` | @@ -225,7 +224,7 @@ flowchart TB | 层级 | 数 | 成员 | |------|-----|------| -| 0 | 29 | `aarch64_sysreg` `ax-arm-pl031` `ax-cap-access` `ax-config-gen` `ax-cpumask` `ax-crate-interface` `ax-crate-interface-lite` `ax-ctor-bare-macros` `ax-errno` `ax-handler-table` `ax-int-ratio` `ax-lazyinit` `ax-linked-list-r4l` `ax-memory-addr` `ax-percpu-macros` `ax-riscv-plic` `ax-timer-list` `axbacktrace` `axpoll` `axvisor_api_proc` `bitmap-allocator` `bwbench-client` `deptool` `mingo` `range-alloc-arceos` `riscv-h` `rsext4` `smoltcp` | +| 0 | 28 | `aarch64_sysreg` `ax-arm-pl031` `ax-cap-access` `ax-config-gen` `ax-cpumask` `ax-crate-interface` `ax-crate-interface-lite` `ax-ctor-bare-macros` `ax-errno` `ax-handler-table` `ax-int-ratio` `ax-lazyinit` `ax-linked-list-r4l` `ax-memory-addr` `ax-percpu-macros` `ax-riscv-plic` `ax-timer-list` `axbacktrace` `axpoll` `axvisor_api_proc` `bitmap-allocator` `bwbench-client` `deptool` `mingo` `riscv-h` `rsext4` `smoltcp` | | 1 | 19 | `ax-allocator` `ax-config-macros` `ax-ctor-bare` `ax-fs-vfs` `ax-io` `ax-kernel-guard` `ax-memory-set` `ax-page-table-entry` `ax-plat-macros` `ax-sched` `axfs-ng-vfs` `axhvc` `axklib` `axvmconfig` `define-simple-traits` `define-weak-traits` `fxmac_rs` `smoltcp-fuzz` `starry-vm` | | 2 | 10 | `ax-config` `ax-fs-devfs` `ax-fs-ramfs` `ax-kspin` `ax-page-table-multiarch` `ax-percpu` `axbuild` `impl-simple-traits` `impl-weak-partial` `impl-weak-traits` | | 3 | 11 | `ax-alloc` `ax-cpu` `ax-log` `ax-plat` `axaddrspace` `scope-local` `starry-process` `test-simple` `test-weak` `test-weak-partial` `tg-xtask` | @@ -341,7 +340,7 @@ flowchart TB | `axaddrspace` | 3 | ArceOS-Hypervisor guest address space management … | `ax-errno` `ax-lazyinit` `ax-memory-addr` `ax-memory-set` `ax-page-table-entry` `ax-page-table-multiarch` | `arm_vcpu` `arm_vgic` `axdevice` `axdevice_base` `axvcpu` `axvisor` `axvisor_api` `axvm` `riscv_vcpu` `riscv_vplic` `x86_vcpu` `x86_vlapic` | | `axbacktrace` | 0 | Backtrace for ArceOS | — | `ax-alloc` `ax-cpu` `ax-feat` `ax-runtime` `starry-kernel` | | `axbuild` | 2 | An OS build lib toolkit used by arceos | `axvmconfig` | `axvisor` `starryos` `tg-xtask` | -| `axdevice` | 6 | A reusable, OS-agnostic device abstraction layer … | `arm_vgic` `ax-errno` `ax-memory-addr` `axaddrspace` `axdevice_base` `axvmconfig` `range-alloc-arceos` `riscv_vplic` | `axvisor` `axvm` | +| `axdevice` | 6 | A reusable, OS-agnostic device abstraction layer … | `arm_vgic` `ax-errno` `ax-memory-addr` `axaddrspace` `axdevice_base` `axvmconfig` `riscv_vplic` | `axvisor` `axvm` | | `axdevice_base` | 4 | Basic traits and structures for emulated devices … | `ax-errno` `axaddrspace` `axvmconfig` | `arm_vcpu` `arm_vgic` `axdevice` `axvisor` `axvm` `riscv_vplic` `x86_vcpu` `x86_vlapic` | | `axfs-ng-vfs` | 1 | Virtual filesystem layer for ArceOS | `ax-errno` `axpoll` | `ax-fs-ng` `ax-net-ng` `starry-kernel` | | `axhvc` | 1 | AxVisor HyperCall definitions for guest-hyperviso… | `ax-errno` | `axvisor` | @@ -367,7 +366,6 @@ flowchart TB | `impl-weak-traits` | 2 | Full implementation of weak_default traits define… | `ax-crate-interface` `define-weak-traits` | `test-weak` | | | 6 | 可复用基础组件 | `ax-config-macros` `ax-cpu` `ax-plat` `ax-plat-aarch64-qemu-virt` `ax-plat-loongarch64-qemu-virt` `ax-plat-riscv64-qemu-virt` `ax-plat-x86-pc` | — | | `mingo` | 0 | ArceOS 配套工具与辅助程序 | — | — | -| `range-alloc-arceos` | 0 | Generic range allocator | — | `axdevice` | | `riscv-h` | 0 | RISC-V virtualization-related registers | — | `riscv_vcpu` `riscv_vplic` | | `riscv_vcpu` | 6 | ArceOS-Hypervisor riscv vcpu module | `ax-crate-interface` `ax-errno` `ax-memory-addr` `ax-page-table-entry` `axaddrspace` `axvcpu` `axvisor_api` `riscv-h` | `axvisor` `axvm` | | `riscv_vplic` | 5 | RISCV Virtual PLIC implementation. | `ax-errno` `axaddrspace` `axdevice_base` `axvisor_api` `riscv-h` | `axdevice` `axvisor` | diff --git a/docs/docs/components/overview.md b/docs/docs/components/overview.md index aad60b4895..df0fc2bcea 100644 --- a/docs/docs/components/overview.md +++ b/docs/docs/components/overview.md @@ -200,7 +200,6 @@ flowchart TB | `impl-weak-partial` | 组件层 | `components/crate_interface/test_crates/impl-weak-partial` | 2 | 1 | [查看](crates/impl-weak-partial) | | `impl-weak-traits` | 组件层 | `components/crate_interface/test_crates/impl-weak-traits` | 2 | 1 | [查看](crates/impl-weak-traits) | | `mingo` | ArceOS 层 | `os/arceos/tools/raspi4/chainloader` | 0 | 0 | [查看](crates/mingo) | -| `range-alloc-arceos` | 组件层 | `memory/range-alloc-arceos` | 0 | 1 | [查看](crates/range-alloc-arceos) | | `riscv-h` | 组件层 | `virtualization/riscv-h` | 0 | 2 | [查看](crates/riscv-h) | | `ax-riscv-plic` | 组件层 | `drivers/intc/riscv_plic` | 0 | 1 | [查看](crates/ax-riscv-plic) | | `riscv_vcpu` | 组件层 | `virtualization/riscv_vcpu` | 8 | 2 | [查看](crates/riscv-vcpu) | diff --git a/memory/range-alloc-arceos/.github/config.json b/memory/range-alloc-arceos/.github/config.json deleted file mode 100644 index 12a9fab08b..0000000000 --- a/memory/range-alloc-arceos/.github/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "targets": [ - "x86_64-unknown-none" - ], - "rust_components": [ - "rust-src", - "clippy", - "rustfmt" - ] -} diff --git a/memory/range-alloc-arceos/.github/workflows/check.yml b/memory/range-alloc-arceos/.github/workflows/check.yml deleted file mode 100644 index 330fa15e9c..0000000000 --- a/memory/range-alloc-arceos/.github/workflows/check.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Quality Checks - -on: - push: - branches: - - '**' - tags-ignore: - - '**' - pull_request: - workflow_call: - -jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - rust_components: ${{ steps.config.outputs.rust_components }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - COMPONENTS=$(jq -r '.rust_components | join(", ")' .github/config.json) - - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - echo "rust_components=$COMPONENTS" >> $GITHUB_OUTPUT - - check: - name: Check - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: ${{ needs.load-config.outputs.rust_components }} - targets: ${{ matrix.target }} - - - name: Check rust version - run: rustc --version --verbose - - - name: Check code format - run: cargo fmt --all -- --check - - - name: Build - run: cargo build --target ${{ matrix.target }} --all-features - - - name: Run clippy - run: cargo clippy --target ${{ matrix.target }} --all-features -- -D warnings - - - name: Build documentation - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: cargo doc --no-deps --target ${{ matrix.target }} --all-features diff --git a/memory/range-alloc-arceos/.github/workflows/deploy.yml b/memory/range-alloc-arceos/.github/workflows/deploy.yml deleted file mode 100644 index fda9a323d2..0000000000 --- a/memory/range-alloc-arceos/.github/workflows/deploy.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Deploy - -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: 'pages' - cancel-in-progress: false - -env: - CARGO_TERM_COLOR: always - RUST_BACKTRACE: 1 - -jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_deploy: ${{ steps.check.outputs.should_deploy }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check if tag is on main or master branch - id: check - run: | - git fetch origin main master || true - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Tag is on main or master branch" - echo "should_deploy=true" >> $GITHUB_OUTPUT - else - echo "✗ Tag is not on main or master branch, skipping deployment" - echo "Tag is on: $BRANCHES" - echo "should_deploy=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_deploy == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_deploy == 'true' - - build: - name: Build documentation - runs-on: ubuntu-latest - needs: [verify-tag, check, test] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - env: - RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs - run: | - # Build documentation - cargo doc --no-deps --all-features - - # Auto-detect documentation directory - # Check if doc exists in target/doc or target/*/doc - if [ -d "target/doc" ]; then - DOC_DIR="target/doc" - else - # Find doc directory under target/*/doc pattern - DOC_DIR=$(find target -type d -name doc -path "target/*/doc" | head -n 1) - if [ -z "$DOC_DIR" ]; then - echo "Error: Could not find documentation directory" - exit 1 - fi - fi - - echo "Documentation found in: $DOC_DIR" - printf '' $(cargo tree | head -1 | cut -d' ' -f1) > "${DOC_DIR}/index.html" - echo "DOC_DIR=${DOC_DIR}" >> $GITHUB_ENV - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ${{ env.DOC_DIR }} - - deploy: - name: Deploy to GitHub Pages - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: [verify-tag, build] - if: needs.verify-tag.outputs.should_deploy == 'true' - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/memory/range-alloc-arceos/.github/workflows/push.yml b/memory/range-alloc-arceos/.github/workflows/push.yml deleted file mode 100644 index a18de4a047..0000000000 --- a/memory/range-alloc-arceos/.github/workflows/push.yml +++ /dev/null @@ -1,147 +0,0 @@ -# ═══════════════════════════════════════════════════════════════════════════════ -# 组件仓库 GitHub Actions 配置模板 -# ═══════════════════════════════════════════════════════════════════════════════ -# -# 此文件用于子仓库,当子仓库有更新时通知主仓库进行 subtree pull 同步。 -# -# 【使用步骤】 -# ───────────────────────────────────────────────────────────────────────────── -# 1. 将此文件复制到子仓库的 .github/workflows/ 目录: -# cp scripts/push.yml <子仓库>/.github/workflows/push.yml -# -# 2. 在子仓库中配置 Secret: -# GitHub 仓库 → Settings → Secrets → Actions → New repository secret -# 名称: PARENT_REPO_TOKEN -# 值: 具有主仓库 repo 权限的 Personal Access Token -# -# 3. 修改下方 env 块中的一个变量(标注了「需要修改」的行): -# PARENT_REPO - 主仓库路径,例如 rcore-os/tgoskits -# (subtree 目录由主仓库自动从 git 历史中推断,无需手动指定) -# -# 【Token 权限要求】 -# ───────────────────────────────────────────────────────────────────────────── -# PARENT_REPO_TOKEN 需要 Classic Personal Access Token,权限包括: -# - repo (Full control of private repositories) -# 或 -# - Fine-grained token: Contents (Read and Write) -# -# 【触发条件】 -# ───────────────────────────────────────────────────────────────────────────── -# - 自动触发:推送到 dev 或 main 分支时 -# - 手动触发:Actions → Notify Parent Repository → Run workflow -# -# 【工作流程】 -# ───────────────────────────────────────────────────────────────────────────── -# 子仓库 push → 触发此工作流 → 调用主仓库 API → 主仓库 subtree pull -# -# 【注意事项】 -# ───────────────────────────────────────────────────────────────────────────── -# - 主仓库需要配置接收 repository_dispatch 事件的同步工作流 -# - 如果不需要子仓库到主仓库的同步,可以不使用此文件 -# -# ═══════════════════════════════════════════════════════════════════════════════ - -name: Notify Parent Repository - -# 当有新的推送时触发 -on: - push: - branches: - - main - - master - workflow_dispatch: - -jobs: - notify: - runs-on: ubuntu-latest - steps: - - name: Get repository info - id: repo - env: - GH_REPO_NAME: ${{ github.event.repository.name }} - GH_REF_NAME: ${{ github.ref_name }} - GH_SERVER_URL: ${{ github.server_url }} - GH_REPOSITORY: ${{ github.repository }} - run: | - # 直接使用 GitHub Actions 内置变量,通过 env 传入避免 shell 注入 - COMPONENT="$GH_REPO_NAME" - BRANCH="$GH_REF_NAME" - # 构造标准 HTTPS URL,供主仓库按 URL 精确匹配 repos.list - REPO_URL="${GH_SERVER_URL}/${GH_REPOSITORY}" - - echo "component=${COMPONENT}" >> $GITHUB_OUTPUT - echo "branch=${BRANCH}" >> $GITHUB_OUTPUT - echo "repo_url=${REPO_URL}" >> $GITHUB_OUTPUT - - echo "Component: ${COMPONENT}" - echo "Branch: ${BRANCH}" - echo "Repo URL: ${REPO_URL}" - - - name: Notify parent repository - env: - # ── 需要修改 ────────────────────────────────────────────────────────── - PARENT_REPO: "rcore-os/tgoskits" # 主仓库路径 - # ── 无需修改 ────────────────────────────────────────────────────────── - DISPATCH_TOKEN: ${{ secrets.PARENT_REPO_TOKEN }} - # 将用户可控内容通过 env 传入,避免直接插值到 shell 脚本 - COMMIT_MESSAGE: ${{ github.event.head_commit.message }} - GIT_ACTOR: ${{ github.actor }} - GIT_SHA: ${{ github.sha }} - STEP_COMPONENT: ${{ steps.repo.outputs.component }} - STEP_BRANCH: ${{ steps.repo.outputs.branch }} - STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} - run: | - COMPONENT="$STEP_COMPONENT" - BRANCH="$STEP_BRANCH" - REPO_URL="$STEP_REPO_URL" - - echo "Notifying parent repository about update in ${COMPONENT}:${BRANCH}" - - # 使用 jq 安全构建 JSON,避免 commit message 中任何特殊字符导致注入 - PAYLOAD=$(jq -n \ - --arg component "$COMPONENT" \ - --arg branch "$BRANCH" \ - --arg repo_url "$REPO_URL" \ - --arg commit "$GIT_SHA" \ - --arg message "$COMMIT_MESSAGE" \ - --arg author "$GIT_ACTOR" \ - '{ - event_type: "subtree-update", - client_payload: { - component: $component, - branch: $branch, - repo_url: $repo_url, - commit: $commit, - message: $message, - author: $author - } - }') - - curl --fail --show-error -X POST \ - -H "Accept: application/vnd.github.v3+json" \ - -H "Authorization: token ${DISPATCH_TOKEN}" \ - https://api.github.com/repos/${PARENT_REPO}/dispatches \ - -d "$PAYLOAD" - - echo "Notification sent successfully" - - - name: Create summary - env: - STEP_COMPONENT: ${{ steps.repo.outputs.component }} - STEP_BRANCH: ${{ steps.repo.outputs.branch }} - STEP_REPO_URL: ${{ steps.repo.outputs.repo_url }} - GIT_SHA: ${{ github.sha }} - GIT_ACTOR: ${{ github.actor }} - run: | - COMPONENT="$STEP_COMPONENT" - BRANCH="$STEP_BRANCH" - REPO_URL="$STEP_REPO_URL" - - echo "## Notification Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Component**: ${COMPONENT}" >> $GITHUB_STEP_SUMMARY - echo "- **Branch**: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - echo "- **Repo URL**: ${REPO_URL}" >> $GITHUB_STEP_SUMMARY - echo "- **Commit**: \`${GIT_SHA}\`" >> $GITHUB_STEP_SUMMARY - echo "- **Author**: ${GIT_ACTOR}" >> $GITHUB_STEP_SUMMARY - echo "- **Status**: ✅ Notification sent" >> $GITHUB_STEP_SUMMARY diff --git a/memory/range-alloc-arceos/.github/workflows/release.yml b/memory/range-alloc-arceos/.github/workflows/release.yml deleted file mode 100644 index 2e857b48b5..0000000000 --- a/memory/range-alloc-arceos/.github/workflows/release.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - - 'v[0-9]+.[0-9]+.[0-9]+-pre.[0-9]+' - -permissions: - contents: write - -jobs: - verify-tag: - name: Verify Tag - runs-on: ubuntu-latest - outputs: - should_release: ${{ steps.check.outputs.should_release }} - is_prerelease: ${{ steps.check.outputs.is_prerelease }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Check tag type and branch - id: check - run: | - git fetch origin main master dev || true - - TAG="${{ github.ref_name }}" - BRANCHES=$(git branch -r --contains ${{ github.ref }}) - - echo "Tag: $TAG" - echo "Branches containing this tag: $BRANCHES" - - # Check if it's a prerelease tag - if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[0-9]+$ ]]; then - echo "📦 Detected prerelease tag" - echo "is_prerelease=true" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -q 'origin/dev'; then - echo "✓ Prerelease tag is on dev branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Prerelease tag must be on dev branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - elif [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "📦 Detected stable release tag" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - - if echo "$BRANCHES" | grep -qE 'origin/(main|master)'; then - echo "✓ Stable release tag is on main or master branch" - echo "should_release=true" >> $GITHUB_OUTPUT - else - echo "✗ Stable release tag must be on main or master branch, skipping release" - echo "should_release=false" >> $GITHUB_OUTPUT - fi - else - echo "✗ Unknown tag format, skipping release" - echo "is_prerelease=false" >> $GITHUB_OUTPUT - echo "should_release=false" >> $GITHUB_OUTPUT - fi - - - name: Verify version consistency - if: steps.check.outputs.should_release == 'true' - run: | - # Extract version from git tag (remove 'v' prefix) - TAG_VERSION="${{ github.ref_name }}" - TAG_VERSION="${TAG_VERSION#v}" - # Extract version from Cargo.toml - CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)"/\1/') - echo "Git tag version: $TAG_VERSION" - echo "Cargo.toml version: $CARGO_VERSION" - if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then - echo "ERROR: Version mismatch! Tag version ($TAG_VERSION) != Cargo.toml version ($CARGO_VERSION)" - exit 1 - fi - echo "✓ Version check passed!" - - check: - uses: ./.github/workflows/check.yml - needs: verify-tag - if: needs.verify-tag.outputs.should_release == 'true' - - test: - uses: ./.github/workflows/test.yml - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - release: - name: Create GitHub Release - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate release notes - id: release_notes - run: | - CURRENT_TAG="${{ github.ref_name }}" - - # Get previous tag - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A1 "^${CURRENT_TAG}$" | tail -n1) - - if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" == "$CURRENT_TAG" ]; then - echo "No previous tag found, this is the first release" - CHANGELOG="Initial release" - else - echo "Generating changelog from $PREVIOUS_TAG to $CURRENT_TAG" - - # Generate changelog with commit messages - CHANGELOG=$(git log --pretty=format:"- %s (%h)" "${PREVIOUS_TAG}..${CURRENT_TAG}") - - if [ -z "$CHANGELOG" ]; then - CHANGELOG="No changes" - fi - fi - - # Write changelog to output file (multi-line) - { - echo "changelog<> $GITHUB_OUTPUT - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - draft: false - prerelease: ${{ needs.verify-tag.outputs.is_prerelease == 'true' }} - body: | - ## Changes - ${{ steps.release_notes.outputs.changelog }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish: - name: Publish to crates.io - runs-on: ubuntu-latest - needs: [verify-tag, check] - if: needs.verify-tag.outputs.should_release == 'true' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - - name: Dry run publish - run: cargo publish --dry-run - - - name: Publish to crates.io - run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/memory/range-alloc-arceos/.github/workflows/test.yml b/memory/range-alloc-arceos/.github/workflows/test.yml deleted file mode 100644 index dc3b293d9d..0000000000 --- a/memory/range-alloc-arceos/.github/workflows/test.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Test - -on: - push: - branches: - - '**' - tags-ignore: - - '**' - pull_request: - workflow_call: - -jobs: - load-config: - name: Load CI Configuration - runs-on: ubuntu-latest - outputs: - targets: ${{ steps.config.outputs.targets }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Load configuration - id: config - run: | - TARGETS=$(jq -c '.targets' .github/config.json) - echo "targets=$TARGETS" >> $GITHUB_OUTPUT - - test: - name: Test - runs-on: ubuntu-latest - needs: load-config - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.load-config.outputs.targets) }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@nightly - - # - name: Run tests - # run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture - - # - name: Run doc tests - # run: cargo test --target ${{ matrix.target }} --doc - - name: Run tests - run: echo "Tests are skipped!" diff --git a/memory/range-alloc-arceos/.gitignore b/memory/range-alloc-arceos/.gitignore deleted file mode 100644 index e3507286da..0000000000 --- a/memory/range-alloc-arceos/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Junk -.DS_Store -.vscode/ -.#* -*.iml -.idea -.fuse_hidden* - -# Compiled -/doc -target/ -examples/generated-wasm - -# Service -Cargo.lock -*.swp diff --git a/memory/range-alloc-arceos/CHANGELOG.md b/memory/range-alloc-arceos/CHANGELOG.md deleted file mode 100644 index 588dfd25f4..0000000000 --- a/memory/range-alloc-arceos/CHANGELOG.md +++ /dev/null @@ -1,14 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.9](https://github.com/rcore-os/tgoskits/compare/range-alloc-arceos-v0.3.8...range-alloc-arceos-v0.3.9) - 2026-05-15 - -### Other - -- *(range-alloc-arceos)* inherit workspace metadata diff --git a/memory/range-alloc-arceos/Cargo.toml b/memory/range-alloc-arceos/Cargo.toml deleted file mode 100644 index 4d971eab8e..0000000000 --- a/memory/range-alloc-arceos/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "range-alloc-arceos" -version = "0.3.9" -description = "Generic range allocator" -homepage = "https://github.com/arceos-hypervisor/range-alloc-arceos" -repository = "https://github.com/arceos-hypervisor/range-alloc-arceos" -keywords = ["allocator"] -authors = ["the gfx-rs Developers"] -documentation = "https://docs.rs/range-alloc-arceos" -categories = ["memory-management"] -edition.workspace = true -readme = "README.md" -license = "Apache-2.0" diff --git a/memory/range-alloc-arceos/LICENSE b/memory/range-alloc-arceos/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/memory/range-alloc-arceos/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/memory/range-alloc-arceos/LICENSE.APACHE b/memory/range-alloc-arceos/LICENSE.APACHE deleted file mode 100644 index d9a10c0d8e..0000000000 --- a/memory/range-alloc-arceos/LICENSE.APACHE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/memory/range-alloc-arceos/LICENSE.MIT b/memory/range-alloc-arceos/LICENSE.MIT deleted file mode 100644 index c706b62c79..0000000000 --- a/memory/range-alloc-arceos/LICENSE.MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 The gfx-rs developers - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/memory/range-alloc-arceos/README.md b/memory/range-alloc-arceos/README.md deleted file mode 100644 index f9d2507153..0000000000 --- a/memory/range-alloc-arceos/README.md +++ /dev/null @@ -1,81 +0,0 @@ -

range-alloc-arceos

- -

Generic range allocator

- -
- -[![Crates.io](https://img.shields.io/crates/v/range-alloc-arceos.svg)](https://crates.io/crates/range-alloc-arceos) -[![Docs.rs](https://docs.rs/range-alloc-arceos/badge.svg)](https://docs.rs/range-alloc-arceos) -[![Rust](https://img.shields.io/badge/edition-2018-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`range-alloc-arceos` provides Generic range allocator. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -range-alloc-arceos = "0.3.4" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd memory/range-alloc-arceos - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use range_alloc_arceos as _; - -fn main() { - // Integrate `range-alloc-arceos` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/range-alloc-arceos](https://docs.rs/range-alloc-arceos) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/memory/range-alloc-arceos/README_CN.md b/memory/range-alloc-arceos/README_CN.md deleted file mode 100644 index 2ec6e1ef78..0000000000 --- a/memory/range-alloc-arceos/README_CN.md +++ /dev/null @@ -1,81 +0,0 @@ -

range-alloc-arceos

- -

Generic range allocator

- -
- -[![Crates.io](https://img.shields.io/crates/v/range-alloc-arceos.svg)](https://crates.io/crates/range-alloc-arceos) -[![Docs.rs](https://docs.rs/range-alloc-arceos/badge.svg)](https://docs.rs/range-alloc-arceos) -[![Rust](https://img.shields.io/badge/edition-2018-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`range-alloc-arceos` 提供了 Generic range allocator。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -range-alloc-arceos = "0.3.4" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd memory/range-alloc-arceos - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use range_alloc_arceos as _; - -fn main() { - // 在这里将 `range-alloc-arceos` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/range-alloc-arceos](https://docs.rs/range-alloc-arceos) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/memory/range-alloc-arceos/src/lib.rs b/memory/range-alloc-arceos/src/lib.rs deleted file mode 100644 index 44d31eda4f..0000000000 --- a/memory/range-alloc-arceos/src/lib.rs +++ /dev/null @@ -1,491 +0,0 @@ -//! A simple, fast range allocator for managing contiguous ranges of resources. -//! -//! This crate provides a [`RangeAllocator`] that efficiently allocates and frees -//! contiguous ranges from an initial range. It uses a best-fit allocation strategy -//! to minimize memory fragmentation. -//! -//! # Example -//! -//! ``` -//! use range_alloc_arceos::RangeAllocator; -//! -//! let mut allocator = RangeAllocator::new(0..100); -//! -//! // Allocate a range of length 10 -//! let range = allocator.allocate_range(10).unwrap(); -//! assert_eq!(range, 0..10); -//! -//! // Free the range when done -//! allocator.free_range(range); -//! ``` - -#![no_std] - -extern crate alloc; - -use alloc::{vec, vec::Vec}; -use core::{ - fmt::Debug, - iter::Sum, - ops::{Add, AddAssign, Range, Sub}, -}; - -/// A range allocator that manages allocation and deallocation of contiguous ranges. -/// -/// The allocator starts with an initial range and maintains a list of free ranges. -/// It uses a best-fit allocation strategy to minimize fragmentation when allocating -/// new ranges. -/// -/// # Type Parameters -/// -/// * `T` - The type used for range bounds. Must support arithmetic operations and ordering. -#[derive(Debug)] -pub struct RangeAllocator { - /// The range this allocator covers. - initial_range: Range, - /// A Vec of ranges in this heap which are unused. - /// Must be ordered with ascending range start to permit short circuiting allocation. - /// No two ranges in this vec may overlap. - free_ranges: Vec>, -} - -/// Error type returned when a range allocation fails. -/// -/// This error indicates that there is not enough contiguous space available -/// to satisfy the allocation request, although there may be enough total free -/// space if it were defragmented. -#[derive(Clone, Debug, PartialEq)] -pub struct RangeAllocationError { - /// The total length of all free ranges combined. - /// - /// This value represents how much space would be available if all fragmented - /// free ranges could be combined into one contiguous range. - pub fragmented_free_length: T, -} - -impl RangeAllocator -where - T: Clone + Copy + Add + AddAssign + Sub + Eq + PartialOrd + Debug, -{ - /// Creates a new range allocator with the specified initial range. - /// - /// The entire initial range is marked as free and available for allocation. - /// - /// # Arguments - /// - /// * `range` - The initial range that this allocator will manage. - /// - /// # Example - /// - /// ``` - /// use range_alloc_arceos::RangeAllocator; - /// - /// let allocator = RangeAllocator::new(0..1024); - /// ``` - pub fn new(range: Range) -> Self { - RangeAllocator { - initial_range: range.clone(), - free_ranges: vec![range], - } - } - - /// Returns a reference to the initial range managed by this allocator. - /// - /// This is the range that was provided when the allocator was created, - /// or the expanded range if [`grow_to`](Self::grow_to) was called. - pub fn initial_range(&self) -> &Range { - &self.initial_range - } - - /// Grows the allocator's range to a new end point. - /// - /// This extends the upper bound of the initial range and makes the new space - /// available for allocation. If the last free range ends at the current upper - /// bound, it is extended; otherwise, a new free range is added. - /// - /// # Arguments - /// - /// * `new_end` - The new end point for the range (must be greater than the current end). - pub fn grow_to(&mut self, new_end: T) { - let initial_range_end = self.initial_range.end; - if let Some(last_range) = self - .free_ranges - .last_mut() - .filter(|last_range| last_range.end == initial_range_end) - { - last_range.end = new_end; - } else { - self.free_ranges.push(self.initial_range.end..new_end); - } - - self.initial_range.end = new_end; - } - - /// Allocates a contiguous range of the specified length. - /// - /// This method uses a best-fit allocation strategy to find the smallest free range - /// that can satisfy the request, minimizing fragmentation. If no single contiguous - /// range is large enough, it returns an error with information about the total - /// fragmented free space. - /// - /// # Arguments - /// - /// * `length` - The length of the range to allocate. - /// - /// # Returns - /// - /// * `Ok(Range)` - The allocated range if successful. - /// * `Err(RangeAllocationError)` - If allocation fails, containing information - /// about the total fragmented free space available. - /// - /// # Example - /// - /// ``` - /// use range_alloc_arceos::RangeAllocator; - /// - /// let mut allocator = RangeAllocator::new(0..100); - /// let range = allocator.allocate_range(20).unwrap(); - /// assert_eq!(range, 0..20); - /// ``` - pub fn allocate_range(&mut self, length: T) -> Result, RangeAllocationError> { - assert_ne!(length + length, length); - let mut best_fit: Option<(usize, Range)> = None; - - // This is actually correct. With the trait bound as it is, we have - // no way to summon a value of 0 directly, so we make one by subtracting - // something from itself. Once the trait bound can be changed, this can - // be fixed. - #[allow(clippy::eq_op)] - let mut fragmented_free_length = length - length; - for (index, range) in self.free_ranges.iter().cloned().enumerate() { - let range_length = range.end - range.start; - fragmented_free_length += range_length; - if range_length < length { - continue; - } else if range_length == length { - // Found a perfect fit, so stop looking. - best_fit = Some((index, range)); - break; - } - best_fit = Some(match best_fit { - Some((best_index, best_range)) => { - // Find best fit for this allocation to reduce memory fragmentation. - if range_length < best_range.end - best_range.start { - (index, range) - } else { - (best_index, best_range.clone()) - } - } - None => (index, range), - }); - } - match best_fit { - Some((index, range)) => { - if range.end - range.start == length { - self.free_ranges.remove(index); - } else { - self.free_ranges[index].start += length; - } - Ok(range.start..(range.start + length)) - } - None => Err(RangeAllocationError { - fragmented_free_length, - }), - } - } - - /// Frees a previously allocated range, making it available for future allocations. - /// - /// This method attempts to merge the freed range with adjacent free ranges to - /// reduce fragmentation. The freed range must be within the initial range and - /// must not be empty. - /// - /// # Arguments - /// - /// * `range` - The range to free. Must be within the allocator's initial range. - /// - /// # Panics - /// - /// Panics if the range is outside the initial range or if the range is empty - /// (start >= end). - /// - /// # Example - /// - /// ``` - /// use range_alloc_arceos::RangeAllocator; - /// - /// let mut allocator = RangeAllocator::new(0..100); - /// let range = allocator.allocate_range(20).unwrap(); - /// allocator.free_range(range); - /// ``` - pub fn free_range(&mut self, range: Range) { - assert!(self.initial_range.start <= range.start && range.end <= self.initial_range.end); - assert!(range.start < range.end); - - // Get insertion position. - let i = self - .free_ranges - .iter() - .position(|r| r.start > range.start) - .unwrap_or(self.free_ranges.len()); - - // Try merging with neighboring ranges in the free list. - // Before: |left|-(range)-|right| - if i > 0 && range.start == self.free_ranges[i - 1].end { - // Merge with |left|. - self.free_ranges[i - 1].end = - if i < self.free_ranges.len() && range.end == self.free_ranges[i].start { - // Check for possible merge with |left| and |right|. - let right = self.free_ranges.remove(i); - right.end - } else { - range.end - }; - - return; - } else if i < self.free_ranges.len() && range.end == self.free_ranges[i].start { - // Merge with |right|. - self.free_ranges[i].start = if i > 0 && range.start == self.free_ranges[i - 1].end { - // Check for possible merge with |left| and |right|. - let left = self.free_ranges.remove(i - 1); - left.start - } else { - range.start - }; - - return; - } - - // Debug checks - assert!( - (i == 0 || self.free_ranges[i - 1].end < range.start) - && (i >= self.free_ranges.len() || range.end < self.free_ranges[i].start) - ); - - self.free_ranges.insert(i, range); - } - - /// Returns an iterator over allocated non-empty ranges - pub fn allocated_ranges(&self) -> impl Iterator> + '_ { - let first = match self.free_ranges.first() { - Some(Range { start, .. }) if *start > self.initial_range.start => { - Some(self.initial_range.start..*start) - } - None => Some(self.initial_range.clone()), - _ => None, - }; - - let last = match self.free_ranges.last() { - Some(Range { end, .. }) if *end < self.initial_range.end => { - Some(*end..self.initial_range.end) - } - _ => None, - }; - - let mid = self - .free_ranges - .iter() - .zip(self.free_ranges.iter().skip(1)) - .map(|(ra, rb)| ra.end..rb.start); - - first.into_iter().chain(mid).chain(last) - } - - /// Resets the allocator to its initial state. - /// - /// This marks the entire initial range as free, effectively deallocating - /// all previously allocated ranges. - pub fn reset(&mut self) { - self.free_ranges.clear(); - self.free_ranges.push(self.initial_range.clone()); - } - - /// Returns `true` if no ranges have been allocated. - /// - /// This checks whether the allocator is in its initial state with all space free. - pub fn is_empty(&self) -> bool { - self.free_ranges.len() == 1 && self.free_ranges[0] == self.initial_range - } -} - -impl + Sum> RangeAllocator { - /// Returns the total amount of free space available across all free ranges. - /// - /// This sums the lengths of all free ranges, giving the total amount of space - /// that could be allocated if fragmentation is not an issue. - pub fn total_available(&self) -> T { - self.free_ranges - .iter() - .map(|range| range.end - range.start) - .sum() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic_allocation() { - let mut alloc = RangeAllocator::new(0..10); - // Test if an allocation works - assert_eq!(alloc.allocate_range(4), Ok(0..4)); - assert!(alloc.allocated_ranges().eq(core::iter::once(0..4))); - // Free the prior allocation - alloc.free_range(0..4); - // Make sure the free actually worked - assert_eq!(alloc.free_ranges, vec![0..10]); - assert!(alloc.allocated_ranges().eq(core::iter::empty())); - } - - #[test] - fn test_out_of_space() { - let mut alloc = RangeAllocator::new(0..10); - // Test if the allocator runs out of space correctly - assert_eq!(alloc.allocate_range(10), Ok(0..10)); - assert!(alloc.allocated_ranges().eq(core::iter::once(0..10))); - assert!(alloc.allocate_range(4).is_err()); - alloc.free_range(0..10); - } - - #[test] - fn test_grow() { - let mut alloc = RangeAllocator::new(0..11); - // Test if the allocator runs out of space correctly - assert_eq!(alloc.allocate_range(10), Ok(0..10)); - assert!(alloc.allocated_ranges().eq(core::iter::once(0..10))); - assert!(alloc.allocate_range(4).is_err()); - alloc.grow_to(20); - assert_eq!(alloc.allocate_range(4), Ok(10..14)); - alloc.free_range(0..14); - } - - #[test] - fn test_grow_with_hole_at_start() { - let mut alloc = RangeAllocator::new(0..6); - - assert_eq!(alloc.allocate_range(3), Ok(0..3)); - assert_eq!(alloc.allocate_range(3), Ok(3..6)); - alloc.free_range(0..3); - - alloc.grow_to(9); - assert_eq!(alloc.allocated_ranges().collect::>(), [3..6]); - } - #[test] - fn test_grow_with_hole_in_middle() { - let mut alloc = RangeAllocator::new(0..6); - - assert_eq!(alloc.allocate_range(2), Ok(0..2)); - assert_eq!(alloc.allocate_range(2), Ok(2..4)); - assert_eq!(alloc.allocate_range(2), Ok(4..6)); - alloc.free_range(2..4); - - alloc.grow_to(9); - assert_eq!(alloc.allocated_ranges().collect::>(), [0..2, 4..6]); - } - - #[test] - fn test_dont_use_block_that_is_too_small() { - let mut alloc = RangeAllocator::new(0..10); - // Allocate three blocks then free the middle one and check for correct state - assert_eq!(alloc.allocate_range(3), Ok(0..3)); - assert_eq!(alloc.allocate_range(3), Ok(3..6)); - assert_eq!(alloc.allocate_range(3), Ok(6..9)); - alloc.free_range(3..6); - assert_eq!(alloc.free_ranges, vec![3..6, 9..10]); - assert_eq!( - alloc.allocated_ranges().collect::>>(), - vec![0..3, 6..9] - ); - // Now request space that the middle block can fill, but the end one can't. - assert_eq!(alloc.allocate_range(3), Ok(3..6)); - } - - #[test] - fn test_free_blocks_in_middle() { - let mut alloc = RangeAllocator::new(0..100); - // Allocate many blocks then free every other block. - assert_eq!(alloc.allocate_range(10), Ok(0..10)); - assert_eq!(alloc.allocate_range(10), Ok(10..20)); - assert_eq!(alloc.allocate_range(10), Ok(20..30)); - assert_eq!(alloc.allocate_range(10), Ok(30..40)); - assert_eq!(alloc.allocate_range(10), Ok(40..50)); - assert_eq!(alloc.allocate_range(10), Ok(50..60)); - assert_eq!(alloc.allocate_range(10), Ok(60..70)); - assert_eq!(alloc.allocate_range(10), Ok(70..80)); - assert_eq!(alloc.allocate_range(10), Ok(80..90)); - assert_eq!(alloc.allocate_range(10), Ok(90..100)); - assert_eq!(alloc.free_ranges, vec![]); - assert!(alloc.allocated_ranges().eq(core::iter::once(0..100))); - alloc.free_range(10..20); - alloc.free_range(30..40); - alloc.free_range(50..60); - alloc.free_range(70..80); - alloc.free_range(90..100); - // Check that the right blocks were freed. - assert_eq!( - alloc.free_ranges, - vec![10..20, 30..40, 50..60, 70..80, 90..100] - ); - assert_eq!( - alloc.allocated_ranges().collect::>>(), - vec![0..10, 20..30, 40..50, 60..70, 80..90] - ); - // Fragment the memory on purpose a bit. - assert_eq!(alloc.allocate_range(6), Ok(10..16)); - assert_eq!(alloc.allocate_range(6), Ok(30..36)); - assert_eq!(alloc.allocate_range(6), Ok(50..56)); - assert_eq!(alloc.allocate_range(6), Ok(70..76)); - assert_eq!(alloc.allocate_range(6), Ok(90..96)); - // Check for fragmentation. - assert_eq!( - alloc.free_ranges, - vec![16..20, 36..40, 56..60, 76..80, 96..100] - ); - assert_eq!( - alloc.allocated_ranges().collect::>>(), - vec![0..16, 20..36, 40..56, 60..76, 80..96] - ); - // Fill up the fragmentation - assert_eq!(alloc.allocate_range(4), Ok(16..20)); - assert_eq!(alloc.allocate_range(4), Ok(36..40)); - assert_eq!(alloc.allocate_range(4), Ok(56..60)); - assert_eq!(alloc.allocate_range(4), Ok(76..80)); - assert_eq!(alloc.allocate_range(4), Ok(96..100)); - // Check that nothing is free. - assert_eq!(alloc.free_ranges, vec![]); - assert!(alloc.allocated_ranges().eq(core::iter::once(0..100))); - } - - #[test] - fn test_ignore_block_if_another_fits_better() { - let mut alloc = RangeAllocator::new(0..10); - // Allocate blocks such that the only free spaces available are 3..6 and 9..10 - // in order to prepare for the next test. - assert_eq!(alloc.allocate_range(3), Ok(0..3)); - assert_eq!(alloc.allocate_range(3), Ok(3..6)); - assert_eq!(alloc.allocate_range(3), Ok(6..9)); - alloc.free_range(3..6); - assert_eq!(alloc.free_ranges, vec![3..6, 9..10]); - assert_eq!( - alloc.allocated_ranges().collect::>>(), - vec![0..3, 6..9] - ); - // Now request space that can be filled by 3..6 but should be filled by 9..10 - // because 9..10 is a perfect fit. - assert_eq!(alloc.allocate_range(1), Ok(9..10)); - } - - #[test] - fn test_merge_neighbors() { - let mut alloc = RangeAllocator::new(0..9); - assert_eq!(alloc.allocate_range(3), Ok(0..3)); - assert_eq!(alloc.allocate_range(3), Ok(3..6)); - assert_eq!(alloc.allocate_range(3), Ok(6..9)); - alloc.free_range(0..3); - alloc.free_range(6..9); - alloc.free_range(3..6); - assert_eq!(alloc.free_ranges, vec![0..9]); - assert!(alloc.allocated_ranges().eq(core::iter::empty())); - } -} diff --git a/memory/range-alloc-arceos/tests/test.rs b/memory/range-alloc-arceos/tests/test.rs deleted file mode 100644 index 23d2a3e1f2..0000000000 --- a/memory/range-alloc-arceos/tests/test.rs +++ /dev/null @@ -1,60 +0,0 @@ -use range_alloc_arceos::RangeAllocator; - -#[test] -fn test_simple_allocation() { - let mut allocator = RangeAllocator::new(0..100); - - let r1 = allocator.allocate_range(10).expect("Alloc 10 failed"); - assert_eq!(r1, 0..10); - - let r2 = allocator.allocate_range(20).expect("Alloc 20 failed"); - assert_eq!(r2, 10..30); - - allocator.free_range(r1); - - let r3 = allocator.allocate_range(5).expect("Alloc 5 failed"); - assert_eq!(r3, 0..5); -} - -#[test] -fn test_out_of_memory() { - let mut allocator = RangeAllocator::new(0..10); - - let _r1 = allocator.allocate_range(10).unwrap(); - - let r2 = allocator.allocate_range(1); - assert!(r2.is_err(), "Should return error when OOM"); -} - -#[test] -fn test_fragmentation_and_merge() { - let mut allocator = RangeAllocator::new(0..100); - - let a = allocator.allocate_range(20).unwrap(); - let b = allocator.allocate_range(20).unwrap(); - let c = allocator.allocate_range(20).unwrap(); - let _d = allocator.allocate_range(40).unwrap(); - - allocator.free_range(a); - allocator.free_range(c); - - assert!(allocator.allocate_range(30).is_err()); - - allocator.free_range(b); - - let big = allocator - .allocate_range(60) - .expect("Should merge ranges A, B, C"); - assert_eq!(big, 0..60); -} - -#[test] -fn test_alignment_gaps() { - let mut allocator = RangeAllocator::new(1000..2000); - - let r1 = allocator.allocate_range(100).unwrap(); - assert_eq!(r1, 1000..1100); - - let r2 = allocator.allocate_range(100).unwrap(); - assert_eq!(r2, 1100..1200); -} diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index 82931c05ef..52ef820ba9 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -12,7 +12,6 @@ https://github.com/arceos-hypervisor/axvcpu,,virtualization/axvcpu,Hypervisor, https://github.com/arceos-hypervisor/axvisor_api,,virtualization/axvisor_api,Hypervisor, https://github.com/arceos-hypervisor/axvm,,virtualization/axvm,Hypervisor, https://github.com/arceos-hypervisor/axvmconfig,,virtualization/axvmconfig,Hypervisor, -https://github.com/arceos-hypervisor/range-alloc,,memory/range-alloc-arceos,Hypervisor, https://github.com/arceos-hypervisor/x86_vcpu,,virtualization/x86_vcpu,Hypervisor, https://github.com/arceos-hypervisor/x86_vlapic.git,,virtualization/x86_vlapic,Hypervisor, https://github.com/arceos-hypervisor/arm_vcpu,,virtualization/arm_vcpu,Hypervisor, diff --git a/scripts/test/std_crates.csv b/scripts/test/std_crates.csv index 0d474d1cc9..c5614430d9 100644 --- a/scripts/test/std_crates.csv +++ b/scripts/test/std_crates.csv @@ -26,7 +26,6 @@ ax-kernel-guard ax-kspin ax-lazyinit ax-linked-list-r4l -range-alloc-arceos riscv-h ax-riscv-plic riscv_vplic diff --git a/virtualization/axdevice/Cargo.toml b/virtualization/axdevice/Cargo.toml index f1113db5e4..5dce2a9698 100644 --- a/virtualization/axdevice/Cargo.toml +++ b/virtualization/axdevice/Cargo.toml @@ -25,7 +25,6 @@ ax-memory-addr = { workspace = true } axvmconfig = { workspace = true, default-features = false } axaddrspace = { workspace = true } axdevice_base = { workspace = true } -range-alloc-arceos = { workspace = true } [target.'cfg(target_arch = "aarch64")'.dependencies] arm_vgic = { workspace = true, features = ["vgicv3"] } [target.'cfg(target_arch = "riscv64")'.dependencies] diff --git a/virtualization/axdevice/src/device.rs b/virtualization/axdevice/src/device.rs index b53693c16d..b79b2dd78b 100644 --- a/virtualization/axdevice/src/device.rs +++ b/virtualization/axdevice/src/device.rs @@ -28,11 +28,10 @@ use axaddrspace::{ }; use axdevice_base::{BaseDeviceOps, BaseMmioDeviceOps, BasePortDeviceOps, BaseSysRegDeviceOps}; use axvmconfig::{EmulatedDeviceConfig, EmulatedDeviceType}; -use range_alloc_arceos::RangeAllocator; #[cfg(target_arch = "riscv64")] use riscv_vplic::VPlicGlobal; -use crate::AxVmDeviceConfig; +use crate::{AxVmDeviceConfig, range_alloc::RangeAllocator}; /// A set of emulated device types that can be accessed by a specific address range type. pub struct AxEmuDevices { @@ -88,7 +87,7 @@ pub struct AxVmDevices { emu_sys_reg_devices: AxEmuSysRegDevices, emu_port_devices: AxEmuPortDevices, /// IVC channel range allocator - ivc_channel: Option>>, + ivc_channel: Option>, } #[inline] @@ -321,8 +320,8 @@ impl AxVmDevices { allocator .lock() .allocate_range(size) - .map_err(|e| { - warn!("Failed to allocate IVC channel range: {e:x?}"); + .ok_or_else(|| { + warn!("Failed to allocate IVC channel range with size {size:#x}"); ax_errno::ax_err_type!(NoMemory, "IVC channel allocation failed") }) .map(|range| { @@ -344,10 +343,13 @@ impl AxVmDevices { } if let Some(allocator) = &self.ivc_channel { - allocator - .lock() - .free_range(addr.as_usize()..addr.as_usize() + size); - Ok(()) + let range = addr.as_usize()..addr.as_usize() + size; + if allocator.lock().free_range(range.clone()) { + debug!("Released IVC channel range: {range:x?}"); + Ok(()) + } else { + ax_err!(InvalidInput, "Invalid IVC channel range") + } } else { ax_err!(InvalidInput, "IVC channel not exists") } diff --git a/virtualization/axdevice/src/lib.rs b/virtualization/axdevice/src/lib.rs index 0b21f651ee..8d0be745c7 100644 --- a/virtualization/axdevice/src/lib.rs +++ b/virtualization/axdevice/src/lib.rs @@ -28,6 +28,7 @@ extern crate log; mod config; mod device; +mod range_alloc; pub use config::AxVmDeviceConfig; pub use device::AxVmDevices; diff --git a/virtualization/axdevice/src/range_alloc.rs b/virtualization/axdevice/src/range_alloc.rs new file mode 100644 index 0000000000..0118316808 --- /dev/null +++ b/virtualization/axdevice/src/range_alloc.rs @@ -0,0 +1,145 @@ +use alloc::{vec, vec::Vec}; +use core::ops::Range; + +/// A minimal best-fit range allocator for IVC GPA ranges. +#[derive(Debug)] +pub(crate) struct RangeAllocator { + initial: Range, + free: Vec>, +} + +impl RangeAllocator { + pub(crate) fn new(range: Range) -> Self { + Self { + initial: range.clone(), + free: vec![range], + } + } + + pub(crate) fn allocate_range(&mut self, size: usize) -> Option> { + debug_assert!(size > 0); + + let mut best_fit = None; + for (index, range) in self.free.iter().enumerate() { + let len = range.end - range.start; + if len < size { + continue; + } + if len == size { + best_fit = Some(index); + break; + } + match best_fit { + Some(best_index) + if len >= self.free[best_index].end - self.free[best_index].start => {} + _ => best_fit = Some(index), + } + } + + let index = best_fit?; + let start = self.free[index].start; + let end = start + size; + if self.free[index].end == end { + self.free.remove(index); + } else { + self.free[index].start = end; + } + Some(start..end) + } + + pub(crate) fn free_range(&mut self, range: Range) -> bool { + if range.start >= range.end + || range.start < self.initial.start + || range.end > self.initial.end + { + return false; + } + + let index = self + .free + .iter() + .position(|free| free.start > range.start) + .unwrap_or(self.free.len()); + + if index > 0 && self.free[index - 1].end > range.start { + return false; + } + if index < self.free.len() && range.end > self.free[index].start { + return false; + } + + if index > 0 && self.free[index - 1].end == range.start { + self.free[index - 1].end = range.end; + if index < self.free.len() && self.free[index - 1].end == self.free[index].start { + let next = self.free.remove(index); + self.free[index - 1].end = next.end; + } + } else if index < self.free.len() && range.end == self.free[index].start { + self.free[index].start = range.start; + } else { + self.free.insert(index, range); + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::RangeAllocator; + + #[test] + fn allocates_and_reuses_ranges() { + let mut allocator = RangeAllocator::new(0..0x4000); + + assert_eq!(allocator.allocate_range(0x1000), Some(0..0x1000)); + assert_eq!(allocator.allocate_range(0x1000), Some(0x1000..0x2000)); + + assert!(allocator.free_range(0..0x1000)); + assert_eq!(allocator.allocate_range(0x1000), Some(0..0x1000)); + } + + #[test] + fn picks_best_fit_range() { + let mut allocator = RangeAllocator::new(0..0x9000); + + assert_eq!(allocator.allocate_range(0x3000), Some(0..0x3000)); + assert_eq!(allocator.allocate_range(0x3000), Some(0x3000..0x6000)); + assert_eq!(allocator.allocate_range(0x3000), Some(0x6000..0x9000)); + + assert!(allocator.free_range(0..0x3000)); + assert!(allocator.free_range(0x6000..0x9000)); + + assert_eq!(allocator.allocate_range(0x3000), Some(0..0x3000)); + assert_eq!(allocator.allocate_range(0x3000), Some(0x6000..0x9000)); + } + + #[test] + fn merges_neighboring_freed_ranges() { + let mut allocator = RangeAllocator::new(0..0x3000); + + assert_eq!(allocator.allocate_range(0x1000), Some(0..0x1000)); + assert_eq!(allocator.allocate_range(0x1000), Some(0x1000..0x2000)); + assert_eq!(allocator.allocate_range(0x1000), Some(0x2000..0x3000)); + + assert!(allocator.free_range(0..0x1000)); + assert!(allocator.free_range(0x2000..0x3000)); + assert!(allocator.free_range(0x1000..0x2000)); + + assert_eq!(allocator.allocate_range(0x3000), Some(0..0x3000)); + } + + #[test] + fn rejects_invalid_or_duplicate_frees() { + let mut allocator = RangeAllocator::new(0x1000..0x3000); + + assert!(!allocator.free_range(0x1000..0x1000)); + assert!(!allocator.free_range(0..0x1000)); + assert!(!allocator.free_range(0x2000..0x4000)); + assert!(!allocator.free_range(0x1000..0x2000)); + + assert_eq!(allocator.allocate_range(0x1000), Some(0x1000..0x2000)); + assert!(allocator.free_range(0x1000..0x2000)); + assert!(!allocator.free_range(0x1000..0x2000)); + } +} From 4a9c73264571f9d48d820f7d3723d95eec592734 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Wed, 27 May 2026 18:48:37 +0800 Subject: [PATCH 63/71] Refactor linker scripts and CI configuration (#992) * refactor(linker-scripts): remove unused linker scripts for hello, irq, and smp kernels * refactor(ci): switch clippy and std tests to self-hosted runners --- .github/workflows/ci.yml | 14 ++--- .../examples/hello-kernel/linker_x86_64.lds | 52 ------------------- .../examples/irq-kernel/linker_x86_64.lds | 52 ------------------- .../examples/smp-kernel/linker_x86_64.lds | 52 ------------------- docs/docs/build/ci.md | 14 +++-- 5 files changed, 14 insertions(+), 170 deletions(-) delete mode 100644 components/axplat_crates/examples/hello-kernel/linker_x86_64.lds delete mode 100644 components/axplat_crates/examples/irq-kernel/linker_x86_64.lds delete mode 100644 components/axplat_crates/examples/smp-kernel/linker_x86_64.lds diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3796fc505..4465357e09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -271,8 +271,9 @@ jobs: matrix: include: - name: Run clippy - use_container: true - runs_on: '["ubuntu-latest"]' + use_container: false + runs_on: '["self-hosted","linux","qcs"]' + self_hosted_owner: rcore-os command: | set -eux git_cmd() { @@ -300,16 +301,17 @@ jobs: else cargo xtask clippy fi - cache_key: clippy + cache_key: "" container_image: base fetch_depth: full limit_to_owner: "" main_pr_only: false - name: Test with std - use_container: true - runs_on: '["ubuntu-latest"]' + use_container: false + runs_on: '["self-hosted","linux","qcs"]' + self_hosted_owner: rcore-os command: cargo xtask test - cache_key: test-std + cache_key: "" container_image: base limit_to_owner: "" main_pr_only: false diff --git a/components/axplat_crates/examples/hello-kernel/linker_x86_64.lds b/components/axplat_crates/examples/hello-kernel/linker_x86_64.lds deleted file mode 100644 index 113e7914c0..0000000000 --- a/components/axplat_crates/examples/hello-kernel/linker_x86_64.lds +++ /dev/null @@ -1,52 +0,0 @@ -ENTRY(_start) -SECTIONS -{ - . = 0xffff800000200000; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data .data.*) - *(.got .got.*) - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * 1; - } - . = _percpu_end; - _edata = .; - - .bss : AT(.) ALIGN(4K) { - *(.bss.stack) - . = ALIGN(4K); - _sbss = .; - *(.bss .bss.*) - *(COMMON) - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) - } -} diff --git a/components/axplat_crates/examples/irq-kernel/linker_x86_64.lds b/components/axplat_crates/examples/irq-kernel/linker_x86_64.lds deleted file mode 100644 index 113e7914c0..0000000000 --- a/components/axplat_crates/examples/irq-kernel/linker_x86_64.lds +++ /dev/null @@ -1,52 +0,0 @@ -ENTRY(_start) -SECTIONS -{ - . = 0xffff800000200000; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data .data.*) - *(.got .got.*) - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * 1; - } - . = _percpu_end; - _edata = .; - - .bss : AT(.) ALIGN(4K) { - *(.bss.stack) - . = ALIGN(4K); - _sbss = .; - *(.bss .bss.*) - *(COMMON) - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) - } -} diff --git a/components/axplat_crates/examples/smp-kernel/linker_x86_64.lds b/components/axplat_crates/examples/smp-kernel/linker_x86_64.lds deleted file mode 100644 index 113e7914c0..0000000000 --- a/components/axplat_crates/examples/smp-kernel/linker_x86_64.lds +++ /dev/null @@ -1,52 +0,0 @@ -ENTRY(_start) -SECTIONS -{ - . = 0xffff800000200000; - _skernel = .; - - .text : ALIGN(4K) { - _stext = .; - *(.text.boot) - *(.text .text.*) - _etext = .; - } - - .rodata : ALIGN(4K) { - _srodata = .; - *(.rodata .rodata.*) - _erodata = .; - } - - .data : ALIGN(4K) { - _sdata = .; - *(.data .data.*) - *(.got .got.*) - } - - . = ALIGN(4K); - _percpu_start = .; - _percpu_end = _percpu_start + SIZEOF(.percpu); - .percpu 0x0 : AT(_percpu_start) { - _percpu_load_start = .; - *(.percpu .percpu.*) - _percpu_load_end = .; - . = _percpu_load_start + ALIGN(64) * 1; - } - . = _percpu_end; - _edata = .; - - .bss : AT(.) ALIGN(4K) { - *(.bss.stack) - . = ALIGN(4K); - _sbss = .; - *(.bss .bss.*) - *(COMMON) - _ebss = .; - } - - _ekernel = .; - - /DISCARD/ : { - *(.comment) - } -} diff --git a/docs/docs/build/ci.md b/docs/docs/build/ci.md index a8c3c462e4..2fa75ee096 100644 --- a/docs/docs/build/ci.md +++ b/docs/docs/build/ci.md @@ -89,8 +89,8 @@ push 到 `main` / `dev` 时强制运行 CI 检查。若非 `main` / `dev` 分支 | Job 名称 | Runner | 使用容器 | Cache Key | 功能说明 | |----------|--------|----------|-----------|----------| -| Run clippy | `ubuntu-latest` | 是(`base`) | `clippy` | `cargo xtask clippy --since `;需要完整 git 历史 | -| Test with std | `ubuntu-latest` | 是(`base`) | `test-std` | `cargo xtask test`,运行 `scripts/test/std_crates.csv` 中的 host 测试 | +| Run clippy | `self-hosted linux qcs` | 否 | 无 | `cargo xtask clippy --since `;需要完整 git 历史;fork PR 回退到 `ubuntu-latest` + `base` 容器 | +| Test with std | `self-hosted linux qcs` | 否 | 无 | `cargo xtask test`,运行 `scripts/test/std_crates.csv` 中的 host 测试;fork PR 回退到 `ubuntu-latest` + `base` 容器 | | Test axvisor aarch64 qemu | `self-hosted linux qcs` | 否 | 无 | `cargo xtask axvisor test qemu --arch aarch64`;`rcore-os` 仓库使用 self-hosted,fork PR 回退到 `ubuntu-latest` + `base` 容器 | | Test axvisor riscv64 qemu | `self-hosted linux qcs` | 否 | 无 | `cargo xtask axvisor test qemu --arch riscv64`;`rcore-os` 仓库使用 self-hosted,fork PR 回退到 `ubuntu-latest` + `base` 容器 | | Test axvisor loongarch64 qemu | `ubuntu-latest` | 是(`axvisor-lvz`) | `test-axvisor-loongarch64` | `cargo xtask axvisor test qemu --arch loongarch64`,使用带 LVZ 支持的镜像 | @@ -112,13 +112,13 @@ StarryOS stress 测试条目保留在 workflow 中,但当前处于注释状态 ## Self-Hosted Runner 约定 -self-hosted runner 任务优先在 `rcore-os` 仓库内运行。带 `self_hosted_owner` 的 QEMU 测试在 fork PR 或非 `rcore-os` 仓库中会回退到 `ubuntu-latest` + 对应容器,避免没有对应 runner 时长时间排队。迁移到 self-hosted 的 QEMU 测试直接在原生 runner 环境中运行,不再套 Docker container。 +self-hosted runner 任务优先在 `rcore-os` 仓库内运行。带 `self_hosted_owner` 的任务在 fork PR 或非 `rcore-os` 仓库中会回退到 `ubuntu-latest` + 对应容器,避免没有对应 runner 时长时间排队。迁移到 self-hosted 的任务直接在原生 runner 环境中运行,不再套 Docker container。 现有 label 约定: | Label | 用途 | |-------|------| -| `self-hosted`, `linux`, `qcs` | ArceOS QEMU 测试、Axvisor aarch64/riscv64 QEMU | +| `self-hosted`, `linux`, `qcs` | clippy、std 测试、ArceOS QEMU 测试、Axvisor aarch64/riscv64 QEMU | | `self-hosted`, `linux`, `intel`, `kvm` | Axvisor x86_64 KVM 测试 | | `self-hosted`, `linux`, `board` | 物理板卡测试 | @@ -127,11 +127,9 @@ self-hosted runner 任务优先在 `rcore-os` 仓库内运行。带 `self_hosted | Cache Key | 使用 Job | 保存时机 | 说明 | |-----------|----------|----------|------| | `sync-lint` | Run sync-lint | `push` 事件 | xtask 与 sync-lint 工具链编译产物 | -| `clippy` | Run clippy | `push` 事件 | xtask 与目标 crate 编译产物 | -| `test-std` | Test with std | `push` 事件 | host std 测试编译产物 | | `test-axvisor-loongarch64` | Test axvisor loongarch64 QEMU | `push` 事件 | Axvisor loongarch64 编译产物 | | `test-starry-riscv64/aarch64/loongarch64/x86_64` | Test starry riscv64/aarch64/loongarch64/x86_64 QEMU | `push` 事件 | StarryOS QEMU 编译产物 | -| 无(`cache_key: ""`) | self-hosted runner job | - | 依赖 self-hosted runner 本地磁盘缓存,不使用 GitHub Actions cache | +| 无(`cache_key: ""`) | self-hosted runner job,包括 Run clippy 和 Test with std | - | 依赖 self-hosted runner 本地磁盘缓存,不使用 GitHub Actions cache | self-hosted runner 不设置 `cache_key`,避免 `Swatinem/rust-cache@v2` 的 post-job 清理影响 runner 上跨次运行自然积累的共享缓存。 @@ -141,7 +139,7 @@ CI 使用两个容器镜像: | 镜像 | Dockerfile | 用途 | |------|------------|------| -| `base` | `container/Dockerfile` | 常规 clippy、sync-lint、std 测试、StarryOS QEMU,以及 self-hosted QEMU 测试在 fork PR 或非 `rcore-os` 仓库中的回退环境 | +| `base` | `container/Dockerfile` | sync-lint、StarryOS QEMU,以及 clippy、std、self-hosted QEMU 测试在 fork PR 或非 `rcore-os` 仓库中的回退环境 | | `axvisor-lvz` | `container/Dockerfile.axvisor-lvz` | Axvisor loongarch64 QEMU,额外包含 LVZ 支持 | 基础镜像以 `ubuntu:24.04` 为底,内置 Rust 工具链、QEMU、musl cross-toolchain、libav、libudev 等依赖。容器内的 musl cross-toolchain 已通过 `PATH` 配置好,`reusable-command.yml` 会在 container job 启动时验证 QEMU user emulators 和 musl compiler 是否存在,不再在运行时动态下载。 From 6f8d618476ea9710764798ba36e0d480bbb91b4e Mon Sep 17 00:00:00 2001 From: wyatt-dai <138414578+wyatt-dai@users.noreply.github.com> Date: Wed, 27 May 2026 21:35:14 +0800 Subject: [PATCH 64/71] cvi_usb_camera: replace SpinNoIrq with ax_sync::Mutex to allow sleeping during init (#964) * cvi_usb_camera: replace SpinNoIrq with ax_sync::Mutex to allow sleeping during init * cvi_usb_camera: replace SpinNoIrq with ax_sync::Mutex to allow sleeping during init * fmt change * fmt change --- os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs b/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs index df48a44dd6..2b978d2908 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs @@ -2,12 +2,12 @@ use core::{any::Any, time::Duration}; use ax_config::plat::PHYS_VIRT_OFFSET; use ax_errno::AxError; -use ax_kspin::SpinNoIrq as Mutex; use ax_memory_addr::{PhysAddr, VirtAddr}; use ax_runtime::hal::{ mem::{phys_to_virt, virt_to_phys}, time::busy_wait, }; +use ax_sync::Mutex; use axfs_ng_vfs::{NodeFlags, VfsResult}; use sg200x_bsp::{ gpio::{Direction, GPIO, GPIO1_BASE}, From 7ec1b0c0a340566b4febfa0604cdfd689484426b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Wed, 27 May 2026 22:09:18 +0800 Subject: [PATCH 65/71] feat(riscv64): support dynamic platform on QEMU and SG2002 (#961) * Add RISC-V support and IRQ handling improvements - Implemented IRQ handling for RISC-V architecture, including local IRQ registration and handling. - Introduced a virtual IRQ injector for hypervisor support. - Enhanced the IRQ interface to accommodate RISC-V specific requirements. - Added PLIC (Platform-Level Interrupt Controller) support for RISC-V, including initialization and IRQ management. - Updated the build system to support RISC-V targets and dynamic platform features. - Modified various configuration files to enable RISC-V features and ensure compatibility with existing systems. - Improved QEMU configuration for RISC-V to handle dynamic root filesystem patches. * fix(axvisor): enable riscv64 qemu platform feature in CI * fix(axvisor): boot riscv64 dynamic hv * feat(riscv64): support dynamic platform drivers * fix(ax-driver): correct feature handling for plat-static and plat-dyn * fix(somehal): use runtime CPU id for riscv64 PLIC context * fix(build): replace pci driver feature with plat-dyn in configuration * Refactor platform feature configurations across various board TOML files - Removed "plat-dyn" features from multiple configurations to streamline platform feature management. - Updated related build scripts to ensure compatibility with the new feature structure. - Adjusted tests to reflect the removal of deprecated features and ensure proper functionality. - Consolidated platform feature handling in build scripts to improve clarity and maintainability. * fix(build): remove unnecessary parameter from build_cargo_args call * feat: add riscv_goldfish dependency and support for Goldfish RTC * refactor(smp): replace cpu_idx with early_current_cpu_idx for better boot-time CPU identification * feat(build): add starry-kernel/input feature to the build configuration * refactor(axruntime): remove alloc feature, make it unconditional (#985) alloc is needed by every consumer of axruntime. Remove the alloc feature entirely and make ax-alloc a required dependency. Propagating changes to axfeat to remove the now-unnecessary alloc forwarding from 15 features. Co-authored-by: Claude Opus 4.7 * Remove range-alloc-arceos crate and its associated files (#991) - Deleted LICENSE, LICENSE.APACHE, LICENSE.MIT, README.md, README_CN.md, and src/lib.rs files. - Removed tests and examples related to the range allocator functionality. * Refactor linker scripts and CI configuration (#992) * refactor(linker-scripts): remove unused linker scripts for hello, irq, and smp kernels * refactor(ci): switch clippy and std tests to self-hosted runners * feat(deps): update spin to version 0.12.0 and bump other dependencies * feat: remove riscv64-qemu-virt references and update platform support in configuration --------- Co-authored-by: Claude Opus 4.7 Co-authored-by: ZCShou <72115@163.com> --- Cargo.lock | 388 +++++++++-------- .../build-aarch64-unknown-none-softfloat.toml | 4 +- .../build-aarch64-unknown-none-softfloat.toml | 2 - components/axcpu/src/exception_table.rs | 24 +- components/axcpu/src/riscv/macros.rs | 10 +- components/someboot/Cargo.toml | 1 + components/someboot/build.rs | 3 +- components/someboot/src/acpi/mod.rs | 5 + .../someboot/src/arch/aarch64/paging/mod.rs | 2 +- .../someboot/src/arch/riscv64/addrspace.rs | 8 +- components/someboot/src/arch/riscv64/entry.rs | 6 +- components/someboot/src/arch/riscv64/link.ld | 17 +- components/someboot/src/arch/riscv64/mod.rs | 66 ++- .../someboot/src/arch/riscv64/paging.rs | 2 +- components/someboot/src/arch/riscv64/trap.rs | 8 +- components/someboot/src/arch/x86_64/paging.rs | 2 +- components/someboot/src/arch/x86_64/power.rs | 9 +- components/someboot/src/fdt/earlycon.rs | 2 +- components/someboot/src/lib.rs | 3 +- components/someboot/src/smp/mod.rs | 23 +- docs/docs/build/configuration.md | 4 +- drivers/ax-driver/Cargo.toml | 59 +-- drivers/ax-driver/build.rs | 37 +- drivers/ax-driver/src/block/cvsd.rs | 160 +++++++ drivers/ax-driver/src/block/mod.rs | 2 + drivers/ax-driver/src/lib.rs | 25 +- drivers/ax-driver/src/net/binding.rs | 13 +- drivers/ax-driver/src/pci/fdt.rs | 68 ++- drivers/ax-driver/src/pci/mod.rs | 16 +- drivers/ax-driver/src/serial/mod.rs | 4 +- drivers/ax-driver/src/soc/mod.rs | 2 + drivers/ax-driver/src/soc/sg2002.rs | 62 +++ drivers/ax-driver/src/time.rs | 51 ++- drivers/ax-driver/src/usb/mod.rs | 2 +- drivers/ax-driver/src/virtio/block.rs | 10 +- drivers/ax-driver/src/virtio/display.rs | 6 +- drivers/ax-driver/src/virtio/input.rs | 6 +- drivers/ax-driver/src/virtio/mod.rs | 2 +- drivers/ax-driver/src/virtio/net.rs | 6 +- drivers/ax-driver/src/virtio/vsock.rs | 6 +- drivers/intc/riscv_plic/src/lib.rs | 39 +- drivers/rdrive/src/probe/fdt/mod.rs | 16 + memory/page_table_entry/src/arch/riscv.rs | 7 +- .../configs/board/licheerv-nano-sg2002.toml | 8 +- .../configs/board/orangepi-5-plus.toml | 4 +- .../configs/board/qemu-aarch64-dyn.toml | 2 - os/StarryOS/configs/board/qemu-aarch64.toml | 2 +- .../configs/board/qemu-loongarch64.toml | 2 +- os/StarryOS/configs/board/qemu-riscv64.toml | 6 +- os/StarryOS/configs/board/qemu-x86_64.toml | 2 +- os/StarryOS/kernel/Cargo.toml | 1 + os/StarryOS/kernel/src/file/mod.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/mod.rs | 20 +- os/StarryOS/kernel/src/pseudofs/sysfs.rs | 6 +- os/StarryOS/kernel/src/syscall/mm/mmap.rs | 2 +- os/StarryOS/starryos/Cargo.toml | 7 + os/arceos/README.md | 2 +- os/arceos/api/axfeat/Cargo.toml | 1 - os/arceos/doc/ixgbe.md | 6 +- .../modules/axconfig/src/driver_dyn_config.rs | 58 ++- os/arceos/modules/axhal/Cargo.toml | 4 - os/arceos/modules/axhal/build.rs | 5 - os/arceos/modules/axruntime/Cargo.toml | 1 + os/arceos/modules/axruntime/src/devices.rs | 28 ++ os/arceos/modules/axruntime/src/lib.rs | 12 +- os/arceos/ulib/arceos-rust/lib/Cargo.toml | 4 +- os/axvisor/Cargo.toml | 1 - os/axvisor/configs/board/qemu-aarch64.toml | 1 - .../configs/board/qemu-loongarch64.toml | 2 +- os/axvisor/configs/board/qemu-riscv64.toml | 3 +- os/axvisor/configs/board/qemu-x86_64.toml | 2 +- os/axvisor/configs/board/rdk-s100.toml | 1 - os/axvisor/configs/board/tac-e400.toml | 1 - os/axvisor/src/hal/arch/riscv64/api.rs | 7 +- os/axvisor/src/hal/arch/riscv64/mod.rs | 7 +- .../ax-plat-aarch64-qemu-virt/Cargo.toml | 2 +- .../ax-plat-loongarch64-qemu-virt/Cargo.toml | 2 +- .../ax-plat-riscv64-qemu-virt/Cargo.toml | 2 +- platforms/ax-plat-riscv64-sg2002/Cargo.toml | 2 +- platforms/ax-plat-x86-pc/Cargo.toml | 2 +- platforms/ax-plat-x86-qemu-q35/Cargo.toml | 2 +- platforms/axplat-dyn/Cargo.toml | 3 +- platforms/axplat-dyn/src/boot.rs | 2 +- platforms/axplat-dyn/src/drivers/mod.rs | 4 + platforms/axplat-dyn/src/init.rs | 8 +- platforms/axplat-dyn/src/irq.rs | 143 ++++++- platforms/axplat-dyn/src/lib.rs | 2 + platforms/somehal/Cargo.toml | 6 + platforms/somehal/src/arch/aarch64/mod.rs | 2 +- platforms/somehal/src/arch/loongarch64/mod.rs | 2 +- platforms/somehal/src/arch/riscv64/mod.rs | 43 +- platforms/somehal/src/arch/riscv64/plic.rs | 393 ++++++++++++++++++ platforms/somehal/src/arch/x86_64/mod.rs | 2 +- platforms/somehal/src/common.rs | 11 +- platforms/somehal/src/driver.rs | 12 + platforms/somehal/src/irq.rs | 8 + platforms/somehal/src/lib.rs | 2 +- scripts/axbuild/src/arceos/build.rs | 7 +- scripts/axbuild/src/arceos/cbuild.rs | 6 +- scripts/axbuild/src/axvisor/board.rs | 1 + scripts/axbuild/src/axvisor/build.rs | 29 +- scripts/axbuild/src/build.rs | 33 +- scripts/axbuild/src/context/tests.rs | 2 +- scripts/axbuild/src/starry/build.rs | 135 ++++-- scripts/axbuild/src/starry/rootfs.rs | 111 ++++- scripts/axbuild/src/starry/test.rs | 23 +- scripts/repo/repos.csv | 112 ++--- .../pie/riscv64gc-unknown-none-elf.json | 36 ++ .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../httpclient/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../display/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../fs/shell/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../echoserver/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../httpclient/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../httpserver/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 2 +- .../udpserver/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 1 - .../build-aarch64-unknown-none-softfloat.toml | 1 - ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 3 +- .../qemu/build-x86_64-unknown-none.toml | 2 +- .../svm/qemu/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 3 - .../build-riscv64gc-unknown-none-elf.toml | 10 +- .../build-aarch64-unknown-none-softfloat.toml | 4 +- .../build-aarch64-unknown-none-softfloat.toml | 2 - .../qemu-dhcp/build-x86_64-unknown-none.toml | 2 +- .../normal/qemu-smp1/bugfix/qemu-aarch64.toml | 2 +- .../qemu-smp1/bugfix/qemu-loongarch64.toml | 2 +- .../normal/qemu-smp1/bugfix/qemu-riscv64.toml | 2 +- .../normal/qemu-smp1/bugfix/qemu-x86_64.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 3 - ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 9 +- .../qemu-smp1/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 3 - .../build-aarch64-unknown-none-softfloat.toml | 3 - ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 8 +- .../qemu-smp4/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 3 - ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 8 +- .../postgresql/build-x86_64-unknown-none.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 3 - ...ld-loongarch64-unknown-none-softfloat.toml | 2 +- .../build-riscv64gc-unknown-none-elf.toml | 8 +- .../build-x86_64-unknown-none.toml | 2 +- virtualization/riscv_vcpu/src/mem_extable.S | 6 +- 169 files changed, 2030 insertions(+), 684 deletions(-) create mode 100644 drivers/ax-driver/src/block/cvsd.rs create mode 100644 drivers/ax-driver/src/soc/sg2002.rs create mode 100644 platforms/somehal/src/arch/riscv64/plic.rs create mode 100644 scripts/targets/pie/riscv64gc-unknown-none-elf.json diff --git a/Cargo.lock b/Cargo.lock index c82c7ac7f9..a882b41b79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,7 +567,7 @@ dependencies = [ "nb", "num-align", "smccc", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -585,7 +585,7 @@ dependencies = [ "axvisor_api", "log", "numeric-enum-macro", - "spin", + "spin 0.12.0", ] [[package]] @@ -602,7 +602,7 @@ dependencies = [ "axvisor_api", "bitmaps", "log", - "spin", + "spin 0.12.0", "tock-registers 0.10.1", ] @@ -664,9 +664,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" @@ -901,15 +901,17 @@ dependencies = [ "rdrive", "rdrive-macros", "realtek-rtl8125", + "riscv_goldfish", "rk3588-pci", "rockchip-npu", "rockchip-pm", "rockchip-soc", "sdhci-host", "sdmmc-protocol", + "sg200x-bsp", "simple-ahci", "some-serial", - "spin", + "spin 0.12.0", "virtio-drivers", ] @@ -960,7 +962,7 @@ dependencies = [ "axfatfs", "log", "rsext4", - "spin", + "spin 0.12.0", ] [[package]] @@ -969,7 +971,7 @@ version = "0.3.11" dependencies = [ "ax-fs-vfs", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -995,7 +997,7 @@ dependencies = [ "rsext4", "scope-local", "slab", - "spin", + "spin 0.12.0", "starry-fatfs", ] @@ -1005,7 +1007,7 @@ version = "0.3.12" dependencies = [ "ax-fs-vfs", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -1036,16 +1038,16 @@ dependencies = [ "ax-plat-loongarch64-qemu-virt", "ax-plat-riscv64-qemu-virt", "ax-plat-riscv64-sg2002", + "ax-plat-riscv64-visionfive2", "ax-plat-x86-pc", + "ax-plat-x86-qemu-q35", "axplat-dyn", - "axplat-riscv64-visionfive2", - "axplat-x86-qemu-q35", "cfg-if", "fdt-parser", "heapless 0.9.3", "log", "rdrive", - "spin", + "spin 0.12.0", "toml 1.1.2+spec-1.1.0", ] @@ -1231,7 +1233,7 @@ dependencies = [ "cfg-if", "log", "smoltcp 0.13.1", - "spin", + "spin 0.12.0", ] [[package]] @@ -1260,7 +1262,7 @@ dependencies = [ "rdif-vsock", "ringbuf", "smoltcp 0.13.1", - "spin", + "spin 0.12.0", ] [[package]] @@ -1295,7 +1297,7 @@ dependencies = [ "ax-kernel-guard", "ax-percpu-macros", "cfg-if", - "spin", + "spin 0.12.0", "x86", ] @@ -1352,7 +1354,7 @@ dependencies = [ "ax-plat", "log", "some-serial", - "spin", + "spin 0.12.0", ] [[package]] @@ -1462,7 +1464,25 @@ dependencies = [ "sbi-rt 0.0.3", "sg200x-bsp", "some-serial", - "spin", + "spin 0.12.0", +] + +[[package]] +name = "ax-plat-riscv64-visionfive2" +version = "0.1.4" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-kspin", + "ax-lazyinit", + "ax-plat", + "ax-riscv-plic", + "log", + "rdrive", + "riscv 0.14.0", + "riscv_goldfish", + "sbi-rt 0.0.3", + "uart_16550 0.4.0", ] [[package]] @@ -1490,6 +1510,31 @@ dependencies = [ "x86_rtc", ] +[[package]] +name = "ax-plat-x86-qemu-q35" +version = "0.4.8" +dependencies = [ + "ax-config-macros", + "ax-cpu", + "ax-driver", + "ax-int-ratio", + "ax-kspin", + "ax-lazyinit", + "ax-percpu", + "ax-plat", + "axklib", + "bitflags 2.11.1", + "heapless 0.9.3", + "log", + "multiboot", + "raw-cpuid 11.6.0", + "uart_16550 0.4.0", + "x2apic", + "x86", + "x86_64", + "x86_rtc", +] + [[package]] name = "ax-posix-api" version = "0.5.16" @@ -1509,7 +1554,7 @@ dependencies = [ "bindgen 0.72.1", "flatten_objects", "scope-local", - "spin", + "spin 0.12.0", ] [[package]] @@ -1553,7 +1598,7 @@ dependencies = [ "rd-block", "rd-net", "rdrive", - "spin", + "spin 0.12.0", ] [[package]] @@ -1583,7 +1628,7 @@ dependencies = [ "ax-kspin", "ax-lazyinit", "lock_api", - "spin", + "spin 0.12.0", ] [[package]] @@ -1622,7 +1667,7 @@ dependencies = [ "extern-trait", "futures-util", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -1670,7 +1715,7 @@ dependencies = [ "gimli 0.33.0", "log", "paste", - "spin", + "spin 0.12.0", ] [[package]] @@ -1829,50 +1874,7 @@ dependencies = [ "log", "rdrive", "somehal", - "spin", -] - -[[package]] -name = "axplat-riscv64-visionfive2" -version = "0.1.4" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-kspin", - "ax-lazyinit", - "ax-plat", - "ax-riscv-plic", - "log", - "rdrive", - "riscv 0.14.0", - "riscv_goldfish", - "sbi-rt 0.0.3", - "uart_16550 0.4.0", -] - -[[package]] -name = "axplat-x86-qemu-q35" -version = "0.4.8" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-driver", - "ax-int-ratio", - "ax-kspin", - "ax-lazyinit", - "ax-percpu", - "ax-plat", - "axklib", - "bitflags 2.11.1", - "heapless 0.9.3", - "log", - "multiboot", - "raw-cpuid 11.6.0", - "uart_16550 0.4.0", - "x2apic", - "x86", - "x86_64", - "x86_rtc", + "spin 0.12.0", ] [[package]] @@ -1883,7 +1885,7 @@ dependencies = [ "bitflags 2.11.1", "futures", "linux-raw-sys 0.12.1", - "spin", + "spin 0.12.0", "tokio", ] @@ -1971,7 +1973,7 @@ dependencies = [ "ax-page-table-multiarch", "ax-percpu", "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", + "ax-plat-x86-qemu-q35", "ax-std", "ax-timer-list", "axaddrspace", @@ -1981,7 +1983,6 @@ dependencies = [ "axhvc", "axklib", "axplat-dyn", - "axplat-x86-qemu-q35", "axvcpu", "axvisor_api", "axvm", @@ -2000,7 +2001,7 @@ dependencies = [ "rdrive", "riscv_vcpu", "riscv_vplic", - "spin", + "spin 0.12.0", "syn 2.0.117", "tokio", "toml 0.9.12+spec-1.1.0", @@ -2050,7 +2051,7 @@ dependencies = [ "log", "loongarch_vcpu", "riscv_vcpu", - "spin", + "spin 0.12.0", "x86_vcpu", ] @@ -2308,7 +2309,7 @@ dependencies = [ "divan", "log", "rand 0.10.1", - "spin", + "spin 0.12.0", ] [[package]] @@ -2323,14 +2324,14 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672b945a3e4f4f40bfd4cd5ee07df9e796a42254ce7cd6d2599ad969244c44a" dependencies = [ - "spin", + "spin 0.10.0", ] [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bwbench-client" @@ -2643,9 +2644,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "7fd622ebbb56a5b2ccb651b32b911cdeb2a9b4b11776b2473bf26a26a286244e" dependencies = [ "castaway", "cfg-if", @@ -2657,9 +2658,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -2823,7 +2824,7 @@ dependencies = [ "mbarrier", "nb", "num_enum", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", "trait-ffi", @@ -3037,9 +3038,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -3121,7 +3122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321ec774d27fafc66e812034d0025f8858bd7d9095304ff8fc200e0b9f9cc257" dependencies = [ "ahash 0.8.12", - "compact_str 0.8.1", + "compact_str 0.8.2", "crossbeam-channel", "cursive-macros", "enum-map", @@ -3381,14 +3382,14 @@ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid", - "crypto-common 0.2.1", + "crypto-common 0.2.2", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -3505,9 +3506,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embedded-graphics" @@ -3629,9 +3630,9 @@ dependencies = [ [[package]] name = "enumset" -version = "1.1.12" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96a4a12fe60ac746ae295a1a4ecb5bb02debc20856506c8635288065f142de" +checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d" dependencies = [ "enumset_derive", ] @@ -3884,9 +3885,9 @@ checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" [[package]] name = "fitimage" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb4d07a1e7a76a7ac4b6b754d45670b1a82c8a0484d80cceeb30c99ad65ce1d" +checksum = "92edd7c1c55efd27b5a17dd6d505affafcfe48dfaed7423d4c4ae7361e56a7d8" dependencies = [ "anyhow", "byteorder", @@ -4063,9 +4064,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" @@ -4181,9 +4182,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "goblin" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" +checksum = "0d494b2004fbc8cf419a6d2115488df4e11140f6f4abd877519de1bbd90c5370" dependencies = [ "log", "plain", @@ -4311,18 +4312,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hello-kernel" -version = "0.3.1" -dependencies = [ - "ax-plat", - "ax-plat-aarch64-qemu-virt", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-pc", - "cfg-if", -] - [[package]] name = "hermit-abi" version = "0.5.2" @@ -4337,9 +4326,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -4734,20 +4723,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "irq-kernel" -version = "0.3.1" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-plat", - "ax-plat-aarch64-qemu-virt", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-pc", - "cfg-if", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -4813,9 +4788,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "392c70591e8749fe235ddaf513e6f58b26bce3dcc16524cecc8936f75afa161e" dependencies = [ "jiff-static", "log", @@ -4826,9 +4801,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "47b605b0c050d845fc355bb11eb3f9a8deddc218ea60c76e61aa1f2adfb2c96a" dependencies = [ "proc-macro2", "quote", @@ -4860,9 +4835,9 @@ dependencies = [ [[package]] name = "jkconfig" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8746ebf553e6e9d0620a918072432b44aa9563f6f19d17432b613828df708a1" +checksum = "d4cbaedffb63dabcbab61e23e0adc1fc7bc0d7420f2f56124e2bc0002d9274ab" dependencies = [ "anyhow", "cargo_metadata", @@ -4948,9 +4923,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -5077,6 +5052,12 @@ dependencies = [ "yaxpeax-x86", ] +[[package]] +name = "ksym" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b543b77d8cfa33302b6b51b6a255f9e85b06997b7c4a0a7354fcc8304036eb09" + [[package]] name = "ktest-helper" version = "0.1.1" @@ -5254,9 +5235,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "loongArch64" @@ -5393,9 +5374,9 @@ checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "memmap2" @@ -5606,9 +5587,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -5728,7 +5709,7 @@ dependencies = [ "mmio-api", "pcie", "rd-block", - "spin", + "spin 0.12.0", "tock-registers 0.10.1", ] @@ -5800,9 +5781,9 @@ dependencies = [ [[package]] name = "ostool" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f785cb91e2622849c3f0e55d5363989934d365967c06f0a8ce8fd944f3b834ba" +checksum = "c4d0470fd8561e21f458d865490c928ebff45c0e1037a45f634a527acb8f15ff" dependencies = [ "anyhow", "async-trait", @@ -5816,7 +5797,7 @@ dependencies = [ "fitimage", "futures", "indicatif", - "jkconfig 0.2.3", + "jkconfig 0.2.4", "log", "lzma-rs", "network-interface", @@ -6395,7 +6376,7 @@ dependencies = [ "dma-api", "rd-block", "rdif-block", - "spin", + "spin 0.12.0", ] [[package]] @@ -6503,7 +6484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.11.1", - "compact_str 0.9.0", + "compact_str 0.9.1", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", @@ -6721,7 +6702,7 @@ dependencies = [ "futures", "heapless 0.9.3", "rdif-base", - "spin", + "spin 0.12.0", "thiserror 2.0.18", ] @@ -6762,7 +6743,7 @@ dependencies = [ "rdif-intc", "rdif-pcie", "rdrive-macros", - "spin", + "spin 0.12.0", "thiserror 2.0.18", ] @@ -6783,7 +6764,7 @@ dependencies = [ "log", "mmio-api", "rdif-eth", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -6863,9 +6844,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -6950,6 +6931,19 @@ dependencies = [ "riscv-pac", ] +[[package]] +name = "riscv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros 0.3.0", + "riscv-pac", +] + [[package]] name = "riscv" version = "0.16.0" @@ -6991,6 +6985,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "riscv-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "riscv-macros" version = "0.4.0" @@ -7122,7 +7127,7 @@ dependencies = [ "mbarrier", "num-align", "rdif-base", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7149,7 +7154,7 @@ dependencies = [ "log", "mbarrier", "num-align", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7160,7 +7165,7 @@ version = "0.4.1" dependencies = [ "bitflags 2.11.1", "log", - "spin", + "spin 0.12.0", ] [[package]] @@ -7448,7 +7453,7 @@ version = "0.3.7" dependencies = [ "ax-percpu", "ctor 0.6.3", - "spin", + "spin 0.12.0", ] [[package]] @@ -7579,9 +7584,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -7855,23 +7860,6 @@ dependencies = [ "managed", ] -[[package]] -name = "smp-kernel" -version = "0.3.1" -dependencies = [ - "ax-config-macros", - "ax-cpu", - "ax-memory-addr", - "ax-percpu", - "ax-plat", - "ax-plat-aarch64-qemu-virt", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-riscv64-qemu-virt", - "ax-plat-x86-pc", - "cfg-if", - "const-str", -] - [[package]] name = "socket2" version = "0.6.3" @@ -7935,7 +7923,7 @@ dependencies = [ "smccc", "some-serial", "somehal-macros", - "spin", + "spin 0.12.0", "syn 2.0.117", "thiserror 2.0.18", "tock-registers 0.10.1", @@ -7951,15 +7939,18 @@ dependencies = [ "aarch64-cpu 11.2.0", "anyhow", "arm-gic-driver", + "ax-riscv-plic", "kernutil", "log", "mmio-api", "page-table-generic", "rdif-intc", "rdrive", + "riscv 0.15.0", + "sbi-rt 0.0.3", "someboot", "somehal-macros", - "spin", + "spin 0.12.0", "thiserror 2.0.18", "tock-registers 0.10.1", ] @@ -7979,6 +7970,12 @@ name = "spin" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + +[[package]] +name = "spin" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1527984ca054dfca79333baec451042863f485fbee01b7bf6d911de915cac865" dependencies = [ "lock_api", ] @@ -8077,6 +8074,7 @@ dependencies = [ "kernel-elf-parser", "kmod-loader", "kprobe", + "ksym", "ktracepoint", "linux-raw-sys 0.12.1", "lock_api", @@ -8091,7 +8089,7 @@ dependencies = [ "sg200x-bsp", "slab", "some-serial", - "spin", + "spin 0.12.0", "starry-process", "starry-signal", "starry-vm", @@ -8326,9 +8324,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" dependencies = [ "filetime", "libc", @@ -9086,9 +9084,9 @@ dependencies = [ [[package]] name = "uboot-shell" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b3d5d2959cc2e9a28cb2c4ad30f99a33f6831f4f159b08c696604b9494b672" +checksum = "2237321472e37a34980340e0a6f8ba406167fb010209ead2f0e594bdfb718e02" dependencies = [ "colored", "futures", @@ -9286,7 +9284,7 @@ dependencies = [ "futures", "log", "num_enum", - "spin", + "spin 0.12.0", "thiserror 2.0.18", ] @@ -9499,9 +9497,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -9512,9 +9510,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -9522,9 +9520,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -9532,9 +9530,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -9545,9 +9543,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -9607,9 +9605,9 @@ checksum = "dba9c05687e501b2710833fbe2cc7ff8cc24411fb0d81967498ca44598942f88" [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -10318,9 +10316,9 @@ dependencies = [ [[package]] name = "yaxpeax-x86" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9a30b7dd533c7b1a73eaf7c4ea162a7a632a2bb29b9fff47d8f2cc8513a883" +checksum = "a159e15f66ce40b5dfc28213366f8ff42ff8f0508e2e72e431c62573f025ac5e" dependencies = [ "cfg-if", "num-traits", diff --git a/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml b/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml index f9a746df72..97f221b4a9 100644 --- a/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml +++ b/apps/starry/orangepi-5-plus-uvc-rknn/build-aarch64-unknown-none-softfloat.toml @@ -2,10 +2,8 @@ target = "aarch64-unknown-none-softfloat" env = {} features = [ - "ax-hal/plat-dyn", - "starry-kernel/plat-dyn", "ax-driver/irq", - "ax-driver/pci-list-devices", + "ax-driver/list-pci-devices", "ax-driver/rk3588-pcie", "ax-driver/realtek-rtl8125", "ax-driver/rockchip-soc", diff --git a/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml b/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml index a931ef284e..ef3f84f4a4 100644 --- a/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml +++ b/apps/starry/orangepi-5-plus-uvc/build-aarch64-unknown-none-softfloat.toml @@ -2,8 +2,6 @@ target = "aarch64-unknown-none-softfloat" env = {} features = [ - "ax-hal/plat-dyn", - "starry-kernel/plat-dyn", "ax-driver/rockchip-soc", "ax-driver/rockchip-dwc-xhci", "ax-driver/rockchip-sdhci", diff --git a/components/axcpu/src/exception_table.rs b/components/axcpu/src/exception_table.rs index cfa5bbe81f..aa475cc2d7 100644 --- a/components/axcpu/src/exception_table.rs +++ b/components/axcpu/src/exception_table.rs @@ -3,13 +3,13 @@ use crate::TrapFrame; #[repr(C)] #[derive(Debug, PartialEq, Eq)] struct ExceptionTableEntry { - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] from: i32, - #[cfg(target_arch = "aarch64")] + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] to: i32, - #[cfg(not(target_arch = "aarch64"))] + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] from: usize, - #[cfg(not(target_arch = "aarch64"))] + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] to: usize, } @@ -22,7 +22,13 @@ impl ExceptionTableEntry { (base + self.from as isize) as usize } - #[cfg(not(target_arch = "aarch64"))] + #[cfg(target_arch = "riscv64")] + { + let base = unsafe { _ex_table_start.as_ptr() } as isize; + (base + self.from as isize) as usize + } + + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] { self.from } @@ -36,7 +42,13 @@ impl ExceptionTableEntry { (base + self.to as isize) as usize } - #[cfg(not(target_arch = "aarch64"))] + #[cfg(target_arch = "riscv64")] + { + let base = unsafe { _ex_table_start.as_ptr() } as isize; + (base + self.to as isize) as usize + } + + #[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] { self.to } diff --git a/components/axcpu/src/riscv/macros.rs b/components/axcpu/src/riscv/macros.rs index cd1cb822f9..35d75bedda 100644 --- a/components/axcpu/src/riscv/macros.rs +++ b/components/axcpu/src/riscv/macros.rs @@ -15,8 +15,8 @@ macro_rules! __asm_macros { .macro _asm_extable, from, to .pushsection __ex_table, "a" .balign 4 - .word \from - .word \to + .word \from - _ex_table_start + .word \to - _ex_table_start .popsection .endm @@ -40,9 +40,9 @@ macro_rules! __asm_macros { .macro _asm_extable, from, to .pushsection __ex_table, "a" - .balign 8 - .quad \from - .quad \to + .balign 4 + .word \from - _ex_table_start + .word \to - _ex_table_start .popsection .endm diff --git a/components/someboot/Cargo.toml b/components/someboot/Cargo.toml index e69b8a700e..79dfb26c82 100644 --- a/components/someboot/Cargo.toml +++ b/components/someboot/Cargo.toml @@ -14,6 +14,7 @@ efi = [] hv = [] mmu = [] percpu-prealloc = [] +thead-mae = [] uspace = ["mmu"] [dependencies] diff --git a/components/someboot/build.rs b/components/someboot/build.rs index 754c099381..923cae1a8a 100644 --- a/components/someboot/build.rs +++ b/components/someboot/build.rs @@ -40,7 +40,6 @@ fn main() { } else if build.uspace { println!("cargo:rustc-cfg=uspace"); } - if build.page_size == 4096 { println!("cargo:rustc-cfg=page_size_4k"); } else if build.page_size == 16384 { @@ -158,7 +157,7 @@ impl Build { fn prepare_riscv64(&mut self) { let ld_src = "src/arch/riscv64/link.ld"; - if self.uspace { + if self.uspace || self.hv { self.kernel_vaddr = 0xffff_ffff_8000_0000; } else { self.kernel_vaddr = 0x8020_0000; diff --git a/components/someboot/src/acpi/mod.rs b/components/someboot/src/acpi/mod.rs index 4eb8a6ecb8..dfd5deab6a 100644 --- a/components/someboot/src/acpi/mod.rs +++ b/components/someboot/src/acpi/mod.rs @@ -35,6 +35,11 @@ fn rsdp() -> Option> { NonNull::new(ptr) } +pub fn rsdp_addr_phys() -> Option { + let rsdp = unsafe { RSDP }; + (rsdp != 0).then_some(rsdp) +} + pub fn tables() -> Result, acpi::AcpiError> { unsafe { let rsdp = if RSDP == 0 { diff --git a/components/someboot/src/arch/aarch64/paging/mod.rs b/components/someboot/src/arch/aarch64/paging/mod.rs index bb10fe2e97..ae1bda059c 100644 --- a/components/someboot/src/arch/aarch64/paging/mod.rs +++ b/components/someboot/src/arch/aarch64/paging/mod.rs @@ -26,7 +26,7 @@ pub fn enable_mmu() -> ! { let mmu_entry_phys = super::entry::mmu_entry as *const () as usize; println!("MMU Entry point at physical address: {:#x}", mmu_entry_phys); - let meta = crate::smp::cpu_meta(crate::smp::cpu_idx()).unwrap(); + let meta = crate::smp::cpu_meta(crate::smp::early_current_cpu_idx()).unwrap(); let v_sp = meta.stack_top_virt; let v_entry = __kimage_va(mmu_entry_phys) as usize; diff --git a/components/someboot/src/arch/riscv64/addrspace.rs b/components/someboot/src/arch/riscv64/addrspace.rs index 7f44c598a8..0614261afe 100644 --- a/components/someboot/src/arch/riscv64/addrspace.rs +++ b/components/someboot/src/arch/riscv64/addrspace.rs @@ -1,11 +1,11 @@ include!(concat!(env!("OUT_DIR"), "/defines.rs")); -#[cfg(uspace)] +#[cfg(any(uspace, hv))] pub const PAGE_OFFSET: usize = 0xffff_ffc0_0000_0000; -#[cfg(not(uspace))] +#[cfg(not(any(uspace, hv)))] pub const PAGE_OFFSET: usize = 0; -#[cfg(uspace)] +#[cfg(any(uspace, hv))] pub const PERCPU_BASE: usize = 0xffff_ffe0_0000_0000; -#[cfg(not(uspace))] +#[cfg(not(any(uspace, hv)))] pub const PERCPU_BASE: usize = PAGE_OFFSET; diff --git a/components/someboot/src/arch/riscv64/entry.rs b/components/someboot/src/arch/riscv64/entry.rs index 2fc518788d..da988f33c7 100644 --- a/components/someboot/src/arch/riscv64/entry.rs +++ b/components/someboot/src/arch/riscv64/entry.rs @@ -50,7 +50,8 @@ pub unsafe extern "C" fn kernel_entry(_hart_id: usize, _fdt_addr: usize) -> ! { "mv t0, a1", "lla sp, __cpu0_stack_top", "mv a0, t0", - "j {primary_head_entry}", + "lla t1, {primary_head_entry}", + "jr t1", primary_head_entry = sym primary_head_entry, ) } @@ -98,7 +99,8 @@ pub(crate) unsafe extern "C" fn _secondary_entry(_hartid: usize, _cpu_meta_paddr "mv t0, a1", "ld sp, {stack_top_offset}(t0)", "mv a0, t0", - "j {secondary_start}", + "lla t1, {secondary_start}", + "jr t1", secondary_start = sym secondary_start, stack_top_offset = const offset_of!(PerCpuMeta, stack_top), ) diff --git a/components/someboot/src/arch/riscv64/link.ld b/components/someboot/src/arch/riscv64/link.ld index 27148429ae..e0eb04c756 100644 --- a/components/someboot/src/arch/riscv64/link.ld +++ b/components/someboot/src/arch/riscv64/link.ld @@ -1,5 +1,5 @@ PROVIDE(PAGE_SIZE = 0x1000); -PROVIDE(STACK_SIZE = 0x4000); +PROVIDE(STACK_SIZE = 0x40000); VM_LOAD_ADDRESS = ${kernel_load_vaddr}; OUTPUT_ARCH(riscv) @@ -54,6 +54,21 @@ SECTIONS _edata = .; __kernel_load_end = .; __bss_start = .; + + .tdata : { + _stdata = .; + *(.tdata .tdata.*) + _etdata = .; + } + + .tbss : { + _stbss = .; + *(.tbss .tbss.*) + *(.init.bss) + *(.tcommon) + _etbss = .; + } + .sbss : { *(.dynsbss) *(.sbss .sbss.*) diff --git a/components/someboot/src/arch/riscv64/mod.rs b/components/someboot/src/arch/riscv64/mod.rs index d96cbf685a..74f4858680 100644 --- a/components/someboot/src/arch/riscv64/mod.rs +++ b/components/someboot/src/arch/riscv64/mod.rs @@ -13,7 +13,7 @@ mod trap; use core::sync::atomic::{AtomicUsize, Ordering}; pub(crate) use entry::_secondary_entry; -use page_table_generic::{PageTableEntry, PhysAddr, PteConfig, TableMeta, VirtAddr}; +use page_table_generic::{MemAttributes, PageTableEntry, PhysAddr, PteConfig, TableMeta, VirtAddr}; pub use relocate::apply as relocate; use crate::{ @@ -21,7 +21,7 @@ use crate::{ mem::{PageTableInfo, mmu}, power::CpuOnError, }; -#[cfg(uspace)] +#[cfg(any(uspace, hv))] use crate::{mem::__kimage_va_to_pa, smp::percpu_va_range}; const KERNEL_LOAD_ADDRESS: usize = 0x8020_0000; @@ -37,10 +37,59 @@ const PTE_U: usize = 1 << 4; const PTE_G: usize = 1 << 5; const PTE_A: usize = 1 << 6; const PTE_D: usize = 1 << 7; +const PTE_PPN_MASK: usize = (1 << 44) - 1; + +#[cfg(feature = "thead-mae")] +const PTE_THEAD_SEC: usize = 1 << 59; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_SH: usize = 1 << 60; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_B: usize = 1 << 61; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_C: usize = 1 << 62; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_SO: usize = 1 << 63; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_PMA: usize = PTE_THEAD_C | PTE_THEAD_B | PTE_THEAD_SH; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_NOCACHE: usize = PTE_THEAD_B | PTE_THEAD_SH; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_IO: usize = PTE_THEAD_SO | PTE_THEAD_SH; +#[cfg(feature = "thead-mae")] +const PTE_THEAD_MT_MASK: usize = + PTE_THEAD_SEC | PTE_THEAD_SH | PTE_THEAD_B | PTE_THEAD_C | PTE_THEAD_SO; static KERNEL_PAGE_TABLE_ADDR: AtomicUsize = AtomicUsize::new(0); static TIMEBASE_FREQ: AtomicUsize = AtomicUsize::new(0); +#[cfg(feature = "thead-mae")] +fn thead_mae_pte_bits(mem_attr: MemAttributes) -> usize { + match mem_attr { + MemAttributes::Device => PTE_THEAD_IO, + MemAttributes::Uncached => PTE_THEAD_NOCACHE, + MemAttributes::Normal | MemAttributes::PerCpu => PTE_THEAD_PMA, + } +} + +#[cfg(not(feature = "thead-mae"))] +fn thead_mae_pte_bits(_mem_attr: MemAttributes) -> usize { + 0 +} + +#[cfg(feature = "thead-mae")] +fn thead_mae_mem_attr(bits: usize) -> MemAttributes { + match bits & PTE_THEAD_MT_MASK { + PTE_THEAD_IO => MemAttributes::Device, + PTE_THEAD_NOCACHE => MemAttributes::Uncached, + _ => MemAttributes::Normal, + } +} + +#[cfg(not(feature = "thead-mae"))] +fn thead_mae_mem_attr(_bits: usize) -> MemAttributes { + MemAttributes::Normal +} + #[derive(Clone, Copy, Debug, Default)] #[repr(transparent)] pub struct Entry(usize); @@ -75,9 +124,10 @@ impl PageTableEntry for Entry { if config.writable || config.dirty { bits |= PTE_D; } + bits |= thead_mae_pte_bits(config.mem_attr); } - bits |= (config.paddr.raw() >> 12) << SV39_PPN_SHIFT; + bits |= ((config.paddr.raw() >> 12) & PTE_PPN_MASK) << SV39_PPN_SHIFT; Self(bits) } @@ -91,7 +141,7 @@ impl PageTableEntry for Entry { let global = (bits & PTE_G) != 0; let dirty = (bits & PTE_D) != 0; let huge = is_dir && (read || writable || executable); - let paddr = PhysAddr::new((bits >> SV39_PPN_SHIFT) << 12); + let paddr = PhysAddr::new(((bits >> SV39_PPN_SHIFT) & PTE_PPN_MASK) << 12); PteConfig { paddr, @@ -104,7 +154,7 @@ impl PageTableEntry for Entry { global, is_dir, huge, - mem_attr: Default::default(), + mem_attr: thead_mae_mem_attr(bits), } } @@ -176,7 +226,7 @@ impl ArchTrait for Arch { fn virt_to_phys(vaddr: *const u8) -> usize { let vaddr = vaddr as usize; - #[cfg(uspace)] + #[cfg(any(uspace, hv))] { if mmu::is_mmu_enabled() { if percpu_va_range().contains(&vaddr) { @@ -237,10 +287,6 @@ impl ArchTrait for Arch { } fn cpu_on(hartid: usize, entry: usize, arg: usize) -> Result<(), CpuOnError> { - if hartid == Self::cpu_current_hartid() { - return Err(CpuOnError::AlreadyOn); - } - match sbi::hart_start(hartid, entry, arg) { Ok(()) => Ok(()), Err(sbi::HartStartError::AlreadyAvailable | sbi::HartStartError::AlreadyStarted) => { diff --git a/components/someboot/src/arch/riscv64/paging.rs b/components/someboot/src/arch/riscv64/paging.rs index 75fc4aa912..dff0fa8741 100644 --- a/components/someboot/src/arch/riscv64/paging.rs +++ b/components/someboot/src/arch/riscv64/paging.rs @@ -15,7 +15,7 @@ pub fn enable_mmu() -> ! { } let mmu_entry_phys = super::entry::mmu_entry as *const () as usize; - let meta = crate::smp::cpu_meta(crate::smp::cpu_idx()).unwrap(); + let meta = crate::smp::cpu_meta(crate::smp::early_current_cpu_idx()).unwrap(); let v_sp = meta.stack_top_virt; let v_entry = __kimage_va(mmu_entry_phys) as usize; diff --git a/components/someboot/src/arch/riscv64/trap.rs b/components/someboot/src/arch/riscv64/trap.rs index e1fdae1d8d..4149eea46b 100644 --- a/components/someboot/src/arch/riscv64/trap.rs +++ b/components/someboot/src/arch/riscv64/trap.rs @@ -3,7 +3,6 @@ use core::{arch::global_asm, mem::offset_of}; use crate::irq; const SCAUSE_INTERRUPT_BIT: usize = 1usize << (usize::BITS as usize - 1); -const SCAUSE_SUPERVISOR_TIMER: usize = 5; #[repr(C)] struct TrapFrame { @@ -66,11 +65,8 @@ pub fn trap_addr() -> usize { extern "C" fn __riscv64_handle_trap(tf: &mut TrapFrame) { let scause = tf.scause; if (scause & SCAUSE_INTERRUPT_BIT) != 0 { - let cause = scause & !SCAUSE_INTERRUPT_BIT; - if cause == SCAUSE_SUPERVISOR_TIMER { - irq::handle_irq(irq::systimer_irq()); - return; - } + irq::handle_irq(irq::IrqId::new(scause)); + return; } panic!( diff --git a/components/someboot/src/arch/x86_64/paging.rs b/components/someboot/src/arch/x86_64/paging.rs index bcac10ab2a..181fa943b5 100644 --- a/components/someboot/src/arch/x86_64/paging.rs +++ b/components/someboot/src/arch/x86_64/paging.rs @@ -124,7 +124,7 @@ pub fn enable_mmu() -> ! { panic!("failed to setup x86_64 page table: {err:?}"); } - let meta = crate::smp::cpu_meta(crate::smp::cpu_idx()).unwrap(); + let meta = crate::smp::cpu_meta(crate::smp::early_current_cpu_idx()).unwrap(); let v_sp = meta.stack_top_virt; let v_entry = __kimage_va(super::entry::mmu_entry as *const () as usize) as usize; diff --git a/components/someboot/src/arch/x86_64/power.rs b/components/someboot/src/arch/x86_64/power.rs index 92948b482f..504e1b3846 100644 --- a/components/someboot/src/arch/x86_64/power.rs +++ b/components/someboot/src/arch/x86_64/power.rs @@ -132,8 +132,15 @@ pub(crate) fn notify_ap_started(apic_id: usize) { AP_BOOTED_ID.store(apic_id, Ordering::Release); } +fn current_apic_id() -> usize { + x86::cpuid::CpuId::new() + .get_feature_info() + .map(|info| info.initial_local_apic_id() as usize) + .unwrap_or(0) +} + pub(crate) fn cpu_on(apic_id: usize, entry: usize, arg: usize) -> Result<(), CpuOnError> { - if apic_id == crate::smp::cpu_hart_id() { + if apic_id == current_apic_id() { return Err(CpuOnError::AlreadyOn); } let apic_id = u8::try_from(apic_id).map_err(|_| CpuOnError::InvalidParameters)?; diff --git a/components/someboot/src/fdt/earlycon.rs b/components/someboot/src/fdt/earlycon.rs index a3fab04bfe..539b04a669 100644 --- a/components/someboot/src/fdt/earlycon.rs +++ b/components/someboot/src/fdt/earlycon.rs @@ -59,7 +59,7 @@ fn set_by_stdout() -> Option<()> { installed = true; break; } - "snps,dw-apb-uart" => { + "snps,dw-apb-uart" | "ns16550a" | "ns16550" => { let mut serial = ns16550::Ns16550::new_mmio(addr, clock, reg_width); serial.open(); let tx = serial.take_tx()?; diff --git a/components/someboot/src/lib.rs b/components/someboot/src/lib.rs index 14f1c1b6e6..c956b65b82 100644 --- a/components/someboot/src/lib.rs +++ b/components/someboot/src/lib.rs @@ -47,6 +47,7 @@ pub mod power; pub mod smp; pub mod timer; +pub use acpi::rsdp_addr_phys; pub use fdt::{fdt_addr, fdt_addr_phys}; pub use page_table_generic::*; pub use somehal_macros::{entry, irq_handler, someboot_secondary_entry as secondary_entry}; @@ -176,7 +177,7 @@ fn prime_entry() -> ! { } let entry = __someboot_main as *const () as usize; - let sp = crate::smp::cpu_meta(crate::smp::cpu_idx()) + let sp = crate::smp::cpu_meta(crate::smp::early_current_cpu_idx()) .unwrap() .stack_top; let sp = __percpu(sp); diff --git a/components/someboot/src/smp/mod.rs b/components/someboot/src/smp/mod.rs index c7d4e803a0..75e7d14929 100644 --- a/components/someboot/src/smp/mod.rs +++ b/components/someboot/src/smp/mod.rs @@ -104,18 +104,23 @@ pub fn percpu_data_ptr(idx: usize) -> Option<*mut u8> { layout::percpu_data_ptr(idx) } -pub fn cpu_hart_id() -> usize { +/// Returns the current hardware CPU ID from the early boot register convention. +/// +/// On RISC-V this reads `tp`, which is only a boot-time hart ID scratch +/// register before handing control to the runtime. Runtime code must use its +/// own per-CPU current-CPU reader instead. +pub fn early_current_hart_id() -> usize { Arch::cpu_current_hartid() } -pub fn cpu_idx() -> usize { - let hart_id = cpu_hart_id(); - for (idx, id) in __cpu_id_list().enumerate() { - if id == hart_id { - return idx; - } - } - panic!("Current CPU hart id {hart_id:#x} not found in CPU list"); +pub fn early_current_cpu_idx() -> usize { + let hart_id = early_current_hart_id(); + cpu_id_to_idx(hart_id) + .unwrap_or_else(|| panic!("Current CPU hart id {hart_id:#x} not found in CPU list")) +} + +pub fn try_early_cpu_idx() -> Option { + cpu_id_to_idx(early_current_hart_id()) } pub fn cpu_id_to_idx(hart_id: usize) -> Option { diff --git a/docs/docs/build/configuration.md b/docs/docs/build/configuration.md index 69d5c27e51..590b1d51e0 100644 --- a/docs/docs/build/configuration.md +++ b/docs/docs/build/configuration.md @@ -64,7 +64,7 @@ flowchart TD 除默认值差异外,各架构还有一些需要注意的特殊行为: -- **plat_dyn**:仅 `aarch64` 支持 `plat_dyn = true`(动态平台加载),其他架构使用静态平台绑定 +- **plat_dyn**:`aarch64` 和 `riscv64` 支持 `plat_dyn = true`(动态平台加载),其他架构使用静态平台绑定 - **to_bin**:`x86_64` 不使用 `--bin`(直接生成 ELF 即可),其余架构默认将 ELF 转为 raw binary - **LoongArch QEMU**:运行 Axvisor loongarch64 时自动搜索 LVZ 版 QEMU(详见 [运行](./run#loongarch-特殊处理)) @@ -286,7 +286,7 @@ axconfig 仅在**静态平台模式**(`plat_dyn = false`)下生成。动态 |--------|-------------|-----------------| | ArceOS | `true`(aarch64)/ `false`(其他) | 仅 `plat_dyn = false` 时 | | StarryOS | `false` | 始终生成 | -| Axvisor | `true`(aarch64)/ `false`(其他) | 仅 `plat_dyn = false` 时 | +| Axvisor | `true`(aarch64/riscv64)/ `false`(其他) | 仅 `plat_dyn = false` 时 | ### 生成流程 diff --git a/drivers/ax-driver/Cargo.toml b/drivers/ax-driver/Cargo.toml index a37c270951..1f05376de1 100644 --- a/drivers/ax-driver/Cargo.toml +++ b/drivers/ax-driver/Cargo.toml @@ -13,14 +13,12 @@ name = "ax_driver" [features] default = [] -fdt = ["dep:fdt-edit"] -acpi = [] -pci = ["dep:pcie", "dep:heapless", "dep:rdif-pcie", "dep:spin"] -pci-fdt = ["pci", "fdt", "dep:rdif-intc"] +plat-static = [] +plat-dyn = ["dep:fdt-edit"] irq = [] -block = ["dep:ax-errno", "dep:ax-kspin", "dep:rd-block", "dep:spin"] -net = ["dep:rd-net", "dep:spin"] +block = ["dep:ax-errno", "dep:ax-kspin", "dep:rd-block"] +net = ["dep:rd-net"] display = ["dep:ax-errno", "dep:rdif-display"] input = ["dep:ax-errno", "dep:rdif-input"] vsock = ["dep:ax-errno", "dep:rdif-vsock"] @@ -35,22 +33,24 @@ virtio-socket = ["vsock", "virtio"] ramdisk = ["block", "dep:ramdisk"] bcm2835-sdhci = ["block", "dep:bcm2835-sdhci"] -ahci = ["block", "pci", "dep:simple-ahci"] -ixgbe = ["net", "pci", "dep:ax-alloc", "dep:ixgbe-driver"] +cvsd = ["block", "plat-dyn", "dep:sg200x-bsp"] +ahci = ["block", "dep:simple-ahci"] +ixgbe = ["net", "dep:ax-alloc", "dep:ixgbe-driver"] fxmac = ["net", "dep:ax-alloc", "dep:ax-crate-interface", "dep:fxmac_rs"] -intel-net = ["net", "pci", "dep:eth-intel"] -realtek-rtl8125 = ["net", "pci", "dep:realtek-rtl8125"] +intel-net = ["net", "dep:eth-intel"] +realtek-rtl8125 = ["net", "dep:realtek-rtl8125"] usb = ["dep:crab-usb"] -rockchip-dwc-xhci = ["usb", "fdt", "rockchip-soc", "rockchip-pm"] -xhci-mmio = ["usb", "fdt"] -xhci-pci = ["usb", "pci"] -serial = ["fdt", "dep:some-serial"] -rtc = ["fdt", "dep:ax-arm-pl031"] -rknpu = ["fdt", "rockchip-pm", "rockchip-soc", "dep:rockchip-npu"] +rockchip-dwc-xhci = ["usb", "plat-dyn", "rockchip-soc", "rockchip-pm"] +xhci-mmio = ["usb", "plat-dyn"] +xhci-pci = ["usb"] +serial = ["plat-dyn", "dep:some-serial"] +sg2002-placeholder = ["plat-dyn"] +rtc = ["plat-dyn", "dep:ax-arm-pl031", "dep:riscv_goldfish"] +rknpu = ["plat-dyn", "rockchip-pm", "rockchip-soc", "dep:rockchip-npu"] rockchip-sdhci = [ "block", - "fdt", + "plat-dyn", "rockchip-soc", "dep:ax-kspin", "dep:rdif-clk", @@ -60,7 +60,7 @@ rockchip-sdhci = [ ] phytium-mci = [ "block", - "fdt", + "plat-dyn", "dep:ax-kspin", "dep:phytium-mci-host", "dep:sdmmc-protocol", @@ -68,7 +68,7 @@ phytium-mci = [ ] rockchip-dwmmc = [ "block", - "fdt", + "plat-dyn", "rockchip-soc", "dep:arm-scmi-rs", "dep:ax-kspin", @@ -78,15 +78,14 @@ rockchip-dwmmc = [ "sdmmc-protocol/sdio", ] rk3588-pcie = [ - "pci-fdt", + "plat-dyn", "rockchip-soc", "rockchip-pm", - "dep:rdif-pcie", "dep:rk3588-pci", ] -rockchip-soc = ["fdt", "dep:rdif-clk", "dep:rockchip-soc"] +rockchip-soc = ["plat-dyn", "dep:rdif-clk", "dep:rockchip-soc"] rockchip-pm = ["rockchip-soc", "dep:rockchip-pm"] -pci-list-devices = ["pci"] +list-pci-devices = [] [dependencies] anyhow = { workspace = true } @@ -105,19 +104,19 @@ dwmmc-host = { workspace = true, optional = true } eth-intel = { workspace = true, optional = true } fdt-edit = { workspace = true, optional = true } fxmac_rs = { workspace = true, optional = true } -heapless = { workspace = true, optional = true } +heapless.workspace = true ixgbe-driver = { workspace = true, optional = true } log.workspace = true mmio-api.workspace = true -pcie = { workspace = true, optional = true } +pcie.workspace = true ramdisk = { workspace = true, optional = true } rd-block = { workspace = true, optional = true } rd-net = { workspace = true, optional = true } rdif-clk = { workspace = true, optional = true } rdif-display = { workspace = true, optional = true } rdif-input = { workspace = true, optional = true } -rdif-intc = { workspace = true, optional = true } -rdif-pcie = { workspace = true, optional = true } +rdif-intc.workspace = true +rdif-pcie.workspace = true rdif-vsock = { workspace = true, optional = true } realtek-rtl8125 = { workspace = true, optional = true } rdrive.workspace = true @@ -129,7 +128,11 @@ rockchip-soc = { workspace = true, optional = true } phytium-mci-host = { workspace = true, optional = true } sdhci-host = { workspace = true, optional = true } sdmmc-protocol = { workspace = true, default-features = false, optional = true } +sg200x-bsp = { workspace = true, optional = true } simple-ahci = { workspace = true, optional = true } some-serial = { workspace = true, optional = true } -spin = { workspace = true, optional = true } +spin.workspace = true virtio-drivers = { workspace = true, optional = true } + +[target.'cfg(target_arch = "riscv64")'.dependencies] +riscv_goldfish = { version = "0.1", optional = true } diff --git a/drivers/ax-driver/build.rs b/drivers/ax-driver/build.rs index 50d1994b23..897e8664f8 100644 --- a/drivers/ax-driver/build.rs +++ b/drivers/ax-driver/build.rs @@ -6,14 +6,6 @@ const VIRTIO_DEV_FEATURES: &[&str] = &[ "virtio-socket", ]; -fn make_cfg_values(str_list: &[&str]) -> String { - str_list - .iter() - .map(|s| format!("{s:?}")) - .collect::>() - .join(", ") -} - fn has_feature(feature: &str) -> bool { std::env::var(format!( "CARGO_FEATURE_{}", @@ -26,10 +18,6 @@ fn has_any_feature(features: &[&str]) -> bool { features.iter().any(|feature| has_feature(feature)) } -fn enable_cfg(key: &str, value: &str) { - println!("cargo:rustc-cfg={key}=\"{value}\""); -} - fn enable_cfg_flag(key: &str) { println!("cargo:rustc-cfg={key}"); } @@ -37,26 +25,25 @@ fn enable_cfg_flag(key: &str) { fn main() { let has_virtio_core = has_feature("virtio-core"); let has_virtio_dev = has_any_feature(VIRTIO_DEV_FEATURES); - let has_pci = has_feature("pci"); - let has_fdt = has_feature("fdt"); - - if has_pci { - enable_cfg("probe", "pci"); - } - if has_fdt { - enable_cfg("probe", "fdt"); + let has_plat_static = has_feature("plat-static"); + let has_plat_dyn = has_feature("plat-dyn"); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let target_has_cvsd = matches!(target_arch.as_str(), "riscv32" | "riscv64"); + + if has_plat_dyn { + enable_cfg_flag("plat_dyn"); + } else if has_plat_static { + enable_cfg_flag("plat_static"); } if has_virtio_core || has_virtio_dev { enable_cfg_flag("virtio_dev"); } - if has_any_feature(&["ahci", "bcm2835-sdhci"]) { + if has_any_feature(&["ahci", "bcm2835-sdhci"]) || (has_feature("cvsd") && target_has_cvsd) { enable_cfg_flag("sync_block_dev"); } - println!( - "cargo::rustc-check-cfg=cfg(probe, values({}))", - make_cfg_values(&["pci", "fdt"]) - ); + println!("cargo::rustc-check-cfg=cfg(plat_static)"); + println!("cargo::rustc-check-cfg=cfg(plat_dyn)"); println!("cargo::rustc-check-cfg=cfg(virtio_dev)"); println!("cargo::rustc-check-cfg=cfg(sync_block_dev)"); } diff --git a/drivers/ax-driver/src/block/cvsd.rs b/drivers/ax-driver/src/block/cvsd.rs new file mode 100644 index 0000000000..a9c87f1149 --- /dev/null +++ b/drivers/ax-driver/src/block/cvsd.rs @@ -0,0 +1,160 @@ +#[cfg(plat_dyn)] +use rdrive::register::FdtInfo; +use rdrive::{PlatformDevice, probe::OnProbeError}; +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +use {alloc::format, sg200x_bsp::sdmmc::Sdmmc}; + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +use super::{SyncBlockOps, register_sync_block}; + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +const BLOCK_SIZE: usize = 512; +pub const DEVICE_NAME: &str = "cvsd"; + +#[cfg(plat_dyn)] +crate::model_register!( + name: "FDT CVSD", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["cvitek,cv181x-sd"], + on_probe: probe_fdt, + }], +); + +#[cfg(plat_dyn)] +fn probe_fdt(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let sdmmc = + info.node.regs().into_iter().next().ok_or_else(|| { + OnProbeError::other(alloc::format!("[{}] has no reg", info.node.name())) + })?; + let syscon = info + .find_compatible(&["syscon"]) + .into_iter() + .find_map(|node| node.regs().into_iter().next()) + .ok_or_else(|| OnProbeError::other("CVSD syscon node not found in FDT"))?; + + register_mmio( + plat_dev, + sdmmc.address as usize, + sdmmc.size.unwrap_or(0x1000) as usize, + syscon.address as usize, + syscon.size.unwrap_or(0x1000) as usize, + ) +} + +fn register_mmio( + plat_dev: PlatformDevice, + sdmmc_paddr: usize, + sdmmc_size: usize, + syscon_paddr: usize, + syscon_size: usize, +) -> Result<(), OnProbeError> { + if sdmmc_paddr == 0 || sdmmc_size == 0 || syscon_paddr == 0 || syscon_size == 0 { + return Err(OnProbeError::NotMatch); + } + register_mmio_target(plat_dev, sdmmc_paddr, sdmmc_size, syscon_paddr, syscon_size) +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +fn register_mmio_target( + plat_dev: PlatformDevice, + sdmmc_paddr: usize, + sdmmc_size: usize, + syscon_paddr: usize, + syscon_size: usize, +) -> Result<(), OnProbeError> { + let sdmmc = map_region(sdmmc_paddr, sdmmc_size, "CVSD")?; + let syscon = map_region(syscon_paddr, syscon_size, "SYSCON")?; + let driver = + CvsdDriver::new(sdmmc, syscon).map_err(|_| OnProbeError::other("CVSD init failed"))?; + register_sync_block(plat_dev, driver); + Ok(()) +} + +#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] +fn register_mmio_target( + _plat_dev: PlatformDevice, + _sdmmc_paddr: usize, + _sdmmc_size: usize, + _syscon_paddr: usize, + _syscon_size: usize, +) -> Result<(), OnProbeError> { + Err(OnProbeError::NotMatch) +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +fn map_region(address: usize, size: usize, name: &str) -> Result { + let mmio = axklib::mmio::ioremap_raw(address.into(), size) + .map_err(|err| OnProbeError::other(format!("failed to map {name}: {err:?}")))?; + Ok(mmio.as_ptr() as usize) +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +struct CvsdDriver(Sdmmc); + +// The SG2002 SD/MMC core stores MMIO registers as `UnsafeCell`-backed +// references, so the raw register block is intentionally not `Sync`. +// `CvsdDriver` is owned by `SyncBlockDevice`, which serializes all access +// through a mutex and never clones the driver, so moving that owner between +// execution contexts is sound. +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +unsafe impl Send for CvsdDriver {} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +impl CvsdDriver { + fn new(sdmmc: usize, syscon: usize) -> Result { + let sdmmc = unsafe { Sdmmc::new(sdmmc, syscon) }; + sdmmc.init().map_err(|_| ())?; + sdmmc.clk_en(true); + Ok(Self(sdmmc)) + } + + fn checked_lba(block_id: u64, offset: usize) -> Result { + let lba = block_id + .checked_add(offset as u64) + .ok_or(rd_block::BlkError::InvalidBlockIndex(block_id as usize))?; + u32::try_from(lba).map_err(|_| rd_block::BlkError::InvalidBlockIndex(block_id as usize)) + } +} + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] +impl SyncBlockOps for CvsdDriver { + fn name(&self) -> &'static str { + DEVICE_NAME + } + + fn num_blocks(&self) -> u64 { + self.0.card_capacity_blocks() + } + + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), rd_block::BlkError> { + if !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::NotSupported); + } + + for (i, block) in buf.chunks_exact_mut(BLOCK_SIZE).enumerate() { + self.0 + .read_block(Self::checked_lba(block_id, i)?, block) + .map_err(|_| rd_block::BlkError::Other("CVSD read failed".into()))?; + } + Ok(()) + } + + fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), rd_block::BlkError> { + if !buf.len().is_multiple_of(BLOCK_SIZE) { + return Err(rd_block::BlkError::NotSupported); + } + + for (i, block) in buf.chunks_exact(BLOCK_SIZE).enumerate() { + self.0 + .write_block(Self::checked_lba(block_id, i)?, block) + .map_err(|_| rd_block::BlkError::Other("CVSD write failed".into()))?; + } + Ok(()) + } +} diff --git a/drivers/ax-driver/src/block/mod.rs b/drivers/ax-driver/src/block/mod.rs index 7b21a02895..15a6ef7685 100644 --- a/drivers/ax-driver/src/block/mod.rs +++ b/drivers/ax-driver/src/block/mod.rs @@ -4,6 +4,8 @@ mod binding; pub mod ahci; #[cfg(feature = "bcm2835-sdhci")] pub mod bcm2835; +#[cfg(feature = "cvsd")] +pub mod cvsd; #[cfg(feature = "phytium-mci")] pub mod phytium_mci; #[cfg(feature = "ramdisk")] diff --git a/drivers/ax-driver/src/lib.rs b/drivers/ax-driver/src/lib.rs index 7289ede1fa..dcb0f0a7c9 100644 --- a/drivers/ax-driver/src/lib.rs +++ b/drivers/ax-driver/src/lib.rs @@ -45,18 +45,19 @@ crate::model_register!( pub mod error; #[cfg(any( - feature = "serial", - all(feature = "rtc", feature = "fdt"), - all(feature = "rockchip-soc", feature = "fdt"), - all(feature = "rockchip-pm", feature = "fdt"), - all(feature = "rockchip-dwmmc", feature = "fdt"), - all(feature = "rockchip-sdhci", feature = "fdt"), - all(feature = "phytium-mci", feature = "fdt"), - all(feature = "rk3588-pcie", feature = "fdt"), - all(feature = "rknpu", feature = "fdt"), - all(feature = "xhci-mmio", target_os = "none"), + all(feature = "serial", plat_dyn), + all(feature = "rtc", plat_dyn), + all(feature = "rockchip-soc", plat_dyn), + all(feature = "rockchip-pm", plat_dyn), + all(feature = "sg2002-placeholder", plat_dyn), + all(feature = "rockchip-dwmmc", plat_dyn), + all(feature = "rockchip-sdhci", plat_dyn), + all(feature = "phytium-mci", plat_dyn), + all(feature = "rk3588-pcie", plat_dyn), + all(feature = "rknpu", plat_dyn), + all(feature = "xhci-mmio", target_os = "none", plat_dyn), all(feature = "xhci-pci", target_os = "none"), - all(virtio_dev, probe = "fdt") + all(virtio_dev, plat_dyn) ))] pub mod mmio; @@ -71,7 +72,6 @@ pub mod net; #[cfg(feature = "vsock")] pub mod vsock; -#[cfg(feature = "pci")] pub mod pci; #[cfg(feature = "rknpu")] pub mod rknpu; @@ -80,6 +80,7 @@ pub mod serial; #[cfg(any( feature = "rockchip-soc", feature = "rockchip-pm", + feature = "sg2002-placeholder", feature = "rockchip-dwmmc" ))] pub mod soc; diff --git a/drivers/ax-driver/src/net/binding.rs b/drivers/ax-driver/src/net/binding.rs index 32d4b0eda0..400bcf7f6e 100644 --- a/drivers/ax-driver/src/net/binding.rs +++ b/drivers/ax-driver/src/net/binding.rs @@ -42,7 +42,17 @@ impl DriverGeneric for PlatformNetDevice { } pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { - #[cfg(all(feature = "pci-fdt", target_os = "none"))] + #[cfg(all( + plat_dyn, + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) + ))] { let interrupt_pin = endpoint.interrupt_pin(); if interrupt_pin != 0 { @@ -57,7 +67,6 @@ pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { } } - #[cfg(feature = "pci")] if let Some(irq) = crate::pci::legacy_irq_for_endpoint(endpoint.address(), endpoint.interrupt_pin()) { diff --git a/drivers/ax-driver/src/pci/fdt.rs b/drivers/ax-driver/src/pci/fdt.rs index b3788fe574..eebeef5e56 100644 --- a/drivers/ax-driver/src/pci/fdt.rs +++ b/drivers/ax-driver/src/pci/fdt.rs @@ -1,14 +1,41 @@ extern crate alloc; use alloc::format; -#[cfg(target_os = "none")] +#[cfg(all( + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] use alloc::vec::Vec; -#[cfg(target_os = "none")] +#[cfg(all( + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] use fdt_edit::Fdt; use fdt_edit::{NodeType, PciRange, PciSpace}; use log::{debug, trace, warn}; -#[cfg(target_os = "none")] +#[cfg(all( + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] use rdrive::probe::pci::PciAddress; use rdrive::{ PlatformDevice, @@ -131,7 +158,16 @@ pub(super) fn register_fdt_legacy_irq(info: &FdtInfo<'_>, logical_bus_end: u8) { super::register_legacy_irq_route(0, logical_bus_end, irq); } -#[cfg(target_os = "none")] +#[cfg(all( + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] pub fn fdt_irq_for_endpoint( address: PciAddress, interrupt_pin: u8, @@ -144,7 +180,16 @@ pub fn fdt_irq_for_endpoint( result.map(Some) } -#[cfg(target_os = "none")] +#[cfg(all( + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] fn resolve_pci_irq_from_fdt( fdt: &Fdt, address: PciAddress, @@ -214,7 +259,16 @@ fn resolve_pci_irq_from_fdt( }) } -#[cfg(target_os = "none")] +#[cfg(all( + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] fn decode_irq_cells(specifier: &[u32]) -> Option { match specifier { [irq] => Some(*irq as usize), @@ -227,7 +281,7 @@ fn decode_irq_cells(specifier: &[u32]) -> Option { } } -#[cfg(feature = "pci-list-devices")] +#[cfg(feature = "list-pci-devices")] mod pci_list_devices { use log::info; use rdrive::probe::pci::{EndpointRc, FnOnProbe}; diff --git a/drivers/ax-driver/src/pci/mod.rs b/drivers/ax-driver/src/pci/mod.rs index c6d063badb..440ba7e553 100644 --- a/drivers/ax-driver/src/pci/mod.rs +++ b/drivers/ax-driver/src/pci/mod.rs @@ -29,9 +29,19 @@ use virtio_drivers::transport::{ #[cfg(virtio_dev)] use crate::virtio::VirtIoHalImpl; -#[cfg(feature = "pci-fdt")] +#[cfg(plat_dyn)] mod fdt; -#[cfg(all(feature = "pci-fdt", target_os = "none"))] +#[cfg(all( + plat_dyn, + target_os = "none", + any( + feature = "intel-net", + feature = "ixgbe", + feature = "realtek-rtl8125", + feature = "virtio-net", + feature = "xhci-pci", + ) +))] pub(crate) use fdt::fdt_irq_for_endpoint; const MAX_PCIE_LEGACY_IRQS: usize = 8; @@ -105,7 +115,7 @@ pub const fn has_static_endpoint_drivers() -> bool { feature = "virtio-gpu", feature = "virtio-input", feature = "virtio-socket", - feature = "pci-list-devices", + feature = "list-pci-devices", )) } diff --git a/drivers/ax-driver/src/serial/mod.rs b/drivers/ax-driver/src/serial/mod.rs index 29fd470335..c6947174c0 100644 --- a/drivers/ax-driver/src/serial/mod.rs +++ b/drivers/ax-driver/src/serial/mod.rs @@ -14,7 +14,7 @@ crate::model_register!( level: ProbeLevel::PreKernel, priority: ProbePriority::DEFAULT, probe_kinds: &[ProbeKind::Fdt { - compatibles: &["arm,pl011", "snps,dw-apb-uart"], + compatibles: &["arm,pl011", "snps,dw-apb-uart", "ns16550a", "ns16550"], on_probe: probe }], ); @@ -49,7 +49,7 @@ fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError break; } - if compatible == "snps,dw-apb-uart" { + if matches!(compatible, "snps,dw-apb-uart" | "ns16550a" | "ns16550") { serial = Some(ns16550::Ns16550::new_mmio_boxed( mmio_base, clock_freq, reg_width, )); diff --git a/drivers/ax-driver/src/soc/mod.rs b/drivers/ax-driver/src/soc/mod.rs index 050818eaf9..9724f5ccab 100644 --- a/drivers/ax-driver/src/soc/mod.rs +++ b/drivers/ax-driver/src/soc/mod.rs @@ -16,6 +16,8 @@ mod rockchip; #[cfg(feature = "rockchip-dwmmc")] pub mod scmi; +#[cfg(feature = "sg2002-placeholder")] +mod sg2002; #[cfg(feature = "rockchip-soc")] pub use rockchip::{ diff --git a/drivers/ax-driver/src/soc/sg2002.rs b/drivers/ax-driver/src/soc/sg2002.rs new file mode 100644 index 0000000000..30da06c141 --- /dev/null +++ b/drivers/ax-driver/src/soc/sg2002.rs @@ -0,0 +1,62 @@ +use log::info; +use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; + +const PLACEHOLDER_COMPATIBLES: &[&str] = &[ + "cvitek,tpu", + "cvitek,cvitek-ion", + "cvitek,cvi-pwm", + "snps,dw-apb-gpio", + "cvitek,cv182x-usb", + "cvitek,cif", + "cvitek,mipi_tx", + "cvitek,sys", + "cvitek,base", + "cvitek,vi", + "cvitek,vpss", + "cvitek,ive", + "cvitek,vo", + "cvitek,fb", + "cvitek,dwa", + "cvitek,asic-vcodec", + "cvitek,asic-jpeg", + "cvitek,cvi_vc_drv", + "cvitek,rtos_cmdqu", + "cvitek,audio", + "cvitek,cv181x-thermal", +]; + +crate::model_register!( + name: "SG2002 placeholder", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["cvitek,cv181x"], + on_probe: probe, + }], +); + +fn probe(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let mut mapped = 0usize; + for node in info.find_compatible(PLACEHOLDER_COMPATIBLES) { + for reg in node.regs() { + let Some(size) = reg.size else { + continue; + }; + if size == 0 { + continue; + } + let mmio = crate::mmio::iomap(reg.address as usize, size as usize)?; + mapped += 1; + info!( + "SG2002 placeholder mapped {}: {:#x}+{:#x} -> {:#x}", + node.name(), + reg.address, + size, + mmio.as_ptr() as usize + ); + } + } + + info!("SG2002 placeholder mapped {mapped} region(s)"); + Ok(()) +} diff --git a/drivers/ax-driver/src/time.rs b/drivers/ax-driver/src/time.rs index 590e194d51..eddffb2a87 100644 --- a/drivers/ax-driver/src/time.rs +++ b/drivers/ax-driver/src/time.rs @@ -1,6 +1,8 @@ -use ax_arm_pl031::Rtc; +use ax_arm_pl031::Rtc as Pl031Rtc; use log::{debug, info}; use rdrive::{PlatformDevice, probe::OnProbeError, register::FdtInfo}; +#[cfg(target_arch = "riscv64")] +use riscv_goldfish::Rtc as GoldfishRtc; use crate::mmio::iomap; @@ -10,11 +12,35 @@ crate::model_register!( priority: ProbePriority::DEFAULT, probe_kinds: &[ProbeKind::Fdt { compatibles: &["arm,pl031"], - on_probe: probe + on_probe: probe_pl031 }], ); -fn probe(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeError> { +#[cfg(target_arch = "riscv64")] +crate::model_register!( + name: "goldfish rtc", + level: ProbeLevel::PostKernel, + priority: ProbePriority::DEFAULT, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &["google,goldfish-rtc"], + on_probe: probe_goldfish + }], +); + +fn probe_pl031(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let mmio_base = map_first_reg(&info)?; + let rtc = unsafe { Pl031Rtc::new(mmio_base.as_ptr().cast()) }; + init_epoch_offset(info.node.name(), u64::from(rtc.get_unix_timestamp())) +} + +#[cfg(target_arch = "riscv64")] +fn probe_goldfish(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeError> { + let mmio_base = map_first_reg(&info)?; + let rtc = GoldfishRtc::new(mmio_base.as_ptr() as usize); + init_epoch_offset(info.node.name(), rtc.get_unix_timestamp()) +} + +fn map_first_reg(info: &FdtInfo<'_>) -> Result, OnProbeError> { let regs = info.node.regs(); let Some(base_reg) = regs.first() else { return Err(OnProbeError::other(alloc::format!( @@ -24,24 +50,21 @@ fn probe(info: FdtInfo<'_>, _plat_dev: PlatformDevice) -> Result<(), OnProbeErro }; let mmio_size = base_reg.size.unwrap_or(0x1000); - let mmio_base = iomap(base_reg.address as usize, mmio_size as usize)?; - let rtc = unsafe { Rtc::new(mmio_base.as_ptr().cast()) }; - let unix_timestamp = rtc.get_unix_timestamp(); + iomap(base_reg.address as usize, mmio_size as usize) +} + +fn init_epoch_offset(node_name: &str, unix_timestamp: u64) -> Result<(), OnProbeError> { if unix_timestamp == 0 { return Err(OnProbeError::other(alloc::format!( - "[{}] returned zero unix timestamp", - info.node.name() + "[{node_name}] returned zero unix timestamp" ))); } - let epoch_time_nanos = u64::from(unix_timestamp) * 1_000_000_000; + let epoch_time_nanos = unix_timestamp * 1_000_000_000; if axklib::time::try_init_epoch_offset(epoch_time_nanos) { - info!("Initialized wall clock from {}", info.node.name()); + info!("Initialized wall clock from {node_name}"); } else { - debug!( - "Skipping RTC {} because epoch offset is already initialized", - info.node.name() - ); + debug!("Skipping RTC {node_name} because epoch offset is already initialized",); } Ok(()) diff --git a/drivers/ax-driver/src/usb/mod.rs b/drivers/ax-driver/src/usb/mod.rs index 1d76c99e08..9977cb1e49 100644 --- a/drivers/ax-driver/src/usb/mod.rs +++ b/drivers/ax-driver/src/usb/mod.rs @@ -240,7 +240,7 @@ fn pci_static_irq(endpoint: &rdrive::probe::pci::EndpointRc) -> Option { pub(crate) fn pci_irq_or_error( endpoint: &rdrive::probe::pci::EndpointRc, ) -> Result { - #[cfg(feature = "pci-fdt")] + #[cfg(plat_dyn)] if let Some(irq) = crate::pci::fdt_irq_for_endpoint(endpoint.address(), endpoint.interrupt_pin())? { diff --git a/drivers/ax-driver/src/virtio/block.rs b/drivers/ax-driver/src/virtio/block.rs index 0eb6a863cf..8081ce07a7 100644 --- a/drivers/ax-driver/src/virtio/block.rs +++ b/drivers/ax-driver/src/virtio/block.rs @@ -3,7 +3,7 @@ extern crate alloc; use alloc::format; use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; -#[cfg(any(probe = "fdt", probe = "pci"))] +#[cfg(any(plat_dyn, plat_static))] use virtio_drivers::transport::DeviceType; use virtio_drivers::{ Error as VirtIoError, @@ -15,7 +15,7 @@ use crate::{block::PlatformDeviceBlock, virtio::VirtIoHalImpl}; const VIRTIO_BLK_DMA_BUFFER_SIZE: usize = 32 * SECTOR_SIZE; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] crate::model_register!( name: "VirtIO Block", level: ProbeLevel::PostKernel, @@ -25,7 +25,7 @@ crate::model_register!( }], ); -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] fn probe_pci( endpoint: &mut rdrive::probe::pci::EndpointRc, plat_dev: PlatformDevice, @@ -34,7 +34,7 @@ fn probe_pci( register_transport(plat_dev, transport) } -#[cfg(probe = "fdt")] +#[cfg(plat_dyn)] crate::model_register!( name: "VirtIO MMIO Block", level: ProbeLevel::PostKernel, @@ -45,7 +45,7 @@ crate::model_register!( }], ); -#[cfg(probe = "fdt")] +#[cfg(plat_dyn)] fn probe_fdt( info: rdrive::register::FdtInfo<'_>, plat_dev: PlatformDevice, diff --git a/drivers/ax-driver/src/virtio/display.rs b/drivers/ax-driver/src/virtio/display.rs index 76e512c5a5..cf09df0f0e 100644 --- a/drivers/ax-driver/src/virtio/display.rs +++ b/drivers/ax-driver/src/virtio/display.rs @@ -4,13 +4,13 @@ use alloc::format; use rdif_display::{DisplayError, DisplayInfo, FrameBuffer, PixelFormat}; use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] use virtio_drivers::transport::DeviceType; use virtio_drivers::{Error as VirtIoError, device::gpu::VirtIOGpu, transport::Transport}; use crate::{display::PlatformDeviceDisplay, virtio::VirtIoHalImpl}; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] crate::model_register!( name: "VirtIO GPU", level: ProbeLevel::PostKernel, @@ -20,7 +20,7 @@ crate::model_register!( }], ); -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] fn probe_pci( endpoint: &mut rdrive::probe::pci::EndpointRc, plat_dev: PlatformDevice, diff --git a/drivers/ax-driver/src/virtio/input.rs b/drivers/ax-driver/src/virtio/input.rs index a60e12d890..db93d0795e 100644 --- a/drivers/ax-driver/src/virtio/input.rs +++ b/drivers/ax-driver/src/virtio/input.rs @@ -4,7 +4,7 @@ use alloc::{borrow::ToOwned, format, string::String}; use rdif_input::{AbsInfo, EventType, InputDeviceId, InputError, InputEvent}; use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] use virtio_drivers::transport::DeviceType; use virtio_drivers::{ Error as VirtIoError, @@ -14,7 +14,7 @@ use virtio_drivers::{ use crate::{input::PlatformDeviceInput, virtio::VirtIoHalImpl}; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] crate::model_register!( name: "VirtIO Input", level: ProbeLevel::PostKernel, @@ -24,7 +24,7 @@ crate::model_register!( }], ); -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] fn probe_pci( endpoint: &mut rdrive::probe::pci::EndpointRc, plat_dev: PlatformDevice, diff --git a/drivers/ax-driver/src/virtio/mod.rs b/drivers/ax-driver/src/virtio/mod.rs index 8ace7d4174..9ac7d1d300 100644 --- a/drivers/ax-driver/src/virtio/mod.rs +++ b/drivers/ax-driver/src/virtio/mod.rs @@ -138,7 +138,7 @@ pub fn register_static_transport( Err(rdrive::probe::OnProbeError::NotMatch) } -#[cfg(probe = "fdt")] +#[cfg(plat_dyn)] pub fn probe_fdt_mmio_device( info: &rdrive::register::FdtInfo<'_>, ) -> Result<(DeviceType, MmioTransport<'static>), rdrive::probe::OnProbeError> { diff --git a/drivers/ax-driver/src/virtio/net.rs b/drivers/ax-driver/src/virtio/net.rs index c89c3460cb..4e07c37579 100644 --- a/drivers/ax-driver/src/virtio/net.rs +++ b/drivers/ax-driver/src/virtio/net.rs @@ -9,7 +9,7 @@ use core::{ use ax_kernel_guard::NoPreemptIrqSave; use rd_net::{DmaBuffer, Event, IRxQueue, ITxQueue, Interface, NetError, QueueConfig}; use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] use virtio_drivers::transport::DeviceType; use virtio_drivers::{Error as VirtIoError, device::net::VirtIONetRaw, transport::Transport}; @@ -21,7 +21,7 @@ use crate::{ const QUEUE_SIZE: usize = 64; const BUFFER_SIZE: usize = 2048; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] crate::model_register!( name: "VirtIO Net", level: ProbeLevel::PostKernel, @@ -305,7 +305,7 @@ struct RxInflight { len: usize, } -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] fn probe_pci( endpoint: &mut rdrive::probe::pci::EndpointRc, plat_dev: PlatformDevice, diff --git a/drivers/ax-driver/src/virtio/vsock.rs b/drivers/ax-driver/src/virtio/vsock.rs index 5fd1eeb279..7571ac1e8e 100644 --- a/drivers/ax-driver/src/virtio/vsock.rs +++ b/drivers/ax-driver/src/virtio/vsock.rs @@ -4,7 +4,7 @@ use alloc::format; use rdif_vsock::{VsockAddr as RdifVsockAddr, VsockConnId, VsockError, VsockEvent}; use rdrive::{DriverGeneric, PlatformDevice, probe::OnProbeError}; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] use virtio_drivers::transport::DeviceType; use virtio_drivers::{ Error as VirtIoError, @@ -19,7 +19,7 @@ use crate::{virtio::VirtIoHalImpl, vsock::PlatformDeviceVsock}; const DEFAULT_RX_BUFFER_CAPACITY: u32 = 32 * 1024; -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] crate::model_register!( name: "VirtIO Socket", level: ProbeLevel::PostKernel, @@ -29,7 +29,7 @@ crate::model_register!( }], ); -#[cfg(probe = "pci")] +#[cfg(any(plat_static, plat_dyn))] fn probe_pci( endpoint: &mut rdrive::probe::pci::EndpointRc, plat_dev: PlatformDevice, diff --git a/drivers/intc/riscv_plic/src/lib.rs b/drivers/intc/riscv_plic/src/lib.rs index e3aadafcba..5de76cf2ab 100644 --- a/drivers/intc/riscv_plic/src/lib.rs +++ b/drivers/intc/riscv_plic/src/lib.rs @@ -65,6 +65,15 @@ pub struct Plic { unsafe impl Send for Plic {} unsafe impl Sync for Plic {} +/// Lock-free PLIC operations used by interrupt entry code. +#[derive(Clone, Copy)] +pub struct PlicIrqHandler { + base: NonNull, +} + +unsafe impl Send for PlicIrqHandler {} +unsafe impl Sync for PlicIrqHandler {} + impl Plic { /// Create a new instance of the PLIC from the base address. /// @@ -76,9 +85,14 @@ impl Plic { Self { base } } + /// Returns a lock-free handler for per-context interrupt entry operations. + pub const fn irq_handler(&self) -> PlicIrqHandler { + PlicIrqHandler { base: self.base } + } + /// Initialize the PLIC by context, setting the priority threshold to 0. pub fn init_by_context(&mut self, ctx: usize) { - self.regs().contexts[ctx].priority_threshold.set(0); + self.irq_handler().init_by_context(ctx); } const fn regs(&self) -> &PLICRegs { @@ -190,7 +204,7 @@ impl Plic { /// See §8. #[inline] pub fn claim(&mut self, ctx: usize) -> Option { - NonZeroU32::new(self.regs().contexts[ctx].interrupt_claim_complete.get()) + self.irq_handler().claim(ctx) } /// Mark that interrupt identified by `source` is completed in `context`. @@ -198,6 +212,27 @@ impl Plic { /// See §9. #[inline] pub fn complete(&mut self, ctx: usize, source: NonZeroU32) { + self.irq_handler().complete(ctx, source); + } +} + +impl PlicIrqHandler { + const fn regs(&self) -> &PLICRegs { + unsafe { self.base.as_ref() } + } + + /// Initialize the PLIC by context, setting the priority threshold to 0. + pub fn init_by_context(&self, ctx: usize) { + self.regs().contexts[ctx].priority_threshold.set(0); + } + + /// Claim an interrupt in `context`, returning its source. + pub fn claim(&self, ctx: usize) -> Option { + NonZeroU32::new(self.regs().contexts[ctx].interrupt_claim_complete.get()) + } + + /// Mark that interrupt identified by `source` is completed in `context`. + pub fn complete(&self, ctx: usize, source: NonZeroU32) { self.regs().contexts[ctx] .interrupt_claim_complete .set(source.get()); diff --git a/drivers/rdrive/src/probe/fdt/mod.rs b/drivers/rdrive/src/probe/fdt/mod.rs index 923d4b8150..8531d0f1d5 100644 --- a/drivers/rdrive/src/probe/fdt/mod.rs +++ b/drivers/rdrive/src/probe/fdt/mod.rs @@ -56,6 +56,14 @@ pub struct FdtInfo<'a> { } impl<'a> FdtInfo<'a> { + pub fn get_by_phandle(&self, phandle: Phandle) -> Option> { + system().get_by_phandle(phandle) + } + + pub fn find_compatible(&self, compatible: &[&str]) -> Vec> { + system().find_compatible(compatible) + } + pub fn phandle_to_device_id(&self, phandle: Phandle) -> Option { self.phandle_2_device_id.get(&phandle).copied() } @@ -91,6 +99,14 @@ impl System { self.phandle_2_device_id.get(&phandle).copied() } + pub fn get_by_phandle(&self, phandle: Phandle) -> Option> { + self.fdt.get_by_phandle(phandle) + } + + pub fn find_compatible(&self, compatible: &[&str]) -> Vec> { + self.fdt.find_compatible(compatible) + } + pub fn new(fdt_addr: NonNull) -> Result { let fdt = unsafe { Fdt::from_ptr(fdt_addr.as_ptr()) } .map_err(|error| DriverError::Fdt(format!("{error:?}")))?; diff --git a/memory/page_table_entry/src/arch/riscv.rs b/memory/page_table_entry/src/arch/riscv.rs index 7e43a0fe63..6eed06635c 100644 --- a/memory/page_table_entry/src/arch/riscv.rs +++ b/memory/page_table_entry/src/arch/riscv.rs @@ -118,16 +118,13 @@ impl Rv64PTE { #[cfg(feature = "xuantie-c9xx")] { // CPU T-Head XUANTIE-C9xx extended flags: - // Memory: Shareable, Bufferable, Cacheable, Non-strong-order - // Device: Shareable, Non-bufferable, Non-cacheable, Strong-order if mflags.contains(MappingFlags::DEVICE) { self.0 |= (PTEFlags::SH | PTEFlags::SO).bits() as u64; + } else if mflags.contains(MappingFlags::UNCACHED) { + self.0 |= (PTEFlags::SH | PTEFlags::B).bits() as u64; } else { self.0 |= (PTEFlags::SH | PTEFlags::B | PTEFlags::C).bits() as u64; } - if mflags.contains(MappingFlags::UNCACHED) { - self.0 &= !((PTEFlags::B | PTEFlags::C).bits() as u64); - } } self.0 |= (extended_flags & !Self::PHYS_ADDR_MASK); PTEFlags::from_bits_truncate(self.0 as usize) diff --git a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml index 273b184467..03a58c86a0 100644 --- a/os/StarryOS/configs/board/licheerv-nano-sg2002.toml +++ b/os/StarryOS/configs/board/licheerv-nano-sg2002.toml @@ -1,7 +1,11 @@ env = { UIMAGE = "y" } features = [ - "sg2002", + "starry-kernel/sg2002", + "axplat-dyn/thead-mae", + "ax-driver/sg2002-placeholder", + "ax-driver/cvsd", + "ax-driver/serial", ] log = "Info" -plat_dyn = false +plat_dyn = true target = "riscv64gc-unknown-none-elf" diff --git a/os/StarryOS/configs/board/orangepi-5-plus.toml b/os/StarryOS/configs/board/orangepi-5-plus.toml index d3d5a0aa93..e88f614967 100644 --- a/os/StarryOS/configs/board/orangepi-5-plus.toml +++ b/os/StarryOS/configs/board/orangepi-5-plus.toml @@ -3,9 +3,7 @@ target = "aarch64-unknown-none-softfloat" # Runtime U-Boot settings live in `orangepi-5-plus-uboot.toml`. env = {} features = [ - "ax-hal/plat-dyn", - "starry-kernel/plat-dyn", - "ax-driver/pci-list-devices", + "ax-driver/list-pci-devices", "ax-driver/rk3588-pcie", "ax-driver/realtek-rtl8125", "ax-driver/rockchip-soc", diff --git a/os/StarryOS/configs/board/qemu-aarch64-dyn.toml b/os/StarryOS/configs/board/qemu-aarch64-dyn.toml index d94a5bb8e3..72d8d8bd27 100644 --- a/os/StarryOS/configs/board/qemu-aarch64-dyn.toml +++ b/os/StarryOS/configs/board/qemu-aarch64-dyn.toml @@ -1,10 +1,8 @@ env = {} features = [ - "ax-hal/plat-dyn", "ax-driver/virtio-blk", "ax-driver/virtio-net", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", "ax-driver/intel-net", ] diff --git a/os/StarryOS/configs/board/qemu-aarch64.toml b/os/StarryOS/configs/board/qemu-aarch64.toml index 100371542c..be8795b4d2 100644 --- a/os/StarryOS/configs/board/qemu-aarch64.toml +++ b/os/StarryOS/configs/board/qemu-aarch64.toml @@ -2,7 +2,7 @@ env = {} features = [ "ax-hal/aarch64-qemu-virt", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/os/StarryOS/configs/board/qemu-loongarch64.toml b/os/StarryOS/configs/board/qemu-loongarch64.toml index 4c35f73930..664004de9a 100644 --- a/os/StarryOS/configs/board/qemu-loongarch64.toml +++ b/os/StarryOS/configs/board/qemu-loongarch64.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/loongarch64-qemu-virt", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/os/StarryOS/configs/board/qemu-riscv64.toml b/os/StarryOS/configs/board/qemu-riscv64.toml index 4902ae62f3..898a6e773d 100644 --- a/os/StarryOS/configs/board/qemu-riscv64.toml +++ b/os/StarryOS/configs/board/qemu-riscv64.toml @@ -1,8 +1,6 @@ env = {} features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", + "ax-driver/serial", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", @@ -10,5 +8,5 @@ features = [ "ax-driver/virtio-socket", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "riscv64gc-unknown-none-elf" diff --git a/os/StarryOS/configs/board/qemu-x86_64.toml b/os/StarryOS/configs/board/qemu-x86_64.toml index 7efd9aa1bd..1128aa2eb9 100644 --- a/os/StarryOS/configs/board/qemu-x86_64.toml +++ b/os/StarryOS/configs/board/qemu-x86_64.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/x86-pc", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 8946edcbf6..ed0a4c35a5 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -22,6 +22,7 @@ ebpf = [] memtrack = ["ax-feat/dwarf", "ax-alloc/tracking", "dep:gimli"] rknpu = ["dep:ax-driver", "ax-driver/rknpu"] plat-dyn = [ + "ax-feat/plat-dyn", "dep:ax-driver", "ax-driver/usb", "dep:crab-usb", diff --git a/os/StarryOS/kernel/src/file/mod.rs b/os/StarryOS/kernel/src/file/mod.rs index 610aa1fbc4..49a438a4d9 100644 --- a/os/StarryOS/kernel/src/file/mod.rs +++ b/os/StarryOS/kernel/src/file/mod.rs @@ -2,7 +2,7 @@ pub mod epoll; pub mod event; mod fs; pub mod inotify; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] pub mod ion; pub mod memfd; mod net; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index fa482e1c1c..d2579d3bb2 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -16,7 +16,7 @@ mod r#loop; mod loop_block; #[cfg(feature = "ext4")] pub use r#loop::LoopDevice; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] pub mod ion; #[cfg(feature = "memtrack")] mod memtrack; @@ -24,19 +24,19 @@ mod memtrack; mod rknpu_card; #[cfg(all(feature = "rknpu", not(any(windows, unix))))] mod rknpu_drm; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] pub mod tpu; pub mod tty; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] mod cvi_camera; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] mod cvi_usb_camera; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] mod pinmux; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] pub(super) mod pwm; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] mod tty_serial; use alloc::{format, sync::Arc}; @@ -45,10 +45,10 @@ use core::any::Any; use ax_errno::AxError; use ax_sync::Mutex; use axfs_ng_vfs::{DeviceId, Filesystem, NodeFlags, NodeType, VfsResult}; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] use spin::Once; -#[cfg(feature = "sg2002")] +#[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] pub static ION_DEVICE: Once> = Once::new(); #[cfg(feature = "dev-log")] pub use log::bind_dev_log; @@ -383,7 +383,7 @@ fn builder(fs: Arc) -> DirMaker { SimpleDir::new_maker(fs.clone(), Arc::new(event::input_devices(fs.clone()))), ); - #[cfg(feature = "sg2002")] + #[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] { root.add( "cvi-tpu0", diff --git a/os/StarryOS/kernel/src/pseudofs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/sysfs.rs index 44fa030137..7f3b5750de 100644 --- a/os/StarryOS/kernel/src/pseudofs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/sysfs.rs @@ -186,9 +186,9 @@ struct ClassDir { impl SimpleDirOps for ClassDir { fn child_names<'a>(&'a self) -> Box> + 'a> { - #[cfg(feature = "sg2002")] + #[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] let names: &'static [&'static str] = &["drm", "graphics", "input", "pwm"]; - #[cfg(not(feature = "sg2002"))] + #[cfg(any(not(feature = "sg2002"), feature = "plat-dyn"))] let names: &'static [&'static str] = &["drm", "graphics", "input"]; Box::new(names.iter().copied().map(Cow::Borrowed)) } @@ -205,7 +205,7 @@ impl SimpleDirOps for ClassDir { Arc::new(ClassSubsystemDir::new(fs, "graphics", &["fb0"])), ), "input" => SimpleDir::new_maker(fs.clone(), Arc::new(InputClassDir { fs })), - #[cfg(feature = "sg2002")] + #[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] "pwm" => crate::pseudofs::dev::pwm::pwm_class_dir_maker(fs), _ => return Err(VfsError::NotFound), })) diff --git a/os/StarryOS/kernel/src/syscall/mm/mmap.rs b/os/StarryOS/kernel/src/syscall/mm/mmap.rs index 9f1d03e712..070855fd38 100644 --- a/os/StarryOS/kernel/src/syscall/mm/mmap.rs +++ b/os/StarryOS/kernel/src/syscall/mm/mmap.rs @@ -239,7 +239,7 @@ pub fn sys_mmap( // IonBufferFile 特殊处理:直接线性映射物理地址,跳过通用 file_mmap/device_mmap 路径。 // 这样可以避免通用路径中 `range.start += offset` 对 Ion buffer 的错误偏移。 - #[cfg(feature = "sg2002")] + #[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] if let Some(ref file) = file { use crate::file::ion::IonBufferFile; if let Some(ion_file) = file.downcast_ref::() { diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index c9fb4538aa..36f14f6c0c 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -30,6 +30,13 @@ sg2002 = [ "ax-hal/riscv64-sg2002", "starry-kernel/sg2002", ] +sg2002-dyn = [ + "plat-dyn", + "axplat-dyn/thead-mae", + "starry-kernel/sg2002", + "ax-driver/sg2002-placeholder", +] +plat-dyn = ["dep:axplat-dyn", "starry-kernel/plat-dyn"] # Enable the GICv3 distributor + redistributor + system-register # CPU interface on aarch64. Only meaningful when the target is the diff --git a/os/arceos/README.md b/os/arceos/README.md index 60aab66a23..4807d32caf 100644 --- a/os/arceos/README.md +++ b/os/arceos/README.md @@ -209,7 +209,7 @@ You may also need to select the corrsponding device drivers by setting the `FEAT # Build the shell app for raspi4, and use the SD card driver make MYPLAT=axplat-aarch64-raspi SMP=4 A=examples/shell FEATURES=page-alloc-4g,ax-driver/bcm2835-sdhci # Build httpserver for the bare-metal x86_64 platform, and use the ixgbe and ramdisk driver -make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml A=examples/httpserver FEATURES=page-alloc-4g,ax-driver/pci,ax-driver/ixgbe,ax-driver/ramdisk SMP=4 +make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml A=examples/httpserver FEATURES=page-alloc-4g,ax-driver/plat-static,ax-driver/ixgbe,ax-driver/ramdisk SMP=4 ``` ## How to reuse ArceOS modules in your own project diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index 7712402f30..05657df2ec 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -28,7 +28,6 @@ ipi = ["irq", "dep:ax-ipi", "ax-hal/ipi", "ax-runtime/ipi", "ax-task?/ipi"] myplat = ["ax-hal/myplat"] defplat = ["ax-hal/defplat"] plat-dyn = [ - "ax-hal/plat-dyn", "paging", "ax-runtime/plat-dyn", "ax-config/plat-dyn", diff --git a/os/arceos/doc/ixgbe.md b/os/arceos/doc/ixgbe.md index 23f1a8597f..4b6e7e340e 100644 --- a/os/arceos/doc/ixgbe.md +++ b/os/arceos/doc/ixgbe.md @@ -6,7 +6,7 @@ You can use the following command to compile an 'httpserver' app application: ```shell make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml defconfig -make A=examples/httpserver PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=ax-driver/pci,ax-driver/ixgbe +make A=examples/httpserver PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=ax-driver/plat-static,ax-driver/ixgbe ``` You can also use the following command to start the iperf application: @@ -14,7 +14,7 @@ You can also use the following command to start the iperf application: ```shell git clone https://github.com/arceos-org/arceos-apps.git make PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml defconfig -make A=arceos-apps/c/iperf PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=ax-driver/pci,ax-driver/ixgbe,ax-driver/ramdisk +make A=arceos-apps/c/iperf PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.toml FEATURES=ax-driver/plat-static,ax-driver/ixgbe,ax-driver/ramdisk ``` ## Use ixgbe NIC in QEMU with PCI passthrough @@ -38,7 +38,7 @@ make A=arceos-apps/c/iperf PLAT_CONFIG=$(pwd)/configs/custom/x86_64-pc-oslab.tom 3. Build and run ArceOS: ```shell - make A=examples/httpserver FEATURES=ax-driver/pci,ax-driver/ixgbe VFIO_PCI=02:00.0 IP=x.x.x.x GW=x.x.x.x run + make A=examples/httpserver FEATURES=ax-driver/plat-static,ax-driver/ixgbe VFIO_PCI=02:00.0 IP=x.x.x.x GW=x.x.x.x run ``` 4. If no longer in use, bind the NIC back to the `ixgbe` driver: diff --git a/os/arceos/modules/axconfig/src/driver_dyn_config.rs b/os/arceos/modules/axconfig/src/driver_dyn_config.rs index 3cc8bab13a..c97818c528 100644 --- a/os/arceos/modules/axconfig/src/driver_dyn_config.rs +++ b/os/arceos/modules/axconfig/src/driver_dyn_config.rs @@ -1,9 +1,49 @@ +#[cfg(target_arch = "aarch64")] +mod arch { + pub const ARCH: &str = "aarch64"; + pub const PACKAGE: &str = "axplat-aarch64-generic"; + pub const PLATFORM: &str = "aarch64-generic"; + pub const TIMER_IRQ: usize = 0xf0; + pub const IPI_IRQ: usize = 0; + pub const KERNEL_ASPACE_BASE: usize = 0xffff_8000_0000_0000; + pub const KERNEL_ASPACE_SIZE: usize = 0x0000_7fff_ffff_f000; + pub const KERNEL_BASE_PADDR: usize = 0x20_0000; + pub const KERNEL_BASE_VADDR: usize = 0xffff_8000_0020_0000; +} + +#[cfg(target_arch = "riscv64")] +mod arch { + pub const ARCH: &str = "riscv64"; + pub const PACKAGE: &str = "axplat-dyn"; + pub const PLATFORM: &str = "riscv64-plat-dyn"; + const INTERRUPT_FLAG: usize = 1usize << (usize::BITS as usize - 1); + pub const TIMER_IRQ: usize = INTERRUPT_FLAG | 5; + pub const IPI_IRQ: usize = INTERRUPT_FLAG | 1; + pub const KERNEL_ASPACE_BASE: usize = 0xffff_ffc0_0000_0000; + pub const KERNEL_ASPACE_SIZE: usize = 0x0000_003f_ffff_f000; + pub const KERNEL_BASE_PADDR: usize = 0x8020_0000; + pub const KERNEL_BASE_VADDR: usize = 0xffff_ffff_8000_0000; +} + +#[cfg(not(any(target_arch = "aarch64", target_arch = "riscv64")))] +mod arch { + pub const ARCH: &str = "aarch64"; + pub const PACKAGE: &str = "axplat-aarch64-generic"; + pub const PLATFORM: &str = "aarch64-generic"; + pub const TIMER_IRQ: usize = 0xf0; + pub const IPI_IRQ: usize = 0; + pub const KERNEL_ASPACE_BASE: usize = 0xffff_8000_0000_0000; + pub const KERNEL_ASPACE_SIZE: usize = 0x0000_7fff_ffff_f000; + pub const KERNEL_BASE_PADDR: usize = 0x20_0000; + pub const KERNEL_BASE_VADDR: usize = 0xffff_8000_0020_0000; +} + #[doc = " Architecture identifier."] -pub const ARCH: &str = "aarch64"; +pub const ARCH: &str = arch::ARCH; #[doc = " Platform package."] -pub const PACKAGE: &str = "axplat-aarch64-generic"; +pub const PACKAGE: &str = arch::PACKAGE; #[doc = " Platform identifier."] -pub const PLATFORM: &str = "aarch64-generic"; +pub const PLATFORM: &str = arch::PLATFORM; #[doc = " Stack size of each task."] pub const TASK_STACK_SIZE: usize = 0x40000; #[doc = " Number of timer ticks per second (Hz). A timer tick may contain several timer"] @@ -28,9 +68,9 @@ pub mod devices { #[doc = " PCI device memory ranges."] pub const PCI_RANGES: &[(usize, usize)] = &[]; #[doc = " Timer interrupt num (PPI, physical timer)."] - pub const TIMER_IRQ: usize = 0xf0; + pub const TIMER_IRQ: usize = super::arch::TIMER_IRQ; #[doc = " IPI interrupt num."] - pub const IPI_IRQ: usize = 0; + pub const IPI_IRQ: usize = super::arch::IPI_IRQ; #[doc = " VirtIO MMIO regions with format (`base_paddr`, `size`)."] pub const VIRTIO_MMIO_REGIONS: &[(usize, usize)] = &[]; #[doc = " SDMMC controller physical address."] @@ -54,13 +94,13 @@ pub mod plat { #[doc = " Platform family (deprecated)."] pub const FAMILY: &str = ""; #[doc = " Kernel address space base."] - pub const KERNEL_ASPACE_BASE: usize = 0xffff_8000_0000_0000; + pub const KERNEL_ASPACE_BASE: usize = super::arch::KERNEL_ASPACE_BASE; #[doc = " Kernel address space size."] - pub const KERNEL_ASPACE_SIZE: usize = 0x0000_7fff_ffff_f000; + pub const KERNEL_ASPACE_SIZE: usize = super::arch::KERNEL_ASPACE_SIZE; #[doc = " No need."] - pub const KERNEL_BASE_PADDR: usize = 0x20_0000; + pub const KERNEL_BASE_PADDR: usize = super::arch::KERNEL_BASE_PADDR; #[doc = " Base virtual address of the kernel image."] - pub const KERNEL_BASE_VADDR: usize = 0xffff_8000_0020_0000; + pub const KERNEL_BASE_VADDR: usize = super::arch::KERNEL_BASE_VADDR; #[doc = " Offset of bus address and phys address. some boards, the bus address is"] #[doc = " different from the physical address."] pub const PHYS_BUS_OFFSET: usize = 0; diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 9e4fb56fd9..ec775a64c8 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -102,10 +102,6 @@ aarch64-phytium-pi = ["dep:ax-plat-aarch64-phytium-pi"] riscv64-qemu-virt = ["dep:ax-plat-riscv64-qemu-virt"] riscv64-sg2002 = ["dep:ax-plat-riscv64-sg2002"] riscv64-visionfive2 = ["dep:ax-plat-riscv64-visionfive2"] -riscv64-qemu-virt-hv = [ - "dep:ax-plat-riscv64-qemu-virt", - "ax-plat-riscv64-qemu-virt/hypervisor", -] loongarch64-qemu-virt = ["dep:ax-plat-loongarch64-qemu-virt"] x86-qemu-q35 = ["dep:ax-plat-x86-qemu-q35", "ax-plat-x86-qemu-q35/reboot-on-system-off"] defplat = [ diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index 116ca0125c..0354c4b780 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -65,11 +65,6 @@ const PLATFORM_FEATURES: &[PlatformFeature] = &[ target_arch: Some("riscv64"), crate_name: "ax_plat_riscv64_visionfive2", }, - PlatformFeature { - feature: "riscv64-qemu-virt-hv", - target_arch: Some("riscv64"), - crate_name: "ax_plat_riscv64_qemu_virt", - }, PlatformFeature { feature: "loongarch64-qemu-virt", target_arch: Some("loongarch64"), diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 9babc5ff1d..65230c060e 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -22,6 +22,7 @@ stack-guard-page = ["multitask", "paging", "ax-task/stack-guard-page"] tls = ["ax-hal/tls", "ax-task?/tls"] plat-dyn = [ + "ax-driver/plat-dyn", "ax-hal/plat-dyn", "paging", "buddy-slab", diff --git a/os/arceos/modules/axruntime/src/devices.rs b/os/arceos/modules/axruntime/src/devices.rs index f7bfe6643f..f218302dfe 100644 --- a/os/arceos/modules/axruntime/src/devices.rs +++ b/os/arceos/modules/axruntime/src/devices.rs @@ -1,5 +1,9 @@ pub(crate) fn probe_all_devices() { info!("Probe platform devices..."); + if !rdrive::is_initialized() { + warn!("rdrive is not initialized; skip platform device probe"); + return; + } rdrive::probe_all(false) .unwrap_or_else(|err| panic!("failed to probe platform devices: {err:?}")); } @@ -9,6 +13,9 @@ pub(crate) fn take_dyn_fs_block_devices() -> alloc::vec::Vec> { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); + } ax_driver::block::take_block_devices() .into_iter() .map(|dev| { @@ -39,6 +46,9 @@ pub(crate) fn take_dyn_fs_ng_block_devices() -> alloc::vec::Vec> { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); + } ax_driver::block::take_block_devices() .into_iter() .map(|dev| { @@ -68,6 +78,10 @@ pub(crate) fn take_static_fs_ng_block_devices() pub(crate) fn init_dyn_display() { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + ax_display::init_display(core::iter::empty::()); + return; + } let devices = ax_driver::display::take_display_devices() .unwrap_or_else(|err| panic!("failed to open display devices: {err:?}")) .into_iter() @@ -100,6 +114,10 @@ pub(crate) fn init_static_display() { pub(crate) fn init_dyn_input() { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + ax_input::init_input(core::iter::empty::()); + return; + } let devices = ax_driver::input::take_input_devices() .unwrap_or_else(|err| panic!("failed to open input devices: {err:?}")) .into_iter() @@ -170,6 +188,10 @@ pub(crate) fn take_static_net_ng_drivers() pub(crate) fn init_dyn_vsock() { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + ax_net_ng::init_vsock(alloc::vec::Vec::new()); + return; + } let devices = ax_driver::vsock::take_vsock_devices() .unwrap_or_else(|err| panic!("failed to open vsock devices: {err:?}")); ax_net_ng::init_vsock(devices); @@ -190,6 +212,9 @@ pub(crate) fn init_static_vsock() { fn take_dyn_net_drivers() -> alloc::vec::Vec> { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); + } let mut devices = alloc::vec::Vec::new(); for dev in rdrive::get_list::() { let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) @@ -211,6 +236,9 @@ fn take_dyn_net_drivers() -> alloc::vec::Vec alloc::vec::Vec> { #[cfg(target_os = "none")] { + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); + } let mut devices = alloc::vec::Vec::new(); for dev in rdrive::get_list::() { let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 1e0b544099..61cba02dee 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -243,13 +243,17 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { info!("Initialize platform devices..."); ax_hal::init_later(cpu_id, arg); - if !rdrive::is_initialized() { + if cfg!(not(feature = "plat-dyn")) && !rdrive::is_initialized() { rdrive::init(rdrive::Platform::Static) .unwrap_or_else(|err| panic!("failed to initialize static rdrive source: {err:?}")); } - registers::append_linker_registers(); - rdrive::probe_pre_kernel() - .unwrap_or_else(|err| panic!("failed to run pre-kernel driver probes: {err:?}")); + if rdrive::is_initialized() { + registers::append_linker_registers(); + rdrive::probe_pre_kernel() + .unwrap_or_else(|err| panic!("failed to run pre-kernel driver probes: {err:?}")); + } else { + warn!("rdrive is not initialized; skip pre-kernel driver probe"); + } #[cfg(feature = "multitask")] ax_task::init_scheduler(); diff --git a/os/arceos/ulib/arceos-rust/lib/Cargo.toml b/os/arceos/ulib/arceos-rust/lib/Cargo.toml index be7f862865..a9a5e45157 100644 --- a/os/arceos/ulib/arceos-rust/lib/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/lib/Cargo.toml @@ -61,11 +61,11 @@ stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] # File system fd = ["ax-posix-api/fd"] -fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs", "ax-driver/pci", "ax-driver/virtio-blk"] +fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs", "ax-driver/virtio-blk"] # Networking dns = [] -net = ["ax-api/net", "ax-posix-api/net", "ax-posix-api/poll", "ax-feat/net", "ax-driver/pci", "ax-driver/virtio-net"] +net = ["ax-api/net", "ax-posix-api/net", "ax-posix-api/poll", "ax-feat/net", "ax-driver/virtio-net"] # Display display = ["ax-api/display", "ax-feat/display"] diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 296e6fa451..ca265eb539 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -105,7 +105,6 @@ arm-gic-driver = { workspace = true, features = ["rdif"] } [target.'cfg(target_arch = "loongarch64")'.dependencies] ax-plat-loongarch64-qemu-virt = { workspace = true, default-features = false, features = ["irq", "smp"] } [target.'cfg(target_arch = "riscv64")'.dependencies] -ax-plat-riscv64-qemu-virt = { workspace = true, features = ["hypervisor"] } riscv_vcpu = { workspace = true } riscv_vplic = { workspace = true } # xtask dependencies (only used on host platforms) diff --git a/os/axvisor/configs/board/qemu-aarch64.toml b/os/axvisor/configs/board/qemu-aarch64.toml index 317e9aae7c..ed80bad75a 100644 --- a/os/axvisor/configs/board/qemu-aarch64.toml +++ b/os/axvisor/configs/board/qemu-aarch64.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/plat-dyn", "ept-level-4", "ax-driver/virtio-blk", "fs", diff --git a/os/axvisor/configs/board/qemu-loongarch64.toml b/os/axvisor/configs/board/qemu-loongarch64.toml index b7945d66d0..06e413dfe1 100644 --- a/os/axvisor/configs/board/qemu-loongarch64.toml +++ b/os/axvisor/configs/board/qemu-loongarch64.toml @@ -2,7 +2,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ "ax-hal/loongarch64-qemu-virt", "ax-driver/virtio-blk", - "ax-driver/pci", + "ax-driver/plat-static", "ept-level-4", "fs", ] diff --git a/os/axvisor/configs/board/qemu-riscv64.toml b/os/axvisor/configs/board/qemu-riscv64.toml index da0062ef6c..ab922ad886 100644 --- a/os/axvisor/configs/board/qemu-riscv64.toml +++ b/os/axvisor/configs/board/qemu-riscv64.toml @@ -1,13 +1,12 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/riscv64-qemu-virt-hv", - "ax-driver/fdt", "ax-driver/virtio-blk", "ept-level-4", "fs", "sstc" ] log = "Info" +plat_dyn = true target = "riscv64gc-unknown-none-elf" vm_configs = [] max_cpu_num = 4 diff --git a/os/axvisor/configs/board/qemu-x86_64.toml b/os/axvisor/configs/board/qemu-x86_64.toml index 64e99f9093..ffae3f21b0 100644 --- a/os/axvisor/configs/board/qemu-x86_64.toml +++ b/os/axvisor/configs/board/qemu-x86_64.toml @@ -2,7 +2,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "ax-hal/x86-qemu-q35", "ax-driver/virtio-blk", - "ax-driver/pci", + "ax-driver/plat-static", "ept-level-4", "fs", ] diff --git a/os/axvisor/configs/board/rdk-s100.toml b/os/axvisor/configs/board/rdk-s100.toml index 252da5797d..c8a96cc301 100644 --- a/os/axvisor/configs/board/rdk-s100.toml +++ b/os/axvisor/configs/board/rdk-s100.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/plat-dyn", "ept-level-4", ] log = "Info" diff --git a/os/axvisor/configs/board/tac-e400.toml b/os/axvisor/configs/board/tac-e400.toml index 252da5797d..c8a96cc301 100644 --- a/os/axvisor/configs/board/tac-e400.toml +++ b/os/axvisor/configs/board/tac-e400.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/plat-dyn", "ept-level-4", ] log = "Info" diff --git a/os/axvisor/src/hal/arch/riscv64/api.rs b/os/axvisor/src/hal/arch/riscv64/api.rs index 93241c1d35..7a845e27f1 100644 --- a/os/axvisor/src/hal/arch/riscv64/api.rs +++ b/os/axvisor/src/hal/arch/riscv64/api.rs @@ -1,5 +1,6 @@ +#[cfg(not(feature = "dyn-plat"))] +compile_error!("riscv64 Axvisor requires the dyn-plat feature"); + pub(super) fn init_platform_irq_injector() { - ax_plat_riscv64_qemu_virt::irq::register_virtual_irq_injector( - crate::hal::arch::inject_interrupt, - ); + axplat_dyn::register_virtual_irq_injector(crate::hal::arch::inject_interrupt); } diff --git a/os/axvisor/src/hal/arch/riscv64/mod.rs b/os/axvisor/src/hal/arch/riscv64/mod.rs index 75b0987487..71dd44c6a0 100644 --- a/os/axvisor/src/hal/arch/riscv64/mod.rs +++ b/os/axvisor/src/hal/arch/riscv64/mod.rs @@ -2,10 +2,11 @@ mod api; pub mod cache; use crate::vmm::vm_list::get_vm_by_id; -use ax_plat_riscv64_qemu_virt::config::devices::PLIC_PADDR; use axaddrspace::{GuestPhysAddr, device::AccessWidth}; use axvisor_api::vmm::current_vm_id; +const GUEST_PLIC_PADDR: usize = 0x0c00_0000; + pub fn hardware_check() { api::init_platform_irq_injector(); // TODO: implement hardware checks for RISC-V64 @@ -19,12 +20,12 @@ pub fn inject_interrupt(irq_id: usize) { let vplic = get_vm_by_id(current_vm_id()) .unwrap() .get_devices() - .find_mmio_dev(GuestPhysAddr::from_usize(PLIC_PADDR)) + .find_mmio_dev(GuestPhysAddr::from_usize(GUEST_PLIC_PADDR)) .unwrap(); // Calulate the pending register offset and value. let reg_offset = riscv_vplic::PLIC_PENDING_OFFSET + (irq_id / 32) * 4; - let addr = GuestPhysAddr::from_usize(PLIC_PADDR + reg_offset); + let addr = GuestPhysAddr::from_usize(GUEST_PLIC_PADDR + reg_offset); let width = AccessWidth::Dword; let val: u32 = 1 << (irq_id % 32); diff --git a/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml b/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml index 677f394324..1eb9dbd453 100644 --- a/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml @@ -30,7 +30,7 @@ ax-page-table-entry = { workspace = true } ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } -ax-driver = { workspace = true, features = ["pci", "virtio"] } +ax-driver = { workspace = true, features = ["plat-static", "virtio"] } ax-plat = { workspace = true } axklib = { workspace = true, optional = true } mmio-api.workspace = true diff --git a/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml b/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml index b0132ff62b..bbef34aa55 100644 --- a/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml @@ -27,7 +27,7 @@ uart_16550 = "0.5" ax-config-macros = { workspace = true } ax-cpu = { workspace = true } -ax-driver = { workspace = true, features = ["pci"] } +ax-driver = { workspace = true, features = ["plat-static"] } ax-plat = { workspace = true } axklib = { workspace = true, optional = true } mmio-api.workspace = true diff --git a/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml b/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml index 12bdbd40fa..a66a880d29 100644 --- a/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml @@ -29,7 +29,7 @@ some-serial.workspace = true ax-config-macros = { workspace = true } ax-cpu = { workspace = true } -ax-driver = { workspace = true, features = ["pci", "virtio"] } +ax-driver = { workspace = true, features = ["plat-static", "virtio"] } ax-plat = { workspace = true } axklib = { workspace = true, optional = true } mmio-api.workspace = true diff --git a/platforms/ax-plat-riscv64-sg2002/Cargo.toml b/platforms/ax-plat-riscv64-sg2002/Cargo.toml index c09cd6fae9..b76056eb81 100644 --- a/platforms/ax-plat-riscv64-sg2002/Cargo.toml +++ b/platforms/ax-plat-riscv64-sg2002/Cargo.toml @@ -24,7 +24,7 @@ smp = ["ax-plat/smp", "ax-kspin/smp"] log = { workspace = true } ax-config-macros = { workspace = true } ax-cpu = { workspace = true, features = ["xuantie-c9xx"] } -ax-driver = { workspace = true, features = ["block"] } +ax-driver = { workspace = true, features = ["plat-static", "block"] } ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } ax-plat = { workspace = true } diff --git a/platforms/ax-plat-x86-pc/Cargo.toml b/platforms/ax-plat-x86-pc/Cargo.toml index 7c035e9652..b544c201c8 100644 --- a/platforms/ax-plat-x86-pc/Cargo.toml +++ b/platforms/ax-plat-x86-pc/Cargo.toml @@ -26,7 +26,7 @@ ax-percpu.workspace = true heapless = "0.9" ax-config-macros = { workspace = true } ax-cpu = { workspace = true } -ax-driver = { workspace = true, features = ["pci"] } +ax-driver = { workspace = true, features = ["plat-static"] } ax-plat = { workspace = true } axklib.workspace = true diff --git a/platforms/ax-plat-x86-qemu-q35/Cargo.toml b/platforms/ax-plat-x86-qemu-q35/Cargo.toml index f4cd07df20..484bbdb6db 100644 --- a/platforms/ax-plat-x86-qemu-q35/Cargo.toml +++ b/platforms/ax-plat-x86-qemu-q35/Cargo.toml @@ -26,7 +26,7 @@ smp = ["ax-plat/smp", "ax-kspin/smp"] [target.'cfg(target_arch = "x86_64")'.dependencies] ax-config-macros = { workspace = true } ax-cpu = { workspace = true, features = ["arm-el2"] } -ax-driver = { workspace = true, features = ["pci"] } +ax-driver = { workspace = true, features = ["plat-static"] } ax-plat.workspace = true axklib.workspace = true bitflags = "2.6" diff --git a/platforms/axplat-dyn/Cargo.toml b/platforms/axplat-dyn/Cargo.toml index aef1d5d1e6..d7e9fdcea1 100644 --- a/platforms/axplat-dyn/Cargo.toml +++ b/platforms/axplat-dyn/Cargo.toml @@ -17,12 +17,13 @@ rtc = [] fp-simd = ["ax-cpu/fp-simd"] uspace = ["somehal/uspace"] hv = ["somehal/hv", "ax-cpu/arm-el2"] +thead-mae = ["somehal/thead-mae", "ax-cpu/xuantie-c9xx"] [dependencies] anyhow = { version = "1", default-features = false } ax-config-macros = { workspace = true } ax-cpu.workspace = true -ax-driver = { workspace = true, features = ["fdt", "pci-fdt"] } +ax-driver = { workspace = true, features = ["plat-dyn"] } ax-errno.workspace = true axklib.workspace = true ax-plat.workspace = true diff --git a/platforms/axplat-dyn/src/boot.rs b/platforms/axplat-dyn/src/boot.rs index d1b16cc13a..667a2860d3 100644 --- a/platforms/axplat-dyn/src/boot.rs +++ b/platforms/axplat-dyn/src/boot.rs @@ -11,7 +11,7 @@ fn main() -> ! { args = fdt; } - ax_plat::call_main(somehal::smp::cpu_idx(), args) + ax_plat::call_main(somehal::smp::early_current_cpu_idx(), args) } #[somehal::secondary_entry] diff --git a/platforms/axplat-dyn/src/drivers/mod.rs b/platforms/axplat-dyn/src/drivers/mod.rs index c2d12a2374..9edd46844a 100644 --- a/platforms/axplat-dyn/src/drivers/mod.rs +++ b/platforms/axplat-dyn/src/drivers/mod.rs @@ -1,5 +1,9 @@ use ax_errno::AxError; pub fn probe_all_devices() -> Result<(), AxError> { + if !rdrive::is_initialized() { + warn!("rdrive is not initialized; skip platform device probe"); + return Ok(()); + } rdrive::probe_all(false).map_err(|_| AxError::BadState) } diff --git a/platforms/axplat-dyn/src/init.rs b/platforms/axplat-dyn/src/init.rs index d68349a3e8..190995198f 100644 --- a/platforms/axplat-dyn/src/init.rs +++ b/platforms/axplat-dyn/src/init.rs @@ -9,7 +9,9 @@ impl InitIf for InitIfImpl { /// This function should be called immediately after the kernel has booted, /// and performed earliest platform configuration and initialization (e.g., /// early console, clocking). - fn init_early(_cpu_id: usize, _dtb: usize) { + fn init_early(cpu_id: usize, _dtb: usize) { + #[cfg(target_arch = "riscv64")] + somehal::arch::register_current_cpu_id(cpu_id, ax_plat::percpu::this_cpu_id); ax_cpu::init::init_trap(); #[cfg(all(target_arch = "aarch64", feature = "fp-simd"))] { @@ -21,7 +23,9 @@ impl InitIf for InitIfImpl { /// Initializes the platform at the early stage for secondary cores. #[cfg(feature = "smp")] - fn init_early_secondary(_cpu_id: usize) { + fn init_early_secondary(cpu_id: usize) { + #[cfg(target_arch = "riscv64")] + somehal::arch::register_current_cpu_id(cpu_id, ax_plat::percpu::this_cpu_id); ax_cpu::init::init_trap(); #[cfg(all(target_arch = "aarch64", feature = "fp-simd"))] { diff --git a/platforms/axplat-dyn/src/irq.rs b/platforms/axplat-dyn/src/irq.rs index 254eaa3253..54c9c9c60a 100644 --- a/platforms/axplat-dyn/src/irq.rs +++ b/platforms/axplat-dyn/src/irq.rs @@ -1,12 +1,37 @@ +use core::sync::atomic::{AtomicPtr, Ordering}; + use ax_plat::irq::{HandlerTable, IrqHandler, IrqIf}; use somehal::irq_handler; /// The maximum number of IRQs. const MAX_IRQ_COUNT: usize = 1024; +#[cfg(target_arch = "aarch64")] const GIC_SPECIAL_IRQ_START: usize = 1020; +#[cfg(target_arch = "riscv64")] +const INTC_IRQ_BASE: usize = 1usize << (usize::BITS as usize - 1); +#[cfg(target_arch = "riscv64")] +const S_SOFT: usize = INTC_IRQ_BASE | 1; +#[cfg(target_arch = "riscv64")] +const S_TIMER: usize = INTC_IRQ_BASE | 5; +#[cfg(target_arch = "riscv64")] +const S_EXT: usize = INTC_IRQ_BASE | 9; + static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); +#[cfg(target_arch = "riscv64")] +static TIMER_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); +#[cfg(target_arch = "riscv64")] +static IPI_HANDLER: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +#[cfg(all(target_arch = "riscv64", feature = "hv"))] +static VIRTUAL_IRQ_INJECTOR: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +#[cfg(all(target_arch = "riscv64", feature = "hv"))] +pub fn register_virtual_irq_injector(injector: fn(usize)) { + VIRTUAL_IRQ_INJECTOR.store(injector as *mut (), Ordering::Release); +} + struct IrqIfImpl; #[impl_plat_interface] @@ -23,6 +48,18 @@ impl IrqIf for IrqIfImpl { fn register(irq_num: usize, handler: IrqHandler) -> bool { debug!("register handler IRQ {}", irq_num); + #[cfg(target_arch = "riscv64")] + { + if register_local_irq(irq_num, handler) { + Self::set_enable(irq_num, true); + return true; + } + if is_riscv_local_irq(irq_num) { + warn!("register handler for local IRQ {} failed", irq_num); + return false; + } + } + if IRQ_HANDLER_TABLE.register_handler(irq_num, handler) { Self::set_enable(irq_num, true); return true; @@ -38,6 +75,15 @@ impl IrqIf for IrqIfImpl { fn unregister(irq_num: usize) -> Option { trace!("unregister handler IRQ {}", irq_num); Self::set_enable(irq_num, false); + #[cfg(target_arch = "riscv64")] + { + if let Some(handler) = unregister_local_irq(irq_num) { + return Some(handler); + } + if is_riscv_local_irq(irq_num) { + return None; + } + } IRQ_HANDLER_TABLE.unregister_handler(irq_num) } @@ -46,8 +92,8 @@ impl IrqIf for IrqIfImpl { /// It is called by the common interrupt handler. It should look up in the /// IRQ handler table and calls the corresponding handler. If necessary, it /// also acknowledges the interrupt controller after handling. - fn handle(_irq_num: usize) -> Option { - let irq = somehal::irq::irq_handler_raw(); + fn handle(irq_num: usize) -> Option { + let irq = somehal::irq::irq_handler_with_raw(irq_num)?; Some(irq.raw()) } @@ -67,11 +113,102 @@ impl IrqIf for IrqIfImpl { #[irq_handler] fn somehal_handle_irq(irq: somehal::irq::IrqId) { + #[cfg(target_arch = "aarch64")] if irq.raw() >= GIC_SPECIAL_IRQ_START { trace!("Ignoring special IRQ {irq:?}"); return; } - if !IRQ_HANDLER_TABLE.handle(irq.raw()) { + + #[cfg(target_arch = "riscv64")] + if handle_riscv_local_irq(irq.raw()) { + return; + } + + #[cfg(all(target_arch = "riscv64", feature = "hv"))] + if inject_virtual_irq(irq.raw()) { + return; + } + + if irq.raw() < MAX_IRQ_COUNT && IRQ_HANDLER_TABLE.handle(irq.raw()) { + return; + } + + if irq.raw() >= MAX_IRQ_COUNT { + warn!("IRQ {irq:?} is outside handler table"); + } else { warn!("Unhandled IRQ {irq:?}"); } } + +#[cfg(target_arch = "riscv64")] +fn is_riscv_local_irq(irq: usize) -> bool { + matches!(irq, S_TIMER | S_SOFT | S_EXT) || irq & INTC_IRQ_BASE != 0 +} + +#[cfg(target_arch = "riscv64")] +fn register_local_irq(irq: usize, handler: IrqHandler) -> bool { + let slot = match irq { + S_TIMER => &TIMER_HANDLER, + S_SOFT => &IPI_HANDLER, + S_EXT => return false, + _ => return false, + }; + slot.compare_exchange( + core::ptr::null_mut(), + handler as *mut (), + Ordering::AcqRel, + Ordering::Acquire, + ) + .is_ok() +} + +#[cfg(target_arch = "riscv64")] +fn unregister_local_irq(irq: usize) -> Option { + let slot = match irq { + S_TIMER => &TIMER_HANDLER, + S_SOFT => &IPI_HANDLER, + _ => return None, + }; + let handler = slot.swap(core::ptr::null_mut(), Ordering::AcqRel); + if handler.is_null() { + None + } else { + Some(unsafe { core::mem::transmute::<*mut (), IrqHandler>(handler) }) + } +} + +#[cfg(target_arch = "riscv64")] +fn handle_riscv_local_irq(irq: usize) -> bool { + let slot = match irq { + S_TIMER => &TIMER_HANDLER, + S_SOFT => &IPI_HANDLER, + S_EXT => return false, + _ if irq & INTC_IRQ_BASE != 0 => { + warn!("Unhandled RISC-V local IRQ {irq:#x}"); + return true; + } + _ => return false, + }; + let handler = slot.load(Ordering::Acquire); + if handler.is_null() { + warn!("Unhandled RISC-V local IRQ {irq:#x}"); + return true; + } + unsafe { + core::mem::transmute::<*mut (), IrqHandler>(handler)(irq); + } + true +} + +#[cfg(all(target_arch = "riscv64", feature = "hv"))] +fn inject_virtual_irq(irq: usize) -> bool { + let injector = VIRTUAL_IRQ_INJECTOR.load(Ordering::Acquire); + if injector.is_null() { + warn!("virtual IRQ injector is not registered"); + return false; + } + unsafe { + core::mem::transmute::<*mut (), fn(usize)>(injector)(irq); + } + true +} diff --git a/platforms/axplat-dyn/src/lib.rs b/platforms/axplat-dyn/src/lib.rs index ffb0a89ee8..cabac9f71a 100644 --- a/platforms/axplat-dyn/src/lib.rs +++ b/platforms/axplat-dyn/src/lib.rs @@ -27,6 +27,8 @@ fn somehal_handle_irq(_irq: somehal::irq::IrqId) {} pub use boot::boot_stack_bounds; pub use generic_timer::try_init_epoch_offset; +#[cfg(all(feature = "irq", target_arch = "riscv64", feature = "hv"))] +pub use irq::register_virtual_irq_injector; // pub mod config { // //! Platform configuration module. diff --git a/platforms/somehal/Cargo.toml b/platforms/somehal/Cargo.toml index 97b6cd2394..274ddf5562 100644 --- a/platforms/somehal/Cargo.toml +++ b/platforms/somehal/Cargo.toml @@ -14,6 +14,7 @@ efi = ["someboot/efi"] hv = ["mmu", "someboot/hv"] mmu = ["someboot/mmu"] percpu-prealloc = ["someboot/percpu-prealloc"] +thead-mae = ["someboot/thead-mae"] uspace = ["mmu", "someboot/uspace"] [dependencies] @@ -34,3 +35,8 @@ spin = { workspace = true } [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu = "11" arm-gic-driver = {workspace = true, features = ["rdif"]} + +[target.'cfg(target_arch = "riscv64")'.dependencies] +ax-riscv-plic = { workspace = true } +riscv = "0.15" +sbi-rt = { version = "0.0.3", features = ["legacy"] } diff --git a/platforms/somehal/src/arch/aarch64/mod.rs b/platforms/somehal/src/arch/aarch64/mod.rs index 063fb5ae98..2200602ced 100644 --- a/platforms/somehal/src/arch/aarch64/mod.rs +++ b/platforms/somehal/src/arch/aarch64/mod.rs @@ -24,7 +24,7 @@ impl PlatOp for Plat { fn secondary_init() {} - fn secondary_init_intc() { + fn secondary_init_intc(_cpu_idx: usize) { gic::init_current_cpu(); } diff --git a/platforms/somehal/src/arch/loongarch64/mod.rs b/platforms/somehal/src/arch/loongarch64/mod.rs index 589a8c1266..078425f223 100644 --- a/platforms/somehal/src/arch/loongarch64/mod.rs +++ b/platforms/somehal/src/arch/loongarch64/mod.rs @@ -15,7 +15,7 @@ impl PlatOp for Plat { fn secondary_init() {} - fn secondary_init_intc() {} + fn secondary_init_intc(_cpu_idx: usize) {} fn secondary_init_systick() {} } diff --git a/platforms/somehal/src/arch/riscv64/mod.rs b/platforms/somehal/src/arch/riscv64/mod.rs index b3ff36e065..55552474e4 100644 --- a/platforms/somehal/src/arch/riscv64/mod.rs +++ b/platforms/somehal/src/arch/riscv64/mod.rs @@ -1,27 +1,54 @@ use crate::common::PlatOp; +mod plic; + pub struct Plat; +pub fn register_current_cpu_id(cpu_idx: usize, reader: fn() -> usize) { + plic::register_current_cpu_id(cpu_idx, reader); +} + impl PlatOp for Plat { fn irq_set_enable(irq: rdrive::IrqId, enable: bool) { - let raw: usize = irq.into(); - let irq = someboot::irq::IrqId::new(raw); - if irq == someboot::irq::systimer_irq() { - someboot::irq::irq_set_enable(irq, enable); - } + plic::irq_set_enable(irq, enable); } fn irq_handler() -> someboot::irq::IrqId { - someboot::irq::systimer_irq() + someboot::irq::IrqId::new(plic::systick_irq().raw()) + } + + fn irq_handler_with_raw(raw: usize) -> Option { + plic::irq_handler_with_raw(raw) } fn systick_irq() -> rdrive::IrqId { - someboot::irq::systimer_irq().raw().into() + plic::systick_irq() } fn secondary_init() {} - fn secondary_init_intc() {} + fn secondary_init_intc(cpu_idx: usize) { + plic::secondary_init_intc(cpu_idx); + } fn secondary_init_systick() {} + + fn send_ipi(_irq: rdrive::IrqId, target: crate::irq::IpiTarget) { + match target { + crate::irq::IpiTarget::Current { cpu_id } | crate::irq::IpiTarget::Other { cpu_id } => { + plic::send_ipi_to_cpu(cpu_id); + } + crate::irq::IpiTarget::AllExceptCurrent { cpu_id, cpu_num } => { + for target_cpu in 0..cpu_num { + if target_cpu != cpu_id { + plic::send_ipi_to_cpu(target_cpu); + } + } + } + } + } + + fn send_ipi_to_cpu(cpu_id: usize) { + plic::send_ipi_to_cpu(cpu_id); + } } diff --git a/platforms/somehal/src/arch/riscv64/plic.rs b/platforms/somehal/src/arch/riscv64/plic.rs new file mode 100644 index 0000000000..a4ac2a72e6 --- /dev/null +++ b/platforms/somehal/src/arch/riscv64/plic.rs @@ -0,0 +1,393 @@ +use alloc::{format, vec::Vec}; +use core::{ + num::NonZeroU32, + ptr::NonNull, + sync::atomic::{AtomicPtr, Ordering}, +}; + +use ax_riscv_plic::{PLICRegs, Plic, PlicIrqHandler}; +use kernutil::StaticCell; +use rdif_intc::Interface; +use rdrive::{ + Device, DriverGeneric, Phandle, PlatformDevice, module_driver, + probe::{OnProbeError, fdt::NodeType}, + register::FdtInfo, +}; +use riscv::register::{sie, sip}; +use sbi_rt::HartMask; + +use crate::{common::ioremap, irq::_handle_irq}; + +const INTC_IRQ_BASE: usize = 1usize << (usize::BITS as usize - 1); +const S_SOFT: usize = INTC_IRQ_BASE | 1; +const S_TIMER: usize = INTC_IRQ_BASE | 5; +const S_EXT: usize = INTC_IRQ_BASE | 9; +const SUPERVISOR_EXTERNAL_INTERRUPT: u32 = 9; +const DEFAULT_PRIORITY: u32 = 1; +const DEFAULT_PLIC_SIZE: usize = 0x400_0000; + +static IRQ_HANDLER: StaticCell = StaticCell::uninit(); +static CURRENT_CPU_ID: AtomicPtr<()> = AtomicPtr::new(core::ptr::null_mut()); + +module_driver!( + name: "RISC-V PLIC", + level: ProbeLevel::PreKernel, + priority: ProbePriority::INTC, + probe_kinds: &[ProbeKind::Fdt { + compatibles: &[ + "riscv,plic0", + "sifive,plic-1.0.0", + "starfive,jh7110-plic", + ], + on_probe: probe_plic + }], +); + +pub fn systick_irq() -> rdrive::IrqId { + S_TIMER.into() +} + +pub fn register_current_cpu_id(_cpu_idx: usize, reader: fn() -> usize) { + CURRENT_CPU_ID.store(reader as *mut (), Ordering::Release); +} + +pub fn irq_set_enable(irq: rdrive::IrqId, enable: bool) { + let raw: usize = irq.into(); + match raw { + S_TIMER => unsafe { + if enable { + sie::set_stimer(); + } else { + sie::clear_stimer(); + } + }, + S_SOFT => unsafe { + if enable { + sie::set_ssoft(); + } else { + sie::clear_ssoft(); + } + }, + S_EXT => unsafe { + if enable { + sie::set_sext(); + } else { + sie::clear_sext(); + } + }, + external if external & INTC_IRQ_BASE == 0 => set_external_irq_enable(external, enable), + other => warn!("unsupported RISC-V local IRQ {other:#x}"), + } +} + +pub fn irq_handler_with_raw(raw: usize) -> Option { + match raw { + S_TIMER => { + _handle_irq(S_TIMER.into()); + Some(S_TIMER.into()) + } + S_SOFT => { + unsafe { + sip::clear_ssoft(); + } + _handle_irq(S_SOFT.into()); + Some(S_SOFT.into()) + } + S_EXT => handle_external_irq(), + external if external & INTC_IRQ_BASE == 0 => { + _handle_irq(external.into()); + Some(external.into()) + } + other => { + warn!("unsupported RISC-V interrupt cause {other:#x}"); + None + } + } +} + +pub fn secondary_init_intc(cpu_idx: usize) { + enable_local_interrupts(); + if let Some(handler) = get_irq_handler() { + handler.init_context(cpu_idx); + } +} + +pub fn send_ipi_to_cpu(cpu_id: usize) { + let Some(hart_id) = someboot::smp::cpu_idx_to_id(cpu_id) else { + warn!("failed to resolve hart id for logical CPU {cpu_id}"); + return; + }; + let res = sbi_rt::send_ipi(HartMask::from_mask_base(1, hart_id)); + if !res.is_ok() { + warn!("send_ipi to hart {hart_id} failed: {res:?}"); + } +} + +fn probe_plic(info: FdtInfo<'_>, dev: PlatformDevice) -> Result<(), OnProbeError> { + let reg = info + .node + .regs() + .into_iter() + .next() + .ok_or_else(|| OnProbeError::other(format!("[{}] has no reg", info.node.name())))?; + let mmio = ioremap( + reg.address, + reg.size.unwrap_or(DEFAULT_PLIC_SIZE as u64) as usize, + ) + .map_err(|err| OnProbeError::other(format!("failed to map PLIC: {err:?}")))?; + let plic = unsafe { + Plic::new( + NonNull::new(mmio.as_ptr() as *mut PLICRegs) + .ok_or_else(|| OnProbeError::other("PLIC MMIO mapping is null"))?, + ) + }; + let ndev = info + .node + .as_node() + .get_property("riscv,ndev") + .and_then(|prop| prop.get_u32()) + .unwrap_or(1024) as usize; + let contexts = parse_supervisor_contexts(&info); + + let irq_handler = RiscvPlicIrqHandler { + inner: plic.irq_handler(), + context_by_cpu: contexts.clone(), + }; + IRQ_HANDLER.init(irq_handler); + IRQ_HANDLER.init_current_context(); + let plic = RiscvPlic { + inner: plic, + context_by_cpu: contexts, + sources: ndev, + }; + enable_local_interrupts(); + + dev.register(rdif_intc::Intc::new(plic)); + Ok(()) +} + +fn parse_supervisor_contexts(info: &FdtInfo<'_>) -> Vec> { + let mut contexts = Vec::new(); + let Some(prop) = info.node.as_node().get_property("interrupts-extended") else { + return contexts; + }; + + let mut reader = prop.as_reader(); + let mut context = 0; + while let (Some(phandle), Some(interrupt)) = (reader.read_u32(), reader.read_u32()) { + if interrupt == SUPERVISOR_EXTERNAL_INTERRUPT + && let Some(cpu_idx) = cpu_idx_from_intc_phandle(info, Phandle::from(phandle)) + { + if contexts.len() <= cpu_idx { + contexts.resize(cpu_idx + 1, None); + } + contexts[cpu_idx] = Some(context); + } + context += 1; + } + contexts +} + +fn cpu_idx_from_intc_phandle(info: &FdtInfo<'_>, phandle: Phandle) -> Option { + let intc = info.get_by_phandle(phandle)?; + if let Some(cpu_idx) = intc.parent().and_then(|cpu| cpu_idx_from_cpu_node(&cpu)) { + return Some(cpu_idx); + } + let cpu = info.get_by_phandle(intc.as_node().interrupt_parent()?)?; + cpu_idx_from_cpu_node(&cpu) +} + +fn cpu_idx_from_cpu_node(cpu: &NodeType<'_>) -> Option { + let hart_id = cpu.regs().first()?.address as usize; + someboot::smp::cpu_id_to_idx(hart_id) +} + +fn enable_local_interrupts() { + unsafe { + sie::set_ssoft(); + sie::set_stimer(); + sie::set_sext(); + } +} + +fn set_external_irq_enable(irq: usize, enable: bool) { + let Some(source) = NonZeroU32::new(irq as u32) else { + return; + }; + with_plic("setting PLIC IRQ enable", |plic| { + if enable { + plic.enable_source(source); + } else { + plic.disable_source(source); + } + }); +} + +fn handle_external_irq() -> Option { + let Some(handler) = get_irq_handler() else { + warn!("RISC-V PLIC IRQ handler is not registered for external IRQ"); + return None; + }; + let source = handler.claim_current()?; + let irq = someboot::irq::IrqId::new(source.get() as usize); + _handle_irq(irq.raw().into()); + handler.complete_current(source); + Some(irq) +} + +fn with_plic(op: &str, f: impl FnOnce(&mut RiscvPlic) -> R) -> Option { + let Some(intc) = get_plic() else { + warn!("RISC-V PLIC is not registered when {op}"); + return None; + }; + let Ok(mut intc) = intc.lock() else { + warn!("failed to lock RISC-V PLIC when {op}"); + return None; + }; + let Some(plic) = intc.typed_mut::() else { + warn!("registered interrupt controller is not RISC-V PLIC when {op}"); + return None; + }; + Some(f(plic)) +} + +fn get_plic() -> Option> { + if !rdrive::is_initialized() { + return None; + } + rdrive::get_one() +} + +fn get_irq_handler() -> Option<&'static RiscvPlicIrqHandler> { + if IRQ_HANDLER.is_init() { + Some(&IRQ_HANDLER) + } else { + None + } +} + +struct RiscvPlic { + inner: Plic, + context_by_cpu: Vec>, + sources: usize, +} + +struct RiscvPlicIrqHandler { + inner: PlicIrqHandler, + context_by_cpu: Vec>, +} + +impl RiscvPlicIrqHandler { + fn current_context(&self) -> Option { + current_context(&self.context_by_cpu) + } + + fn init_current_context(&self) { + if let Some(context) = self.current_context() { + self.init_context_by_context_id(context); + } else { + warn_missing_current_context(); + } + } + + fn init_context(&self, cpu_idx: usize) { + if let Some(context) = self.context_by_cpu.get(cpu_idx).and_then(|ctx| *ctx) { + self.init_context_by_context_id(context); + } else { + warn!("PLIC supervisor context for logical CPU {cpu_idx} is not found"); + } + } + + fn init_context_by_context_id(&self, context: usize) { + self.inner.init_by_context(context); + trace!("PLIC context {context} initialized"); + } + + fn claim_current(&self) -> Option { + let Some(context) = self.current_context() else { + warn_missing_current_context(); + return None; + }; + let Some(source) = self.inner.claim(context) else { + debug!("Spurious external IRQ"); + return None; + }; + Some(source) + } + + fn complete_current(&self, source: NonZeroU32) { + let Some(context) = self.current_context() else { + warn_missing_current_context(); + return; + }; + self.inner.complete(context, source); + } +} + +impl RiscvPlic { + fn enable_source(&mut self, source: NonZeroU32) { + if source.get() as usize > self.sources { + warn!("skip enabling out-of-range PLIC source {}", source.get()); + return; + } + self.inner.set_priority(source, DEFAULT_PRIORITY); + let current = current_context(&self.context_by_cpu); + for context in self.context_by_cpu.iter().filter_map(|context| *context) { + self.inner.enable(source, context); + } + if current.is_none() { + warn_missing_current_context(); + } + } + + fn disable_source(&mut self, source: NonZeroU32) { + for context in self.context_by_cpu.iter().filter_map(|context| *context) { + self.inner.disable(source, context); + } + } +} + +fn current_context(context_by_cpu: &[Option]) -> Option { + let cpu_idx = current_cpu_idx()?; + context_by_cpu.get(cpu_idx).and_then(|ctx| *ctx) +} + +fn current_cpu_idx() -> Option { + let reader = CURRENT_CPU_ID.load(Ordering::Acquire); + if !reader.is_null() { + let reader = unsafe { core::mem::transmute::<*mut (), fn() -> usize>(reader) }; + return Some(reader()); + } + + someboot::smp::try_early_cpu_idx() +} + +fn warn_missing_current_context() { + if let Some(cpu_idx) = current_cpu_idx() { + warn!("PLIC supervisor context for logical CPU {cpu_idx} is not found"); + } else { + warn!("PLIC supervisor context for current logical CPU is not found"); + } +} + +impl DriverGeneric for RiscvPlic { + fn name(&self) -> &str { + "RISC-V PLIC" + } +} + +impl Interface for RiscvPlic { + fn setup_irq_by_fdt(&mut self, irq_prop: &[u32]) -> rdrive::IrqId { + let Some(source) = irq_prop.first().copied().map(|source| source as usize) else { + warn!("empty PLIC interrupt specifier"); + return 0usize.into(); + }; + if source > self.sources { + warn!( + "PLIC interrupt source {} exceeds riscv,ndev {}", + source, self.sources + ); + } + source.into() + } +} diff --git a/platforms/somehal/src/arch/x86_64/mod.rs b/platforms/somehal/src/arch/x86_64/mod.rs index b4da28abf0..be2d02e560 100644 --- a/platforms/somehal/src/arch/x86_64/mod.rs +++ b/platforms/somehal/src/arch/x86_64/mod.rs @@ -22,7 +22,7 @@ impl PlatOp for Plat { fn secondary_init() {} - fn secondary_init_intc() {} + fn secondary_init_intc(_cpu_idx: usize) {} fn secondary_init_systick() {} } diff --git a/platforms/somehal/src/common.rs b/platforms/somehal/src/common.rs index f58cc8e5a8..d36fe418d7 100644 --- a/platforms/somehal/src/common.rs +++ b/platforms/somehal/src/common.rs @@ -12,13 +12,22 @@ pub trait PlatOp { fn irq_handler() -> someboot::irq::IrqId; + fn irq_handler_with_raw(raw: usize) -> Option { + let _ = raw; + Some(Self::irq_handler()) + } + fn systick_irq() -> IrqId; fn secondary_init(); - fn secondary_init_intc(); + fn secondary_init_intc(cpu_idx: usize); fn secondary_init_systick(); + + fn send_ipi_to_cpu(cpu_id: usize) { + let _ = cpu_id; + } } #[allow(dead_code)] diff --git a/platforms/somehal/src/driver.rs b/platforms/somehal/src/driver.rs index 7cc021f467..0336f165c9 100644 --- a/platforms/somehal/src/driver.rs +++ b/platforms/somehal/src/driver.rs @@ -7,5 +7,17 @@ pub fn rdrive_setup() { addr: NonNull::new(addr).unwrap(), }) .unwrap(); + } else if let Some(rsdp) = someboot::rsdp_addr_phys() { + info!("Initializing rdrive with ACPI RSDP at {:#x}", rsdp); + if let Err(err) = rdrive::init(rdrive::Platform::Acpi(rdrive::probe::acpi::AcpiRoot { + rsdp, + })) { + warn!( + "failed to initialize rdrive with ACPI RSDP {:#x}: {:?}", + rsdp, err + ); + } + } else { + warn!("No FDT or ACPI RSDP found; skip rdrive initialization"); } } diff --git a/platforms/somehal/src/irq.rs b/platforms/somehal/src/irq.rs index 8d976368e6..7ae604567e 100644 --- a/platforms/somehal/src/irq.rs +++ b/platforms/somehal/src/irq.rs @@ -59,3 +59,11 @@ pub(crate) fn _handle_irq(hwirq: IrqId) { pub fn irq_handler_raw() -> IrqId { Plat::irq_handler().raw().into() } + +pub fn irq_handler_with_raw(raw: usize) -> Option { + Plat::irq_handler_with_raw(raw).map(|irq| irq.raw().into()) +} + +pub fn send_ipi_to_cpu(cpu_id: usize) { + Plat::send_ipi_to_cpu(cpu_id); +} diff --git a/platforms/somehal/src/lib.rs b/platforms/somehal/src/lib.rs index b3f7f66212..bec4b660b3 100644 --- a/platforms/somehal/src/lib.rs +++ b/platforms/somehal/src/lib.rs @@ -59,7 +59,7 @@ pub fn __somehal_secondary_default() -> ! { fn secondary_entry() -> ! { someboot::set_kernel_page_table_paddr(meta.primary_table_paddr); arch::Plat::secondary_init(); - arch::Plat::secondary_init_intc(); + arch::Plat::secondary_init_intc(meta.cpu_idx); arch::Plat::secondary_init_systick(); unsafe extern "Rust" { diff --git a/scripts/axbuild/src/arceos/build.rs b/scripts/axbuild/src/arceos/build.rs index 46857f4653..b86fb0182c 100644 --- a/scripts/axbuild/src/arceos/build.rs +++ b/scripts/axbuild/src/arceos/build.rs @@ -182,7 +182,7 @@ mod tests { build_info.resolve_features("ax-helloworld", "aarch64-unknown-none-softfloat", true); assert!(build_info.features.contains(&"ax-std/plat-dyn".to_string())); - assert!(build_info.features.contains(&"ax-hal/plat-dyn".to_string())); + assert!(!build_info.features.contains(&"ax-hal/plat-dyn".to_string())); assert!(!build_info.features.contains(&"ax-std/defplat".to_string())); let args = ArceosBuildInfo::build_cargo_args( @@ -411,6 +411,11 @@ AX_IP = "10.0.2.15" true, Some(false) )); + assert!(build::resolve_effective_plat_dyn( + "riscv64gc-unknown-none-elf", + true, + None + )); assert!(!build::resolve_effective_plat_dyn( "x86_64-unknown-none", true, diff --git a/scripts/axbuild/src/arceos/cbuild.rs b/scripts/axbuild/src/arceos/cbuild.rs index a432758f7f..cb38846663 100644 --- a/scripts/axbuild/src/arceos/cbuild.rs +++ b/scripts/axbuild/src/arceos/cbuild.rs @@ -418,7 +418,7 @@ mod tests { let features = c_config_features(&strings(&[ "ax-libc/net", "ax-feat/paging", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-net", "ax-hal/riscv64-qemu-virt", "some-crate/feature", @@ -433,13 +433,13 @@ mod tests { #[test] fn map_c_app_features_preserves_driver_features() { let features = map_c_app_features( - &strings(&["net", "ax-driver/pci", "ax-driver/virtio-net"]), + &strings(&["net", "ax-driver/plat-static", "ax-driver/virtio-net"]), &strings(&["ax-hal/riscv64-qemu-virt"]), ); assert!(features.contains(&"ax-libc/net".to_string())); assert!(features.contains(&"ax-libc/fd".to_string())); - assert!(features.contains(&"ax-driver/pci".to_string())); + assert!(features.contains(&"ax-driver/plat-static".to_string())); assert!(features.contains(&"ax-driver/virtio-net".to_string())); assert!(features.contains(&"ax-hal/riscv64-qemu-virt".to_string())); } diff --git a/scripts/axbuild/src/axvisor/board.rs b/scripts/axbuild/src/axvisor/board.rs index 7f03fdb8c9..f6a8c87094 100644 --- a/scripts/axbuild/src/axvisor/board.rs +++ b/scripts/axbuild/src/axvisor/board.rs @@ -152,6 +152,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "riscv64gc-unknown-none-elf" features = ["ept-level-4"] log = "Info" +plat_dyn = true "#, ); diff --git a/scripts/axbuild/src/axvisor/build.rs b/scripts/axbuild/src/axvisor/build.rs index f09c126e01..1d8e6a5637 100644 --- a/scripts/axbuild/src/axvisor/build.rs +++ b/scripts/axbuild/src/axvisor/build.rs @@ -125,6 +125,9 @@ fn to_cargo_config( metadata: &cargo_metadata::Metadata, ) -> anyhow::Result { config.target = request.target.clone(); + let plat_dyn = config + .build_info + .effective_plat_dyn(&config.target, request.plat_dyn); let mut cargo = config .build_info .into_prepared_base_cargo_config_with_metadata( @@ -133,6 +136,15 @@ fn to_cargo_config( request.plat_dyn, metadata, )?; + if plat_dyn { + cargo.features.retain(|feature| { + !matches!( + feature.as_str(), + "ax-std/plat-dyn" | "ax-feat/plat-dyn" | "dyn-plat" + ) + }); + cargo.features.push("dyn-plat".to_string()); + } patch_axvisor_cargo_config(&mut cargo, request, &config.vm_configs)?; Ok(cargo) } @@ -398,7 +410,7 @@ mod tests { r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["ept-level-4", "ax-driver/fdt"] +features = ["ept-level-4"] log = "Info" plat_dyn = true vm_configs = [] @@ -413,7 +425,17 @@ vm_configs = [] .unwrap(); assert!(cargo.features.contains(&"ept-level-4".to_string())); - assert!(cargo.features.contains(&"ax-driver/fdt".to_string())); + assert!(cargo.features.contains(&"dyn-plat".to_string())); + assert!( + !cargo + .features + .contains(&concat!("ax-driver/", "plat-dyn").to_string()) + ); + assert!( + !cargo + .features + .contains(&concat!("ax-hal/", "plat-dyn").to_string()) + ); assert!(path.exists()); } @@ -600,8 +622,9 @@ plat_dyn = false &config_path, r#" env = {} -features = ["ept-level-4", "ax-driver/fdt"] +features = ["ept-level-4"] log = "Info" +plat_dyn = false "#, ) .unwrap(); diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index 6b80f68708..d2dce88ebc 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -345,16 +345,12 @@ impl BuildInfo { has_myplat: bool, metadata: Option<&Metadata>, ) { - if has_myplat || has_ax_hal_platform_feature(&self.features, metadata) { + if plat_dyn || has_myplat || has_ax_hal_platform_feature(&self.features, metadata) { return; } - let feature = if plat_dyn { - "ax-hal/plat-dyn".to_string() - } else { - default_ax_hal_platform_feature(target, metadata) - .unwrap_or_else(|_| "ax-hal/defplat".to_string()) - }; + let feature = default_ax_hal_platform_feature(target, metadata) + .unwrap_or_else(|_| "ax-hal/defplat".to_string()); self.features.push(feature); } @@ -474,7 +470,10 @@ pub(crate) fn cargo_target_json_path(target: &str, plat_dyn: bool) -> anyhow::Re }; if plat_dyn { - if target != "aarch64-unknown-none-softfloat" { + if !matches!( + target, + "aarch64-unknown-none-softfloat" | "riscv64gc-unknown-none-elf" + ) { bail!("unsupported PIE target `{target}`"); } Ok(Path::new(TARGET_JSON_ROOT) @@ -664,7 +663,7 @@ pub(crate) fn resolve_effective_plat_dyn( } fn supports_platform_dynamic(target: &str) -> bool { - target.starts_with("aarch64-") + target.starts_with("aarch64-") || target.starts_with("riscv64") } fn default_to_bin_for_target(target: &str) -> bool { @@ -929,7 +928,6 @@ fn ax_hal_platform_feature_name<'a>( let platform = feature.strip_prefix("ax-hal/")?; match platform { "plat-dyn" => Some(platform), - "riscv64-qemu-virt-hv" => Some(platform), _ if metadata .map(|metadata| platform_package_by_name(metadata, platform).is_some()) .unwrap_or_else(|| is_known_ax_hal_platform_feature(platform)) => @@ -1035,10 +1033,6 @@ fn platform_packages(metadata: &Metadata) -> Vec { } fn platform_package_by_name(metadata: &Metadata, platform_name: &str) -> Option { - if platform_name == "riscv64-qemu-virt-hv" { - return platform_package_by_name(metadata, "riscv64-qemu-virt"); - } - platform_packages(metadata) .into_iter() .find(|platform| platform.metadata.platform == platform_name) @@ -1501,7 +1495,7 @@ mod tests { let mut envs = HashMap::new(); let mut features = vec![ "ax-hal/riscv64-qemu-virt".to_string(), - "ax-driver/pci".to_string(), + "ax-driver/plat-dyn".to_string(), "ax-driver/virtio-blk".to_string(), "ax-driver/virtio-net".to_string(), "dns".to_string(), @@ -1513,7 +1507,8 @@ mod tests { assert_eq!( envs.get("ARCEOS_RUST_FEATURES"), Some( - &"ax-hal/riscv64-qemu-virt,ax-driver/pci,ax-driver/virtio-blk,ax-driver/virtio-net" + &"ax-hal/riscv64-qemu-virt,ax-driver/plat-dyn,ax-driver/virtio-blk,ax-driver/\ + virtio-net" .to_string() ) ); @@ -1564,10 +1559,10 @@ mod tests { } #[test] - fn cargo_target_json_path_rejects_unsupported_pie_target() { - let err = cargo_target_json_path("riscv64gc-unknown-none-elf", true).unwrap_err(); + fn cargo_target_json_path_maps_riscv64_pie_target() { + let path = cargo_target_json_path("riscv64gc-unknown-none-elf", true).unwrap(); - assert!(err.to_string().contains("unsupported PIE target")); + assert!(path.ends_with("scripts/targets/pie/riscv64gc-unknown-none-elf.json")); } #[test] diff --git a/scripts/axbuild/src/context/tests.rs b/scripts/axbuild/src/context/tests.rs index 1288a3ccd6..c81a09985e 100644 --- a/scripts/axbuild/src/context/tests.rs +++ b/scripts/axbuild/src/context/tests.rs @@ -489,7 +489,7 @@ fn prepare_axvisor_request_prefers_cli_over_snapshot() { config = "os/axvisor/.build.toml" arch = "riscv64" target = "riscv64gc-unknown-none-elf" -plat_dyn = false +plat_dyn = true vmconfigs = ["tmp/snapshot-vm.toml"] [qemu] diff --git a/scripts/axbuild/src/starry/build.rs b/scripts/axbuild/src/starry/build.rs index e304ffbd3e..f0a2c55990 100644 --- a/scripts/axbuild/src/starry/build.rs +++ b/scripts/axbuild/src/starry/build.rs @@ -90,12 +90,22 @@ pub(crate) fn load_cargo_config(request: &ResolvedStarryRequest) -> anyhow::Resu if let Some(smp) = request.smp { build_info.max_cpu_num = Some(smp); } + let plat_dyn = build_info.effective_plat_dyn(&request.target, request.plat_dyn); let mut cargo = build_info.into_prepared_base_cargo_config_with_metadata( &request.package, &request.target, request.plat_dyn, metadata, )?; + if plat_dyn { + cargo.features.retain(|feature| { + !matches!( + feature.as_str(), + "ax-feat/plat-dyn" | "ax-std/plat-dyn" | "starry-kernel/plat-dyn" + ) + }); + cargo.features.push("plat-dyn".to_string()); + } patch_starry_cargo_config(&mut cargo, request, metadata)?; Ok(cargo) } @@ -180,31 +190,55 @@ fn uimg_arch_for(arch: &str) -> String { fn inject_uimage_post_build_cmd(cargo: &mut Cargo, arch: &str) -> anyhow::Result<()> { let uimg_arch = uimg_arch_for(arch); - let config_path = cargo - .env - .get("AX_CONFIG_PATH") - .cloned() - .ok_or_else(|| anyhow::anyhow!("AX_CONFIG_PATH is required for UIMAGE generation"))?; + let paddr = uimage_load_paddr_expr(cargo, arch)?; let cmd = format!( - "paddr=$(ax-config-gen {config_path} -r plat.kernel-base-paddr | tr -d _) && \ - bin=${{KERNEL_ELF%.elf}}.bin && mkimage -A {uimg_arch} -O linux -T kernel -C none -a \ - \"$paddr\" -d \"$bin\" \"${{bin%.bin}}.uimg\"" + "paddr={paddr} && bin=${{KERNEL_ELF%.elf}}.bin && mkimage -A {uimg_arch} -O linux -T \ + kernel -C none -a \"$paddr\" -d \"$bin\" \"${{bin%.bin}}.uimg\"" ); cargo.post_build_cmds.push(cmd); Ok(()) } +fn uimage_load_paddr_expr(cargo: &Cargo, arch: &str) -> anyhow::Result { + if let Some(config_path) = cargo.env.get("AX_CONFIG_PATH") { + return Ok(format!( + "$(ax-config-gen {config_path} -r plat.kernel-base-paddr | tr -d _)" + )); + } + + if !uses_dynamic_platform(&cargo.features) { + return Err(anyhow::anyhow!( + "AX_CONFIG_PATH is required for UIMAGE generation" + )); + } + + match arch { + "aarch64" => Ok("0x200000".to_string()), + "riscv64" => Ok("0x80200000".to_string()), + other => Err(anyhow::anyhow!( + "AX_CONFIG_PATH is required for UIMAGE generation on {other}" + )), + } +} + fn remove_qemu_feature_for_dynamic_platform(cargo: &mut Cargo) { - let uses_dynamic_platform = cargo.features.iter().any(|feature| { + if uses_dynamic_platform(&cargo.features) { + cargo.features.retain(|feature| feature != "qemu"); + } +} + +fn uses_dynamic_platform(features: &[String]) -> bool { + features.iter().any(|feature| { matches!( feature.as_str(), - "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" | "ax-hal/plat-dyn" + "plat-dyn" + | "ax-feat/plat-dyn" + | "ax-std/plat-dyn" + | "starry-kernel/plat-dyn" + | "ax-hal/plat-dyn" ) - }); - if uses_dynamic_platform { - cargo.features.retain(|feature| feature != "qemu"); - } + }) } fn uses_default_qemu_platform(features: &[String]) -> bool { @@ -214,12 +248,7 @@ fn uses_default_qemu_platform(features: &[String]) -> bool { "defplat" | "ax-feat/defplat" | "ax-std/defplat" ) || default_starry_qemu_platform_feature(feature).is_some() }); - let has_dynamic = features.iter().any(|feature| { - matches!( - feature.as_str(), - "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" | "ax-hal/plat-dyn" - ) - }); + let has_dynamic = uses_dynamic_platform(features); let has_custom = features.iter().any(|feature| { matches!( feature.as_str(), @@ -525,7 +554,7 @@ HELLO = "world" env: HashMap::new(), features: vec![ "common".to_string(), - "ax-feat/plat-dyn".to_string(), + "plat-dyn".to_string(), "ax-driver/rockchip-soc".to_string(), "ax-driver/rockchip-sdhci".to_string(), ], @@ -574,11 +603,7 @@ HELLO = "world" ); let build_info = StarryBuildInfo { env: HashMap::new(), - features: vec![ - "qemu".to_string(), - "ax-feat/plat-dyn".to_string(), - "starry-kernel/plat-dyn".to_string(), - ], + features: vec!["qemu".to_string(), "plat-dyn".to_string()], log: LogLevel::Info, max_cpu_num: None, axconfig_overrides: Vec::new(), @@ -601,6 +626,62 @@ HELLO = "world" assert!(!cargo.env.contains_key("AX_PLATFORM")); } + #[test] + fn uimage_load_paddr_uses_dynamic_riscv64_fallback_without_axconfig() { + let cargo = Cargo { + env: HashMap::new(), + target: "scripts/targets/pie/riscv64gc-unknown-none-elf.json".to_string(), + package: STARRY_PACKAGE.to_string(), + bin: None, + features: vec!["plat-dyn".to_string()], + log: None, + extra_config: None, + profile: None, + disable_someboot_build_config: true, + args: Vec::new(), + pre_build_cmds: Vec::new(), + post_build_cmds: Vec::new(), + to_bin: true, + }; + + assert_eq!( + uimage_load_paddr_expr(&cargo, "riscv64").unwrap(), + "0x80200000" + ); + } + + #[test] + fn uimage_load_paddr_prefers_axconfig_when_available() { + let mut cargo = Cargo { + env: HashMap::from([( + "AX_CONFIG_PATH".to_string(), + "/tmp/generated.axconfig.toml".to_string(), + )]), + target: "riscv64gc-unknown-none-elf".to_string(), + package: STARRY_PACKAGE.to_string(), + bin: None, + features: vec!["plat-dyn".to_string()], + log: None, + extra_config: None, + profile: None, + disable_someboot_build_config: true, + args: Vec::new(), + pre_build_cmds: Vec::new(), + post_build_cmds: Vec::new(), + to_bin: true, + }; + + assert_eq!( + uimage_load_paddr_expr(&cargo, "riscv64").unwrap(), + "$(ax-config-gen /tmp/generated.axconfig.toml -r plat.kernel-base-paddr | tr -d _)" + ); + + inject_uimage_post_build_cmd(&mut cargo, "riscv64").unwrap(); + assert!( + cargo.post_build_cmds[0].contains("paddr=$(ax-config-gen /tmp/generated.axconfig.toml") + ); + } + #[test] fn load_cargo_config_treats_sg2002_as_explicit_platform_feature() { let mut request = request( @@ -728,7 +809,7 @@ HELLO = "world" let mut cargo = build_info.into_base_cargo_config_with_log( request.package.clone(), request.target.clone(), - StarryBuildInfo::build_cargo_args(&request.target, false, &[]), + StarryBuildInfo::build_cargo_args(&request.target, &[]), ); let metadata = crate::build::workspace_metadata().unwrap(); diff --git a/scripts/axbuild/src/starry/rootfs.rs b/scripts/axbuild/src/starry/rootfs.rs index abfe3e0602..59aeecf7c3 100644 --- a/scripts/axbuild/src/starry/rootfs.rs +++ b/scripts/axbuild/src/starry/rootfs.rs @@ -107,10 +107,11 @@ pub(super) async fn load_patched_qemu_config( } }; + let mode = rootfs_patch_mode(request, cargo); if let Some(rootfs) = explicit_rootfs { - patch_qemu_rootfs_path(&mut qemu, rootfs); + patch_qemu_rootfs_path_with_mode(&mut qemu, rootfs, mode); } else if apply_default_args { - patch_qemu_rootfs(&mut qemu, request, starry.app.workspace_root(), None)?; + patch_qemu_rootfs(&mut qemu, request, starry.app.workspace_root(), None, mode)?; } qemu_test::apply_smp_qemu_arg(&mut qemu, request.smp); @@ -241,6 +242,7 @@ pub(crate) fn patch_qemu_rootfs( request: &ResolvedStarryRequest, workspace_root: &Path, explicit_rootfs: Option<&Path>, + mode: RootfsPatchMode, ) -> anyhow::Result<()> { let expected_target = starry_target_for_arch_checked(&request.arch)?; if request.target != expected_target { @@ -251,7 +253,7 @@ pub(crate) fn patch_qemu_rootfs( ); } let rootfs_path = qemu_rootfs_path(request, workspace_root, explicit_rootfs)?; - patch_qemu_rootfs_path(qemu, &rootfs_path); + patch_qemu_rootfs_path_with_mode(qemu, &rootfs_path, mode); Ok(()) } @@ -269,8 +271,27 @@ pub(crate) fn qemu_rootfs_path( } /// Patches a QEMU config with a concrete Starry rootfs path. -pub(crate) fn patch_qemu_rootfs_path(qemu: &mut QemuConfig, rootfs_path: &Path) { - patch_rootfs(qemu, rootfs_path, RootfsPatchMode::EnsureDiskBootNet); +pub(crate) fn patch_qemu_rootfs_path_with_mode( + qemu: &mut QemuConfig, + rootfs_path: &Path, + mode: RootfsPatchMode, +) { + patch_rootfs(qemu, rootfs_path, mode); +} + +fn rootfs_patch_mode(request: &ResolvedStarryRequest, cargo: &Cargo) -> RootfsPatchMode { + if request.plat_dyn == Some(true) + || cargo.features.iter().any(|feature| { + matches!( + feature.as_str(), + "plat-dyn" | "ax-feat/plat-dyn" | "ax-std/plat-dyn" | "starry-kernel/plat-dyn" + ) + }) + { + RootfsPatchMode::ReplaceDriveOnly + } else { + RootfsPatchMode::EnsureDiskBootNet + } } #[cfg(test)] @@ -306,7 +327,14 @@ mod tests { }; let mut qemu = QemuConfig::default(); - patch_qemu_rootfs(&mut qemu, &request, root.path(), None).unwrap(); + patch_qemu_rootfs( + &mut qemu, + &request, + root.path(), + None, + RootfsPatchMode::EnsureDiskBootNet, + ) + .unwrap(); assert_eq!( qemu.args, @@ -367,7 +395,14 @@ mod tests { ..Default::default() }; - patch_qemu_rootfs(&mut qemu, &request, root.path(), None).unwrap(); + patch_qemu_rootfs( + &mut qemu, + &request, + root.path(), + None, + RootfsPatchMode::EnsureDiskBootNet, + ) + .unwrap(); assert_eq!( qemu.args, @@ -393,4 +428,66 @@ mod tests { ] ); } + + #[tokio::test] + async fn patch_qemu_rootfs_for_dynamic_platform_replaces_drive_only() { + let root = tempdir().unwrap(); + let rootfs_dir = root.path().join("tmp/axbuild/rootfs"); + fs::create_dir_all(&rootfs_dir).unwrap(); + fs::write( + rootfs_dir.join("rootfs-riscv64-alpine.img"), + vec![0; 1024 * 1024], + ) + .unwrap(); + + let request = ResolvedStarryRequest { + package: "starryos".to_string(), + arch: "riscv64".to_string(), + target: "riscv64gc-unknown-none-elf".to_string(), + plat_dyn: Some(true), + smp: None, + debug: false, + build_info_path: PathBuf::from("/tmp/.build.toml"), + build_info_override: None, + qemu_config: None, + uboot_config: None, + }; + let mut qemu = QemuConfig { + args: vec![ + "-machine".to_string(), + "virt".to_string(), + "-device".to_string(), + "virtio-blk-device,drive=disk0".to_string(), + "-drive".to_string(), + "id=disk0,if=none,format=raw,file=/old/rootfs.img".to_string(), + ], + ..Default::default() + }; + + patch_qemu_rootfs( + &mut qemu, + &request, + root.path(), + None, + RootfsPatchMode::ReplaceDriveOnly, + ) + .unwrap(); + + assert_eq!( + qemu.args, + vec![ + "-machine".to_string(), + "virt".to_string(), + "-device".to_string(), + "virtio-blk-device,drive=disk0".to_string(), + "-drive".to_string(), + format!( + "id=disk0,if=none,format=raw,file={}", + root.path() + .join("tmp/axbuild/rootfs/rootfs-riscv64-alpine.img") + .display() + ), + ] + ); + } } diff --git a/scripts/axbuild/src/starry/test.rs b/scripts/axbuild/src/starry/test.rs index 4dd8eaf7a1..76795e87b4 100644 --- a/scripts/axbuild/src/starry/test.rs +++ b/scripts/axbuild/src/starry/test.rs @@ -1340,12 +1340,24 @@ mod tests { #[test] fn bug_ext4_dir_ops_qemu_configs_fail_on_lockdep_fatal() { let workspace_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); - let case_dir = workspace_root.join("test-suit/starryos/normal/qemu-smp1/bug-ext4-dir-ops"); + let case_dir = workspace_root.join("test-suit/starryos/normal/qemu-smp1/bugfix"); for arch in ["aarch64", "loongarch64", "riscv64", "x86_64"] { let path = case_dir.join(format!("qemu-{arch}.toml")); let content = fs::read_to_string(&path).unwrap(); let config: toml::Value = toml::from_str(&content).unwrap(); + let test_commands = config + .get("test_commands") + .and_then(toml::Value::as_array) + .unwrap(); + assert!( + test_commands + .iter() + .filter_map(toml::Value::as_str) + .any(|command| command == "/usr/bin/bug-ext4-dir-ops"), + "{} must include the bug-ext4-dir-ops grouped command", + path.display() + ); let fail_regex = config .get("fail_regex") .and_then(toml::Value::as_array) @@ -1778,8 +1790,8 @@ mod tests { fs::create_dir_all(build_config.parent().unwrap()).unwrap(); fs::write( &build_config, - "target = \"aarch64-unknown-none-softfloat\"\nenv = {}\nfeatures = [\"qemu\", \ - \"starry-kernel/plat-dyn\"]\nlog = \"Warn\"\nplat_dyn = true\n", + "target = \"aarch64-unknown-none-softfloat\"\nenv = {}\nfeatures = [\"qemu\"]\nlog = \ + \"Warn\"\nplat_dyn = true\n", ) .unwrap(); let mut request = starry_request( @@ -1799,9 +1811,10 @@ mod tests { let (_group_request, cargo) = Starry::qemu_group_build_context(&request, &build_config).unwrap(); - assert!(cargo.features.contains(&"ax-feat/plat-dyn".to_string())); + assert!(cargo.features.contains(&"plat-dyn".to_string())); + assert!(!cargo.features.contains(&"ax-feat/plat-dyn".to_string())); assert!( - cargo + !cargo .features .contains(&"starry-kernel/plat-dyn".to_string()) ); diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index 52ef820ba9..a1ea8b50e0 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -1,57 +1,57 @@ -url,branch,target_dir,category,description -https://github.com/arceos-org/arceos,dev,os/arceos,OS,ArceOS -https://github.com/arceos-hypervisor/axvisor,,os/axvisor,OS,axvisor - ArceOS Hypervisor -https://github.com/Starry-OS/StarryOS,,os/StarryOS,OS,StarryOS - 教学操作系统 -https://github.com/arceos-hypervisor/aarch64_sysreg,,virtualization/aarch64_sysreg,Hypervisor, -https://github.com/arceos-hypervisor/axaddrspace,,memory/axaddrspace,Hypervisor, -https://github.com/arceos-hypervisor/axdevice,,virtualization/axdevice,Hypervisor, -https://github.com/arceos-hypervisor/axdevice_base,,virtualization/axdevice_base,Hypervisor, -https://github.com/arceos-hypervisor/axhvc,,virtualization/axhvc,Hypervisor, -https://github.com/arceos-hypervisor/axklib,,components/axklib,Hypervisor, -https://github.com/arceos-hypervisor/axvcpu,,virtualization/axvcpu,Hypervisor, -https://github.com/arceos-hypervisor/axvisor_api,,virtualization/axvisor_api,Hypervisor, -https://github.com/arceos-hypervisor/axvm,,virtualization/axvm,Hypervisor, -https://github.com/arceos-hypervisor/axvmconfig,,virtualization/axvmconfig,Hypervisor, -https://github.com/arceos-hypervisor/x86_vcpu,,virtualization/x86_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/x86_vlapic.git,,virtualization/x86_vlapic,Hypervisor, -https://github.com/arceos-hypervisor/arm_vcpu,,virtualization/arm_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/arm_vgic,,virtualization/arm_vgic,Hypervisor, -https://github.com/arceos-hypervisor/riscv_vcpu,,virtualization/riscv_vcpu,Hypervisor, -https://github.com/arceos-hypervisor/riscv-h,,virtualization/riscv-h,Hypervisor, -https://github.com/arceos-hypervisor/riscv_vplic,,virtualization/riscv_vplic,Hypervisor, -https://github.com/arceos-org/allocator,,memory/axallocator,ArceOS, -https://github.com/arceos-org/axconfig-gen,,components/axconfig-gen,ArceOS, -https://github.com/arceos-org/ax-cpu,dev,components/axcpu,ArceOS, -https://github.com/arceos-org/axerrno,,components/axerrno,ArceOS, -https://github.com/arceos-org/axio,dev2,components/axio,ArceOS, -https://github.com/arceos-org/axsched,,components/axsched,ArceOS, -https://github.com/arceos-org/cpumask,,components/cpumask,ArceOS, -https://github.com/arceos-org/crate_interface,,components/crate_interface,ArceOS, -https://github.com/arceos-org/ctor_bare,,components/ctor_bare,ArceOS, -https://github.com/arceos-org/ax-handler-table,,components/handler_table,ArceOS, -https://github.com/arceos-org/kernel_guard,,components/kernel_guard,ArceOS, -https://github.com/arceos-org/kspin,,components/kspin,ArceOS, -https://github.com/arceos-org/lazyinit,,components/ax-lazyinit,ArceOS, -https://github.com/arceos-org/linked_list_r4l,,components/linked_list_r4l,ArceOS, -https://github.com/arceos-org/percpu,dev,components/percpu,ArceOS, -https://github.com/arceos-org/timer_list,,components/timer_list,ArceOS, -https://github.com/arceos-org/ax-int-ratio,,components/int_ratio,ArceOS, -https://github.com/arceos-org/cap_access,,components/cap_access,ArceOS, -https://github.com/arceos-org/arm_pl031,,drivers/rtc/arm_pl031,ArceOS, -https://github.com/arceos-org/riscv_plic,,drivers/intc/riscv_plic,ArceOS, -https://github.com/rcore-os/bitmap-allocator,,memory/bitmap-allocator,rCore, -https://github.com/Starry-OS/axbacktrace,,components/axbacktrace,Starry, -https://github.com/Starry-OS/axpoll,,components/axpoll,Starry, -https://github.com/Starry-OS/axfs-ng-vfs,,components/axfs-ng-vfs,Starry, -https://github.com/Starry-OS/rsext4,,components/rsext4,Starry, -https://github.com/Starry-OS/scope-local,,components/scope-local,Starry, -https://github.com/Starry-OS/starry-process,,components/starry-process,Starry, -https://github.com/Starry-OS/starry-signal,,components/starry-signal,Starry, -https://github.com/Starry-OS/starry-vm,,components/starry-vm,Starry, -https://github.com/Starry-OS/smoltcp,,components/starry-smoltcp,Starry, -https://github.com/arceos-org/axfs_crates,main,components/axfs_crates,ArceOS, -https://github.com/DeathWish5/fxmac_rs,,drivers/net/fxmac_rs,ArceOS, -https://github.com/drivercraft/rknpu,,drivers/npu/rockchip-npu,drivers, -https://github.com/drivercraft/rockchip-pm,,drivers/soc/rockchip/rockchip-pm,drivers, -https://github.com/drivercraft/rockchip-soc,,drivers/soc/rockchip/rockchip-soc,, +url,branch,target_dir,category,description +https://github.com/arceos-org/arceos,dev,os/arceos,OS,ArceOS +https://github.com/arceos-hypervisor/axvisor,,os/axvisor,OS,axvisor - ArceOS Hypervisor +https://github.com/Starry-OS/StarryOS,,os/StarryOS,OS,StarryOS - 教学操作系统 +https://github.com/arceos-hypervisor/aarch64_sysreg,,virtualization/aarch64_sysreg,Hypervisor, +https://github.com/arceos-hypervisor/axaddrspace,,memory/axaddrspace,Hypervisor, +https://github.com/arceos-hypervisor/axdevice,,virtualization/axdevice,Hypervisor, +https://github.com/arceos-hypervisor/axdevice_base,,virtualization/axdevice_base,Hypervisor, +https://github.com/arceos-hypervisor/axhvc,,virtualization/axhvc,Hypervisor, +https://github.com/arceos-hypervisor/axklib,,components/axklib,Hypervisor, +https://github.com/arceos-hypervisor/axvcpu,,virtualization/axvcpu,Hypervisor, +https://github.com/arceos-hypervisor/axvisor_api,,virtualization/axvisor_api,Hypervisor, +https://github.com/arceos-hypervisor/axvm,,virtualization/axvm,Hypervisor, +https://github.com/arceos-hypervisor/axvmconfig,,virtualization/axvmconfig,Hypervisor, +https://github.com/arceos-hypervisor/x86_vcpu,,virtualization/x86_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/x86_vlapic.git,,virtualization/x86_vlapic,Hypervisor, +https://github.com/arceos-hypervisor/arm_vcpu,,virtualization/arm_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/arm_vgic,,virtualization/arm_vgic,Hypervisor, +https://github.com/arceos-hypervisor/riscv_vcpu,,virtualization/riscv_vcpu,Hypervisor, +https://github.com/arceos-hypervisor/riscv-h,,virtualization/riscv-h,Hypervisor, +https://github.com/arceos-hypervisor/riscv_vplic,,virtualization/riscv_vplic,Hypervisor, +https://github.com/arceos-org/allocator,,memory/axallocator,ArceOS, +https://github.com/arceos-org/axconfig-gen,,components/axconfig-gen,ArceOS, +https://github.com/arceos-org/ax-cpu,dev,components/axcpu,ArceOS, +https://github.com/arceos-org/axerrno,,components/axerrno,ArceOS, +https://github.com/arceos-org/axio,dev2,components/axio,ArceOS, +https://github.com/arceos-org/axsched,,components/axsched,ArceOS, +https://github.com/arceos-org/cpumask,,components/cpumask,ArceOS, +https://github.com/arceos-org/crate_interface,,components/crate_interface,ArceOS, +https://github.com/arceos-org/ctor_bare,,components/ctor_bare,ArceOS, +https://github.com/arceos-org/ax-handler-table,,components/handler_table,ArceOS, +https://github.com/arceos-org/kernel_guard,,components/kernel_guard,ArceOS, +https://github.com/arceos-org/kspin,,components/kspin,ArceOS, +https://github.com/arceos-org/lazyinit,,components/ax-lazyinit,ArceOS, +https://github.com/arceos-org/linked_list_r4l,,components/linked_list_r4l,ArceOS, +https://github.com/arceos-org/percpu,dev,components/percpu,ArceOS, +https://github.com/arceos-org/timer_list,,components/timer_list,ArceOS, +https://github.com/arceos-org/ax-int-ratio,,components/int_ratio,ArceOS, +https://github.com/arceos-org/cap_access,,components/cap_access,ArceOS, +https://github.com/arceos-org/arm_pl031,,drivers/rtc/arm_pl031,ArceOS, +https://github.com/arceos-org/riscv_plic,,drivers/intc/riscv_plic,ArceOS, +https://github.com/rcore-os/bitmap-allocator,,memory/bitmap-allocator,rCore, +https://github.com/Starry-OS/axbacktrace,,components/axbacktrace,Starry, +https://github.com/Starry-OS/axpoll,,components/axpoll,Starry, +https://github.com/Starry-OS/axfs-ng-vfs,,components/axfs-ng-vfs,Starry, +https://github.com/Starry-OS/rsext4,,components/rsext4,Starry, +https://github.com/Starry-OS/scope-local,,components/scope-local,Starry, +https://github.com/Starry-OS/starry-process,,components/starry-process,Starry, +https://github.com/Starry-OS/starry-signal,,components/starry-signal,Starry, +https://github.com/Starry-OS/starry-vm,,components/starry-vm,Starry, +https://github.com/Starry-OS/smoltcp,,components/starry-smoltcp,Starry, +https://github.com/arceos-org/axfs_crates,main,components/axfs_crates,ArceOS, +https://github.com/DeathWish5/fxmac_rs,,drivers/net/fxmac_rs,ArceOS, +https://github.com/drivercraft/rknpu,,drivers/npu/rockchip-npu,drivers, +https://github.com/drivercraft/rockchip-pm,,drivers/soc/rockchip/rockchip-pm,drivers, +https://github.com/drivercraft/rockchip-soc,,drivers/soc/rockchip/rockchip-soc,, https://github.com/drivercraft/arm-scmi.git,,drivers/firmware/arm-scmi-rs,Starry, diff --git a/scripts/targets/pie/riscv64gc-unknown-none-elf.json b/scripts/targets/pie/riscv64gc-unknown-none-elf.json new file mode 100644 index 0000000000..1e18d1b92a --- /dev/null +++ b/scripts/targets/pie/riscv64gc-unknown-none-elf.json @@ -0,0 +1,36 @@ +{ + "arch": "riscv64", + "cpu": "generic-rv64", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "features": "+m,+a,+f,+d,+c,+zicsr,+zifencei", + "linker": "rust-lld", + "linker-flavor": "gnu-lld", + "llvm-abiname": "lp64d", + "llvm-target": "riscv64", + "max-atomic-width": 64, + "metadata": { + "description": "Bare RISC-V (RV64IMAFDC ISA)", + "host_tools": false, + "std": false, + "tier": 2 + }, + "panic-strategy": "abort", + "position-independent-executables": true, + "pre-link-args": { + "gnu-lld": [ + "-pie", + "-znostart-stop-gc", + "-Taxplat.x" + ] + }, + "relocation-model": "pic", + "static-position-independent-executables": true, + "supported-sanitizers": [ + "shadow-call-stack", + "kernel-address" + ], + "target-pointer-width": 64 +} diff --git a/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml index df8bbaf350..09a79b30c2 100644 --- a/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/c/httpclient/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["alloc", "paging", "net", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml index df8bbaf350..09a79b30c2 100644 --- a/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/c/httpclient/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["alloc", "paging", "net", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml index df8bbaf350..09a79b30c2 100644 --- a/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/c/httpclient/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["alloc", "paging", "net", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml b/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml index df8bbaf350..09a79b30c2 100644 --- a/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/c/httpclient/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["alloc", "paging", "net", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["alloc", "paging", "net", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Info" [env] diff --git a/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml index eda3a4a847..d50567d1c3 100644 --- a/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/display/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-gpu"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml index 92f3749d99..4404257456 100644 --- a/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/display/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-gpu"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml index d490390524..e71ba13870 100644 --- a/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/display/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-gpu"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml index 4a36bd895c..22ce22029f 100644 --- a/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/display/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-gpu"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/plat-static", "ax-driver/virtio-gpu"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml index 928217c53f..772b5e3785 100644 --- a/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/fs/shell/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/virtio-blk"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml index 3f3e37f2da..356b482ab5 100644 --- a/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/fs/shell/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-blk"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml index 1f6cd09f11..9b91c050c3 100644 --- a/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/fs/shell/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-blk"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml index 9b12da4c8d..4c89f16752 100644 --- a/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/fs/shell/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-blk"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/plat-static", "ax-driver/virtio-blk"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml index 702f89231d..d885073496 100644 --- a/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/echoserver/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = false diff --git a/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml index 90c976edd0..71b6acb4b4 100644 --- a/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/echoserver/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml index 5187b10d7a..62722e59d6 100644 --- a/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/echoserver/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml index 47ca5d96c6..eff1acc096 100644 --- a/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/echoserver/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml index fa1c413f97..0fcc053015 100644 --- a/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpclient/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/plat-dyn", "ax-driver/virtio-net"] +features = ["ax-std", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = true diff --git a/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml index 610dc403ec..0e39320b3d 100644 --- a/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpclient/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml index ee9b43a5d6..d19a749068 100644 --- a/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/httpclient/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml index 47ca5d96c6..eff1acc096 100644 --- a/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/httpclient/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml index 702f89231d..d885073496 100644 --- a/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpserver/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = false diff --git a/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml index 90c976edd0..71b6acb4b4 100644 --- a/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/httpserver/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml index 5187b10d7a..62722e59d6 100644 --- a/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/httpserver/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml index 47ca5d96c6..eff1acc096 100644 --- a/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/httpserver/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml index 702f89231d..d885073496 100644 --- a/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/udpserver/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/aarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 plat_dyn = false diff --git a/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml index 90c976edd0..71b6acb4b4 100644 --- a/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/arceos/rust/net/udpserver/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/loongarch64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml index 5187b10d7a..62722e59d6 100644 --- a/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/arceos/rust/net/udpserver/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/riscv64-qemu-virt", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml b/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml index 47ca5d96c6..eff1acc096 100644 --- a/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml +++ b/test-suit/arceos/rust/net/udpserver/build-x86_64-unknown-none.toml @@ -1,4 +1,4 @@ -features = ["ax-std", "ax-hal/x86-pc", "ax-driver/pci", "ax-driver/virtio-net"] +features = ["ax-std", "ax-hal/x86-pc", "ax-driver/plat-static", "ax-driver/virtio-net"] log = "Warn" max_cpu_num = 4 diff --git a/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml index 3744939244..82d078a057 100644 --- a/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-rdk-s100/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/plat-dyn", "ept-level-4", ] log = "Info" diff --git a/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml index 85565cb686..7079f96928 100644 --- a/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/plat-dyn", "ept-level-4", "ax-driver/virtio-blk", "fs", diff --git a/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml index d16951f9f4..1e7fa7e04b 100644 --- a/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml @@ -1,7 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ "ax-driver/virtio-blk", - "ax-driver/pci", + "ax-driver/plat-static", "ept-level-4", "fs", ] diff --git a/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml b/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml index 4e9112e70c..7cbfe678fb 100644 --- a/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml @@ -1,13 +1,12 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/riscv64-qemu-virt-hv", - "ax-driver/fdt", "ax-driver/virtio-blk", "ept-level-4", "fs", "sstc" ] log = "Info" +plat_dyn = true target = "riscv64gc-unknown-none-elf" vm_configs = ["os/axvisor/configs/vms/linux-riscv64-qemu-smp1.toml"] max_cpu_num = 4 diff --git a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml index fccc9c223a..96a1fc057a 100644 --- a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml @@ -1,7 +1,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "ax-driver/virtio-blk", - "ax-driver/pci", + "ax-driver/plat-static", "ept-level-4", "fs", "vmx", diff --git a/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml index 832d68593f..c160743f7c 100644 --- a/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml @@ -1,7 +1,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "ax-driver/virtio-blk", - "ax-driver/pci", + "ax-driver/plat-static", "ept-level-4", "fs", "svm", diff --git a/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml index 8255f9fe7d..ad16f45be8 100644 --- a/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/aarch64-hvf/test-aarch64-gicv3-smoke/build-aarch64-unknown-none-softfloat.toml @@ -1,16 +1,13 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "ax-hal/plat-dyn", "ax-feat/display", "ax-feat/rtc", - "ax-driver/pci", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "gic-v3", "cntv-timer", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", ] # CNTV PPI 11 = IRQ 27. The kernel's `cntv-timer` path programs the diff --git a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml index f4d0d68008..5d1445e2fb 100644 --- a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml @@ -1,7 +1,13 @@ # Build-time config template for StarryOS on SG2002. target = "riscv64gc-unknown-none-elf" env = {} -features = ["sg2002"] +features = [ + "starry-kernel/sg2002", + "axplat-dyn/thead-mae", + "ax-driver/sg2002-placeholder", + "ax-driver/cvsd", + "ax-driver/serial", +] log = "Info" max_cpu_num = 1 -plat_dyn = false +plat_dyn = true diff --git a/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml index 5ca9f332f2..3a3a986c62 100644 --- a/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml @@ -3,9 +3,7 @@ target = "aarch64-unknown-none-softfloat" # Runtime U-Boot settings live in `orangepi-5-plus-uboot.toml`. env = {} features = [ - "ax-hal/plat-dyn", - "starry-kernel/plat-dyn", - "ax-driver/pci-list-devices", + "ax-driver/list-pci-devices", "ax-driver/rk3588-pcie", "ax-driver/realtek-rtl8125", "ax-driver/rockchip-soc", diff --git a/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml index 3069e0b191..71d3294162 100644 --- a/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-aarch64-plat-dyn/build-aarch64-unknown-none-softfloat.toml @@ -1,12 +1,10 @@ env = {} features = [ - "ax-hal/plat-dyn", "ax-feat/rtc", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", "ax-driver/intel-net", "ax-driver/xhci-pci", diff --git a/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml index 7efd9aa1bd..1128aa2eb9 100644 --- a/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-dhcp/build-x86_64-unknown-none.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/x86-pc", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml index d719a0adf8..4bfffbcf74 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-aarch64.toml @@ -96,5 +96,5 @@ test_commands = [ "/usr/bin/bug-waitid-basic", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", '(?m)^STARRY_GROUPED_TEST_FAILED:'] timeout = 360 diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml index 50a7e7d342..7b92c33e8f 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-loongarch64.toml @@ -97,5 +97,5 @@ test_commands = [ "/usr/bin/bug-waitid-basic", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", '(?m)^STARRY_GROUPED_TEST_FAILED:'] timeout = 360 diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml index 343b4466cc..dd019dcaa5 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-riscv64.toml @@ -105,5 +105,5 @@ test_commands = [ "/usr/bin/bug-waitid-basic", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", '(?m)^STARRY_GROUPED_TEST_FAILED:'] timeout = 360 diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml index 21f1152c8e..e8b1a476ed 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/qemu-x86_64.toml @@ -102,5 +102,5 @@ test_commands = [ "/usr/bin/bug-waitid-basic", ] success_regex = ["(?m)^STARRY_GROUPED_TESTS_PASSED\\s*$"] -fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)^STARRY_GROUPED_TEST_FAILED:'] +fail_regex = ['(?i)\bpanic(?:ked)?\b', "(?m)^lockdep fatal violation\\s*$", '(?m)^STARRY_GROUPED_TEST_FAILED:'] timeout = 360 diff --git a/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml index 857f3a3dbd..6642260288 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-aarch64-unknown-none-softfloat.toml @@ -1,9 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/plat-dyn", "ax-feat/display", "ax-feat/rtc", - "ax-driver/pci", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", @@ -11,7 +9,6 @@ features = [ "ax-driver/virtio-input", "ax-driver/virtio-socket", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", ] log = "Warn" diff --git a/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml index 0dc0ec2bb6..b6e12457d5 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/loongarch64-qemu-virt", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml index 2df21769c5..66c877a2a2 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-riscv64gc-unknown-none-elf.toml @@ -1,14 +1,15 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", + "ax-feat/rtc", + "ax-driver/rtc", + "ax-driver/serial", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", "ax-driver/virtio-input", "ax-driver/virtio-socket", + "starry-kernel/input", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml index 94d8986bbb..48f79a5df5 100644 --- a/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-smp1/build-x86_64-unknown-none.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/x86-pc", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml index f24592654f..8b785b5ae5 100644 --- a/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml @@ -1,9 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/plat-dyn", "ax-feat/display", "ax-feat/rtc", - "ax-driver/pci", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", @@ -11,7 +9,6 @@ features = [ "ax-driver/virtio-input", "ax-driver/virtio-socket", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", "starryos/buddy-slab", ] diff --git a/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml index 0cc1c3db01..3f72491b2b 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-aarch64-unknown-none-softfloat.toml @@ -1,9 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/plat-dyn", "ax-feat/display", "ax-feat/rtc", - "ax-driver/pci", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", @@ -11,7 +9,6 @@ features = [ "ax-driver/virtio-input", "ax-driver/virtio-socket", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", ] max_cpu_num = 4 diff --git a/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml index aea79288ad..fb74bee24c 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-loongarch64-unknown-none-softfloat.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/loongarch64-qemu-virt", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml index f9e4ff6cb8..a4668fafd3 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-riscv64gc-unknown-none-elf.toml @@ -1,8 +1,8 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", + "ax-feat/rtc", + "ax-driver/rtc", + "ax-driver/serial", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", @@ -11,5 +11,5 @@ features = [ ] max_cpu_num = 4 log = "Warn" -plat_dyn = false +plat_dyn = true target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml b/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml index 3f616671f3..415120f3e0 100644 --- a/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/normal/qemu-smp4/build-x86_64-unknown-none.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/x86-pc", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml index 857f3a3dbd..6642260288 100644 --- a/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/postgresql/build-aarch64-unknown-none-softfloat.toml @@ -1,9 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/plat-dyn", "ax-feat/display", "ax-feat/rtc", - "ax-driver/pci", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", @@ -11,7 +9,6 @@ features = [ "ax-driver/virtio-input", "ax-driver/virtio-socket", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", ] log = "Warn" diff --git a/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml index 0dc0ec2bb6..b6e12457d5 100644 --- a/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/postgresql/build-loongarch64-unknown-none-softfloat.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/loongarch64-qemu-virt", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml index 2df21769c5..d5a03b3263 100644 --- a/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/stress/postgresql/build-riscv64gc-unknown-none-elf.toml @@ -1,8 +1,8 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", + "ax-feat/rtc", + "ax-driver/rtc", + "ax-driver/serial", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", @@ -10,5 +10,5 @@ features = [ "ax-driver/virtio-socket", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml b/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml index 94d8986bbb..48f79a5df5 100644 --- a/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/stress/postgresql/build-x86_64-unknown-none.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/x86-pc", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml index 857f3a3dbd..6642260288 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-aarch64-unknown-none-softfloat.toml @@ -1,9 +1,7 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/plat-dyn", "ax-feat/display", "ax-feat/rtc", - "ax-driver/pci", "ax-driver/rtc", "ax-driver/virtio-blk", "ax-driver/virtio-net", @@ -11,7 +9,6 @@ features = [ "ax-driver/virtio-input", "ax-driver/virtio-socket", "starry-kernel/input", - "starry-kernel/plat-dyn", "starry-kernel/vsock", ] log = "Warn" diff --git a/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml b/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml index 0dc0ec2bb6..b6e12457d5 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-loongarch64-unknown-none-softfloat.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/loongarch64-qemu-virt", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml index 2df21769c5..d5a03b3263 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-riscv64gc-unknown-none-elf.toml @@ -1,8 +1,8 @@ env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} features = [ - "ax-hal/riscv64-qemu-virt", - "qemu", - "ax-driver/pci", + "ax-feat/rtc", + "ax-driver/rtc", + "ax-driver/serial", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", @@ -10,5 +10,5 @@ features = [ "ax-driver/virtio-socket", ] log = "Warn" -plat_dyn = false +plat_dyn = true target = "riscv64gc-unknown-none-elf" diff --git a/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml b/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml index 94d8986bbb..48f79a5df5 100644 --- a/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml +++ b/test-suit/starryos/stress/stress-ng-0/build-x86_64-unknown-none.toml @@ -4,7 +4,7 @@ log = "Warn" features = [ "ax-hal/x86-pc", "qemu", - "ax-driver/pci", + "ax-driver/plat-static", "ax-driver/virtio-blk", "ax-driver/virtio-net", "ax-driver/virtio-gpu", diff --git a/virtualization/riscv_vcpu/src/mem_extable.S b/virtualization/riscv_vcpu/src/mem_extable.S index 2033de9573..de5d4bbff1 100644 --- a/virtualization/riscv_vcpu/src/mem_extable.S +++ b/virtualization/riscv_vcpu/src/mem_extable.S @@ -7,9 +7,9 @@ // Adds the faulting instruction and fixup target to the exception table. .macro add_extable from, to .pushsection __ex_table, "a" -.balign 8 -.quad \from -.quad \to +.balign 4 +.word \from - _ex_table_start +.word \to - _ex_table_start .popsection .endm .option push From 6872d2784b24225b07139b0baf59ad986fd0e61f Mon Sep 17 00:00:00 2001 From: Kevin Choo <3056063115@qq.com> Date: Thu, 28 May 2026 09:45:28 +0800 Subject: [PATCH 66/71] feat(starry-kernel): add initial GDB ptrace support (#931) * feat(starry-kernel): add initial GDB ptrace support * ci: trigger starry gdb checks * ci: trigger starry gdb checks * ci: trigger starry gdb checks * fix(starry-kernel): correct ptrace waitid uid and hwcap * fix(starry-kernel): stabilize self-signal ptrace stops * fix(starry-kernel): preserve kill pid zero existence check * fix(starry-kernel): validate ptrace resume state * test(starry): tolerate apk-curl mirror outages * Revert "test(starry): tolerate apk-curl mirror outages" This reverts commit b5bd45b3529d83dd10dec0711853eab624df0a22. * test(starry): move gdb smoke scenarios to apps --- Cargo.lock | 1 + apps/starry/README.md | 11 + .../build-riscv64gc-unknown-none-elf.toml | 14 + .../gdb-smoke/gdbserver/gdbserver-smoke.gdb | 9 + .../gdb-smoke/gdbserver/gdbserver-smoke.sh | 35 + apps/starry/gdb-smoke/gdbserver/src/main.c | 22 + .../gdb-smoke/native/gdb-native-smoke.gdb | 9 + apps/starry/gdb-smoke/native/src/main.c | 13 + apps/starry/gdb-smoke/prebuild.sh | 198 ++ .../gdb-smoke/qemu-riscv64-gdbserver.toml | 22 + apps/starry/gdb-smoke/qemu-riscv64.toml | 23 + os/StarryOS/kernel/Cargo.toml | 1 + os/StarryOS/kernel/src/entry.rs | 7 +- os/StarryOS/kernel/src/mm/loader.rs | 25 +- os/StarryOS/kernel/src/pseudofs/proc.rs | 15 + os/StarryOS/kernel/src/syscall/mod.rs | 19 +- os/StarryOS/kernel/src/syscall/signal.rs | 19 +- os/StarryOS/kernel/src/syscall/task/clone.rs | 48 +- os/StarryOS/kernel/src/syscall/task/ctl.rs | 11 + os/StarryOS/kernel/src/syscall/task/execve.rs | 7 +- os/StarryOS/kernel/src/syscall/task/mod.rs | 4 +- os/StarryOS/kernel/src/syscall/task/ptrace.rs | 1228 ++++++++++++ os/StarryOS/kernel/src/syscall/task/wait.rs | 156 +- os/StarryOS/kernel/src/task/mod.rs | 419 +++- os/StarryOS/kernel/src/task/ops.rs | 42 +- os/StarryOS/kernel/src/task/signal.rs | 214 ++- os/StarryOS/kernel/src/task/user.rs | 87 +- .../test-ptrace-exec-stop/c/CMakeLists.txt | 15 + .../test-ptrace-exec-stop/c/src/main.c | 198 ++ .../test-ptrace-exec-stop/qemu-riscv64.toml | 14 + .../test-ptrace-gdb/c/CMakeLists.txt | 11 + .../qemu-smp1/test-ptrace-gdb/c/src/main.c | 1711 +++++++++++++++++ .../test-ptrace-gdb/qemu-riscv64.toml | 14 + .../test-ptrace-traceme-stop/c/CMakeLists.txt | 11 + .../test-ptrace-traceme-stop/c/src/main.c | 297 +++ .../qemu-riscv64.toml | 14 + .../test-user-backtrace/c/CMakeLists.txt | 18 + .../test-user-backtrace/c/src/main.c | 58 + .../test-user-backtrace/qemu-riscv64.toml | 14 + 39 files changed, 4971 insertions(+), 63 deletions(-) create mode 100644 apps/starry/gdb-smoke/build-riscv64gc-unknown-none-elf.toml create mode 100644 apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.gdb create mode 100755 apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.sh create mode 100644 apps/starry/gdb-smoke/gdbserver/src/main.c create mode 100644 apps/starry/gdb-smoke/native/gdb-native-smoke.gdb create mode 100644 apps/starry/gdb-smoke/native/src/main.c create mode 100755 apps/starry/gdb-smoke/prebuild.sh create mode 100644 apps/starry/gdb-smoke/qemu-riscv64-gdbserver.toml create mode 100644 apps/starry/gdb-smoke/qemu-riscv64.toml create mode 100644 os/StarryOS/kernel/src/syscall/task/ptrace.rs create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-riscv64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/qemu-riscv64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/qemu-riscv64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-user-backtrace/qemu-riscv64.toml diff --git a/Cargo.lock b/Cargo.lock index a882b41b79..c3f22e9a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8029,6 +8029,7 @@ version = "0.5.12" dependencies = [ "ax-alloc", "ax-config", + "ax-cpu", "ax-display", "ax-dma", "ax-driver", diff --git a/apps/starry/README.md b/apps/starry/README.md index 25a8adf79e..ce5a0567ef 100644 --- a/apps/starry/README.md +++ b/apps/starry/README.md @@ -65,6 +65,17 @@ cargo xtask starry app run -t redis --arch riscv64 Stress configs are available through explicit QEMU config variants; see `redis/README.md`. +## GDB Smoke + +The `gdb-smoke` case is a RISC-V QEMU app workflow that prepares a temporary +rootfs overlay with GDB, GDBServer, and two tiny target programs. + +```bash +cargo xtask starry app run -t gdb-smoke --arch riscv64 +cargo xtask starry app run -t gdb-smoke --arch riscv64 \ + --qemu-config qemu-riscv64-gdbserver.toml +``` + ## Orange Pi 5 Plus UVC The `orangepi-5-plus-uvc` case needs `/usr/bin/uvc-fps` to be installed in the diff --git a/apps/starry/gdb-smoke/build-riscv64gc-unknown-none-elf.toml b/apps/starry/gdb-smoke/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..2df21769c5 --- /dev/null +++ b/apps/starry/gdb-smoke/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,14 @@ +env = {AX_IP = "10.0.2.15", AX_GW = "10.0.2.2"} +features = [ + "ax-hal/riscv64-qemu-virt", + "qemu", + "ax-driver/pci", + "ax-driver/virtio-blk", + "ax-driver/virtio-net", + "ax-driver/virtio-gpu", + "ax-driver/virtio-input", + "ax-driver/virtio-socket", +] +log = "Warn" +plat_dyn = false +target = "riscv64gc-unknown-none-elf" diff --git a/apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.gdb b/apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.gdb new file mode 100644 index 0000000000..3378e070d0 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.gdb @@ -0,0 +1,9 @@ +set pagination off +set confirm off +set debuginfod enabled off +set sysroot / +set solib-search-path /lib:/usr/lib +set remotetimeout 10 +target remote 127.0.0.1:1234 +continue +quit diff --git a/apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.sh b/apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.sh new file mode 100755 index 0000000000..6349680d49 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/gdbserver-smoke.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -eu + +log=/tmp/gdbserver-smoke.log +rm -f "$log" + +/usr/bin/gdbserver 0.0.0.0:1234 /usr/bin/gdbserver-smoke-target >"$log" 2>&1 & +server_pid=$! +trap 'kill "$server_pid" 2>/dev/null || true' EXIT + +listening=0 +for _ in 1 2 3 4 5 6 7 8 9 10; do + if grep -q "Listening on port 1234" "$log"; then + listening=1 + break + fi + sleep 1 +done + +cat "$log" +if [ "$listening" -ne 1 ]; then + echo "FAIL: gdbserver did not start listening" + exit 1 +fi + +/usr/bin/gdb -q -batch -x /usr/bin/gdbserver-smoke.gdb /usr/bin/gdbserver-smoke-target + +if ! wait "$server_pid"; then + cat "$log" + exit 1 +fi +trap - EXIT + +cat "$log" +echo GDBSERVER_SMOKE_DONE diff --git a/apps/starry/gdb-smoke/gdbserver/src/main.c b/apps/starry/gdb-smoke/gdbserver/src/main.c new file mode 100644 index 0000000000..a4d22467a1 --- /dev/null +++ b/apps/starry/gdb-smoke/gdbserver/src/main.c @@ -0,0 +1,22 @@ +#include +#include + +#define RISCV_HWCAP_ISA_D (1UL << ('D' - 'A')) + +static int compute_value(void) +{ + return 40 + 2; +} + +int main(void) +{ + unsigned long hwcap = getauxval(AT_HWCAP); + if ((hwcap & RISCV_HWCAP_ISA_D) == 0) { + printf("gdbserver-smoke-target missing riscv D hwcap: %#lx\n", hwcap); + return 1; + } + + int value = compute_value(); + printf("gdbserver-smoke-target value=%d hwcap=%#lx\n", value, hwcap); + return value == 42 ? 0 : 1; +} diff --git a/apps/starry/gdb-smoke/native/gdb-native-smoke.gdb b/apps/starry/gdb-smoke/native/gdb-native-smoke.gdb new file mode 100644 index 0000000000..cf919ce3d3 --- /dev/null +++ b/apps/starry/gdb-smoke/native/gdb-native-smoke.gdb @@ -0,0 +1,9 @@ +set pagination off +set confirm off +set debuginfod enabled off +break native_marker +run +bt +continue +echo GDB_NATIVE_SMOKE_DONE\n +quit diff --git a/apps/starry/gdb-smoke/native/src/main.c b/apps/starry/gdb-smoke/native/src/main.c new file mode 100644 index 0000000000..93e014cd8e --- /dev/null +++ b/apps/starry/gdb-smoke/native/src/main.c @@ -0,0 +1,13 @@ +#include + +__attribute__((noinline)) static int native_marker(int value) +{ + return value + 1; +} + +int main(void) +{ + int value = native_marker(41); + printf("gdb-native-smoke-target value=%d\n", value); + return value == 42 ? 0 : 1; +} diff --git a/apps/starry/gdb-smoke/prebuild.sh b/apps/starry/gdb-smoke/prebuild.sh new file mode 100755 index 0000000000..4702945684 --- /dev/null +++ b/apps/starry/gdb-smoke/prebuild.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +set -euo pipefail + +app_dir="${STARRY_APP_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}" +base_rootfs="${STARRY_BASE_ROOTFS:-}" +staging_root="${STARRY_STAGING_ROOT:-}" +overlay_dir="${STARRY_OVERLAY_DIR:-}" +apk_cache="${STARRY_WORKSPACE:-$(cd "$app_dir/../../.." && pwd)}/target/gdb-smoke-apk-cache" +qemu_runner="" + +require_env() { + local name="$1" + local value="$2" + if [[ -z "$value" ]]; then + echo "error: $name is required" >&2 + exit 1 + fi +} + +ensure_host_packages() { + local missing=() + + command -v clang >/dev/null 2>&1 || missing+=(clang) + command -v debugfs >/dev/null 2>&1 || missing+=(e2fsprogs) + command -v install >/dev/null 2>&1 || missing+=(coreutils) + command -v ld.lld >/dev/null 2>&1 || missing+=(lld) + command -v readelf >/dev/null 2>&1 || missing+=(binutils) + + if [[ ${#missing[@]} -eq 0 ]]; then + return + fi + + if ! command -v apt-get >/dev/null 2>&1; then + echo "error: missing required host packages and apt-get is unavailable: ${missing[*]}" >&2 + exit 1 + fi + + echo "installing missing host packages: ${missing[*]}" + apt-get update + apt-get install -y --no-install-recommends "${missing[@]}" +} + +extract_base_rootfs() { + debugfs -R "rdump / $staging_root" "$base_rootfs" +} + +find_qemu_runner() { + if command -v qemu-riscv64-static >/dev/null 2>&1; then + qemu_runner="$(command -v qemu-riscv64-static)" + elif command -v qemu-riscv64 >/dev/null 2>&1; then + qemu_runner="$(command -v qemu-riscv64)" + else + echo "error: qemu-riscv64-static or qemu-riscv64 is required" >&2 + exit 1 + fi +} + +install_guest_packages() { + local guest_apk="$staging_root/sbin/apk" + + mkdir -p "$apk_cache" + if [[ ! -x "$guest_apk" ]]; then + echo "error: staging root is missing guest apk: $guest_apk" >&2 + exit 1 + fi + + QEMU_LD_PREFIX="$staging_root" \ + LD_LIBRARY_PATH="$staging_root/lib:$staging_root/usr/lib:$staging_root/usr/local/lib" \ + "$qemu_runner" -L "$staging_root" "$guest_apk" \ + --root "$staging_root" \ + --repositories-file "$staging_root/etc/apk/repositories" \ + --keys-dir "$staging_root/etc/apk/keys" \ + --cache-dir "$apk_cache" \ + --update-cache \ + --timeout 60 \ + --no-interactive \ + --force-no-chroot \ + --scripts=no \ + add gdb gcc musl-dev +} + +compile_target() { + local source="$1" + local output="$2" + shift 2 + + install -d "$(dirname "$overlay_dir$output")" + clang \ + --target=riscv64-linux-musl \ + --sysroot="$staging_root" \ + --gcc-toolchain="$staging_root/usr" \ + -fuse-ld=lld \ + -static \ + "$@" \ + "$source" \ + -o "$overlay_dir$output" +} + +copy_file_to_overlay() { + local guest_path="$1" + local mode="$2" + local source="$staging_root${guest_path}" + local target="$overlay_dir${guest_path}" + + if [[ ! -e "$source" ]]; then + echo "error: missing guest file after gdb package install: $guest_path" >&2 + exit 1 + fi + + if [[ -L "$source" ]]; then + source="$(readlink -f "$source")" + fi + + install -Dm"$mode" "$source" "$target" +} + +find_library_path() { + local library="$1" + local dir + + for dir in lib usr/lib usr/local/lib; do + if [[ -e "$staging_root/$dir/$library" ]]; then + printf '/%s/%s\n' "$dir" "$library" + return 0 + fi + done + + return 1 +} + +copy_runtime_dependencies() { + local pending=("$@") + local seen=" " + local guest_path library + + while [[ ${#pending[@]} -gt 0 ]]; do + guest_path="${pending[0]}" + pending=("${pending[@]:1}") + + if [[ "$seen" == *" $guest_path "* ]]; then + continue + fi + seen+="$guest_path " + + while IFS= read -r library; do + local library_path + if ! library_path="$(find_library_path "$library")"; then + continue + fi + copy_file_to_overlay "$library_path" 0644 + pending+=("$library_path") + done < <( + readelf -d "$staging_root$guest_path" 2>/dev/null | + sed -n 's/.*Shared library: \[\(.*\)\].*/\1/p' + ) + done +} + +populate_overlay() { + compile_target \ + "$app_dir/native/src/main.c" \ + /usr/bin/gdb-native-smoke-target \ + -Wall -Wextra -Werror -O0 -g + compile_target \ + "$app_dir/gdbserver/src/main.c" \ + /usr/bin/gdbserver-smoke-target \ + -Wall -Wextra -Werror -g0 -s + + copy_file_to_overlay /usr/bin/gdb 0755 + copy_file_to_overlay /usr/bin/gdbserver 0755 + copy_runtime_dependencies /usr/bin/gdb /usr/bin/gdbserver + + if [[ -d "$staging_root/usr/share/gdb" ]]; then + mkdir -p "$overlay_dir/usr/share" + cp -a "$staging_root/usr/share/gdb" "$overlay_dir/usr/share/" + fi + if [[ -d "$staging_root/usr/lib/python3.12" ]]; then + mkdir -p "$overlay_dir/usr/lib" + cp -a "$staging_root/usr/lib/python3.12" "$overlay_dir/usr/lib/" + fi + + install -Dm0755 "$app_dir/native/gdb-native-smoke.gdb" \ + "$overlay_dir/usr/bin/gdb-native-smoke.gdb" + install -Dm0644 "$app_dir/gdbserver/gdbserver-smoke.gdb" \ + "$overlay_dir/usr/bin/gdbserver-smoke.gdb" + install -Dm0755 "$app_dir/gdbserver/gdbserver-smoke.sh" \ + "$overlay_dir/usr/bin/gdbserver-smoke.sh" +} + +require_env STARRY_BASE_ROOTFS "$base_rootfs" +require_env STARRY_STAGING_ROOT "$staging_root" +require_env STARRY_OVERLAY_DIR "$overlay_dir" + +ensure_host_packages +extract_base_rootfs +find_qemu_runner +install_guest_packages +populate_overlay diff --git a/apps/starry/gdb-smoke/qemu-riscv64-gdbserver.toml b/apps/starry/gdb-smoke/qemu-riscv64-gdbserver.toml new file mode 100644 index 0000000000..05fccfeea6 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-riscv64-gdbserver.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-gdb-smoke.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/gdbserver-smoke.sh" +success_regex = ["GDBSERVER_SMOKE_DONE"] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Remote communication error', + 'ptrace: Function not implemented', +] +timeout = 120 diff --git a/apps/starry/gdb-smoke/qemu-riscv64.toml b/apps/starry/gdb-smoke/qemu-riscv64.toml new file mode 100644 index 0000000000..7cb5a4b481 --- /dev/null +++ b/apps/starry/gdb-smoke/qemu-riscv64.toml @@ -0,0 +1,23 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-gdb-smoke.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/gdb -q -batch -x /usr/bin/gdb-native-smoke.gdb /usr/bin/gdb-native-smoke-target" +success_regex = ["GDB_NATIVE_SMOKE_DONE"] +fail_regex = [ + '(?i)\bpanic(?:ked)?\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Cannot insert breakpoint', + 'ptrace: Function not implemented', + 'No stack', +] +timeout = 120 diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index ed0a4c35a5..c002da5516 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -63,6 +63,7 @@ ax-alloc = { workspace = true, features = ["default"] } ax-config.workspace = true ax-display.workspace = true ax-fs = { version = "0.5.15", path = "../../arceos/modules/axfs-ng", package = "ax-fs-ng" } +ax-cpu = { workspace = true, features = ["fp-simd"] } ax-input = { workspace = true, optional = true } ax-lazyinit.workspace = true ax-log.workspace = true diff --git a/os/StarryOS/kernel/src/entry.rs b/os/StarryOS/kernel/src/entry.rs index 5e5ac35e70..46aeb7a88c 100644 --- a/os/StarryOS/kernel/src/entry.rs +++ b/os/StarryOS/kernel/src/entry.rs @@ -13,7 +13,7 @@ use crate::{ file::FD_TABLE, mm::{copy_from_kernel, load_user_app, new_user_aspace_empty}, pseudofs::{self, dev::tty::N_TTY}, - task::{ProcessData, Thread, add_task_to_table, new_user_task, spawn_alarm_task}, + task::{ProcessData, ProcessImage, Thread, add_task_to_table, new_user_task, spawn_alarm_task}, tracepoint::tracepoint_init, }; @@ -46,7 +46,7 @@ pub fn init(args: &[String], envs: &[String]) { }) .expect("Failed to create user address space"); - let (entry_vaddr, ustack_top) = load_user_app(&mut uspace, None, args, envs) + let (entry_vaddr, ustack_top, auxv) = load_user_app(&mut uspace, None, args, envs) .unwrap_or_else(|e| panic!("Failed to load user app: {}", e)); let uctx = UserContext::new(entry_vaddr.into(), ustack_top, 0); @@ -61,8 +61,7 @@ pub fn init(args: &[String], envs: &[String]) { let proc = ProcessData::new( proc, - path.to_string(), - Arc::new(args.to_vec()), + ProcessImage::new(path.to_string(), Arc::new(args.to_vec()), auxv), Arc::new(Mutex::new(uspace)), Arc::default(), None, diff --git a/os/StarryOS/kernel/src/mm/loader.rs b/os/StarryOS/kernel/src/mm/loader.rs index 31b0c08427..a20d3f6e2e 100644 --- a/os/StarryOS/kernel/src/mm/loader.rs +++ b/os/StarryOS/kernel/src/mm/loader.rs @@ -23,6 +23,14 @@ use crate::{ mm::aspace::{AddrSpace, Backend}, }; +#[cfg(target_arch = "riscv64")] +const RISCV_COMPAT_HWCAP_IMAFDC: usize = (1 << (b'I' - b'A')) + | (1 << (b'M' - b'A')) + | (1 << (b'A' - b'A')) + | (1 << (b'F' - b'A')) + | (1 << (b'D' - b'A')) + | (1 << (b'C' - b'A')); + /// Creates a new empty user address space. pub fn new_user_aspace_empty() -> AxResult { AddrSpace::new_empty(VirtAddr::from_usize(USER_SPACE_BASE), USER_SPACE_SIZE) @@ -186,10 +194,9 @@ impl ElfCacheEntry { /// import unless `HWCAP_LOONGARCH_LSX` (bit 4) is set. LASX (256-bit, bit 5) /// is intentionally *not* set because the kernel does not enable `EUEN.ASXE`; /// claiming it could trap when userspace executes 256-bit ops. -/// - **x86_64 / aarch64 / riscv64**: 0. glibc/musl on these arches do not gate -/// numpy on `AT_HWCAP` (x86 uses CPUID; aarch64 ASIMD/NEON is mandatory), and -/// numpy already imports successfully there today with `AT_HWCAP` absent. -/// Reporting 0 preserves the current (passing) behavior. +/// - **riscv64**: report the baseline ISA bits expected by Linux-compatible +/// user space (`IMAFDC`). +/// - **x86_64 / aarch64**: 0. x86 uses CPUID; aarch64 ASIMD/NEON is mandatory. const fn hwcap_value() -> usize { #[cfg(target_arch = "loongarch64")] { @@ -205,7 +212,11 @@ const fn hwcap_value() -> usize { | HWCAP_LOONGARCH_FPU | HWCAP_LOONGARCH_LSX } - #[cfg(not(target_arch = "loongarch64"))] + #[cfg(target_arch = "riscv64")] + { + RISCV_COMPAT_HWCAP_IMAFDC + } + #[cfg(not(any(target_arch = "loongarch64", target_arch = "riscv64")))] { 0 } @@ -331,7 +342,7 @@ pub fn load_user_app( path: Option<&str>, args: &[String], envs: &[String], -) -> AxResult<(VirtAddr, VirtAddr)> { +) -> AxResult<(VirtAddr, VirtAddr, Vec)> { let path = path .or_else(|| args.first().map(String::as_str)) .ok_or(AxError::InvalidInput)?; @@ -400,5 +411,5 @@ pub fn load_user_app( Backend::new_alloc(heap_start, PageSize::Size4K, "[heap]"), )?; - Ok((entry, user_sp)) + Ok((entry, user_sp, auxv)) } diff --git a/os/StarryOS/kernel/src/pseudofs/proc.rs b/os/StarryOS/kernel/src/pseudofs/proc.rs index b16c7dce0b..8ddb9a4ffd 100644 --- a/os/StarryOS/kernel/src/pseudofs/proc.rs +++ b/os/StarryOS/kernel/src/pseudofs/proc.rs @@ -11,6 +11,7 @@ use core::{ ffi::CStr, fmt::Write, iter, + mem::size_of, sync::atomic::{AtomicUsize, Ordering}, }; @@ -22,8 +23,10 @@ use ax_runtime::hal::{ }; use ax_task::{AxCpuMask, AxTaskRef, TaskState, WeakAxTaskRef, current}; use axfs_ng_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult}; +use kernel_elf_parser::{AuxEntry, AuxType}; use ksym::KallsymsMapped; use starry_process::{Pid, Process}; +use zerocopy::IntoBytes; use crate::{ file::FD_TABLE, @@ -657,6 +660,16 @@ fn render_thread_statm(task: &WeakAxTaskRef) -> VfsResult { )) } +fn render_thread_auxv(task: &AxTaskRef) -> Vec { + let mut entries = task.as_thread().proc_data.auxv.read().clone(); + entries.push(AuxEntry::new(AuxType::NULL, 0)); + let mut bytes = Vec::with_capacity(entries.len() * size_of::()); + for entry in entries { + bytes.extend_from_slice(entry.as_bytes()); + } + bytes +} + impl SimpleDirOps for ThreadDir { fn child_names<'a>(&'a self) -> Box> + 'a> { Box::new( @@ -667,6 +680,7 @@ impl SimpleDirOps for ThreadDir { "oom_score_adj", "task", "maps", + "auxv", "mounts", "cmdline", "comm", @@ -755,6 +769,7 @@ impl SimpleDirOps for ThreadDir { ) .into() } + "auxv" => SimpleFile::new_regular(fs, move || Ok(render_thread_auxv(&task))).into(), "mounts" => SimpleFile::new_regular(fs, move || { Ok("proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0\n") }) diff --git a/os/StarryOS/kernel/src/syscall/mod.rs b/os/StarryOS/kernel/src/syscall/mod.rs index e46ec8c3e0..5bd7624a02 100644 --- a/os/StarryOS/kernel/src/syscall/mod.rs +++ b/os/StarryOS/kernel/src/syscall/mod.rs @@ -31,7 +31,6 @@ pub fn handle_syscall(uctx: &mut UserContext) { }; trace!("Syscall {sysno:?}"); - // Snapshot sepc before dispatching: if a signal handler is installed // during the syscall, the handler redirects uctx.ip() elsewhere. // We must not overwrite retval when that happens, because on @@ -272,6 +271,22 @@ pub fn handle_syscall(uctx: &mut UserContext) { uctx.arg4() as _, uctx.arg5() as _, ), + Sysno::process_vm_readv => sys_process_vm_readv( + uctx.arg0() as _, + uctx.arg1() as _, + uctx.arg2() as _, + uctx.arg3() as _, + uctx.arg4() as _, + uctx.arg5() as _, + ), + Sysno::process_vm_writev => sys_process_vm_writev( + uctx.arg0() as _, + uctx.arg1() as _, + uctx.arg2() as _, + uctx.arg3() as _, + uctx.arg4() as _, + uctx.arg5() as _, + ), Sysno::sendfile => sys_sendfile( uctx.arg0() as _, uctx.arg1() as _, @@ -517,6 +532,7 @@ pub fn handle_syscall(uctx: &mut UserContext) { Sysno::capget => sys_capget(uctx.arg0() as _, uctx.arg1() as _), Sysno::capset => sys_capset(uctx.arg0() as _, uctx.arg1() as _), Sysno::umask => sys_umask(uctx.arg0() as _), + Sysno::personality => sys_personality(uctx.arg0()), Sysno::setreuid => sys_setreuid(uctx.arg0() as _, uctx.arg1() as _), Sysno::setregid => sys_setregid(uctx.arg0() as _, uctx.arg1() as _), Sysno::setresuid => sys_setresuid(uctx.arg0() as _, uctx.arg1() as _, uctx.arg2() as _), @@ -556,6 +572,7 @@ pub fn handle_syscall(uctx: &mut UserContext) { uctx.arg2() as _, uctx.arg3() as _, ), + Sysno::ptrace => sys_ptrace(uctx.arg0() as _, uctx.arg1(), uctx.arg2(), uctx.arg3()), Sysno::getsid => sys_getsid(uctx.arg0() as _), Sysno::setsid => sys_setsid(), Sysno::getpgid => sys_getpgid(uctx.arg0() as _), diff --git a/os/StarryOS/kernel/src/syscall/signal.rs b/os/StarryOS/kernel/src/syscall/signal.rs index 546c97d5b1..bcca884cf4 100644 --- a/os/StarryOS/kernel/src/syscall/signal.rs +++ b/os/StarryOS/kernel/src/syscall/signal.rs @@ -166,7 +166,24 @@ pub fn sys_kill(pid: i32, signo: u32) -> AxResult { match pid { 1.. => { check_kill_permission(pid as _)?; - send_signal_to_process(pid as _, sig)?; + if let Some(sig) = sig { + let curr = current(); + let thread = curr.as_thread(); + let signo = sig.signo(); + if pid as Pid == thread.proc_data.proc.pid() && !thread.signal.signal_blocked(signo) + { + // A process-directed signal may be delivered to any + // unblocked thread. Prefer the current thread for + // self-signals so `kill(getpid(), SIGSTOP)` cannot return + // to userspace and race into the next syscall before this + // thread observes the stop. + send_signal_to_thread(None, curr.id().as_u64() as Pid, Some(sig))?; + } else { + send_signal_to_process(pid as _, Some(sig))?; + } + } else { + send_signal_to_process(pid as _, None)?; + } } 0 => { let pgid = current().as_thread().proc_data.proc.group().pgid(); diff --git a/os/StarryOS/kernel/src/syscall/task/clone.rs b/os/StarryOS/kernel/src/syscall/task/clone.rs index 214519f46c..3b647e063a 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone.rs @@ -14,7 +14,7 @@ use starry_vm::VmMutPtr; use crate::{ file::{FD_TABLE, FileLike, PidFd, close_file_like}, mm::copy_from_kernel, - task::{AsThread, ProcessData, Thread, add_task_to_table, new_user_task}, + task::{AsThread, ProcessData, ProcessImage, Thread, add_task_to_table, new_user_task}, }; bitflags! { @@ -224,8 +224,11 @@ impl CloneArgs { let proc_data = ProcessData::new( proc, - old_proc_data.exe_path.read().clone(), - old_proc_data.cmdline.read().clone(), + ProcessImage::new( + old_proc_data.exe_path.read().clone(), + old_proc_data.cmdline.read().clone(), + old_proc_data.auxv.read().clone(), + ), aspace, signal_actions, exit_signal, @@ -234,6 +237,7 @@ impl CloneArgs { proc_data.set_umask(old_proc_data.umask()); proc_data.set_nice(old_proc_data.nice()); proc_data.set_heap_top(old_proc_data.get_heap_top()); + proc_data.replace_personality(old_proc_data.personality()); // Inherit parent dumpable (PR_SET_DUMPABLE state). Linux: child // fork/clone copies mm->dumpable from parent; without this, a // child of `prctl(PR_SET_DUMPABLE, 0) -> fork()` would reset to @@ -300,12 +304,50 @@ impl CloneArgs { new_proc_data.set_vfork_done(poll); } + let parent_pid = curr.as_thread().proc_data.proc.pid(); + let parent_tid = curr.id().as_u64() as Pid; + let ptrace_event = if flags.contains(CloneFlags::THREAD) { + super::ptrace::PTRACE_EVENT_CLONE + } else if flags.contains(CloneFlags::VFORK) { + super::ptrace::PTRACE_EVENT_VFORK + } else { + super::ptrace::PTRACE_EVENT_FORK + }; + let trace_clone = super::ptrace::ptrace_notify_clone(parent_pid, tid as Pid, ptrace_event); + if trace_clone + && !flags.contains(CloneFlags::THREAD) + && let Some(tracer_pid) = curr.as_thread().proc_data.ptrace_tracer_pid() + { + new_proc_data.set_ptrace_tracer_pid(tracer_pid); + new_proc_data.set_ptrace_attached(); + new_proc_data.set_ptrace_stop(starry_signal::Signo::SIGSTOP, &new_uctx); + } + let task = spawn_task(new_task); add_task_to_table(&task); + if trace_clone { + let _ = crate::task::send_signal_to_thread( + None, + parent_tid, + Some(starry_signal::SignalInfo::new_kernel( + starry_signal::Signo::SIGTRAP, + )), + ); + } + // Block the parent until the child exec's or exits. if needs_vfork_block { new_proc_data.wait_vfork_done(); + if super::ptrace::ptrace_notify_vfork_done(parent_pid, tid as Pid) { + let _ = crate::task::send_signal_to_thread( + None, + parent_tid, + Some(starry_signal::SignalInfo::new_kernel( + starry_signal::Signo::SIGTRAP, + )), + ); + } } Ok(tid as _) diff --git a/os/StarryOS/kernel/src/syscall/task/ctl.rs b/os/StarryOS/kernel/src/syscall/task/ctl.rs index 6f80ba5722..9b157d79f5 100644 --- a/os/StarryOS/kernel/src/syscall/task/ctl.rs +++ b/os/StarryOS/kernel/src/syscall/task/ctl.rs @@ -11,6 +11,7 @@ use crate::{ }; const CAPABILITY_VERSION_3: u32 = 0x20080522; +const PERSONALITY_GET: u32 = 0xffff_ffff; /// Validate the cap header and return the target pid (0 means self). fn validate_cap_header(header_ptr: *mut __user_cap_header_struct) -> AxResult { @@ -87,6 +88,16 @@ pub fn sys_umask(mask: u32) -> AxResult { Ok(old as isize) } +pub fn sys_personality(persona: usize) -> AxResult { + let curr = current(); + let proc_data = &curr.as_thread().proc_data; + let old = proc_data.personality(); + if persona as u32 != PERSONALITY_GET { + proc_data.replace_personality(persona); + } + Ok(old as isize) +} + pub fn sys_get_mempolicy( _policy: *mut i32, _nodemask: *mut usize, diff --git a/os/StarryOS/kernel/src/syscall/task/execve.rs b/os/StarryOS/kernel/src/syscall/task/execve.rs index 0a57d45581..0eb5560c30 100644 --- a/os/StarryOS/kernel/src/syscall/task/execve.rs +++ b/os/StarryOS/kernel/src/syscall/task/execve.rs @@ -118,7 +118,7 @@ pub fn sys_execve( // the pathname (the FS could change while siblings are being reaped). let mut new_aspace = new_user_aspace_empty()?; copy_from_kernel(&mut new_aspace)?; - let (entry_point, user_stack_base) = + let (entry_point, user_stack_base, auxv) = match load_user_app(&mut new_aspace, Some(path.as_str()), &args, &envs) { Ok(result) => result, Err(AxError::InvalidExecutable) => { @@ -234,6 +234,7 @@ pub fn sys_execve( curr.set_name(&new_name); *proc_data.exe_path.write() = new_exe_path; *proc_data.cmdline.write() = Arc::new(args); + *proc_data.auxv.write() = auxv; proc_data.set_heap_top(USER_HEAP_BASE); @@ -326,6 +327,10 @@ pub fn sys_execve( // explicitly preserved above. *uctx = UserContext::new(entry_point.as_usize(), user_stack_base, 0); + if proc_data.is_ptrace_traceme() { + proc_data.set_ptrace_exec_stop_pending(); + } + // Unblock a vfork parent waiting for this child to exec. // Must be last: by now CLOEXEC fds are closed so the parent's pipe // read will see EOF correctly. diff --git a/os/StarryOS/kernel/src/syscall/task/mod.rs b/os/StarryOS/kernel/src/syscall/task/mod.rs index 2143a0e822..bdf195dd6f 100644 --- a/os/StarryOS/kernel/src/syscall/task/mod.rs +++ b/os/StarryOS/kernel/src/syscall/task/mod.rs @@ -4,10 +4,12 @@ mod ctl; mod execve; mod exit; mod job; +pub mod ptrace; mod schedule; mod thread; mod wait; pub use self::{ - clone::*, clone3::*, ctl::*, execve::*, exit::*, job::*, schedule::*, thread::*, wait::*, + clone::*, clone3::*, ctl::*, execve::*, exit::*, job::*, ptrace::*, schedule::*, thread::*, + wait::*, }; diff --git a/os/StarryOS/kernel/src/syscall/task/ptrace.rs b/os/StarryOS/kernel/src/syscall/task/ptrace.rs new file mode 100644 index 0000000000..ca3903eb72 --- /dev/null +++ b/os/StarryOS/kernel/src/syscall/task/ptrace.rs @@ -0,0 +1,1228 @@ +use alloc::{sync::Arc, vec, vec::Vec}; +use core::mem::{MaybeUninit, size_of}; +#[cfg(target_arch = "riscv64")] +use core::slice; + +use ax_errno::{AxError, AxResult, LinuxError}; +use ax_memory_addr::{MemoryAddr, VirtAddr}; +use ax_runtime::hal::paging::MappingFlags; +use ax_task::current; +use starry_process::Pid; +use starry_signal::Signo; +use starry_vm::{VmMutPtr, VmPtr, vm_read_slice, vm_write_slice}; + +use crate::{ + mm::{AddrSpace, IoVec}, + task::{AsThread, ProcessData, get_process_data}, +}; + +const PTRACE_TRACEME: u32 = 0; +const PTRACE_PEEKTEXT: u32 = 1; +const PTRACE_PEEKDATA: u32 = 2; +const PTRACE_POKETEXT: u32 = 4; +const PTRACE_POKEDATA: u32 = 5; +const PTRACE_CONT: u32 = 7; +const PTRACE_KILL: u32 = 8; +const PTRACE_SINGLESTEP: u32 = 9; +const PTRACE_GETREGS: u32 = 12; +const PTRACE_SETREGS: u32 = 13; +const PTRACE_GETFPREGS: u32 = 14; +const PTRACE_SETFPREGS: u32 = 15; +const PTRACE_ATTACH: u32 = 16; +const PTRACE_DETACH: u32 = 17; +const PTRACE_SYSCALL: u32 = 24; +const PTRACE_SETOPTIONS: u32 = 0x4200; +const PTRACE_GETEVENTMSG: u32 = 0x4201; +const PTRACE_GETSIGINFO: u32 = 0x4202; +const PTRACE_SETSIGINFO: u32 = 0x4203; +const PTRACE_GETREGSET: u32 = 0x4204; +const PTRACE_SETREGSET: u32 = 0x4205; +const PTRACE_SEIZE: u32 = 0x4206; +const PTRACE_INTERRUPT: u32 = 0x4207; + +const NT_PRSTATUS: usize = 1; +#[cfg(target_arch = "riscv64")] +const NT_FPREGSET: usize = 2; + +const PTRACE_O_TRACESYSGOOD: usize = 1; +const PTRACE_O_TRACEFORK: usize = 1 << 1; +const PTRACE_O_TRACEVFORK: usize = 1 << 2; +const PTRACE_O_TRACECLONE: usize = 1 << 3; +const PTRACE_O_TRACEEXEC: usize = 1 << 4; +const PTRACE_O_TRACEVFORKDONE: usize = 1 << 5; +const PTRACE_O_TRACEEXIT: usize = 1 << 6; + +pub const PTRACE_EVENT_FORK: u32 = 1; +pub const PTRACE_EVENT_VFORK: u32 = 2; +pub const PTRACE_EVENT_CLONE: u32 = 3; +const PTRACE_EVENT_EXEC: u32 = 4; +pub const PTRACE_EVENT_VFORK_DONE: u32 = 5; +const PTRACE_EVENT_EXIT: u32 = 6; + +#[cfg(target_arch = "riscv64")] +const EBREAK_INSN: u16 = 0x9002; + +#[cfg(target_arch = "riscv64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct RiscvUserRegs { + pc: usize, + ra: usize, + sp: usize, + gp: usize, + tp: usize, + t0: usize, + t1: usize, + t2: usize, + s0: usize, + s1: usize, + a0: usize, + a1: usize, + a2: usize, + a3: usize, + a4: usize, + a5: usize, + a6: usize, + a7: usize, + s2: usize, + s3: usize, + s4: usize, + s5: usize, + s6: usize, + s7: usize, + s8: usize, + s9: usize, + s10: usize, + s11: usize, + t3: usize, + t4: usize, + t5: usize, + t6: usize, +} + +#[cfg(target_arch = "riscv64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct RiscvFpRegs { + f: [u64; 32], + fcsr: usize, +} + +pub fn sys_ptrace(request: u32, pid: usize, addr: usize, data: usize) -> AxResult { + info!("sys_ptrace <= request: {request}, pid: {pid}, addr: {addr:#x}, data: {data:#x}"); + + match request { + PTRACE_TRACEME => ptrace_traceme(), + PTRACE_PEEKTEXT | PTRACE_PEEKDATA => ptrace_peekdata(pid, addr, data), + PTRACE_POKETEXT | PTRACE_POKEDATA => ptrace_pokedata(pid, addr, data), + PTRACE_CONT => ptrace_cont(pid, data), + PTRACE_KILL => ptrace_kill(pid), + PTRACE_SINGLESTEP => ptrace_singlestep(pid, data), + PTRACE_GETREGS => ptrace_getregs(pid, data), + PTRACE_SETREGS => ptrace_setregs(pid, data), + PTRACE_GETFPREGS => ptrace_getfpregs(pid, data), + PTRACE_SETFPREGS => ptrace_setfpregs(pid, data), + PTRACE_ATTACH => ptrace_attach(pid), + PTRACE_DETACH => ptrace_detach(pid, data), + PTRACE_SYSCALL => ptrace_syscall(pid, data), + PTRACE_SETOPTIONS => ptrace_setoptions(pid, addr), + PTRACE_GETEVENTMSG => ptrace_geteventmsg(pid, data), + PTRACE_GETSIGINFO => ptrace_getsiginfo(pid, data), + PTRACE_SETSIGINFO => ptrace_setsiginfo(pid, data), + PTRACE_GETREGSET => ptrace_getregset(pid, addr, data), + PTRACE_SETREGSET => ptrace_setregset(pid, addr, data), + PTRACE_SEIZE => ptrace_seize(pid, addr), + PTRACE_INTERRUPT => ptrace_interrupt(pid), + _ => Err(AxError::Unsupported), + } +} + +fn ptrace_traceme() -> AxResult { + let curr = current(); + let proc_data = &curr.as_thread().proc_data; + if proc_data.proc.parent().is_none() + || proc_data.is_ptrace_traceme() + || proc_data.is_ptrace_attached() + || proc_data.ptrace_tracer_pid().is_some() + { + return Err(AxError::from(LinuxError::EPERM)); + } + proc_data.set_ptrace_traceme(); + Ok(0) +} + +fn ptrace_resume_signo(data: usize) -> AxResult { + if data == 0 { + return Ok(0); + } + let signo = u8::try_from(data).map_err(|_| AxError::from(LinuxError::EIO))?; + Signo::from_repr(signo).ok_or_else(|| AxError::from(LinuxError::EIO))?; + Ok(signo as u32) +} + +fn ptrace_cont(pid: usize, data: usize) -> AxResult { + let signo = ptrace_resume_signo(data)?; + let tracee = ptrace_stopped_tracee(pid)?; + tracee.set_ptrace_singlestep(false); + tracee.set_ptrace_syscall_trace(false); + tracee.resume_ptrace_stop_with_signal(signo); + Ok(0) +} + +fn ptrace_kill(pid: usize) -> AxResult { + let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + if tracee.is_ptrace_traceme() || tracee.is_ptrace_attached() { + tracee.clear_ptrace_stop(); + tracee.clear_ptrace_traceme(); + tracee.clear_ptrace_attached(); + } + use starry_signal::SignalInfo; + + use crate::task::send_signal_to_process; + let _ = send_signal_to_process(tracee_pid, Some(SignalInfo::new_kernel(Signo::SIGKILL))); + Ok(0) +} + +fn ptrace_singlestep(pid: usize, data: usize) -> AxResult { + let signo = ptrace_resume_signo(data)?; + let tracee = ptrace_stopped_tracee(pid)?; + tracee.set_ptrace_singlestep(true); + tracee.set_ptrace_syscall_trace(false); + tracee.resume_ptrace_stop_with_signal(signo); + Ok(0) +} + +fn ptrace_attach(pid: usize) -> AxResult { + let tracer_pid = current().as_thread().proc_data.proc.pid(); + let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + if tracee_pid == tracer_pid { + return Err(AxError::from(LinuxError::EPERM)); + } + let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + if tracee.is_ptrace_traceme() || tracee.is_ptrace_attached() { + return Err(AxError::from(LinuxError::EPERM)); + } + let is_child = tracee.proc.parent().is_some_and(|p| p.pid() == tracer_pid); + if !is_child { + return Err(AxError::from(LinuxError::EPERM)); + } + tracee.set_ptrace_tracer_pid(tracer_pid); + tracee.set_ptrace_attached(); + use starry_signal::SignalInfo; + let _ = crate::task::send_signal_to_process( + tracee_pid, + Some(SignalInfo::new_kernel(Signo::SIGSTOP)), + ); + Ok(0) +} + +fn ptrace_detach(pid: usize, data: usize) -> AxResult { + let signo = ptrace_resume_signo(data)?; + let tracee = ptrace_stopped_tracee(pid)?; + tracee.clear_ptrace_traceme(); + tracee.clear_ptrace_attached(); + tracee.clear_ptrace_tracer_pid(); + tracee.set_ptrace_singlestep(false); + tracee.set_ptrace_syscall_trace(false); + tracee.set_ptrace_options(0); + tracee.resume_ptrace_stop_with_signal(signo); + Ok(0) +} + +fn ptrace_syscall(pid: usize, data: usize) -> AxResult { + let signo = ptrace_resume_signo(data)?; + let tracee = ptrace_stopped_tracee(pid)?; + tracee.set_ptrace_singlestep(false); + tracee.set_ptrace_syscall_trace(true); + tracee.resume_ptrace_stop_with_signal(signo); + Ok(0) +} + +fn ptrace_setoptions(pid: usize, options: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let valid_mask = PTRACE_O_TRACESYSGOOD + | PTRACE_O_TRACEFORK + | PTRACE_O_TRACEVFORK + | PTRACE_O_TRACECLONE + | PTRACE_O_TRACEEXEC + | PTRACE_O_TRACEVFORKDONE + | PTRACE_O_TRACEEXIT; + if options & !valid_mask != 0 { + return Err(AxError::InvalidInput); + } + tracee.set_ptrace_options(options); + Ok(0) +} + +fn ptrace_geteventmsg(pid: usize, data: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let msg = tracee.ptrace_event_msg(); + (data as *mut usize).vm_write(msg)?; + Ok(0) +} + +fn ptrace_getsiginfo(pid: usize, data: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let siginfo = tracee + .ptrace_stop_siginfo() + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + + #[cfg(target_arch = "riscv64")] + { + let bytes = unsafe { + slice::from_raw_parts( + (&siginfo.0 as *const linux_raw_sys::general::siginfo_t).cast::(), + size_of::(), + ) + }; + vm_write_slice(data as *mut u8, bytes)?; + Ok(0) + } + + #[cfg(not(target_arch = "riscv64"))] + { + let _ = (data, siginfo); + Err(AxError::Unsupported) + } +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let tracee = ptrace_stopped_tracee(pid)?; + let siginfo = ptrace_read_user_siginfo(data)?; + let signo = ptrace_siginfo_signo(&siginfo)?; + if !tracee.set_ptrace_stop_siginfo(signo, starry_signal::SignalInfo(siginfo)) { + return Err(AxError::from(LinuxError::ESRCH)); + } + Ok(0) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +fn ptrace_getregset(pid: usize, addr: usize, data: usize) -> AxResult { + match addr { + NT_PRSTATUS => ptrace_getregset_prstatus(pid, data), + #[cfg(target_arch = "riscv64")] + NT_FPREGSET => ptrace_getregset_fpregset(pid, data), + _ => Err(AxError::Unsupported), + } +} + +fn ptrace_setregset(pid: usize, addr: usize, data: usize) -> AxResult { + match addr { + NT_PRSTATUS => ptrace_setregset_prstatus(pid, data), + #[cfg(target_arch = "riscv64")] + NT_FPREGSET => ptrace_setregset_fpregset(pid, data), + _ => Err(AxError::Unsupported), + } +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_getregs(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_stopped_user_regs(pid)?; + let bytes = unsafe { + slice::from_raw_parts( + (®s as *const RiscvUserRegs).cast::(), + size_of::(), + ) + }; + vm_write_slice(data as *mut u8, bytes)?; + Ok(0) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_getregs(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_setregs(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_user_regs(data)?; + ptrace_write_stopped_user_regs(pid, regs) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_setregs(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_stopped_fp_regs(pid)?; + let bytes = unsafe { + slice::from_raw_parts( + (®s as *const RiscvFpRegs).cast::(), + size_of::(), + ) + }; + vm_write_slice(data as *mut u8, bytes)?; + Ok(0) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_getfpregs(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_user_fpregs(data)?; + ptrace_write_stopped_fp_regs(pid, regs) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_setfpregs(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +fn ptrace_seize(pid: usize, _addr: usize) -> AxResult { + let tracer_pid = current().as_thread().proc_data.proc.pid(); + let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + if tracee_pid == tracer_pid { + return Err(AxError::from(LinuxError::EPERM)); + } + let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + if tracee.is_ptrace_traceme() || tracee.is_ptrace_attached() { + return Err(AxError::from(LinuxError::EPERM)); + } + let is_child = tracee.proc.parent().is_some_and(|p| p.pid() == tracer_pid); + if !is_child { + return Err(AxError::from(LinuxError::EPERM)); + } + tracee.set_ptrace_tracer_pid(tracer_pid); + tracee.set_ptrace_attached(); + Ok(0) +} + +fn ptrace_interrupt(pid: usize) -> AxResult { + let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + let tracee = get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + if !tracee.is_ptrace_attached() && !tracee.is_ptrace_traceme() { + return Err(AxError::from(LinuxError::ESRCH)); + } + if tracee.ptrace_stop_signo().is_some() { + return Ok(0); + } + use starry_signal::SignalInfo; + let _ = crate::task::send_signal_to_process( + tracee_pid, + Some(SignalInfo::new_kernel(Signo::SIGSTOP)), + ); + Ok(0) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_stopped_user_regs(pid)?; + let mut iov = (data as *const IoVec).vm_read()?; + if iov.iov_len < 0 { + return Err(AxError::InvalidInput); + } + + let bytes = unsafe { + slice::from_raw_parts( + (®s as *const RiscvUserRegs).cast::(), + size_of::(), + ) + }; + let copy_len = (iov.iov_len as usize).min(bytes.len()); + vm_write_slice(iov.iov_base, &bytes[..copy_len])?; + iov.iov_len = copy_len as isize; + (data as *mut IoVec).vm_write(iov)?; + Ok(0) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let iov = (data as *const IoVec).vm_read()?; + if iov.iov_len < size_of::() as isize { + return Err(AxError::InvalidInput); + } + + let regs = ptrace_read_user_regs(iov.iov_base as usize)?; + ptrace_write_stopped_user_regs(pid, regs) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_stopped_user_regs(pid: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let uctx = tracee + .ptrace_stop_user_context() + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + Ok(RiscvUserRegs::from(&uctx)) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_user_regs(data: usize) -> AxResult { + let mut regs = MaybeUninit::::uninit(); + let bytes = unsafe { + slice::from_raw_parts_mut( + regs.as_mut_ptr().cast::>(), + size_of::(), + ) + }; + starry_vm::vm_read_slice(data as *const u8, bytes)?; + Ok(unsafe { regs.assume_init() }) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_write_stopped_user_regs(pid: usize, regs: RiscvUserRegs) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let mut uctx = tracee + .ptrace_stop_user_context() + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + regs.write_to(&mut uctx); + if !tracee.set_ptrace_stop_user_context(uctx) { + return Err(AxError::from(LinuxError::ESRCH)); + } + Ok(0) +} + +#[cfg(not(target_arch = "riscv64"))] +fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_getregset_fpregset(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_stopped_fp_regs(pid)?; + let mut iov = (data as *const IoVec).vm_read()?; + if iov.iov_len < size_of::() as isize { + return Err(AxError::InvalidInput); + } + let bytes = unsafe { + slice::from_raw_parts( + (®s as *const RiscvFpRegs).cast::(), + size_of::(), + ) + }; + vm_write_slice(iov.iov_base, bytes)?; + iov.iov_len = bytes.len() as isize; + (data as *mut IoVec).vm_write(iov)?; + Ok(0) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_setregset_fpregset(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let iov = (data as *const IoVec).vm_read()?; + if iov.iov_len < size_of::() as isize { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_user_fpregs(iov.iov_base as usize)?; + ptrace_write_stopped_fp_regs(pid, regs) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_stopped_fp_regs(pid: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let fp_data = tracee + .ptrace_stop_fp_data() + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + Ok(RiscvFpRegs { + f: fp_data.0, + fcsr: fp_data.1, + }) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_user_fpregs(data: usize) -> AxResult { + let mut regs = MaybeUninit::::uninit(); + let bytes = unsafe { + slice::from_raw_parts_mut( + regs.as_mut_ptr().cast::>(), + size_of::(), + ) + }; + starry_vm::vm_read_slice(data as *const u8, bytes)?; + Ok(unsafe { regs.assume_init() }) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_user_siginfo(data: usize) -> AxResult { + let mut siginfo = MaybeUninit::::uninit(); + let bytes = unsafe { + slice::from_raw_parts_mut( + siginfo.as_mut_ptr().cast::>(), + size_of::(), + ) + }; + starry_vm::vm_read_slice(data as *const u8, bytes)?; + Ok(unsafe { siginfo.assume_init() }) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_siginfo_signo(siginfo: &linux_raw_sys::general::siginfo_t) -> AxResult { + let signo = unsafe { siginfo.__bindgen_anon_1.__bindgen_anon_1.si_signo }; + Signo::from_repr(signo as u8).ok_or(AxError::InvalidInput) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_write_stopped_fp_regs(pid: usize, regs: RiscvFpRegs) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + if !tracee.set_ptrace_stop_fp_data((regs.f, regs.fcsr)) { + return Err(AxError::from(LinuxError::ESRCH)); + } + Ok(0) +} + +fn ptrace_peekdata(pid: usize, addr: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let tracee = ptrace_stopped_tracee(pid)?; + (data as *mut usize).vm_write(ptrace_read_word(&tracee, addr)?)?; + Ok(0) +} + +fn ptrace_pokedata(pid: usize, addr: usize, data: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + ptrace_write_word(&tracee, addr, data)?; + Ok(0) +} + +fn ptrace_read_word(tracee: &ProcessData, addr: usize) -> AxResult { + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + ptrace_populate_remote_range(&mut aspace, addr, size_of::(), MappingFlags::READ)?; + let mut bytes = [0u8; size_of::()]; + aspace.read(VirtAddr::from_usize(addr), &mut bytes)?; + Ok(usize::from_ne_bytes(bytes)) +} + +fn ptrace_write_word(tracee: &ProcessData, addr: usize, data: usize) -> AxResult { + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + ptrace_populate_remote_range(&mut aspace, addr, size_of::(), MappingFlags::WRITE)?; + aspace.write(VirtAddr::from_usize(addr), &data.to_ne_bytes())?; + ax_runtime::hal::cpu::asm::flush_icache_all(); + Ok(()) +} + +fn ptrace_populate_remote_range( + aspace: &mut AddrSpace, + addr: usize, + len: usize, + access_flags: MappingFlags, +) -> AxResult { + let start = VirtAddr::from_usize(addr); + let end = VirtAddr::from_usize(addr.checked_add(len).ok_or(AxError::BadAddress)?); + let page_start = start.align_down_4k(); + let page_end = end.align_up_4k(); + aspace.populate_area(page_start, page_end - page_start, access_flags) +} + +pub fn sys_process_vm_readv( + pid: usize, + local_iov: *const IoVec, + liovcnt: usize, + remote_iov: *const IoVec, + riovcnt: usize, + flags: usize, +) -> AxResult { + if flags != 0 { + return Err(AxError::InvalidInput); + } + + process_vm_copy(pid, local_iov, liovcnt, remote_iov, riovcnt, false) +} + +pub fn sys_process_vm_writev( + pid: usize, + local_iov: *const IoVec, + liovcnt: usize, + remote_iov: *const IoVec, + riovcnt: usize, + flags: usize, +) -> AxResult { + if flags != 0 { + return Err(AxError::InvalidInput); + } + + process_vm_copy(pid, local_iov, liovcnt, remote_iov, riovcnt, true) +} + +fn process_vm_copy( + pid: usize, + local_iov: *const IoVec, + liovcnt: usize, + remote_iov: *const IoVec, + riovcnt: usize, + write_remote: bool, +) -> AxResult { + let tracee = process_vm_tracee(pid)?; + let local = read_iovecs(local_iov, liovcnt)?; + let remote = read_iovecs(remote_iov, riovcnt)?; + + let mut local_idx = 0; + let mut remote_idx = 0; + let mut local_off = 0; + let mut remote_off = 0; + let mut copied = 0usize; + + while local_idx < local.len() && remote_idx < remote.len() { + skip_empty_iovecs(&local, &mut local_idx, &mut local_off); + skip_empty_iovecs(&remote, &mut remote_idx, &mut remote_off); + if local_idx >= local.len() || remote_idx >= remote.len() { + break; + } + + let local_len = local[local_idx].iov_len as usize - local_off; + let remote_len = remote[remote_idx].iov_len as usize - remote_off; + let chunk_len = local_len.min(remote_len); + if chunk_len == 0 { + break; + } + + let local_addr = local[local_idx].iov_base.wrapping_add(local_off); + let remote_addr = (remote[remote_idx].iov_base as usize) + .checked_add(remote_off) + .ok_or(AxError::BadAddress)?; + let result: AxResult<()> = if write_remote { + let mut data = vec![0; chunk_len]; + let bytes = unsafe { + core::slice::from_raw_parts_mut( + data.as_mut_ptr().cast::>(), + data.len(), + ) + }; + vm_read_slice(local_addr, bytes)?; + remote_write(&tracee, remote_addr, &data) + } else { + let data = remote_read(&tracee, remote_addr, chunk_len)?; + vm_write_slice(local_addr, &data)?; + Ok(()) + }; + + if let Err(err) = result { + return if copied == 0 { + Err(err) + } else { + Ok(copied as isize) + }; + } + + copied = copied.checked_add(chunk_len).ok_or(AxError::InvalidInput)?; + local_off += chunk_len; + remote_off += chunk_len; + } + + Ok(copied as isize) +} + +fn process_vm_tracee(pid: usize) -> AxResult> { + let tracee_pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + let current_pid = current().as_thread().proc_data.proc.pid(); + if tracee_pid == current_pid { + get_process_data(tracee_pid).map_err(|_| AxError::from(LinuxError::ESRCH)) + } else { + ptrace_stopped_tracee(pid) + } +} + +fn read_iovecs(iov: *const IoVec, iovcnt: usize) -> AxResult> { + if iovcnt > 1024 { + return Err(AxError::InvalidInput); + } + + let mut iovecs = Vec::with_capacity(iovcnt); + let mut total = 0usize; + for idx in 0..iovcnt { + let iov = iov.wrapping_add(idx).vm_read()?; + if iov.iov_len < 0 { + return Err(AxError::InvalidInput); + } + total = total + .checked_add(iov.iov_len as usize) + .filter(|len| *len <= isize::MAX as usize) + .ok_or(AxError::InvalidInput)?; + iovecs.push(iov); + } + Ok(iovecs) +} + +fn skip_empty_iovecs(iovecs: &[IoVec], idx: &mut usize, offset: &mut usize) { + while *idx < iovecs.len() && *offset >= iovecs[*idx].iov_len as usize { + *idx += 1; + *offset = 0; + } +} + +fn remote_read(tracee: &ProcessData, addr: usize, len: usize) -> AxResult> { + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + ptrace_populate_remote_range(&mut aspace, addr, len, MappingFlags::READ)?; + let mut data = vec![0; len]; + aspace.read(VirtAddr::from_usize(addr), &mut data)?; + Ok(data) +} + +fn remote_write(tracee: &ProcessData, addr: usize, data: &[u8]) -> AxResult { + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + ptrace_populate_remote_range(&mut aspace, addr, data.len(), MappingFlags::WRITE)?; + aspace.write(VirtAddr::from_usize(addr), data)?; + ax_runtime::hal::cpu::asm::flush_icache_all(); + Ok(()) +} + +fn ptrace_stopped_tracee(pid: usize) -> AxResult> { + let pid = Pid::try_from(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + let tracer_pid = current().as_thread().proc_data.proc.pid(); + let tracee = get_process_data(pid).map_err(|_| AxError::from(LinuxError::ESRCH))?; + let is_tracer = (tracee.is_ptrace_traceme() || tracee.is_ptrace_attached()) + && tracee + .ptrace_tracer_pid() + .is_some_and(|pid| pid == tracer_pid); + if !is_tracer || tracee.ptrace_stop_signo().is_none() { + return Err(AxError::from(LinuxError::ESRCH)); + } + Ok(tracee) +} + +#[cfg(target_arch = "riscv64")] +pub fn ptrace_setup_singlestep( + tracee: &ProcessData, + uctx: &mut ax_runtime::hal::cpu::uspace::UserContext, +) { + let pc = uctx.ip(); + let aspace = tracee.aspace(); + let mut aspace = aspace.lock(); + + let saved = tracee.take_ptrace_ss_saved_insn(); + if let Some((saved_addr, saved_insn)) = saved { + let _ = ptrace_write_u16_unlocked(&mut aspace, saved_addr, saved_insn as u16); + } + + let first_half = match ptrace_read_u16_unlocked(&aspace, pc) { + Ok(half) => half, + Err(_) => { + tracee.set_ptrace_ss_saved_insn(None); + return; + } + }; + let insn_len = riscv_insn_len(first_half); + let current_insn = if insn_len == 2 { + first_half as u32 + } else { + match ptrace_read_u32_unlocked(&aspace, pc) { + Ok(word) => word, + Err(_) => { + tracee.set_ptrace_ss_saved_insn(None); + return; + } + } + }; + let next_insn_addr = riscv_next_pc(current_insn, insn_len, pc, uctx); + if next_insn_addr == pc { + tracee.set_ptrace_ss_saved_insn(None); + return; + } + let orig_insn = match ptrace_read_u16_unlocked(&aspace, next_insn_addr) { + Ok(half) => half, + Err(_) => { + tracee.set_ptrace_ss_saved_insn(None); + return; + } + }; + if orig_insn == EBREAK_INSN { + tracee.set_ptrace_ss_saved_insn(None); + ax_runtime::hal::cpu::asm::flush_icache_all(); + return; + } + + let _ = ptrace_write_u16_unlocked(&mut aspace, next_insn_addr, EBREAK_INSN); + tracee.set_ptrace_ss_saved_insn(Some((next_insn_addr, orig_insn as usize))); + ax_runtime::hal::cpu::asm::flush_icache_all(); +} + +#[cfg(target_arch = "riscv64")] +fn riscv_insn_len(first_half: u16) -> usize { + if first_half & 0x3 != 0x3 { 2 } else { 4 } +} + +#[cfg(target_arch = "riscv64")] +fn riscv_next_pc( + insn: u32, + insn_len: usize, + pc: usize, + uctx: &ax_runtime::hal::cpu::uspace::UserContext, +) -> usize { + if insn_len == 2 { + return riscv_compressed_next_pc(insn as u16, pc, uctx); + } + + match insn & 0x7f { + 0x63 => { + let rs1 = ((insn >> 15) & 0x1f) as usize; + let rs2 = ((insn >> 20) & 0x1f) as usize; + let lhs = riscv_reg(uctx, rs1); + let rhs = riscv_reg(uctx, rs2); + let take = match (insn >> 12) & 0x7 { + 0x0 => lhs == rhs, + 0x1 => lhs != rhs, + 0x4 => (lhs as isize) < (rhs as isize), + 0x5 => (lhs as isize) >= (rhs as isize), + 0x6 => lhs < rhs, + 0x7 => lhs >= rhs, + _ => false, + }; + if take { + riscv_add_pc( + pc, + riscv_sign_extend( + (((insn >> 31) & 0x1) << 12) + | (((insn >> 7) & 0x1) << 11) + | (((insn >> 25) & 0x3f) << 5) + | (((insn >> 8) & 0xf) << 1), + 13, + ), + ) + } else { + pc + 4 + } + } + 0x67 => { + let rs1 = ((insn >> 15) & 0x1f) as usize; + let imm = riscv_sign_extend((insn >> 20) & 0xfff, 12); + riscv_add_pc(riscv_reg(uctx, rs1), imm) & !0x1 + } + 0x6f => riscv_add_pc( + pc, + riscv_sign_extend( + (((insn >> 31) & 0x1) << 20) + | (((insn >> 12) & 0xff) << 12) + | (((insn >> 20) & 0x1) << 11) + | (((insn >> 21) & 0x3ff) << 1), + 21, + ), + ), + _ => pc + 4, + } +} + +#[cfg(target_arch = "riscv64")] +fn riscv_compressed_next_pc( + insn: u16, + pc: usize, + uctx: &ax_runtime::hal::cpu::uspace::UserContext, +) -> usize { + let quadrant = insn & 0x3; + let funct3 = (insn >> 13) & 0x7; + + if quadrant == 0x1 { + match funct3 { + 0x1 | 0x5 => { + return riscv_add_pc(pc, riscv_sign_extend(riscv_cj_imm(insn) as u32, 12)); + } + 0x6 | 0x7 => { + let rs1 = 8 + (((insn >> 7) & 0x7) as usize); + let take = if funct3 == 0x6 { + riscv_reg(uctx, rs1) == 0 + } else { + riscv_reg(uctx, rs1) != 0 + }; + return if take { + riscv_add_pc(pc, riscv_sign_extend(riscv_cb_imm(insn) as u32, 9)) + } else { + pc + 2 + }; + } + _ => {} + } + } + + if quadrant == 0x2 && funct3 == 0x4 { + let rs1 = ((insn >> 7) & 0x1f) as usize; + let rs2 = ((insn >> 2) & 0x1f) as usize; + if rs1 != 0 && rs2 == 0 { + return riscv_reg(uctx, rs1); + } + } + + pc + 2 +} + +#[cfg(target_arch = "riscv64")] +fn riscv_cj_imm(insn: u16) -> u16 { + (((insn >> 12) & 0x1) << 11) + | (((insn >> 11) & 0x1) << 4) + | (((insn >> 9) & 0x3) << 8) + | (((insn >> 8) & 0x1) << 10) + | (((insn >> 7) & 0x1) << 6) + | (((insn >> 6) & 0x1) << 7) + | (((insn >> 3) & 0x7) << 1) + | (((insn >> 2) & 0x1) << 5) +} + +#[cfg(target_arch = "riscv64")] +fn riscv_cb_imm(insn: u16) -> u16 { + (((insn >> 12) & 0x1) << 8) + | (((insn >> 10) & 0x3) << 3) + | (((insn >> 5) & 0x3) << 6) + | (((insn >> 3) & 0x3) << 1) + | (((insn >> 2) & 0x1) << 5) +} + +#[cfg(target_arch = "riscv64")] +fn riscv_sign_extend(value: u32, bits: u32) -> isize { + let shift = usize::BITS - bits; + ((value as usize) << shift) as isize >> shift +} + +#[cfg(target_arch = "riscv64")] +fn riscv_add_pc(base: usize, offset: isize) -> usize { + if offset >= 0 { + base.wrapping_add(offset as usize) + } else { + base.wrapping_sub((-offset) as usize) + } +} + +#[cfg(target_arch = "riscv64")] +fn riscv_reg(uctx: &ax_runtime::hal::cpu::uspace::UserContext, index: usize) -> usize { + match index { + 0 => 0, + 1 => uctx.regs.ra, + 2 => uctx.regs.sp, + 3 => uctx.regs.gp, + 4 => uctx.regs.tp, + 5 => uctx.regs.t0, + 6 => uctx.regs.t1, + 7 => uctx.regs.t2, + 8 => uctx.regs.s0, + 9 => uctx.regs.s1, + 10 => uctx.regs.a0, + 11 => uctx.regs.a1, + 12 => uctx.regs.a2, + 13 => uctx.regs.a3, + 14 => uctx.regs.a4, + 15 => uctx.regs.a5, + 16 => uctx.regs.a6, + 17 => uctx.regs.a7, + 18 => uctx.regs.s2, + 19 => uctx.regs.s3, + 20 => uctx.regs.s4, + 21 => uctx.regs.s5, + 22 => uctx.regs.s6, + 23 => uctx.regs.s7, + 24 => uctx.regs.s8, + 25 => uctx.regs.s9, + 26 => uctx.regs.s10, + 27 => uctx.regs.s11, + 28 => uctx.regs.t3, + 29 => uctx.regs.t4, + 30 => uctx.regs.t5, + 31 => uctx.regs.t6, + _ => 0, + } +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_u16_unlocked(aspace: &AddrSpace, addr: usize) -> AxResult { + let mut bytes = [0u8; size_of::()]; + aspace.read(VirtAddr::from_usize(addr), &mut bytes)?; + Ok(u16::from_ne_bytes(bytes)) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_read_u32_unlocked(aspace: &AddrSpace, addr: usize) -> AxResult { + let mut bytes = [0u8; size_of::()]; + aspace.read(VirtAddr::from_usize(addr), &mut bytes)?; + Ok(u32::from_ne_bytes(bytes)) +} + +#[cfg(target_arch = "riscv64")] +fn ptrace_write_u16_unlocked(aspace: &mut AddrSpace, addr: usize, data: u16) -> AxResult { + aspace.write(VirtAddr::from_usize(addr), &data.to_ne_bytes())?; + Ok(()) +} + +pub fn ptrace_notify_clone(parent_pid: Pid, child_pid: Pid, event: u32) -> bool { + let Ok(parent) = get_process_data(parent_pid) else { + return false; + }; + if !parent.is_ptrace_traceme() && !parent.is_ptrace_attached() { + return false; + } + let options = parent.ptrace_options(); + let option_flag = match event { + PTRACE_EVENT_FORK => PTRACE_O_TRACEFORK, + PTRACE_EVENT_VFORK => PTRACE_O_TRACEVFORK, + PTRACE_EVENT_CLONE => PTRACE_O_TRACECLONE, + _ => return false, + }; + if options & option_flag == 0 { + return false; + } + parent.set_ptrace_event_msg(child_pid as usize); + parent.set_ptrace_event(event); + true +} + +pub fn ptrace_notify_exec(tracee_pid: Pid) -> bool { + let Ok(tracee) = get_process_data(tracee_pid) else { + return false; + }; + let options = tracee.ptrace_options(); + if options & PTRACE_O_TRACEEXEC == 0 { + return false; + } + tracee.set_ptrace_event_msg(tracee_pid as usize); + tracee.set_ptrace_event(PTRACE_EVENT_EXEC); + true +} + +pub fn ptrace_notify_exit(tracee_pid: Pid, exit_code: i32) -> bool { + let Ok(tracee) = get_process_data(tracee_pid) else { + return false; + }; + if !tracee.is_ptrace_traceme() && !tracee.is_ptrace_attached() { + return false; + } + let options = tracee.ptrace_options(); + if options & PTRACE_O_TRACEEXIT == 0 { + return false; + } + tracee.set_ptrace_event_msg(exit_code as usize); + tracee.set_ptrace_event(PTRACE_EVENT_EXIT); + true +} + +pub fn ptrace_notify_vfork_done(parent_pid: Pid, child_pid: Pid) -> bool { + let Ok(parent) = get_process_data(parent_pid) else { + return false; + }; + if !parent.is_ptrace_traceme() && !parent.is_ptrace_attached() { + return false; + } + let options = parent.ptrace_options(); + if options & PTRACE_O_TRACEVFORKDONE == 0 { + return false; + } + parent.set_ptrace_event_msg(child_pid as usize); + parent.set_ptrace_event(PTRACE_EVENT_VFORK_DONE); + true +} + +#[cfg(target_arch = "riscv64")] +impl From<&ax_runtime::hal::cpu::uspace::UserContext> for RiscvUserRegs { + fn from(uctx: &ax_runtime::hal::cpu::uspace::UserContext) -> Self { + let r = &uctx.regs; + Self { + pc: uctx.sepc, + ra: r.ra, + sp: r.sp, + gp: r.gp, + tp: r.tp, + t0: r.t0, + t1: r.t1, + t2: r.t2, + s0: r.s0, + s1: r.s1, + a0: r.a0, + a1: r.a1, + a2: r.a2, + a3: r.a3, + a4: r.a4, + a5: r.a5, + a6: r.a6, + a7: r.a7, + s2: r.s2, + s3: r.s3, + s4: r.s4, + s5: r.s5, + s6: r.s6, + s7: r.s7, + s8: r.s8, + s9: r.s9, + s10: r.s10, + s11: r.s11, + t3: r.t3, + t4: r.t4, + t5: r.t5, + t6: r.t6, + } + } +} + +#[cfg(target_arch = "riscv64")] +impl RiscvUserRegs { + fn write_to(&self, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext) { + uctx.sepc = self.pc; + let r = &mut uctx.regs; + r.ra = self.ra; + r.sp = self.sp; + r.gp = self.gp; + r.tp = self.tp; + r.t0 = self.t0; + r.t1 = self.t1; + r.t2 = self.t2; + r.s0 = self.s0; + r.s1 = self.s1; + r.a0 = self.a0; + r.a1 = self.a1; + r.a2 = self.a2; + r.a3 = self.a3; + r.a4 = self.a4; + r.a5 = self.a5; + r.a6 = self.a6; + r.a7 = self.a7; + r.s2 = self.s2; + r.s3 = self.s3; + r.s4 = self.s4; + r.s5 = self.s5; + r.s6 = self.s6; + r.s7 = self.s7; + r.s8 = self.s8; + r.s9 = self.s9; + r.s10 = self.s10; + r.s11 = self.s11; + r.t3 = self.t3; + r.t4 = self.t4; + r.t5 = self.t5; + r.t6 = self.t6; + } +} diff --git a/os/StarryOS/kernel/src/syscall/task/wait.rs b/os/StarryOS/kernel/src/syscall/task/wait.rs index 003cf1751c..2c9edf8cfd 100644 --- a/os/StarryOS/kernel/src/syscall/task/wait.rs +++ b/os/StarryOS/kernel/src/syscall/task/wait.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{sync::Arc, vec::Vec}; use core::{future::poll_fn, task::Poll}; use ax_errno::{AxError, AxResult, LinuxError}; @@ -11,14 +11,16 @@ use linux_raw_sys::general::{ __WALL, __WCLONE, __WNOTHREAD, P_ALL, P_PID, WCONTINUED, WEXITED, WNOHANG, WNOWAIT, WUNTRACED, }; use starry_process::{Pid, Process}; -use starry_signal::SignalInfo; +use starry_signal::{SignalInfo, Signo}; use starry_vm::{VmMutPtr, VmPtr}; use crate::task::{ - AsThread, JobStatus, decode_wait_status, get_process_data, get_task, get_zombie_cred, - remove_process, unregister_zombie, + AsThread, JobStatus, ProcessData, decode_wait_status, get_process_data, get_task, + get_zombie_cred, processes, remove_process, traced_zombies_for, unregister_zombie, }; +const PTRACE_O_TRACESYSGOOD: usize = 1; + bitflags! { /// Options accepted by wait4 / waitpid. #[derive(Debug)] @@ -67,6 +69,74 @@ impl WaitTarget { } } +fn stopped_wait_signo(data: &ProcessData, signo: Signo) -> i32 { + let event = data.ptrace_event().unwrap_or(0); + let mut wait_signo = if event != 0 { + Signo::SIGTRAP as i32 + } else { + signo as i32 + }; + if event == 0 + && signo == Signo::SIGTRAP + && data.is_ptrace_syscall_stop() + && data.ptrace_options() & PTRACE_O_TRACESYSGOOD != 0 + { + wait_signo |= 0x80; + } + wait_signo +} + +fn stopped_wait_status(data: &ProcessData, signo: Signo) -> i32 { + let event = data.ptrace_event().unwrap_or(0) as i32; + let wait_signo = stopped_wait_signo(data, signo); + (event << 16) | (wait_signo << 8) | 0x7f +} + +fn child_uid(child: &Process) -> u32 { + get_zombie_cred(child.pid()) + .map(|cred| cred.uid) + .or_else(|| { + child + .threads() + .into_iter() + .find_map(|tid| get_task(tid).ok().map(|task| task.as_thread().cred().uid)) + }) + .unwrap_or(0) +} + +fn waitable_processes(proc: &Process, target: WaitTarget, tracer_pid: Pid) -> Vec> { + let mut candidates = proc + .children() + .into_iter() + .filter(|child| target.matches(child)) + .collect::>(); + + for data in processes() { + let traced = data.ptrace_tracer_pid() == Some(tracer_pid); + let proc = data.proc.clone(); + if traced + && target.matches(&proc) + && !candidates + .iter() + .any(|candidate| candidate.pid() == proc.pid()) + { + candidates.push(proc); + } + } + + for zombie in traced_zombies_for(tracer_pid) { + if target.matches(&zombie) + && !candidates + .iter() + .any(|candidate| candidate.pid() == zombie.pid()) + { + candidates.push(zombie); + } + } + + candidates +} + pub fn sys_waitpid(pid: i32, exit_code: *mut i32, options: u32) -> AxResult { let options = WaitPidOptions::from_bits(options).ok_or(AxError::InvalidInput)?; info!("sys_waitpid <= pid: {pid:?}, options: {options:?}"); @@ -86,18 +156,25 @@ pub fn sys_waitpid(pid: i32, exit_code: *mut i32, options: u32) -> AxResult>(); + let children = waitable_processes(proc, target, proc.pid()); if children.is_empty() { return Err(AxError::from(LinuxError::ECHILD)); } let proc_data = curr.as_thread().proc_data.clone(); let check_children = || { - if let Some(child) = children.iter().find(|child| child.is_zombie()) { + if let Some((child, data, signo)) = children.iter().find_map(|child| { + get_process_data(child.pid()) + .ok() + .filter(|data| !data.ptrace_stop_reported()) + .and_then(|data| data.ptrace_stop_signo().map(|signo| (child, data, signo))) + }) { + if let Some(exit_code) = exit_code.nullable() { + exit_code.vm_write(stopped_wait_status(&data, signo))?; + } + data.mark_ptrace_stop_reported(); + return Ok(Some(child.pid() as _)); + } else if let Some(child) = children.iter().find(|child| child.is_zombie()) { // Accumulate child's CPU time before freeing. for tid in child.threads() { if let Ok(task) = get_task(tid) { @@ -195,12 +272,10 @@ pub fn sys_waitid( _ => return Err(AxError::InvalidInput), }; - // Validate options — WEXITED must be present (we don't support WSTOPPED/WCONTINUED yet). let options = WaitIdOptions::from_bits(options).ok_or(AxError::InvalidInput)?; - if !options.contains(WaitIdOptions::WEXITED) { - return Err(AxError::InvalidInput); - } - if options.contains(WaitIdOptions::WUNTRACED) || options.contains(WaitIdOptions::WCONTINUED) { + if !options + .intersects(WaitIdOptions::WEXITED | WaitIdOptions::WUNTRACED | WaitIdOptions::WCONTINUED) + { return Err(AxError::InvalidInput); } @@ -209,21 +284,48 @@ pub fn sys_waitid( let curr = current(); let proc = &curr.as_thread().proc_data.proc; - let children: Vec<_> = proc - .children() - .into_iter() - .filter(|child| target.matches(child)) - .collect(); + let children = waitable_processes(proc, target, proc.pid()); if children.is_empty() { return Err(AxError::from(LinuxError::ECHILD)); } let proc_data = curr.as_thread().proc_data.clone(); let check_children = || { - if let Some(child) = children.iter().find(|child| child.is_zombie()) { + if options.contains(WaitIdOptions::WUNTRACED) + && let Some((child, data, signo)) = children.iter().find_map(|child| { + get_process_data(child.pid()) + .ok() + .filter(|data| !data.ptrace_stop_reported()) + .and_then(|data| data.ptrace_stop_signo().map(|signo| (child, data, signo))) + }) + { + let child_pid = child.pid(); + let child_uid = child_uid(child); + + if let Some(infop) = infop.nullable() { + let siginfo = SignalInfo::new_sigchld( + child_pid, + child_uid, + linux_raw_sys::general::CLD_TRAPPED as i32, + stopped_wait_signo(&data, signo), + ); + infop.vm_write(siginfo.0)?; + } + if !options.contains(WaitIdOptions::WNOWAIT) + && let Ok(data) = get_process_data(child_pid) + { + data.mark_ptrace_stop_reported(); + } + + return Ok(Some(0)); + } + + if options.contains(WaitIdOptions::WEXITED) + && let Some(child) = children.iter().find(|child| child.is_zombie()) + { let child_pid = child.pid(); let (code, status) = decode_wait_status(child.exit_code()); - let child_uid = get_zombie_cred(child_pid).map(|c| c.uid).unwrap_or(0); + let child_uid = child_uid(child); if let Some(infop) = infop.nullable() { let siginfo = SignalInfo::new_sigchld(child_pid, child_uid, code, status); @@ -231,7 +333,6 @@ pub fn sys_waitid( } if !options.contains(WaitIdOptions::WNOWAIT) { - // Accumulate child's CPU time before freeing. for tid in child.threads() { if let Ok(task) = get_task(tid) { let thr = task.as_thread(); @@ -243,11 +344,10 @@ pub fn sys_waitid( remove_process(child_pid); unregister_zombie(child_pid); } - // waitid(2): on success returns 0 (not the child PID). - Ok(Some(0)) - } else if options.contains(WaitIdOptions::WNOHANG) { - // WNOHANG with no ready child: return 0. Zero out siginfo if - // infop is non-NULL so callers don't read stale data. + return Ok(Some(0)); + } + + if options.contains(WaitIdOptions::WNOHANG) { if let Some(infop) = infop.nullable() { let zeroed: linux_raw_sys::general::siginfo = unsafe { core::mem::zeroed() }; infop.vm_write(zeroed)?; diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index bb0aa15e9a..e925d04e65 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -17,16 +17,17 @@ use core::{ sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicUsize, Ordering}, }; -use ax_runtime::hal::time::TimeValue; +use ax_runtime::hal::{cpu::uspace::UserContext, time::TimeValue}; use ax_sync::{Mutex, spin::SpinNoIrq}; use ax_task::{TaskExt, TaskInner}; use axpoll::PollSet; use extern_trait::extern_trait; +use kernel_elf_parser::AuxEntry; use scope_local::{ActiveScope, Scope}; use spin::RwLock; use starry_process::Process; use starry_signal::{ - Signo, + SignalInfo, Signo, api::{ProcessSignalManager, SignalActions, ThreadSignalManager}, }; @@ -34,6 +35,14 @@ pub use self::{ cred::*, futex::*, ops::*, posix_timer::PosixTimerTable, resources::*, signal::*, stat::*, timer::*, user::*, }; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum SyscallTraceState { + #[default] + None, + Entry, + Exit, +} use crate::mm::AddrSpace; /// Size of the syscall instruction for the current architecture. @@ -474,6 +483,22 @@ struct JobControl { } /// [`Process`]-shared data. +pub struct ProcessImage { + pub exe_path: String, + pub cmdline: Arc>, + pub auxv: Vec, +} + +impl ProcessImage { + pub fn new(exe_path: String, cmdline: Arc>, auxv: Vec) -> Self { + Self { + exe_path, + cmdline, + auxv, + } + } +} + pub struct ProcessData { /// The process. pub proc: Arc, @@ -481,6 +506,8 @@ pub struct ProcessData { pub exe_path: RwLock, /// The command line arguments pub cmdline: RwLock>>, + /// Auxiliary vector entries exported via `/proc/[pid]/auxv`. + pub auxv: RwLock>, /// The virtual memory address space. // TODO: scopify aspace: SpinNoIrq>>, @@ -538,6 +565,73 @@ pub struct ProcessData { /// Updated when wait() reaps a child. children_cpu_time: SpinNoIrq<(TimeValue, TimeValue)>, + /// Pid of the process currently tracing this process, if any. + ptrace_tracer_pid: AtomicU32, + + /// Set by `ptrace(PTRACE_TRACEME)` to let the parent observe debugger-style + /// stops from this process. + ptrace_traceme: AtomicBool, + + /// Non-zero signal number while this traced process is stopped. + ptrace_stop_signo: AtomicU32, + + /// User register snapshot captured when entering the current ptrace stop. + ptrace_stop_uctx: SpinNoIrq>, + + /// siginfo for the current ptrace stop, used by `PTRACE_GETSIGINFO`. + ptrace_stop_siginfo: SpinNoIrq>, + + /// True when the current SIGTRAP stop is a PTRACE_SYSCALL entry/exit stop. + ptrace_stop_is_syscall: AtomicBool, + + /// True after wait* has already reported the current ptrace stop. + ptrace_stop_reported: AtomicBool, + + /// Wakes a traced task that is sleeping in a ptrace stop. + ptrace_stop_event: Arc, + + /// Signal number to deliver on resume, set by `PTRACE_CONT(sig)`. + /// 0 means suppress the signal; non-zero means deliver that signal. + ptrace_resume_signo: AtomicU32, + + /// One-shot signal number that came from ptrace resume injection. + /// The signal subsystem still handles disposition and handlers, but the + /// next matching signal delivery must not stop for ptrace again. + ptrace_resume_signal_bypass: AtomicU32, + + /// Set by `execve` when the calling thread was `PTRACE_TRACEME`. + /// Cleared after the exec-stop is delivered in the user-return loop. + ptrace_exec_stop_pending: AtomicBool, + + /// Set by `PTRACE_ATTACH` / `PTRACE_SEIZE`. + ptrace_attached: AtomicBool, + + /// Set by `PTRACE_SINGLESTEP`; causes a temporary EBREAK insertion. + ptrace_singlestep: AtomicBool, + + /// Set by `PTRACE_SYSCALL`; causes syscall-entry/exit stops. + ptrace_syscall_trace: SpinNoIrq, + + /// Bitmask of PTRACE_O_* options set via `PTRACE_SETOPTIONS`. + ptrace_options: AtomicUsize, + + /// Event message stored by `PTRACE_EVENT_*` (e.g. new child PID). + ptrace_event_msg: AtomicUsize, + + /// Pending ptrace event code (PTRACE_EVENT_FORK etc.), or 0 for none. + ptrace_event: AtomicU32, + + /// Saved instruction overwritten by single-step EBREAK, if any. + ptrace_ss_saved_insn: SpinNoIrq>, + + /// FP register snapshot captured when entering ptrace stop. + /// Stored as raw bytes to avoid arch-specific crate dependency. + ptrace_stop_fp_data: SpinNoIrq>, + + /// Linux process personality flags. Starry does not randomize userspace + /// mappings yet, but debuggers still probe and set ADDR_NO_RANDOMIZE. + personality: AtomicUsize, + /// POSIX per-process interval timers (timer_create/timer_settime/etc.) pub posix_timers: Arc, @@ -566,8 +660,7 @@ impl ProcessData { /// Create a new [`ProcessData`]. pub fn new( proc: Arc, - exe_path: String, - cmdline: Arc>, + image: ProcessImage, aspace: Arc>, signal_actions: Arc>, exit_signal: Option, @@ -575,8 +668,9 @@ impl ProcessData { ) -> Arc { let this = Arc::new(Self { proc, - exe_path: RwLock::new(exe_path), - cmdline: RwLock::new(cmdline), + exe_path: RwLock::new(image.exe_path), + cmdline: RwLock::new(image.cmdline), + auxv: RwLock::new(image.auxv), aspace: SpinNoIrq::new(aspace), scope: RwLock::new(Scope::new()), heap_top: AtomicUsize::new(crate::config::USER_HEAP_BASE), @@ -605,6 +699,28 @@ impl ProcessData { children_cpu_time: SpinNoIrq::new((TimeValue::ZERO, TimeValue::ZERO)), + ptrace_tracer_pid: AtomicU32::new(0), + ptrace_traceme: AtomicBool::new(false), + ptrace_stop_signo: AtomicU32::new(0), + ptrace_stop_uctx: SpinNoIrq::new(None), + ptrace_stop_siginfo: SpinNoIrq::new(None), + ptrace_stop_is_syscall: AtomicBool::new(false), + ptrace_stop_reported: AtomicBool::new(false), + ptrace_stop_event: Arc::default(), + ptrace_resume_signo: AtomicU32::new(0), + ptrace_resume_signal_bypass: AtomicU32::new(0), + ptrace_exec_stop_pending: AtomicBool::new(false), + ptrace_attached: AtomicBool::new(false), + ptrace_singlestep: AtomicBool::new(false), + ptrace_syscall_trace: SpinNoIrq::new(SyscallTraceState::None), + ptrace_options: AtomicUsize::new(0), + ptrace_event_msg: AtomicUsize::new(0), + ptrace_event: AtomicU32::new(0), + ptrace_ss_saved_insn: SpinNoIrq::new(None), + ptrace_stop_fp_data: SpinNoIrq::new(None), + + personality: AtomicUsize::new(0), + posix_timers: Arc::new(PosixTimerTable::default()), vm_aspace_shared: AtomicBool::new(vm_aspace_shared), @@ -824,6 +940,297 @@ impl ProcessData { time.1 += stime; } + /// Mark this process as traceable by its parent. + pub fn set_ptrace_traceme(&self) { + if let Some(parent) = self.proc.parent() { + self.set_ptrace_tracer_pid(parent.pid()); + } + self.ptrace_traceme.store(true, Ordering::Release); + } + + pub fn clear_ptrace_traceme(&self) { + self.ptrace_traceme.store(false, Ordering::Release); + } + + pub fn is_ptrace_traceme(&self) -> bool { + self.ptrace_traceme.load(Ordering::Acquire) + } + + pub fn set_ptrace_tracer_pid(&self, pid: starry_process::Pid) { + self.ptrace_tracer_pid.store(pid, Ordering::Release); + } + + pub fn clear_ptrace_tracer_pid(&self) { + self.ptrace_tracer_pid.store(0, Ordering::Release); + } + + pub fn ptrace_tracer_pid(&self) -> Option { + let pid = self.ptrace_tracer_pid.load(Ordering::Acquire); + if pid == 0 { None } else { Some(pid) } + } + + /// Record that this tracee is stopped by `signo`. + pub fn set_ptrace_stop(&self, signo: Signo, uctx: &UserContext) { + *self.ptrace_stop_uctx.lock() = Some(*uctx); + *self.ptrace_stop_siginfo.lock() = Some(SignalInfo::new_kernel(signo)); + self.ptrace_stop_is_syscall.store(false, Ordering::Release); + self.ptrace_stop_reported.store(false, Ordering::Release); + self.ptrace_stop_signo + .store(signo as u32, Ordering::Release); + } + + /// Record that this tracee is stopped at a syscall entry or exit boundary. + pub fn set_ptrace_syscall_stop(&self, signo: Signo, uctx: &UserContext) { + self.set_ptrace_stop(signo, uctx); + self.ptrace_stop_is_syscall.store(true, Ordering::Release); + } + + pub fn is_ptrace_syscall_stop(&self) -> bool { + self.ptrace_stop_is_syscall.load(Ordering::Acquire) + } + + /// Return the siginfo for the current ptrace stop. + pub fn ptrace_stop_siginfo(&self) -> Option { + self.ptrace_stop_siginfo.lock().clone() + } + + /// Replace the siginfo held for the current ptrace stop. + pub fn set_ptrace_stop_siginfo(&self, signo: Signo, siginfo: SignalInfo) -> bool { + let mut saved = self.ptrace_stop_siginfo.lock(); + if saved.is_none() { + return false; + } + *saved = Some(siginfo); + self.ptrace_stop_signo + .store(signo as u32, Ordering::Release); + true + } + + /// Return the current ptrace stop signal, if any. + pub fn ptrace_stop_signo(&self) -> Option { + let signo = self.ptrace_stop_signo.load(Ordering::Acquire); + if signo == 0 { + None + } else { + Signo::from_repr(signo as u8) + } + } + + /// Return the saved user context for the current ptrace stop. + pub fn ptrace_stop_user_context(&self) -> Option { + *self.ptrace_stop_uctx.lock() + } + + pub fn ptrace_stop_reported(&self) -> bool { + self.ptrace_stop_reported.load(Ordering::Acquire) + } + + pub fn mark_ptrace_stop_reported(&self) { + self.ptrace_stop_reported.store(true, Ordering::Release); + } + + /// Replace registers held for a stopped tracee. + pub fn set_ptrace_stop_user_context(&self, uctx: UserContext) -> bool { + let mut saved = self.ptrace_stop_uctx.lock(); + if saved.is_none() { + return false; + } + *saved = Some(uctx); + true + } + + /// Resume the stopped task, optionally injecting a signal. + pub fn resume_ptrace_stop_with_signal(&self, signo: u32) { + self.ptrace_resume_signo.store(signo, Ordering::Release); + *self.ptrace_stop_siginfo.lock() = None; + self.ptrace_stop_is_syscall.store(false, Ordering::Release); + self.ptrace_stop_reported.store(false, Ordering::Release); + self.ptrace_event.store(0, Ordering::Release); + self.ptrace_stop_signo.store(0, Ordering::Release); + self.ptrace_stop_event.wake(); + } + + /// Resume the stopped task without injecting a signal. + pub fn resume_ptrace_stop(&self) { + self.resume_ptrace_stop_with_signal(0); + } + + /// Consume the signal chosen by the tracer on resume. + pub fn take_ptrace_resume_signo(&self) -> Option { + let signo = self.ptrace_resume_signo.swap(0, Ordering::AcqRel); + Signo::from_repr(signo as u8) + } + + pub fn set_ptrace_resume_signal_bypass(&self, signo: Signo) { + self.ptrace_resume_signal_bypass + .store(signo as u32, Ordering::Release); + } + + pub fn take_ptrace_resume_signal_bypass(&self, signo: Signo) -> bool { + self.ptrace_resume_signal_bypass + .compare_exchange(signo as u32, 0, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + } + + /// Take registers once the stopped task resumes. + pub fn take_ptrace_stop_user_context(&self) -> Option { + self.ptrace_stop_uctx.lock().take() + } + + /// Cancel the current ptrace stop and discard its saved registers. + pub fn clear_ptrace_stop(&self) { + *self.ptrace_stop_uctx.lock() = None; + *self.ptrace_stop_siginfo.lock() = None; + self.ptrace_stop_is_syscall.store(false, Ordering::Release); + self.ptrace_stop_reported.store(false, Ordering::Release); + self.ptrace_event.store(0, Ordering::Release); + self.ptrace_stop_signo.store(0, Ordering::Release); + self.ptrace_stop_event.wake(); + } + + pub fn set_ptrace_exec_stop_pending(&self) { + self.ptrace_exec_stop_pending + .store(true, core::sync::atomic::Ordering::Release); + } + + pub fn take_ptrace_exec_stop_pending(&self) -> bool { + self.ptrace_exec_stop_pending + .swap(false, core::sync::atomic::Ordering::AcqRel) + } + + /// Register a waiter for changes to this process's ptrace stop state. + pub fn register_ptrace_stop_waker(&self, waker: &core::task::Waker) { + self.ptrace_stop_event.register(waker); + } + + pub fn set_ptrace_attached(&self) { + self.ptrace_attached.store(true, Ordering::Release); + } + + pub fn clear_ptrace_attached(&self) { + self.ptrace_attached.store(false, Ordering::Release); + } + + pub fn is_ptrace_attached(&self) -> bool { + self.ptrace_attached.load(Ordering::Acquire) + } + + pub fn set_ptrace_singlestep(&self, val: bool) { + self.ptrace_singlestep.store(val, Ordering::Release); + } + + pub fn is_ptrace_singlestep(&self) -> bool { + self.ptrace_singlestep.load(Ordering::Acquire) + } + + pub fn set_ptrace_syscall_trace(&self, trace: bool) { + *self.ptrace_syscall_trace.lock() = if trace { + SyscallTraceState::Entry + } else { + SyscallTraceState::None + }; + } + + pub fn set_ptrace_syscall_trace_state(&self, state: SyscallTraceState) { + *self.ptrace_syscall_trace.lock() = state; + } + + pub fn take_ptrace_syscall_trace(&self) -> SyscallTraceState { + core::mem::take(&mut *self.ptrace_syscall_trace.lock()) + } + + pub fn set_ptrace_options(&self, opts: usize) { + self.ptrace_options.store(opts, Ordering::Release); + } + + pub fn ptrace_options(&self) -> usize { + self.ptrace_options.load(Ordering::Acquire) + } + + pub fn set_ptrace_event_msg(&self, msg: usize) { + self.ptrace_event_msg.store(msg, Ordering::Release); + } + + pub fn ptrace_event_msg(&self) -> usize { + self.ptrace_event_msg.load(Ordering::Acquire) + } + + pub fn set_ptrace_event(&self, event: u32) { + self.ptrace_event.store(event, Ordering::Release); + } + + pub fn ptrace_event(&self) -> Option { + let event = self.ptrace_event.load(Ordering::Acquire); + if event == 0 { None } else { Some(event) } + } + + pub fn take_ptrace_event(&self) -> Option { + let event = self.ptrace_event.swap(0, Ordering::AcqRel); + if event == 0 { None } else { Some(event) } + } + + pub fn set_ptrace_ss_saved_insn(&self, saved: Option<(usize, usize)>) { + *self.ptrace_ss_saved_insn.lock() = saved; + } + + pub fn take_ptrace_ss_saved_insn(&self) -> Option<(usize, usize)> { + self.ptrace_ss_saved_insn.lock().take() + } + + #[cfg(target_arch = "riscv64")] + pub fn save_current_fp_for_ptrace(&self) { + let mut fp = ax_cpu::FpState::default(); + fp.save(); + fp.fs = riscv::register::sstatus::read().fs(); + *self.ptrace_stop_fp_data.lock() = Some((fp.fp, fp.fcsr)); + } + + #[cfg(not(target_arch = "riscv64"))] + pub fn save_current_fp_for_ptrace(&self) {} + + #[cfg(target_arch = "riscv64")] + pub fn restore_current_fp_for_ptrace(&self, uctx: &mut UserContext) { + let Some((fp, fcsr)) = self.ptrace_stop_fp_data() else { + return; + }; + + let fp_state = ax_cpu::FpState { + fp, + fcsr, + fs: riscv::register::sstatus::FS::Dirty, + }; + + unsafe { + riscv::register::sstatus::set_fs(riscv::register::sstatus::FS::Dirty); + } + fp_state.restore(); + uctx.sstatus.set_fs(riscv::register::sstatus::FS::Dirty); + } + + #[cfg(not(target_arch = "riscv64"))] + pub fn restore_current_fp_for_ptrace(&self, _uctx: &mut UserContext) {} + + pub fn ptrace_stop_fp_data(&self) -> Option<([u64; 32], usize)> { + *self.ptrace_stop_fp_data.lock() + } + + pub fn set_ptrace_stop_fp_data(&self, data: ([u64; 32], usize)) -> bool { + let mut guard = self.ptrace_stop_fp_data.lock(); + if guard.is_none() { + return false; + } + *guard = Some(data); + true + } + + pub fn personality(&self) -> usize { + self.personality.load(Ordering::Acquire) + } + + pub fn replace_personality(&self, personality: usize) -> usize { + self.personality.swap(personality, Ordering::AcqRel) + } + /// Returns a clone of the address space Arc. pub fn aspace(&self) -> Arc> { self.aspace.lock().clone() diff --git a/os/StarryOS/kernel/src/task/ops.rs b/os/StarryOS/kernel/src/task/ops.rs index f8ef2ac024..9d57885e22 100644 --- a/os/StarryOS/kernel/src/task/ops.rs +++ b/os/StarryOS/kernel/src/task/ops.rs @@ -57,6 +57,7 @@ static PROCESS_TABLE: RwLock>> = RwLock::new(Weak struct ZombieEntry { proc: Arc, cred: Arc, + ptrace_tracer_pid: Option, } /// Zombie processes: exited but not yet reaped by waitpid(). @@ -155,8 +156,20 @@ pub fn remove_process(pid: Pid) { /// `Arc` (for `getsid`/`getpgid`/etc.) and a snapshot of the /// exiting thread's final credentials (for `check_kill_permission` after /// the task has been GC'd). -pub fn register_zombie(pid: Pid, proc: Arc, cred: Arc) { - ZOMBIE_TABLE.write().insert(pid, ZombieEntry { proc, cred }); +pub fn register_zombie( + pid: Pid, + proc: Arc, + cred: Arc, + ptrace_tracer_pid: Option, +) { + ZOMBIE_TABLE.write().insert( + pid, + ZombieEntry { + proc, + cred, + ptrace_tracer_pid, + }, + ); } /// Removes a PID from the zombie table. @@ -188,6 +201,15 @@ pub fn get_zombie_cred(pid: Pid) -> Option> { ZOMBIE_TABLE.read().get(&pid).map(|e| e.cred.clone()) } +pub fn traced_zombies_for(tracer_pid: Pid) -> Vec> { + ZOMBIE_TABLE + .read() + .values() + .filter(|entry| entry.ptrace_tracer_pid == Some(tracer_pid)) + .map(|entry| entry.proc.clone()) + .collect() +} + /// Finds the process with the given PID. /// /// A zombie process may no longer have live [`ProcessData`] after its last @@ -502,7 +524,13 @@ pub fn do_exit(exit_code: i32, group_exit: bool) { // check_kill_permission can still authorise signals to this zombie // after the task has been GC'd (mirrors Linux task_struct lifetime). let zombie_cred = thr.cred(); - register_zombie(process.pid(), process.clone(), zombie_cred); + let ptrace_tracer_pid = thr.proc_data.ptrace_tracer_pid(); + register_zombie( + process.pid(), + process.clone(), + zombie_cred, + ptrace_tracer_pid, + ); process.exit(); if let Some(parent) = process.parent() { if let Some(signo) = thr.proc_data.exit_signal { @@ -522,6 +550,14 @@ pub fn do_exit(exit_code: i32, group_exit: bool) { data.child_exit_event.wake(); } } + if let Some(tracer_pid) = ptrace_tracer_pid + && process + .parent() + .is_none_or(|parent| parent.pid() != tracer_pid) + && let Ok(data) = get_process_data(tracer_pid) + { + data.child_exit_event.wake(); + } // Send pdeathsig to child processes for child in children_snapshot { let child_pid = child.pid(); diff --git a/os/StarryOS/kernel/src/task/signal.rs b/os/StarryOS/kernel/src/task/signal.rs index 1360c1f4ff..759199cdff 100644 --- a/os/StarryOS/kernel/src/task/signal.rs +++ b/os/StarryOS/kernel/src/task/signal.rs @@ -1,11 +1,18 @@ +#[cfg(target_arch = "riscv64")] +use core::mem::{MaybeUninit, align_of, size_of}; use core::{future::poll_fn, task::Poll}; use ax_errno::{AxError, AxResult}; use ax_runtime::hal::cpu::uspace::UserContext; -use ax_task::{TaskInner, current, future::block_on}; -use linux_raw_sys::general::{CLD_CONTINUED, CLD_STOPPED}; +use ax_task::{ + TaskInner, current, + future::{block_on, interruptible}, +}; +use linux_raw_sys::general::{CLD_CONTINUED, CLD_STOPPED, CLD_TRAPPED}; use starry_process::Pid; use starry_signal::{SignalInfo, SignalOSAction, SignalSet, Signo}; +#[cfg(target_arch = "riscv64")] +use starry_vm::vm_read_slice; use super::{ AsThread, ProcessData, SYSCALL_INSN_LEN, Thread, do_exit, get_process_data, get_process_group, @@ -22,6 +29,69 @@ pub struct SyscallRestartInfo { pub saved_sysno: usize, } +#[cfg(target_arch = "riscv64")] +#[derive(Clone, Copy)] +struct UserStackFrame { + fp: usize, + ra: usize, +} + +#[cfg(target_arch = "riscv64")] +fn read_user_stack_frame(fp: usize) -> Option { + let frame_addr = fp.checked_sub(size_of::())?; + if frame_addr == 0 || !frame_addr.is_multiple_of(align_of::()) { + return None; + } + + let mut words = [MaybeUninit::::uninit(); 2]; + vm_read_slice(frame_addr as *const usize, &mut words).ok()?; + + Some(UserStackFrame { + fp: unsafe { words[0].assume_init() }, + ra: unsafe { words[1].assume_init() }, + }) +} + +#[cfg(target_arch = "riscv64")] +fn dump_user_backtrace(uctx: &UserContext) { + const MAX_USER_FRAMES: usize = 32; + + let mut fp = uctx.regs.s0; + let sp = uctx.regs.sp; + warn!( + "user backtrace:\n #00 pc={:#018x} ra={:#018x} sp={:#018x} fp={:#018x}", + uctx.sepc, uctx.regs.ra, sp, fp + ); + + for depth in 1..MAX_USER_FRAMES { + let Some(frame) = read_user_stack_frame(fp) else { + warn!(" ", fp); + break; + }; + + if frame.fp == 0 || frame.ra == 0 { + break; + } + if frame.fp <= fp { + warn!( + " ", + frame.fp, fp + ); + break; + } + + let frame_sp = frame.fp - size_of::(); + warn!( + " #{:02} pc={:#018x} sp={:#018x} fp={:#018x}", + depth, frame.ra, frame_sp, frame.fp + ); + fp = frame.fp; + } +} + +#[cfg(not(target_arch = "riscv64"))] +fn dump_user_backtrace(_uctx: &UserContext) {} + /// Dump user-mode register state once the signal disposition really terminates. fn dump_user_crash_context(uctx: &UserContext) { #[cfg(target_arch = "riscv64")] @@ -29,9 +99,40 @@ fn dump_user_crash_context(uctx: &UserContext) { let r = &uctx.regs; warn!( "user register dump:\n pc(sepc)={:#018x} ra={:#018x} sp={:#018x}\n gp={:#018x} \ - tp={:#018x} s0={:#018x}\n a0={:#018x} a1={:#018x} a2={:#018x} a3={:#018x}\n \ - a4={:#018x} a5={:#018x} a6={:#018x} a7={:#018x}", - uctx.sepc, r.ra, r.sp, r.gp, r.tp, r.s0, r.a0, r.a1, r.a2, r.a3, r.a4, r.a5, r.a6, r.a7, + tp={:#018x} s0/fp={:#018x} s1={:#018x}\n a0={:#018x} a1={:#018x} a2={:#018x} \ + a3={:#018x}\n a4={:#018x} a5={:#018x} a6={:#018x} a7={:#018x}\n s2={:#018x} \ + s3={:#018x} s4={:#018x} s5={:#018x}\n s6={:#018x} s7={:#018x} s8={:#018x} \ + s9={:#018x}\n s10={:#018x} s11={:#018x} t3={:#018x} t4={:#018x}\n t5={:#018x} \ + t6={:#018x}", + uctx.sepc, + r.ra, + r.sp, + r.gp, + r.tp, + r.s0, + r.s1, + r.a0, + r.a1, + r.a2, + r.a3, + r.a4, + r.a5, + r.a6, + r.a7, + r.s2, + r.s3, + r.s4, + r.s5, + r.s6, + r.s7, + r.s8, + r.s9, + r.s10, + r.s11, + r.t3, + r.t4, + r.t5, + r.t6, ); } #[cfg(target_arch = "aarch64")] @@ -68,6 +169,91 @@ fn dump_user_crash_context(uctx: &UserContext) { { warn!("user register dump: not implemented for this arch"); } + + dump_user_backtrace(uctx); +} + +/// Block the current thread in a ptrace stop. +/// +/// Returns `Some(resume_signo)` if the thread was traced and is now being +/// resumed by the tracer. `None` means the thread was not traced (no +/// `PTRACE_TRACEME`). The optional `resume_signo` is the signal the tracer +/// chose to inject on resume (via `PTRACE_CONT(sig)`); `None` within the +/// outer `Some` means suppress the original signal. +pub fn ptrace_stop_current( + thr: &Thread, + signo: Signo, + uctx: &mut UserContext, +) -> Option> { + ptrace_stop_current_impl(thr, signo, uctx, false) +} + +pub fn ptrace_syscall_stop_current( + thr: &Thread, + signo: Signo, + uctx: &mut UserContext, +) -> Option> { + ptrace_stop_current_impl(thr, signo, uctx, true) +} + +fn ptrace_stop_current_impl( + thr: &Thread, + signo: Signo, + uctx: &mut UserContext, + is_syscall_stop: bool, +) -> Option> { + if !thr.proc_data.is_ptrace_traceme() && !thr.proc_data.is_ptrace_attached() { + return None; + } + + #[cfg(target_arch = "riscv64")] + { + thr.proc_data.save_current_fp_for_ptrace(); + } + if is_syscall_stop { + thr.proc_data.set_ptrace_syscall_stop(signo, uctx); + } else { + thr.proc_data.set_ptrace_stop(signo, uctx); + } + + let waiter_pid = thr + .proc_data + .ptrace_tracer_pid() + .or_else(|| thr.proc_data.proc.parent().map(|parent| parent.pid())); + if let Some(waiter_pid) = waiter_pid + && let Ok(parent_data) = get_process_data(waiter_pid) + { + let sigchld = SignalInfo::new_sigchld( + thr.proc_data.proc.pid(), + thr.cred().uid, + CLD_TRAPPED as i32, + signo as i32, + ); + let _ = send_signal_to_process(waiter_pid, Some(sigchld)); + parent_data.child_exit_event.wake(); + } + + current().clear_interrupt(); + let wait_result = block_on(interruptible(poll_fn(|cx| { + if thr.proc_data.ptrace_stop_signo().is_none() { + Poll::Ready(()) + } else { + thr.proc_data.register_ptrace_stop_waker(cx.waker()); + if thr.proc_data.ptrace_stop_signo().is_none() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + }))); + + if wait_result.is_err() { + thr.proc_data.clear_ptrace_stop(); + } else if let Some(resume_uctx) = thr.proc_data.take_ptrace_stop_user_context() { + *uctx = resume_uctx; + thr.proc_data.restore_current_fp_for_ptrace(uctx); + } + Some(thr.proc_data.take_ptrace_resume_signo()) } pub fn check_signals( @@ -121,6 +307,21 @@ pub fn check_signals( let signo = sig.signo(); + if signo != Signo::SIGKILL + && !thr.proc_data.take_ptrace_resume_signal_bypass(signo) + && let Some(resume_signo) = ptrace_stop_current(thr, signo, uctx) + { + match resume_signo { + None => return true, + Some(new_signo) if new_signo != signo => { + thr.proc_data.set_ptrace_resume_signal_bypass(new_signo); + let _ = thr.signal.send_signal(SignalInfo::new_kernel(new_signo)); + return true; + } + Some(_) => {} + } + } + // Only dump register state when the terminating signal is the same // synchronous fault signo that raise_signal_fatal force-delivered to // this thread. Matching by signo prevents a low-numbered pending @@ -316,6 +517,9 @@ pub fn send_signal_to_process(pid: Pid, sig: Option) -> AxResult<()> if let Some(sig) = sig { let signo = sig.signo(); info!("Send signal {signo:?} to process {pid}"); + if signo == Signo::SIGKILL && proc_data.ptrace_stop_signo().is_some() { + proc_data.clear_ptrace_stop(); + } if let Some(tid) = proc_data.signal.send_signal(sig) { // A thread was found that doesn't have the signal blocked — wake it. if let Ok(task) = get_task(tid) { diff --git a/os/StarryOS/kernel/src/task/user.rs b/os/StarryOS/kernel/src/task/user.rs index e6d5bc324b..423ce48ad4 100644 --- a/os/StarryOS/kernel/src/task/user.rs +++ b/os/StarryOS/kernel/src/task/user.rs @@ -3,9 +3,11 @@ use ax_task::TaskInner; use starry_process::Pid; use starry_signal::{SignalInfo, Signo}; use starry_vm::{VmMutPtr, VmPtr}; +use syscalls::Sysno; use super::{ - AsThread, SyscallRestartInfo, TimerState, check_signals, raise_signal_fatal, set_timer_state, + AsThread, SyscallRestartInfo, SyscallTraceState, TimerState, check_signals, + ptrace_stop_current, ptrace_syscall_stop_current, raise_signal_fatal, set_timer_state, unblock_next_signal, }; use crate::syscall::{handle_syscall, syscall_allows_signal_restart}; @@ -23,7 +25,17 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> info!("Enter user space: ip={:#x}, sp={:#x}", uctx.ip(), uctx.sp()); let thr = curr.as_thread(); + if thr.proc_data.ptrace_stop_signo().is_some() { + let _ = ptrace_stop_current(thr, Signo::SIGSTOP, &mut uctx); + } while !thr.pending_exit() { + if thr.proc_data.is_ptrace_singlestep() + && (thr.proc_data.is_ptrace_traceme() || thr.proc_data.is_ptrace_attached()) + { + #[cfg(target_arch = "riscv64")] + crate::syscall::ptrace_setup_singlestep(&thr.proc_data, &mut uctx); + } + let reason = uctx.run(); set_timer_state(&curr, TimerState::Kernel); @@ -33,7 +45,45 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> let is_syscall = matches!(reason, ReturnReason::Syscall); match reason { - ReturnReason::Syscall => handle_syscall(&mut uctx), + ReturnReason::Syscall => { + let trace_state = thr.proc_data.take_ptrace_syscall_trace(); + if matches!(trace_state, SyscallTraceState::Entry) + && ptrace_syscall_stop_current(thr, Signo::SIGTRAP, &mut uctx).is_some() + { + match thr.proc_data.take_ptrace_syscall_trace() { + SyscallTraceState::Entry | SyscallTraceState::Exit => thr + .proc_data + .set_ptrace_syscall_trace_state(SyscallTraceState::Exit), + SyscallTraceState::None => {} + } + } + + if let Some(exit_code) = ptrace_exit_event_code(saved_sysno, saved_a0) + && crate::syscall::ptrace_notify_exit( + thr.proc_data.proc.pid(), + exit_code, + ) + { + let _ = ptrace_stop_current(thr, Signo::SIGTRAP, &mut uctx); + } + + handle_syscall(&mut uctx); + if matches!( + thr.proc_data.take_ptrace_syscall_trace(), + SyscallTraceState::Exit + ) { + let _ = ptrace_syscall_stop_current(thr, Signo::SIGTRAP, &mut uctx); + } + if thr.proc_data.take_ptrace_exec_stop_pending() { + let _is_event = + crate::syscall::ptrace_notify_exec(thr.proc_data.proc.pid()); + if let Some(_resume_sig) = + ptrace_stop_current(thr, Signo::SIGTRAP, &mut uctx) + { + continue; + } + } + } ReturnReason::PageFault(addr, flags) => { if !thr.proc_data.aspace().lock().handle_page_fault(addr, flags) { info!( @@ -47,8 +97,32 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> ReturnReason::Interrupt => {} #[allow(unused_labels)] ReturnReason::Exception(exc_info) => 'exc: { - // TODO: detailed handling let kind = exc_info.kind(); + if matches!(kind, ExceptionKind::Breakpoint) + && (thr.proc_data.is_ptrace_traceme() + || thr.proc_data.is_ptrace_attached()) + { + let saved_insn = thr.proc_data.take_ptrace_ss_saved_insn(); + if let Some((addr, insn)) = saved_insn { + if addr == uctx.ip() { + let aspace = thr.proc_data.aspace(); + let aspace = aspace.lock(); + let _ = aspace.write( + ax_memory_addr::VirtAddr::from_usize(addr), + &(insn as u16).to_ne_bytes(), + ); + #[cfg(target_arch = "riscv64")] + ax_runtime::hal::cpu::asm::flush_icache_all(); + } else { + thr.proc_data.set_ptrace_ss_saved_insn(Some((addr, insn))); + } + } + if let Some(_resume_sig) = + ptrace_stop_current(thr, Signo::SIGTRAP, &mut uctx) + { + break 'exc; + } + } warn!( "user exception: ip={:#x}, fault_addr={:#x}, kind={:?}, esr={:#x}, \ ec={:#x}, iss={:#x}, info={:?}", @@ -113,6 +187,13 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> ) } +fn ptrace_exit_event_code(sysno: usize, arg0: usize) -> Option { + match Sysno::new(sysno) { + Some(Sysno::exit | Sysno::exit_group) => Some((arg0 as i32) << 8), + _ => None, + } +} + #[cfg(target_arch = "aarch64")] fn exception_fault_addr(exc_info: &ExceptionInfo) -> usize { exc_info.far diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/CMakeLists.txt new file mode 100644 index 0000000000..438c528b4f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ptrace-exec-stop C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-ptrace-exec-stop src/main.c) +target_compile_options(test-ptrace-exec-stop PRIVATE + -Wall + -Wextra + -Werror +) + +install(TARGETS test-ptrace-exec-stop RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/src/main.c new file mode 100644 index 0000000000..2cfb042064 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/c/src/main.c @@ -0,0 +1,198 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PTRACE_GETSIGINFO +#define PTRACE_GETSIGINFO 0x4202 +#endif +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif + +static int fail(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +static int test_exec_stop(void) +{ + printf("test 1: execve SIGTRAP stop\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + execl("/usr/bin/test-ptrace-exec-stop", "test-ptrace-exec-stop", "--after-exec", NULL); + _exit(101); + } + + int status = 0; + pid_t got = waitpid(pid, &status, WUNTRACED); + if (got != pid) { + return fail("waitpid exec child"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected SIGTRAP after exec, status=%#x\n", status); + return 1; + } + printf(" ok: child stopped with SIGTRAP after exec\n"); + + siginfo_t si; + memset(&si, 0, sizeof(si)); + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si) != 0) { + return fail("ptrace getsiginfo"); + } + if (si.si_signo != SIGTRAP) { + printf("FAIL: getsiginfo si_signo=%d, expected SIGTRAP(%d)\n", + si.si_signo, SIGTRAP); + return 1; + } + printf(" ok: GETSIGINFO returns si_signo=SIGTRAP\n"); + + if (ptrace(PTRACE_DETACH, pid, NULL, NULL) != 0) { + return fail("ptrace detach"); + } + printf(" ok: PTRACE_DETACH succeeded\n"); + + got = waitpid(pid, &status, 0); + if (got != pid) { + return fail("waitpid after detach"); + } + printf(" ok: child reaped after detach, status=%#x\n", status); + return 0; +} + +static int test_detach_with_signal(void) +{ + printf("test 2: PTRACE_DETACH with signal injection\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(0); + } + + int status = 0; + pid_t got = waitpid(pid, &status, WUNTRACED); + if (got != pid) { + return fail("waitpid sigstop child"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { + printf("FAIL: expected SIGSTOP, status=%#x\n", status); + return 1; + } + + if (ptrace(PTRACE_DETACH, pid, NULL, (void *)(long)SIGCONT) != 0) { + return fail("ptrace detach with SIGCONT"); + } + printf(" ok: PTRACE_DETACH(pid, SIGCONT) succeeded\n"); + + got = waitpid(pid, &status, 0); + if (got != pid) { + return fail("waitpid after detach+signal"); + } + printf(" ok: child reaped after detach+signal, status=%#x\n", status); + return 0; +} + +static int test_cont_suppress_signal(void) +{ + printf("test 3: PTRACE_CONT suppresses signal\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGUSR1); + _exit(42); + } + + int status = 0; + pid_t got = waitpid(pid, &status, WUNTRACED); + if (got != pid) { + return fail("waitpid signal child"); + } + if (!WIFSTOPPED(status)) { + printf("FAIL: expected stopped child, status=%#x\n", status); + return 1; + } + printf(" ok: child stopped with signal %d\n", WSTOPSIG(status)); + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("ptrace cont (suppress signal)"); + } + printf(" ok: PTRACE_CONT with signal=0 suppressed the signal\n"); + + got = waitpid(pid, &status, 0); + if (got != pid) { + return fail("waitpid after cont"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { + printf("FAIL: expected exit 42, status=%#x\n", status); + return 1; + } + printf(" ok: child exited with 42 (signal suppressed)\n"); + return 0; +} + +int main(int argc, char **argv) +{ + if (argc == 2 && strcmp(argv[1], "--after-exec") == 0) { + return 0; + } + + int pass = 0; + int fail_count = 0; + + if (test_exec_stop() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_detach_with_signal() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_cont_suppress_signal() == 0) { + pass++; + } else { + fail_count++; + } + + printf("DONE: %d pass, %d fail\n", pass, fail_count); + return fail_count > 0 ? 1 : 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-riscv64.toml new file mode 100644 index 0000000000..920ecea2ef --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-riscv64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-exec-stop" +success_regex = ["DONE: 3 pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/CMakeLists.txt new file mode 100644 index 0000000000..1d3e1d09bc --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ptrace-gdb C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-ptrace-gdb src/main.c) +target_compile_options(test-ptrace-gdb PRIVATE -Wall -Wextra -Werror) + +install(TARGETS test-ptrace-gdb RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/src/main.c new file mode 100644 index 0000000000..6b769bd0f8 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/c/src/main.c @@ -0,0 +1,1711 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif +#ifndef NT_FPREGSET +#define NT_FPREGSET 2 +#endif +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef PTRACE_SETREGSET +#define PTRACE_SETREGSET 0x4205 +#endif +#ifndef PTRACE_GETREGS +#define PTRACE_GETREGS 12 +#endif +#ifndef PTRACE_SETREGS +#define PTRACE_SETREGS 13 +#endif +#ifndef PTRACE_GETFPREGS +#define PTRACE_GETFPREGS 14 +#endif +#ifndef PTRACE_SETFPREGS +#define PTRACE_SETFPREGS 15 +#endif +#ifndef PTRACE_SINGLESTEP +#define PTRACE_SINGLESTEP 9 +#endif +#ifndef PTRACE_ATTACH +#define PTRACE_ATTACH 16 +#endif +#ifndef PTRACE_KILL +#define PTRACE_KILL 8 +#endif +#ifndef PTRACE_SETOPTIONS +#define PTRACE_SETOPTIONS 0x4200 +#endif +#ifndef PTRACE_GETEVENTMSG +#define PTRACE_GETEVENTMSG 0x4201 +#endif +#ifndef PTRACE_GETSIGINFO +#define PTRACE_GETSIGINFO 0x4202 +#endif +#ifndef PTRACE_SETSIGINFO +#define PTRACE_SETSIGINFO 0x4203 +#endif +#ifndef PTRACE_SYSCALL +#define PTRACE_SYSCALL 24 +#endif +#ifndef PTRACE_O_TRACEFORK +#define PTRACE_O_TRACEFORK 0x00000002 +#endif +#ifndef PTRACE_O_TRACESYSGOOD +#define PTRACE_O_TRACESYSGOOD 0x00000001 +#endif +#ifndef PTRACE_O_TRACEEXEC +#define PTRACE_O_TRACEEXEC 0x00000010 +#endif +#ifndef PTRACE_O_TRACEEXIT +#define PTRACE_O_TRACEEXIT 0x00000040 +#endif +#ifndef PTRACE_O_TRACEVFORKDONE +#define PTRACE_O_TRACEVFORKDONE 0x00000020 +#endif + +#define PTRACE_EVENT_FORK 1 +#define PTRACE_EVENT_CLONE 3 +#define PTRACE_EVENT_EXEC 4 +#define PTRACE_EVENT_VFORK_DONE 5 +#define PTRACE_EVENT_EXIT 6 + +struct riscv_user_regs { + unsigned long pc; + unsigned long ra; + unsigned long sp; + unsigned long gp; + unsigned long tp; + unsigned long t0; + unsigned long t1; + unsigned long t2; + unsigned long s0; + unsigned long s1; + unsigned long a0; + unsigned long a1; + unsigned long a2; + unsigned long a3; + unsigned long a4; + unsigned long a5; + unsigned long a6; + unsigned long a7; + unsigned long s2; + unsigned long s3; + unsigned long s4; + unsigned long s5; + unsigned long s6; + unsigned long s7; + unsigned long s8; + unsigned long s9; + unsigned long s10; + unsigned long s11; + unsigned long t3; + unsigned long t4; + unsigned long t5; + unsigned long t6; +}; + +struct riscv_user_fpregs { + unsigned long f[32]; + unsigned long fcsr; +}; + +static int fail(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +static int get_regs(pid_t pid, struct riscv_user_regs *regs) +{ + struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; + if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return -1; + } + return 0; +} + +static int set_regs(pid_t pid, struct riscv_user_regs *regs) +{ + struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; + if (ptrace(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return -1; + } + return 0; +} + +static int wait_stop(pid_t pid, int expected_sig) +{ + siginfo_t si; + si.si_pid = 0; + + if (waitid(P_PID, pid, &si, WSTOPPED | WEXITED) != 0) { + printf("FAIL: waitid errno=%d (%s)\n", errno, strerror(errno)); + return -1; + } + if (si.si_pid != pid) { + printf("FAIL: waitid returned pid=%d, expected %d\n", si.si_pid, pid); + return -1; + } + if (si.si_uid != 0) { + printf("FAIL: waitid returned uid=%d, expected 0\n", si.si_uid); + return -1; + } + if (si.si_code == CLD_EXITED) { + printf("FAIL: child exited prematurely, status=%d\n", si.si_status); + return -1; + } + if (si.si_code != CLD_TRAPPED) { + printf("FAIL: expected CLD_TRAPPED, got code=%d status=%d\n", + si.si_code, si.si_status); + return -1; + } + if (expected_sig > 0 && si.si_status != expected_sig) { + printf("FAIL: expected signal %d, got %d\n", expected_sig, si.si_status); + return -1; + } + return 0; +} + +__attribute__((naked, noinline, aligned(4))) static void ss_step_target(void) +{ + __asm__ volatile( + "addi a0, zero, 123\n" + "ebreak\n"); +} + +__attribute__((naked, noinline, aligned(4))) static void ss_branch_target(void) +{ + __asm__ volatile( + "beq a0, a1, 1f\n" + "addi a2, zero, 111\n" + "ebreak\n" + "1:\n" + "addi a2, zero, 222\n" + "ebreak\n"); +} + +__attribute__((naked, noinline, aligned(4))) static void ss_jal_target(void) +{ + __asm__ volatile( + "jal ra, 1f\n" + "addi a2, zero, 111\n" + "ebreak\n" + "1:\n" + "addi a2, zero, 333\n" + "ebreak\n"); +} + +__attribute__((naked, noinline, aligned(4))) static void ss_jalr_target(void) +{ + __asm__ volatile( + "jalr zero, 0(a3)\n" + "addi a2, zero, 111\n" + "ebreak\n"); +} + +__attribute__((naked, noinline, aligned(4))) static void ss_jalr_landing(void) +{ + __asm__ volatile( + "addi a2, zero, 444\n" + "ebreak\n"); +} + +__attribute__((naked, noinline, aligned(2))) static void ss_c_j_target(void) +{ + __asm__ volatile( + ".option push\n" + ".option rvc\n" + "c.j 1f\n" + "addi a2, zero, 111\n" + "ebreak\n" + "1:\n" + "addi a2, zero, 555\n" + "ebreak\n" + ".option pop\n"); +} + +__attribute__((naked, noinline, aligned(2))) static void ss_c_beqz_target(void) +{ + __asm__ volatile( + ".option push\n" + ".option rvc\n" + "c.beqz s0, 1f\n" + "addi a2, zero, 111\n" + "ebreak\n" + "1:\n" + "addi a2, zero, 666\n" + "ebreak\n" + ".option pop\n"); +} + +__attribute__((naked, noinline, aligned(2))) static void ss_c_jr_target(void) +{ + __asm__ volatile( + ".option push\n" + ".option rvc\n" + "c.jr a3\n" + "addi a2, zero, 111\n" + "ebreak\n" + ".option pop\n"); +} + +__attribute__((naked, noinline, aligned(4))) static void ss_c_jr_landing(void) +{ + __asm__ volatile( + "addi a2, zero, 777\n" + "ebreak\n"); +} + +static int wait_sigtrap(pid_t pid) +{ + int status = 0; + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected SIGTRAP, status=%#x\n", status); + return -1; + } + return 0; +} + +static int check_singlestep_stops_before_target_side_effect(pid_t pid, + struct riscv_user_regs *regs, + unsigned long pc, + unsigned long a0, + unsigned long a1, + unsigned long s0, + unsigned long a3, + unsigned long expected_a2) +{ + memset(regs, 0, sizeof(*regs)); + if (get_regs(pid, regs) != 0) { + return fail("getregset before control-flow singlestep"); + } + regs->pc = pc; + regs->a0 = a0; + regs->a1 = a1; + regs->a2 = 0; + regs->a3 = a3; + regs->s0 = s0; + if (set_regs(pid, regs) != 0) { + return fail("setregset control-flow singlestep target"); + } + + if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { + return fail("singlestep control-flow instruction"); + } + if (wait_sigtrap(pid) != 0) { + return 1; + } + memset(regs, 0, sizeof(*regs)); + if (get_regs(pid, regs) != 0) { + return fail("getregset after control-flow singlestep"); + } + if (regs->a2 != 0) { + printf("FAIL: singlestep ran target side effect early, a2=%#lx\n", regs->a2); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont after control-flow singlestep"); + } + if (wait_sigtrap(pid) != 0) { + return 1; + } + memset(regs, 0, sizeof(*regs)); + if (get_regs(pid, regs) != 0) { + return fail("getregset after control-flow landing"); + } + if (regs->a2 != expected_a2) { + printf("FAIL: control-flow landing a2=%#lx expected %#lx\n", + regs->a2, expected_a2); + return 1; + } + return 0; +} + +static int test_singlestep(void) +{ + printf("test 1: PTRACE_SINGLESTEP\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial stop\n"); + return 1; + } + + struct riscv_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (get_regs(pid, ®s) != 0) { + return fail("getregset initial"); + } + + regs.pc = (unsigned long)ss_step_target; + regs.a0 = 0; + if (set_regs(pid, ®s) != 0) { + return fail("setregset singlestep target"); + } + + if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { + return fail("singlestep known instruction"); + } + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected SIGTRAP after known single-step, status=%#x\n", status); + return 1; + } + memset(®s, 0, sizeof(regs)); + if (get_regs(pid, ®s) != 0) { + return fail("getregset after known single-step"); + } + if (regs.a0 != 123) { + printf("FAIL: singlestep skipped instruction, a0=%#lx expected 123\n", regs.a0); + return 1; + } + + if (check_singlestep_stops_before_target_side_effect( + pid, ®s, (unsigned long)ss_branch_target, 7, 7, 0, 0, 222) + != 0) { + return 1; + } + if (check_singlestep_stops_before_target_side_effect( + pid, ®s, (unsigned long)ss_jal_target, 0, 0, 0, 0, 333) + != 0) { + return 1; + } + if (check_singlestep_stops_before_target_side_effect( + pid, ®s, (unsigned long)ss_jalr_target, 0, 0, 0, + (unsigned long)ss_jalr_landing, 444) + != 0) { + return 1; + } + if (check_singlestep_stops_before_target_side_effect( + pid, ®s, (unsigned long)ss_c_j_target, 0, 0, 0, 0, 555) + != 0) { + return 1; + } + if (check_singlestep_stops_before_target_side_effect( + pid, ®s, (unsigned long)ss_c_beqz_target, 0, 0, 0, 0, 666) + != 0) { + return 1; + } + if (check_singlestep_stops_before_target_side_effect( + pid, ®s, (unsigned long)ss_c_jr_target, 0, 0, 0, + (unsigned long)ss_c_jr_landing, 777) + != 0) { + return 1; + } + + if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { + return fail("kill after known single-step"); + } + waitpid(pid, &status, 0); + printf(" ok: singlestep handled 32-bit and compressed control-flow instructions\n"); + return 0; +} + +static int test_waitid_wstopped(void) +{ + printf("test 2: waitid with WSTOPPED\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(7); + } + + siginfo_t si; + memset(&si, 0, sizeof(si)); + if (waitid(P_PID, pid, &si, WSTOPPED | WEXITED) != 0) { + return fail("waitid WSTOPPED"); + } + if (si.si_code != CLD_TRAPPED || si.si_status != SIGSTOP) { + printf("FAIL: waitid si_code=%d si_status=%d\n", si.si_code, si.si_status); + return 1; + } + if (si.si_uid != 0) { + printf("FAIL: waitid si_uid=%d, expected 0\n", si.si_uid); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont"); + } + int wstatus = 0; + if (waitpid(pid, &wstatus, 0) != pid || !WIFEXITED(wstatus) + || WEXITSTATUS(wstatus) != 7) { + printf("FAIL: waitid child exit\n"); + return 1; + } + + printf(" ok: waitid(WSTOPPED) correctly reported CLD_TRAPPED/SIGSTOP\n"); + return 0; +} + +static int test_fpregs(void) +{ + printf("test 3: NT_FPREGSET get/set\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + + unsigned long f0_bits = 0; + __asm__ volatile("fmv.x.d %0, f0" : "=r"(f0_bits)); + _exit(f0_bits == 0x4008000000000000UL ? 42 : 1); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial stop\n"); + return 1; + } + + struct riscv_user_fpregs fpregs; + struct iovec iov = {.iov_base = &fpregs, .iov_len = sizeof(fpregs)}; + if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_FPREGSET, &iov) != 0) { + printf(" SKIP: NT_FPREGSET not supported (errno=%d)\n", errno); + ptrace(PTRACE_CONT, pid, NULL, NULL); + waitpid(pid, &status, 0); + return 0; + } + + int all_zero = 1; + for (int i = 0; i < 32; i++) { + if (fpregs.f[i] != 0) { + all_zero = 0; + break; + } + } + printf(" ok: NT_FPREGSET read, %s, fcsr=%#lx, iov_len=%zu\n", + all_zero ? "all-zero (FP unused)" : "has non-zero values", + fpregs.fcsr, (size_t)iov.iov_len); + + fpregs.f[0] = 0x4008000000000000UL; + fpregs.f[1] = 0xCAFEBABEu; + iov.iov_len = sizeof(fpregs); + if (ptrace(PTRACE_SETREGSET, pid, (void *)NT_FPREGSET, &iov) != 0) { + return fail("setregset fpregs"); + } + + struct riscv_user_fpregs fpregs2; + iov.iov_base = &fpregs2; + iov.iov_len = sizeof(fpregs2); + if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_FPREGSET, &iov) != 0) { + return fail("getregset fpregs after set"); + } + if (fpregs2.f[0] != 0x4008000000000000UL || fpregs2.f[1] != 0xCAFEBABEu) { + printf("FAIL: fpregs write-back mismatch: f[0]=%#lx f[1]=%#lx\n", + fpregs2.f[0], fpregs2.f[1]); + return 1; + } + printf(" ok: NT_FPREGSET write and read-back match\n"); + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: FP register state was not restored to tracee, status=%#x\n", status); + return 1; + } + printf(" ok: NT_FPREGSET restored f0 into tracee execution state\n"); + return 0; +} + +static volatile int attach_child_reached = 0; + +static int test_attach(void) +{ + printf("test 4: PTRACE_ATTACH\n"); + + int pipefd[2]; + if (pipe(pipefd) != 0) { + return fail("pipe"); + } + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + close(pipefd[0]); + attach_child_reached = 1; + char c = 'A'; + write(pipefd[1], &c, 1); + close(pipefd[1]); + + for (volatile int i = 0; i < 10000000; i++) { + } + _exit(42); + } + + close(pipefd[1]); + char c; + if (read(pipefd[0], &c, 1) != 1) { + return fail("read pipe from child"); + } + close(pipefd[0]); + + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0) { + return fail("ptrace attach"); + } + + int status = 0; + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid after attach"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { + printf("FAIL: attach stop status=%#x, expected SIGSTOP\n", status); + return 1; + } + + struct riscv_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (get_regs(pid, ®s) != 0) { + return fail("getregset after attach"); + } + printf(" ok: attached, child stopped, pc=%#lx sp=%#lx\n", + regs.pc, regs.sp); + + if (ptrace(PTRACE_DETACH, pid, NULL, NULL) != 0) { + return fail("detach"); + } + + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: after detach, status=%#x\n", status); + return 1; + } + printf(" ok: detached, child exited 42\n"); + return 0; +} + +static int test_setoptions(void) +{ + printf("test 5: PTRACE_SETOPTIONS (TRACEFORK)\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + pid_t grandchild = fork(); + if (grandchild == 0) { + _exit(0); + } + if (grandchild > 0) { + waitpid(grandchild, NULL, 0); + } + _exit(0); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial stop\n"); + return 1; + } + + if (ptrace(PTRACE_SETOPTIONS, pid, (void *)(long)PTRACE_O_TRACEFORK, NULL) != 0) { + return fail("setoptions"); + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont"); + } + + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid fork event"); + } + if (!WIFSTOPPED(status)) { + printf("FAIL: expected stopped after fork, status=%#x\n", status); + return 1; + } + + int event = (status >> 16) & 0xFF; + if (event != PTRACE_EVENT_FORK) { + printf("FAIL: expected PTRACE_EVENT_FORK, got event=%d status=%#x\n", + event, status); + return 1; + } + unsigned long event_msg = 0; + if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &event_msg) != 0) { + return fail("geteventmsg"); + } + if (event_msg == 0) { + printf("FAIL: expected non-zero fork event message\n"); + return 1; + } + printf(" ok: PTRACE_O_TRACEFORK delivered event child=%lu\n", event_msg); + + int grandchild_status = 0; + if (waitpid((pid_t)event_msg, &grandchild_status, 0) != (pid_t)event_msg) { + return fail("waitpid traced fork child"); + } + if (!WIFSTOPPED(grandchild_status)) { + printf("FAIL: traced fork child was not stopped, status=%#x\n", + grandchild_status); + return 1; + } + if (ptrace(PTRACE_KILL, (pid_t)event_msg, NULL, NULL) != 0) { + return fail("kill traced fork child"); + } + if (waitpid((pid_t)event_msg, &grandchild_status, 0) != (pid_t)event_msg) { + return fail("waitpid killed traced fork child"); + } + if (!WIFSIGNALED(grandchild_status) || WTERMSIG(grandchild_status) != SIGKILL) { + printf("FAIL: killed traced fork child status=%#x\n", grandchild_status); + return 1; + } + printf(" ok: traced fork child stopped and PTRACE_KILL reported SIGKILL\n"); + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("final cont"); + } + waitpid(pid, &status, 0); + return 0; +} + +__attribute__((naked, noinline, aligned(4))) static void setregs_pc_landing(void) +{ + __asm__ volatile( + "addi a2, zero, 77\n" + "ebreak\n"); +} + +static int test_setregs(void) +{ + printf("test 6: SETREGSET modify PC\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(0); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial stop\n"); + return 1; + } + + struct riscv_user_regs regs; + memset(®s, 0, sizeof(regs)); + regs.pc = (unsigned long)setregs_pc_landing; + regs.a2 = 0; + if (set_regs(pid, ®s) != 0) { + return fail("setregset pc landing"); + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont after setregset"); + } + if (wait_sigtrap(pid) != 0) { + return 1; + } + memset(®s, 0, sizeof(regs)); + if (get_regs(pid, ®s) != 0) { + return fail("getregset after setregset pc landing"); + } + if (regs.a2 != 77) { + printf("FAIL: SETREGSET PC landing a2=%#lx expected 77\n", regs.a2); + return 1; + } + if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { + return fail("kill after setregset"); + } + waitpid(pid, &status, 0); + + printf(" ok: SETREGSET modified PC to a known landing block\n"); + return 0; +} + +static int test_waitid_attach(void) +{ + printf("test 7: waitid(WSTOPPED) after ATTACH\n"); + + int pipefd[2]; + if (pipe(pipefd) != 0) { + return fail("pipe"); + } + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + close(pipefd[0]); + char c = 'x'; + write(pipefd[1], &c, 1); + close(pipefd[1]); + for (volatile int i = 0; i < 10000000; i++) { + } + _exit(0); + } + + close(pipefd[1]); + char c; + if (read(pipefd[0], &c, 1) != 1) { + return fail("read pipe"); + } + close(pipefd[0]); + + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0) { + return fail("attach"); + } + + if (wait_stop(pid, SIGSTOP) != 0) { + return 1; + } + + printf(" ok: waitid(WSTOPPED) after ATTACH got CLD_TRAPPED/SIGSTOP\n"); + + if (ptrace(PTRACE_DETACH, pid, NULL, NULL) != 0) { + return fail("detach"); + } + int wstatus = 0; + waitpid(pid, &wstatus, 0); + return 0; +} + +static int test_syscall_trace(void) +{ + printf("test 8: PTRACE_SYSCALL entry/exit stops\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + pid_t self = getpid(); + _exit(self == getpid() ? 42 : 1); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial stop\n"); + return 1; + } + if (ptrace(PTRACE_SETOPTIONS, pid, (void *)(long)PTRACE_O_TRACESYSGOOD, NULL) != 0) { + return fail("setoptions TRACESYSGOOD"); + } + + struct riscv_user_regs regs; + int expect_entry = 1; + int saw_getpid = 0; + for (int stops = 0; stops < 80 && !saw_getpid; stops++) { + if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) != 0) { + return fail(expect_entry ? "ptrace syscall entry" : "ptrace syscall exit"); + } + if (wait_stop(pid, SIGTRAP | 0x80) != 0) { + printf("FAIL: expected syscall waitid SIGTRAP|0x80\n"); + return 1; + } + + memset(®s, 0, sizeof(regs)); + if (get_regs(pid, ®s) != 0) { + return fail("getregset syscall stop"); + } + + if (expect_entry && regs.a7 == SYS_getpid) { + if (ptrace(PTRACE_SYSCALL, pid, NULL, NULL) != 0) { + return fail("ptrace getpid exit"); + } + if (wait_stop(pid, SIGTRAP | 0x80) != 0) { + printf("FAIL: expected getpid-exit waitid SIGTRAP|0x80\n"); + return 1; + } + memset(®s, 0, sizeof(regs)); + if (get_regs(pid, ®s) != 0) { + return fail("getregset getpid exit"); + } + if (regs.a0 != (unsigned long)pid) { + printf("FAIL: getpid exit a0=%lu, expected pid=%d\n", regs.a0, pid); + return 1; + } + saw_getpid = 1; + break; + } + + expect_entry = !expect_entry; + } + + if (!saw_getpid) { + printf("FAIL: did not observe SYS_getpid entry/exit stops\n"); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont after syscall trace"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: syscall trace child exit status=%#x\n", status); + return 1; + } + printf(" ok: PTRACE_SYSCALL reported entry and exit stops\n"); + return 0; +} + +static int test_signal_resume(void) +{ + printf("test 9: PTRACE_CONT signal suppression/injection\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork suppress child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + raise(SIGUSR1); + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial suppress stop\n"); + return 1; + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont to SIGUSR1 stop"); + } + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGUSR1) { + printf("FAIL: expected SIGUSR1 stop, status=%#x\n", status); + return 1; + } + if (ptrace(PTRACE_CONT, pid, NULL, 0) != 0) { + return fail("cont suppress SIGUSR1"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: CONT(0) did not suppress SIGUSR1, status=%#x\n", status); + return 1; + } + + pid = fork(); + if (pid < 0) { + return fail("fork inject child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + raise(SIGUSR1); + _exit(43); + } + + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial inject stop\n"); + return 1; + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont inject child to SIGUSR1 stop"); + } + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGUSR1) { + printf("FAIL: expected inject SIGUSR1 stop, status=%#x\n", status); + return 1; + } + if (ptrace(PTRACE_CONT, pid, NULL, (void *)(long)SIGTERM) != 0) { + return fail("cont inject SIGTERM"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("wait inject SIGTERM result"); + } + if (WIFSTOPPED(status)) { + printf("FAIL: injected SIGTERM caused an extra ptrace stop, status=%#x\n", status); + ptrace(PTRACE_KILL, pid, NULL, NULL); + waitpid(pid, &status, 0); + return 1; + } + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGTERM) { + printf("FAIL: injected SIGTERM was not delivered, status=%#x\n", status); + return 1; + } + + printf(" ok: CONT(0) suppresses and CONT(SIGTERM) injects without extra stop\n"); + return 0; +} + +__attribute__((naked, noinline, aligned(4))) static void legacy_setregs_landing(void) +{ + __asm__ volatile( + "addi a2, zero, 88\n" + "ebreak\n"); +} + +static int test_legacy_regsets(void) +{ + printf("test 10: legacy PTRACE_GETREGS/GETFPREGS\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork legacy regs"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(1); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial legacy regs stop\n"); + return 1; + } + + struct riscv_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) { + return fail("legacy getregs"); + } + if (regs.pc == 0 || regs.sp == 0) { + printf("FAIL: legacy GETREGS returned pc=%#lx sp=%#lx\n", regs.pc, regs.sp); + return 1; + } + + regs.pc = (unsigned long)legacy_setregs_landing; + regs.a2 = 0; + if (ptrace(PTRACE_SETREGS, pid, NULL, ®s) != 0) { + return fail("legacy setregs"); + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("legacy cont after setregs"); + } + if (wait_sigtrap(pid) != 0) { + return 1; + } + memset(®s, 0, sizeof(regs)); + if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) { + return fail("legacy getregs after landing"); + } + if (regs.a2 != 88) { + printf("FAIL: legacy SETREGS landing a2=%#lx expected 88\n", regs.a2); + return 1; + } + if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { + return fail("legacy kill regs child"); + } + waitpid(pid, &status, 0); + + pid = fork(); + if (pid < 0) { + return fail("fork legacy fpregs"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + + unsigned long f0_bits = 0; + __asm__ volatile("fmv.x.d %0, f0" : "=r"(f0_bits)); + _exit(f0_bits == 0x4010000000000000UL ? 42 : 1); + } + + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial legacy fpregs stop\n"); + return 1; + } + + struct riscv_user_fpregs fpregs; + memset(&fpregs, 0, sizeof(fpregs)); + if (ptrace(PTRACE_GETFPREGS, pid, NULL, &fpregs) != 0) { + return fail("legacy getfpregs"); + } + fpregs.f[0] = 0x4010000000000000UL; + fpregs.f[2] = 0x12345678UL; + if (ptrace(PTRACE_SETFPREGS, pid, NULL, &fpregs) != 0) { + return fail("legacy setfpregs"); + } + + struct riscv_user_fpregs fpregs2; + memset(&fpregs2, 0, sizeof(fpregs2)); + if (ptrace(PTRACE_GETFPREGS, pid, NULL, &fpregs2) != 0) { + return fail("legacy getfpregs after set"); + } + if (fpregs2.f[0] != 0x4010000000000000UL || fpregs2.f[2] != 0x12345678UL) { + printf("FAIL: legacy fpregs mismatch f0=%#lx f2=%#lx\n", + fpregs2.f[0], fpregs2.f[2]); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("legacy cont fpregs child"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: legacy SETFPREGS was not restored, status=%#x\n", status); + return 1; + } + + printf(" ok: legacy register requests share regset semantics\n"); + return 0; +} + +static int test_siginfo_roundtrip(void) +{ + printf("test 11: PTRACE_GETSIGINFO/SETSIGINFO roundtrip\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork siginfo child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + raise(SIGUSR1); + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial siginfo stop\n"); + return 1; + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont to SIGUSR1 for siginfo"); + } + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGUSR1) { + printf("FAIL: expected SIGUSR1 siginfo stop, status=%#x\n", status); + return 1; + } + + siginfo_t si; + memset(&si, 0, sizeof(si)); + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si) != 0) { + return fail("getsiginfo"); + } + if (si.si_signo != SIGUSR1) { + printf("FAIL: GETSIGINFO signo=%d expected %d\n", si.si_signo, SIGUSR1); + return 1; + } + + si.si_code = SI_QUEUE; + if (ptrace(PTRACE_SETSIGINFO, pid, NULL, &si) != 0) { + return fail("setsiginfo"); + } + + siginfo_t si2; + memset(&si2, 0, sizeof(si2)); + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si2) != 0) { + return fail("getsiginfo after set"); + } + if (si2.si_signo != SIGUSR1 || si2.si_code != SI_QUEUE) { + printf("FAIL: siginfo after set signo=%d code=%d\n", si2.si_signo, si2.si_code); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, 0) != 0) { + return fail("cont suppress SIGUSR1 after siginfo set"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: siginfo child exit status=%#x\n", status); + return 1; + } + + printf(" ok: SETSIGINFO updates the current ptrace stop siginfo\n"); + return 0; +} + +static int test_traceexit_event(void) +{ + printf("test 12: PTRACE_O_TRACEEXIT event\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork traceexit child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial traceexit stop\n"); + return 1; + } + + if (ptrace(PTRACE_SETOPTIONS, pid, (void *)(long)PTRACE_O_TRACEEXIT, NULL) != 0) { + return fail("setoptions TRACEEXIT"); + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont to traceexit event"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid traceexit event"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: traceexit stop status=%#x\n", status); + return 1; + } + + int event = (status >> 16) & 0xFF; + if (event != PTRACE_EVENT_EXIT) { + printf("FAIL: expected PTRACE_EVENT_EXIT, got event=%d status=%#x\n", + event, status); + return 1; + } + + unsigned long event_msg = 0; + if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &event_msg) != 0) { + return fail("geteventmsg traceexit"); + } + if (event_msg != (42UL << 8)) { + printf("FAIL: traceexit event msg=%#lx expected %#lx\n", + event_msg, 42UL << 8); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont after traceexit event"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: traceexit final status=%#x\n", status); + return 1; + } + + printf(" ok: TRACEEXIT stopped before final zombie state\n"); + return 0; +} + +static char clone_thread_stack[16384] __attribute__((aligned(16))); + +static long raw_clone_thread(void *stack_top) +{ + register long a0 __asm__("a0") = + CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD; + register long a1 __asm__("a1") = (long)stack_top; + register long a2 __asm__("a2") = 0; + register long a3 __asm__("a3") = 0; + register long a4 __asm__("a4") = 0; + register long a7 __asm__("a7") = SYS_clone; + + __asm__ volatile( + "ecall\n" + "bnez a0, 1f\n" + "li a7, 93\n" + "li a0, 0\n" + "ecall\n" + "1:\n" + : "+r"(a0) + : "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a7) + : "memory"); + return a0; +} + +static int test_traceclone_thread_event(void) +{ + printf("test 13: PTRACE_O_TRACECLONE thread event\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork traceclone child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + + void *stack_top = clone_thread_stack + sizeof(clone_thread_stack); + long tid = raw_clone_thread(stack_top); + if (tid < 0) { + _exit(2); + } + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial traceclone stop\n"); + return 1; + } + + if (ptrace(PTRACE_SETOPTIONS, pid, (void *)(long)PTRACE_O_TRACECLONE, NULL) != 0) { + return fail("setoptions TRACECLONE"); + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont to traceclone event"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid traceclone event"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: traceclone stop status=%#x\n", status); + return 1; + } + + int event = (status >> 16) & 0xFF; + if (event != PTRACE_EVENT_CLONE) { + printf("FAIL: expected PTRACE_EVENT_CLONE, got event=%d status=%#x\n", + event, status); + return 1; + } + + unsigned long event_msg = 0; + if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &event_msg) != 0) { + return fail("geteventmsg traceclone"); + } + if (event_msg == 0) { + printf("FAIL: expected non-zero clone event message\n"); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont after traceclone event"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) { + printf("FAIL: traceclone final status=%#x\n", status); + return 1; + } + + printf(" ok: TRACECLONE reported thread tid=%lu\n", event_msg); + return 0; +} + +static int test_tracevforkdone_event(void) +{ + printf("test 14: PTRACE_O_TRACEVFORKDONE event\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork tracevforkdone child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + + pid_t child = vfork(); + if (child == 0) { + _exit(0); + } + if (child < 0) { + _exit(2); + } + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial tracevforkdone stop\n"); + return 1; + } + + if (ptrace(PTRACE_SETOPTIONS, pid, (void *)(long)PTRACE_O_TRACEVFORKDONE, NULL) != 0) { + return fail("setoptions TRACEVFORKDONE"); + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont to tracevforkdone event"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid tracevforkdone event"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: tracevforkdone stop status=%#x\n", status); + return 1; + } + + int event = (status >> 16) & 0xFF; + if (event != PTRACE_EVENT_VFORK_DONE) { + printf("FAIL: expected PTRACE_EVENT_VFORK_DONE, got event=%d status=%#x\n", + event, status); + return 1; + } + + unsigned long event_msg = 0; + if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &event_msg) != 0) { + return fail("geteventmsg tracevforkdone"); + } + if (event_msg == 0) { + printf("FAIL: expected non-zero vfork-done event message\n"); + return 1; + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont after tracevforkdone event"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) + || WEXITSTATUS(status) != 42) { + printf("FAIL: tracevforkdone final status=%#x\n", status); + return 1; + } + + printf(" ok: TRACEVFORKDONE reported child pid=%lu\n", event_msg); + return 0; +} + +static int test_ptrace_kill_reports_signaled(void) +{ + printf("test 15: PTRACE_KILL reports SIGKILL termination\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork ptrace-kill child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + for (;;) { + } + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial ptrace-kill stop status=%#x\n", status); + return 1; + } + if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { + return fail("ptrace kill stopped child"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("wait ptrace-kill result"); + } + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) { + printf("FAIL: PTRACE_KILL status=%#x is not SIGKILL-signaled\n", status); + return 1; + } + + printf(" ok: PTRACE_KILL wakes and reports SIGKILL\n"); + return 0; +} + +static int test_sigkill_event_stopped_tracee(void) +{ + printf("test 16: SIGKILL reports termination from event stop\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork event-kill child"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + pid_t grandchild = fork(); + if (grandchild == 0) { + for (;;) { + } + } + for (;;) { + } + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status)) { + printf("FAIL: initial event-kill stop status=%#x\n", status); + return 1; + } + if (ptrace(PTRACE_SETOPTIONS, pid, (void *)(long)PTRACE_O_TRACEFORK, NULL) != 0) { + return fail("setoptions event-kill TRACEFORK"); + } + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("cont to event-kill fork event"); + } + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || ((status >> 16) & 0xff) != PTRACE_EVENT_FORK) { + printf("FAIL: expected fork event before event-kill, status=%#x\n", status); + return 1; + } + + unsigned long event_msg = 0; + if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, &event_msg) != 0) { + return fail("geteventmsg event-kill"); + } + if (event_msg != 0) { + ptrace(PTRACE_KILL, (pid_t)event_msg, NULL, NULL); + } + kill(pid, SIGKILL); + if (waitpid(pid, &status, 0) != pid) { + return fail("wait event-stopped SIGKILL result"); + } + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) { + printf("FAIL: event-stopped SIGKILL status=%#x is not SIGKILL-signaled\n", + status); + return 1; + } + + printf(" ok: event-stopped SIGKILL reports SIGKILL\n"); + return 0; +} + +static int test_traceme_rejects_second_call(void) +{ + printf("test 17: repeated PTRACE_TRACEME is rejected\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork traceme repeat"); + } + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(10); + } + errno = 0; + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == 0 || errno != EPERM) { + _exit(11); + } + _exit(0); + } + + int status = 0; + if (waitpid(pid, &status, 0) != pid) { + return fail("wait traceme repeat"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("FAIL: repeated TRACEME child status=%#x\n", status); + return 1; + } + + printf(" ok: second TRACEME failed with EPERM\n"); + return 0; +} + +static int test_invalid_resume_signal(void) +{ + printf("test 18: invalid ptrace resume signal is rejected\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork invalid resume signal"); + } + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(1); + } + raise(SIGSTOP); + _exit(0); + } + + int status = 0; + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status)) { + printf("FAIL: invalid-resume initial stop status=%#x\n", status); + return 1; + } + + errno = 0; + if (ptrace(PTRACE_CONT, pid, NULL, (void *)(long)9999) == 0 || errno != EIO) { + printf("FAIL: invalid resume signo errno=%d (%s)\n", errno, strerror(errno)); + ptrace(PTRACE_KILL, pid, NULL, NULL); + waitpid(pid, &status, 0); + return 1; + } + + if (ptrace(PTRACE_KILL, pid, NULL, NULL) != 0) { + return fail("kill invalid-resume child"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("wait invalid-resume child"); + } + + printf(" ok: invalid resume signal failed with EIO\n"); + return 0; +} + +int main(void) +{ + int pass = 0; + int fail_count = 0; + + if (test_singlestep() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_waitid_wstopped() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_fpregs() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_attach() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_setoptions() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_setregs() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_waitid_attach() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_syscall_trace() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_signal_resume() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_legacy_regsets() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_siginfo_roundtrip() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_traceexit_event() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_traceclone_thread_event() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_tracevforkdone_event() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_ptrace_kill_reports_signaled() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_sigkill_event_stopped_tracee() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_traceme_rejects_second_call() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_invalid_resume_signal() == 0) { + pass++; + } else { + fail_count++; + } + + printf("DONE: %d pass, %d fail\n", pass, fail_count); + return fail_count > 0 ? 1 : 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/qemu-riscv64.toml new file mode 100644 index 0000000000..37ac810004 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-gdb/qemu-riscv64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-gdb" +success_regex = ["DONE: 18 pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/CMakeLists.txt new file mode 100644 index 0000000000..fe01e59745 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ptrace-traceme-stop C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-ptrace-traceme-stop src/main.c) +target_compile_options(test-ptrace-traceme-stop PRIVATE -Wall -Wextra -Werror) + +install(TARGETS test-ptrace-traceme-stop RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/src/main.c new file mode 100644 index 0000000000..4892670572 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/c/src/main.c @@ -0,0 +1,297 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef PTRACE_SETREGSET +#define PTRACE_SETREGSET 0x4205 +#endif + +#define TRACE_WORD_INITIAL 0x12345678UL +#define TRACE_WORD_PATCHED 0x55aa7711UL +#define RISCV_EBREAK_INSN 0x00100073UL + +struct trace_addrs { + uintptr_t data_addr; + uintptr_t text_addr; +}; + +static volatile int trace_return_value = 7; +static volatile sig_atomic_t sigchld_seen = 0; + +__attribute__((noinline, aligned(8))) static int trace_target_function(void) +{ + return trace_return_value; +} + +static void sigchld_handler(int signo) +{ + (void)signo; + sigchld_seen = 1; +} + +static int traceme_stop_child(void *arg) +{ + (void)arg; + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(101); + } + if (kill(getpid(), SIGSTOP) != 0) { + _exit(102); + } + _exit(0); +} + +struct riscv_user_regs { + unsigned long pc; + unsigned long ra; + unsigned long sp; + unsigned long gp; + unsigned long tp; + unsigned long t0; + unsigned long t1; + unsigned long t2; + unsigned long s0; + unsigned long s1; + unsigned long a0; + unsigned long a1; + unsigned long a2; + unsigned long a3; + unsigned long a4; + unsigned long a5; + unsigned long a6; + unsigned long a7; + unsigned long s2; + unsigned long s3; + unsigned long s4; + unsigned long s5; + unsigned long s6; + unsigned long s7; + unsigned long s8; + unsigned long s9; + unsigned long s10; + unsigned long s11; + unsigned long t3; + unsigned long t4; + unsigned long t5; + unsigned long t6; +}; + +static int fail(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +int main(void) +{ + struct sigaction chld_action; + memset(&chld_action, 0, sizeof(chld_action)); + chld_action.sa_handler = sigchld_handler; + sigemptyset(&chld_action.sa_mask); + chld_action.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &chld_action, NULL) != 0) { + return fail("sigaction SIGCHLD"); + } + + pid_t check_pid = fork(); + if (check_pid < 0) { + return fail("initial fork"); + } + + if (check_pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(101); + } + if (kill(getpid(), SIGSTOP) != 0) { + _exit(102); + } + _exit(0); + } + + int check_status = 0; + if (waitpid(check_pid, &check_status, 0) != check_pid || !WIFSTOPPED(check_status) + || WSTOPSIG(check_status) != SIGSTOP) { + printf("FAIL: expected linux_check_ptrace_features-style SIGSTOP, status=%#x\n", + check_status); + return 1; + } + if (kill(check_pid, SIGKILL) != 0) { + return fail("kill initial child"); + } + if (waitpid(check_pid, &check_status, 0) != check_pid || !WIFSIGNALED(check_status) + || WTERMSIG(check_status) != SIGKILL) { + printf("FAIL: expected initial child SIGKILL, status=%#x\n", check_status); + return 1; + } + + enum { STACK_SIZE = 4096 * 4 }; + char *clone_stack = malloc(STACK_SIZE); + if (clone_stack == NULL) { + return fail("malloc clone stack"); + } + pid_t clone_pid = clone(traceme_stop_child, clone_stack + STACK_SIZE, CLONE_VM | SIGCHLD, NULL); + if (clone_pid < 0) { + free(clone_stack); + return fail("clone"); + } + int clone_status = 0; + if (waitpid(clone_pid, &clone_status, 0) != clone_pid || !WIFSTOPPED(clone_status) + || WSTOPSIG(clone_status) != SIGSTOP) { + printf("FAIL: expected CLONE_VM traced child SIGSTOP, status=%#x\n", clone_status); + free(clone_stack); + return 1; + } + if (kill(clone_pid, SIGKILL) != 0) { + free(clone_stack); + return fail("kill clone child"); + } + if (waitpid(clone_pid, &clone_status, 0) != clone_pid || !WIFSIGNALED(clone_status) + || WTERMSIG(clone_status) != SIGKILL) { + printf("FAIL: expected CLONE_VM traced child SIGKILL, status=%#x\n", clone_status); + free(clone_stack); + return 1; + } + free(clone_stack); + + int pipefd[2]; + if (pipe(pipefd) != 0) { + return fail("pipe"); + } + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + close(pipefd[0]); + volatile unsigned long *trace_word = malloc(sizeof(*trace_word)); + if (trace_word == NULL) { + _exit(100); + } + *trace_word = TRACE_WORD_INITIAL; + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(101); + } + struct trace_addrs addrs = { + .data_addr = (uintptr_t)trace_word, + .text_addr = (uintptr_t)trace_target_function, + }; + if (write(pipefd[1], &addrs, sizeof(addrs)) != (ssize_t)sizeof(addrs)) { + _exit(102); + } + close(pipefd[1]); + if (kill(getpid(), SIGSTOP) != 0) { + _exit(103); + } + if (*trace_word != TRACE_WORD_PATCHED) { + _exit(104); + } + if (trace_target_function() != 7) { + _exit(105); + } + _exit(42); + } + + close(pipefd[1]); + struct trace_addrs addrs = {0}; + if (read(pipefd[0], &addrs, sizeof(addrs)) != (ssize_t)sizeof(addrs)) { + return fail("read trace addrs"); + } + close(pipefd[0]); + + int status = 0; + if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGSTOP) { + printf("FAIL: expected initial SIGSTOP, status=%#x\n", status); + return 1; + } + + struct riscv_user_regs regs = {0}; + struct iovec iov = {.iov_base = ®s, .iov_len = sizeof(regs)}; + if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return fail("ptrace getregset"); + } + if (regs.pc == 0 || regs.sp == 0 || (size_t)iov.iov_len != sizeof(regs)) { + printf("FAIL: invalid initial registers pc=%#lx sp=%#lx len=%zu\n", + regs.pc, regs.sp, (size_t)iov.iov_len); + return 1; + } + + errno = 0; + long word = ptrace(PTRACE_PEEKDATA, pid, (void *)addrs.data_addr, NULL); + if ((word == -1 && errno != 0) || (unsigned long)word != TRACE_WORD_INITIAL) { + return fail("ptrace peekdata"); + } + if (ptrace(PTRACE_POKEDATA, pid, (void *)addrs.data_addr, (void *)TRACE_WORD_PATCHED) != 0) { + return fail("ptrace pokedata"); + } + + errno = 0; + long text_word = ptrace(PTRACE_PEEKDATA, pid, (void *)addrs.text_addr, NULL); + if (text_word == -1 && errno != 0) { + return fail("ptrace peek text"); + } + unsigned long breakpoint_word = + (((unsigned long)text_word) & ~0xffffffffUL) | RISCV_EBREAK_INSN; + if (ptrace(PTRACE_POKEDATA, pid, (void *)addrs.text_addr, (void *)breakpoint_word) != 0) { + return fail("ptrace poke breakpoint"); + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("ptrace cont to breakpoint"); + } + if (waitpid(pid, &status, WUNTRACED) != pid || !WIFSTOPPED(status) + || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected breakpoint SIGTRAP, status=%#x\n", status); + return 1; + } + + memset(®s, 0, sizeof(regs)); + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return fail("ptrace getregset at breakpoint"); + } + if (regs.pc != addrs.text_addr) { + printf("FAIL: expected breakpoint pc=%#lx, got %#lx\n", + (unsigned long)addrs.text_addr, regs.pc); + return 1; + } + if (ptrace(PTRACE_POKEDATA, pid, (void *)addrs.text_addr, (void *)text_word) != 0) { + return fail("ptrace restore text"); + } + regs.pc = addrs.text_addr; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return fail("ptrace reset breakpoint pc"); + } + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("ptrace cont after breakpoint"); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || WEXITSTATUS(status) != 42) { + printf("FAIL: expected child exit 42, status=%#x\n", status); + return 1; + } + + puts("DONE: 1 pass, 0 fail"); + return 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/qemu-riscv64.toml new file mode 100644 index 0000000000..e3c1cc8c2f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-traceme-stop/qemu-riscv64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-traceme-stop" +success_regex = ["DONE: 1 pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/CMakeLists.txt new file mode 100644 index 0000000000..b871517dc9 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.20) +project(test-user-backtrace C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-user-backtrace src/main.c) +target_compile_options(test-user-backtrace PRIVATE + -Wall + -Wextra + -Werror + -O0 + -fno-omit-frame-pointer + -fno-optimize-sibling-calls +) + +install(TARGETS test-user-backtrace RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/src/main.c new file mode 100644 index 0000000000..2d89d60489 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/c/src/main.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +__attribute__((noinline)) static void crash_leaf(void) +{ + volatile int *p = (volatile int *)0; + *p = 42; +} + +__attribute__((noinline)) static void crash_level_3(void) +{ + crash_leaf(); +} + +__attribute__((noinline)) static void crash_level_2(void) +{ + crash_level_3(); +} + +__attribute__((noinline)) static void crash_level_1(void) +{ + crash_level_2(); +} + +int main(void) +{ + pid_t pid = fork(); + if (pid < 0) { + puts("FAIL: fork"); + return 1; + } + + if (pid == 0) { + crash_level_1(); + _exit(0); + } + + int status = 0; + pid_t got = waitpid(pid, &status, 0); + if (got != pid) { + puts("FAIL: waitpid"); + return 1; + } + + int by_signal = WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV; + int by_exit = WIFEXITED(status) + && (WEXITSTATUS(status) == SIGSEGV + || WEXITSTATUS(status) == 128 + SIGSEGV); + if (!by_signal && !by_exit) { + printf("FAIL: child status %#x\n", status); + return 1; + } + + puts("DONE: 1 pass, 0 fail"); + return 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/qemu-riscv64.toml new file mode 100644 index 0000000000..79ccc70273 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-user-backtrace/qemu-riscv64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", "-cpu", "rv64", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/target/rootfs/rootfs-riscv64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-user-backtrace" +success_regex = ["(?s)user register dump:.*?user backtrace:.*?DONE: 1 pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 From 86ef92036adb3d079326321cfb396d18238da862 Mon Sep 17 00:00:00 2001 From: Josen-B <65878371+Josen-B@users.noreply.github.com> Date: Thu, 28 May 2026 10:58:31 +0800 Subject: [PATCH 67/71] feat(axvisor): support x86_64 Linux guest boot (vmx) (#930) * docs(axvisor): add x86_64 Linux guest support plan and phased breakdown * feat(axvisor): add x86_64 Linux SMP1 guest config and initramfs build script * feat(axvisor): add x86_64 Linux bzImage header detection and parsing * feat(axvisor): load x86_64 Linux bzImage payload and initramfs into guest memory * feat(axvisor): construct x86_64 Linux boot_params and reorganize x86 image modules * feat(axvisor): add x86_64 Linux boot stub for direct-boot protected-mode entry * feat(axvisor): add x86_64 Linux command line support and complete initramfs boot * feat(axvisor): enable x86_64 APIC virtualization, PCI passthrough, and MP table for Linux guest * fix(axvisor): improve x86 vLAPIC timer, APIC MMIO write decoding, and IO APIC setup * feat(axvisor): add vIOAPIC emulation and EPT MMIO decode for x86_64 Linux guest * feat(axvisor): switch x86_64 Linux to real ext4 rootfs with default boot params * feat(axvisor): add x86 PIT/serial emulation, VMX virtual interrupt delivery, and generalize IOAPIC IRQ routing * feat(axvisor): replace RefCell with Mutex in axvcpu, consolidate x86 pending events, and add IRQ hook for IOAPIC forwarding * feat(axvisor): unmount host filesystem before guest passthrough and switch to Linux guest smoke test * fix(axvisor): harden filesystem release gate, clean up IOAPIC teardown, and tighten timer lock scope * docs(axvisor): remove completed x86_64 Linux guest support phase documents * chore(axvisor): clean up x86 linux rebase artifacts * refactor(axvisor): extract x86 device IRQ forwarding into dedicated devices module * test(axdevice): add mock axvisor_api implementations for x86_64 unit tests * fix(axvisor): resolve x86 linux review feedback * fix(axvisor): enable raw_apic_id under irq feature in addition to smp * refactor(axvisor): introduce InterruptTriggerMode enum and rename VTimer to PreemptionTimer * fix(axvisor): repair x86 linux rebase fallout --- Cargo.lock | 4 + os/arceos/modules/axfs/src/fs/ext4fs.rs | 12 + os/arceos/modules/axfs/src/lib.rs | 11 +- os/arceos/modules/axfs/src/root.rs | 15 + .../configs/board/qemu-x86_64-linux.toml | 8 + .../configs/qemu/qemu-x86_64-linux.toml | 28 + os/axvisor/configs/qemu/qemu-x86_64.toml | 2 +- .../configs/vms/arceos-x86_64-qemu-smp1.toml | 7 - .../configs/vms/linux-x86_64-qemu-smp1.toml | 76 +++ .../configs/vms/nimbos-x86_64-qemu-smp1.toml | 7 - .../scripts/build_x86_64_linux_initramfs.sh | 148 +++++ os/axvisor/src/hal/arch/x86_64/mod.rs | 15 +- os/axvisor/src/hal/impl_console.rs | 15 + os/axvisor/src/hal/impl_vmm.rs | 18 +- os/axvisor/src/hal/mod.rs | 1 + os/axvisor/src/vmm/config.rs | 14 +- os/axvisor/src/vmm/devices/mod.rs | 2 + os/axvisor/src/vmm/devices/x86.rs | 221 +++++++ os/axvisor/src/vmm/images/mod.rs | 328 +++++++++- os/axvisor/src/vmm/images/x86/boot_params.rs | 615 ++++++++++++++++++ os/axvisor/src/vmm/images/x86/linux.rs | 458 +++++++++++++ os/axvisor/src/vmm/images/x86/linux_boot.rs | 177 +++++ os/axvisor/src/vmm/images/x86/mod.rs | 8 + os/axvisor/src/vmm/images/x86/mptable.rs | 200 ++++++ .../images/{x86_boot.rs => x86/multiboot.rs} | 0 os/axvisor/src/vmm/mod.rs | 22 + os/axvisor/src/vmm/timer.rs | 36 +- os/axvisor/src/vmm/vcpus.rs | 37 +- platforms/ax-plat-x86-pc/src/time.rs | 3 +- platforms/ax-plat-x86-qemu-q35/src/apic.rs | 47 +- platforms/ax-plat-x86-qemu-q35/src/time.rs | 3 +- .../qemu/build-x86_64-unknown-none.toml | 2 +- .../normal/qemu/smoke/qemu-x86_64.toml | 22 +- virtualization/axdevice/Cargo.toml | 5 + virtualization/axdevice/src/device.rs | 97 +++ virtualization/axdevice/tests/test.rs | 40 ++ virtualization/axvcpu/Cargo.toml | 1 + virtualization/axvcpu/src/arch_vcpu.rs | 31 + virtualization/axvcpu/src/exit.rs | 12 + virtualization/axvcpu/src/lib.rs | 2 +- virtualization/axvcpu/src/vcpu.rs | 68 +- virtualization/axvisor_api/src/console.rs | 13 + virtualization/axvisor_api/src/lib.rs | 1 + virtualization/axvm/src/config.rs | 8 + virtualization/axvm/src/vcpu.rs | 1 + virtualization/axvm/src/vm.rs | 40 +- virtualization/axvmconfig/src/lib.rs | 12 + virtualization/x86_vcpu/src/lib.rs | 16 +- virtualization/x86_vcpu/src/svm/vcpu.rs | 15 +- virtualization/x86_vcpu/src/vmx/mod.rs | 8 +- virtualization/x86_vcpu/src/vmx/vcpu.rs | 527 +++++++++++++-- virtualization/x86_vlapic/Cargo.toml | 1 + virtualization/x86_vlapic/src/lib.rs | 42 +- virtualization/x86_vlapic/src/pit.rs | 228 +++++++ virtualization/x86_vlapic/src/regs/mod.rs | 4 +- virtualization/x86_vlapic/src/serial.rs | 211 ++++++ virtualization/x86_vlapic/src/timer.rs | 174 +++-- virtualization/x86_vlapic/src/vioapic.rs | 266 ++++++++ virtualization/x86_vlapic/src/vlapic.rs | 137 ++-- 59 files changed, 4288 insertions(+), 234 deletions(-) create mode 100644 os/axvisor/configs/board/qemu-x86_64-linux.toml create mode 100644 os/axvisor/configs/qemu/qemu-x86_64-linux.toml create mode 100644 os/axvisor/configs/vms/linux-x86_64-qemu-smp1.toml create mode 100755 os/axvisor/scripts/build_x86_64_linux_initramfs.sh create mode 100644 os/axvisor/src/hal/impl_console.rs create mode 100644 os/axvisor/src/vmm/devices/mod.rs create mode 100644 os/axvisor/src/vmm/devices/x86.rs create mode 100644 os/axvisor/src/vmm/images/x86/boot_params.rs create mode 100644 os/axvisor/src/vmm/images/x86/linux.rs create mode 100644 os/axvisor/src/vmm/images/x86/linux_boot.rs create mode 100644 os/axvisor/src/vmm/images/x86/mod.rs create mode 100644 os/axvisor/src/vmm/images/x86/mptable.rs rename os/axvisor/src/vmm/images/{x86_boot.rs => x86/multiboot.rs} (100%) create mode 100644 virtualization/axvisor_api/src/console.rs create mode 100644 virtualization/x86_vlapic/src/pit.rs create mode 100644 virtualization/x86_vlapic/src/serial.rs create mode 100644 virtualization/x86_vlapic/src/vioapic.rs diff --git a/Cargo.lock b/Cargo.lock index c3f22e9a35..5aeecab1cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1772,10 +1772,12 @@ dependencies = [ "ax-memory-addr", "axaddrspace", "axdevice_base", + "axvisor_api", "axvmconfig", "cfg-if", "log", "riscv_vplic", + "x86_vlapic", ] [[package]] @@ -1946,6 +1948,7 @@ name = "axvcpu" version = "0.5.9" dependencies = [ "ax-errno", + "ax-kspin", "ax-memory-addr", "ax-percpu", "axaddrspace", @@ -10243,6 +10246,7 @@ name = "x86_vlapic" version = "0.4.10" dependencies = [ "ax-errno", + "ax-kspin", "ax-memory-addr", "axaddrspace", "axdevice_base", diff --git a/os/arceos/modules/axfs/src/fs/ext4fs.rs b/os/arceos/modules/axfs/src/fs/ext4fs.rs index f0ea2117b3..6cbffb328c 100644 --- a/os/arceos/modules/axfs/src/fs/ext4fs.rs +++ b/os/arceos/modules/axfs/src/fs/ext4fs.rs @@ -93,6 +93,12 @@ impl Ext4FileSystem { /// The [`VfsOps`] trait provides operations on a filesystem. impl VfsOps for Ext4FileSystem { + fn umount(&self) -> VfsResult { + let mut fs = self.fs.lock(); + let mut inner = self.inner.lock(); + fs.umount(&mut inner).map_err(|_| VfsError::Io) + } + fn root_dir(&self) -> VfsNodeRef { debug!("Get root_dir"); Arc::new(FileWrapper::new( @@ -105,6 +111,12 @@ impl VfsOps for Ext4FileSystem { /// The [`VfsOps`] trait provides operations on a filesystem. impl VfsOps for Ext4FileSystemPartition { + fn umount(&self) -> VfsResult { + let mut fs = self.fs.lock(); + let mut inner = self.inner.lock(); + fs.umount(&mut inner).map_err(|_| VfsError::Io) + } + fn root_dir(&self) -> VfsNodeRef { debug!("Get root_dir"); Arc::new(FileWrapper::new( diff --git a/os/arceos/modules/axfs/src/lib.rs b/os/arceos/modules/axfs/src/lib.rs index 79c059dc0e..1ca7764330 100644 --- a/os/arceos/modules/axfs/src/lib.rs +++ b/os/arceos/modules/axfs/src/lib.rs @@ -41,7 +41,11 @@ use crate::{dev::Disk, partition::PartitionInfo}; pub fn init_filesystems(mut block_devs: Vec>, bootargs: Option<&str>) { info!("Initialize filesystems..."); - let dev = block_devs.pop().expect("No block device found!"); + let Some(dev) = block_devs.pop() else { + warn!("No block device found, mount ramfs as rootfs"); + self::root::init_rootfs_with_ramfs(); + return; + }; info!(" use block device 0: {:?}", dev.name()); let mut disk = Disk::new(dev); @@ -63,6 +67,11 @@ pub fn init_filesystems(mut block_devs: Vec>, bootargs: O } } +/// Cleanly unmounts the initialized root filesystem. +pub fn shutdown_filesystems() -> ax_errno::AxResult { + self::root::shutdown_rootfs() +} + /// Initialize filesystems with detected partitions fn initialize_with_partitions(disk: Disk, partitions: Vec, root_spec: &RootSpec) { info!( diff --git a/os/arceos/modules/axfs/src/root.rs b/os/arceos/modules/axfs/src/root.rs index 5ba0e364d9..97570a6d7b 100644 --- a/os/arceos/modules/axfs/src/root.rs +++ b/os/arceos/modules/axfs/src/root.rs @@ -96,6 +96,14 @@ impl RootDirectory { self.mounts.iter().any(|mp| mp.path == path) } + pub fn shutdown(&self) -> AxResult { + for mp in &self.mounts { + mp.fs.umount()?; + } + self.main_fs.umount()?; + Ok(()) + } + /// Normalize path by trimming leading '/' and handling './' prefix fn normalize_path<'a>(&self, path: &'a str) -> &'a str { let path = path.trim_matches('/'); @@ -443,6 +451,13 @@ pub fn mount_virtual_fs(mut root_dir: RootDirectory) { *CURRENT_DIR_PATH.lock() = "/".into(); } +pub(crate) fn shutdown_rootfs() -> AxResult { + if let Some(root_dir) = ROOT_DIR.get() { + root_dir.shutdown()?; + } + Ok(()) +} + fn parent_node_of(dir: Option<&VfsNodeRef>, path: &str) -> VfsNodeRef { if path.starts_with('/') { ROOT_DIR.clone() diff --git a/os/axvisor/configs/board/qemu-x86_64-linux.toml b/os/axvisor/configs/board/qemu-x86_64-linux.toml new file mode 100644 index 0000000000..6f4740549f --- /dev/null +++ b/os/axvisor/configs/board/qemu-x86_64-linux.toml @@ -0,0 +1,8 @@ +env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } +features = [ + "ept-level-4", + "fs", +] +log = "Info" +target = "x86_64-unknown-none" +vm_configs = [] diff --git a/os/axvisor/configs/qemu/qemu-x86_64-linux.toml b/os/axvisor/configs/qemu/qemu-x86_64-linux.toml new file mode 100644 index 0000000000..eb74d36a28 --- /dev/null +++ b/os/axvisor/configs/qemu/qemu-x86_64-linux.toml @@ -0,0 +1,28 @@ +args = [ + "-nodefaults", + "-no-user-config", + "-display", + "none", + "-serial", + "stdio", + "-monitor", + "none", + "-cpu", + "host", + "-machine", + "q35,sata=off,smbus=off,i8042=off,usb=off,graphics=off", + "-smp", + "1", + "-accel", + "kvm", + "-device", + "virtio-blk-pci,drive=disk0,addr=03.0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-m", + "512M", +] +fail_regex = [] +success_regex = [] +to_bin = false +uefi = false diff --git a/os/axvisor/configs/qemu/qemu-x86_64.toml b/os/axvisor/configs/qemu/qemu-x86_64.toml index 17029daad0..34d8b99472 100644 --- a/os/axvisor/configs/qemu/qemu-x86_64.toml +++ b/os/axvisor/configs/qemu/qemu-x86_64.toml @@ -13,7 +13,7 @@ args = [ "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", "-m", - "128M", + "512M", ] fail_regex = [] success_regex = [] diff --git a/os/axvisor/configs/vms/arceos-x86_64-qemu-smp1.toml b/os/axvisor/configs/vms/arceos-x86_64-qemu-smp1.toml index 792b76a1a9..a959d42124 100644 --- a/os/axvisor/configs/vms/arceos-x86_64-qemu-smp1.toml +++ b/os/axvisor/configs/vms/arceos-x86_64-qemu-smp1.toml @@ -65,13 +65,6 @@ passthrough_devices = [ 0x1000, 0x1, ], - [ - "Local APIC", - 0xfee0_0000, - 0xfee0_0000, - 0x1000, - 0x1, - ], [ "HPET", 0xfed0_0000, diff --git a/os/axvisor/configs/vms/linux-x86_64-qemu-smp1.toml b/os/axvisor/configs/vms/linux-x86_64-qemu-smp1.toml new file mode 100644 index 0000000000..377c1bf500 --- /dev/null +++ b/os/axvisor/configs/vms/linux-x86_64-qemu-smp1.toml @@ -0,0 +1,76 @@ +# Vm base info configs +# +[base] +# Guest vm id. +id = 1 +# Guest vm name. +name = "linux-x86_64-qemu" +# Virtualization type. +vm_type = 1 +# The number of virtual CPUs. +cpu_num = 1 +# Guest vm physical cpu sets. +phys_cpu_sets = [1] + +# +# Vm kernel configs +# +[kernel] +# The entry point of the kernel image. +# Linux direct boot uses the Linux-specific boot stub at this GPA. +entry_point = 0x8000 +# The location of image: "memory" | "fs". +image_location = "fs" +# The file path of the kernel image. +kernel_path = "/guest/linux/linux-qemu" +# The load address of the kernel image. +kernel_load_addr = 0x20_0000 +# Whether to enable BIOS boot flow. +enable_bios = false + +## The path of the disk image. +# disk_path = "" + +# Memory regions with format (`base_paddr`, `size`, `flags`, `map_type`). +# For `map_type`, 0 means `MAP_ALLOC`, 1 means `MAP_IDENTICAL`, 2 means `MAP_RESERVED`. +memory_regions = [ + [0x0000_0000, 0x0800_0000, 0x7, 1], # System RAM 128M MAP_IDENTICAL for DMA-capable passthrough + [0x0000_0000, 0x0010_0000, 0x7, 0], # Low boot scratch for real-mode stub and boot_params +] + +# +# Device specifications +# +[devices] +# The interrupt mode. +interrupt_mode = "passthrough" + +# Emu_devices. +# Name Base-Ipa Ipa_len Alloc-Irq Emu-Type EmuConfig. +emu_devices = [ + ["x86-com1", 0x3f8, 0x8, 0x0, 0x2, []], + ["x86-ioapic", 0xfec0_0000, 0x1000, 0x0, 0x23, []], + ["x86-pit", 0x40, 0x22, 0x0, 0x24, []], +] + +# Pass-through devices. +# Name Base-Ipa Base-Pa Length Alloc-Irq. +passthrough_devices = [ + [ + "HPET", + 0xfed0_0000, + 0xfed0_0000, + 0x1000, + 0x1, + ], +] + +# Passthrough addresses. +# Base-GPA Length. +passthrough_addresses = [ + # QEMU q35 low MMIO window needed by PCI/virtio-blk probing. + [0xfe00_0000, 0x00c0_0000], +] + +# Devices that are not desired to be passed through to the guest +excluded_devices = [] diff --git a/os/axvisor/configs/vms/nimbos-x86_64-qemu-smp1.toml b/os/axvisor/configs/vms/nimbos-x86_64-qemu-smp1.toml index 29a6183ab2..4c07aae8e9 100644 --- a/os/axvisor/configs/vms/nimbos-x86_64-qemu-smp1.toml +++ b/os/axvisor/configs/vms/nimbos-x86_64-qemu-smp1.toml @@ -67,13 +67,6 @@ passthrough_devices = [ 0x1000, 0x1, ], - [ - "Local APIC", - 0xfee0_0000, - 0xfee0_0000, - 0x1000, - 0x1, - ], [ "HPET", 0xfed0_0000, diff --git a/os/axvisor/scripts/build_x86_64_linux_initramfs.sh b/os/axvisor/scripts/build_x86_64_linux_initramfs.sh new file mode 100755 index 0000000000..b00eee2a28 --- /dev/null +++ b/os/axvisor/scripts/build_x86_64_linux_initramfs.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Build a tiny x86_64 Linux initramfs for Axvisor phase-0 bring-up. +# +# The archive contains a tiny static /init that mounts devtmpfs, opens +# /dev/console, writes one line, optionally reads one sector from /dev/vda, and +# then idles forever. The block check is read-only and non-fatal so the same +# initramfs can serve early bring-up and PCI/virtio-blk smoke tests. Serial +# output is provided by the kernel command line's console=ttyS0 setting rather +# than by userspace I/O port access. + +OUT="${1:-tmp/linux-x86_64/initramfs.cpio}" +WORKDIR="$(mktemp -d)" +trap 'rm -rf "${WORKDIR}"' EXIT + +case "${OUT}" in + /*) OUT_ABS="${OUT}" ;; + *) OUT_ABS="${PWD}/${OUT}" ;; +esac + +mkdir -p "${WORKDIR}/root/dev" "${WORKDIR}/root/proc" "${WORKDIR}/root/sys" "$(dirname "${OUT_ABS}")" + +cat > "${WORKDIR}/init.c" <<'INIT_EOF' +#define SYS_read 0 +#define SYS_write 1 +#define SYS_open 2 +#define SYS_close 3 +#define SYS_mount 165 +#define SYS_dup2 33 +#define SYS_nanosleep 35 + +#define O_RDONLY 0 +#define O_RDWR 02 + +struct timespec { + long tv_sec; + long tv_nsec; +}; + +static long syscall6(long nr, long a0, long a1, long a2, long a3, long a4, long a5) { + long ret; + register long r10 __asm__("r10") = a3; + register long r8 __asm__("r8") = a4; + register long r9 __asm__("r9") = a5; + __asm__ volatile ( + "syscall" + : "=a"(ret) + : "a"(nr), "D"(a0), "S"(a1), "d"(a2), "r"(r10), "r"(r8), "r"(r9) + : "rcx", "r11", "memory" + ); + return ret; +} + +static long sys_write(long fd, const char *buf, long len) { + return syscall6(SYS_write, fd, (long)buf, len, 0, 0, 0); +} + +static long sys_open(const char *path, long flags, long mode) { + return syscall6(SYS_open, (long)path, flags, mode, 0, 0, 0); +} + +static long sys_mount(const char *src, const char *target, const char *fstype) { + return syscall6(SYS_mount, (long)src, (long)target, (long)fstype, 0, 0, 0); +} + +static long sys_read(long fd, char *buf, long len) { + return syscall6(SYS_read, fd, (long)buf, len, 0, 0, 0); +} + +static void sys_dup2(long oldfd, long newfd) { + syscall6(SYS_dup2, oldfd, newfd, 0, 0, 0, 0); +} + +static void sleep_forever(void) { + struct timespec ts = { .tv_sec = 3600, .tv_nsec = 0 }; + for (;;) { + syscall6(SYS_nanosleep, (long)&ts, 0, 0, 0, 0, 0); + } +} + +void _start(void) { + const char msg[] = "axvisor x86_64 linux initramfs reached /init\n"; + const char blk_ok[] = "axvisor x86_64 linux virtio-blk read /dev/vda ok\n"; + const char blk_skip[] = "axvisor x86_64 linux virtio-blk /dev/vda not ready\n"; + char sector[512]; + + sys_mount("devtmpfs", "/dev", "devtmpfs"); + long kmsg = sys_open("/dev/kmsg", O_RDWR, 0); + if (kmsg >= 0) { + sys_write(kmsg, msg, sizeof(msg) - 1); + } + long console = sys_open("/dev/console", O_RDWR, 0); + if (console < 0) { + console = sys_open("/dev/ttyS0", O_RDWR, 0); + } + if (console >= 0) { + sys_dup2(console, 0); + sys_dup2(console, 1); + sys_dup2(console, 2); + sys_write(console, msg, sizeof(msg) - 1); + } + sys_write(1, msg, sizeof(msg) - 1); + sys_write(2, msg, sizeof(msg) - 1); + + long vda = sys_open("/dev/vda", O_RDONLY, 0); + if (vda >= 0 && sys_read(vda, sector, sizeof(sector)) == sizeof(sector)) { + if (kmsg >= 0) { + sys_write(kmsg, blk_ok, sizeof(blk_ok) - 1); + } + sys_write(1, blk_ok, sizeof(blk_ok) - 1); + } else { + if (kmsg >= 0) { + sys_write(kmsg, blk_skip, sizeof(blk_skip) - 1); + } + sys_write(1, blk_skip, sizeof(blk_skip) - 1); + } + + sleep_forever(); +} +INIT_EOF + +command -v cpio >/dev/null 2>&1 || { + echo "ERROR: cpio is required to build the initramfs" >&2 + exit 1 +} + +if command -v musl-gcc >/dev/null 2>&1; then + CC=musl-gcc +else + CC=${CC:-gcc} +fi + +command -v "${CC}" >/dev/null 2>&1 || { + echo "ERROR: ${CC} is required to build the initramfs" >&2 + exit 1 +} + +"${CC}" -static -Os -nostdlib -ffreestanding -fno-stack-protector \ + -o "${WORKDIR}/root/init" "${WORKDIR}/init.c" +chmod 0755 "${WORKDIR}/root/init" + +( + cd "${WORKDIR}/root" + find . -print0 | cpio --null -o --format=newc > "${OUT_ABS}" +) + +echo "Wrote ${OUT_ABS}" diff --git a/os/axvisor/src/hal/arch/x86_64/mod.rs b/os/axvisor/src/hal/arch/x86_64/mod.rs index 01e0ec5e76..733f6330f8 100644 --- a/os/axvisor/src/hal/arch/x86_64/mod.rs +++ b/os/axvisor/src/hal/arch/x86_64/mod.rs @@ -12,7 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod cache; +use axvisor_api::vmm::{current_vcpu_id, current_vm_id, inject_interrupt as inject_vcpu_interrupt}; pub fn hardware_check() {} -pub fn inject_interrupt(_vector: u8) {} + +pub mod cache; + +pub fn inject_interrupt(vector: u8) { + let vm_id = current_vm_id(); + let vcpu_id = current_vcpu_id(); + + debug!( + "Injecting x86_64 virtual interrupt: vm_id={vm_id}, vcpu_id={vcpu_id}, vector={vector:#x}" + ); + inject_vcpu_interrupt(vm_id, vcpu_id, vector); +} diff --git a/os/axvisor/src/hal/impl_console.rs b/os/axvisor/src/hal/impl_console.rs new file mode 100644 index 0000000000..dce2105e33 --- /dev/null +++ b/os/axvisor/src/hal/impl_console.rs @@ -0,0 +1,15 @@ +use axvisor_api::console::ConsoleIf; +use std::os::arceos::modules::ax_hal; + +pub struct ConsoleImpl; + +#[axvisor_api::api_impl] +impl ConsoleIf for ConsoleImpl { + fn write_bytes(bytes: &[u8]) { + ax_hal::console::write_bytes(bytes); + } + + fn read_bytes(bytes: &mut [u8]) -> usize { + ax_hal::console::read_bytes(bytes) + } +} diff --git a/os/axvisor/src/hal/impl_vmm.rs b/os/axvisor/src/hal/impl_vmm.rs index 9fdde61cbf..0b444c9f6f 100644 --- a/os/axvisor/src/hal/impl_vmm.rs +++ b/os/axvisor/src/hal/impl_vmm.rs @@ -20,8 +20,16 @@ impl VmmIf for VmmImpl { vmm::with_vm(vm_id, |vm| vm.vcpu_num()) } - fn active_vcpus(_vm_id: VMId) -> Option { - todo!("active_vcpus") + fn active_vcpus(vm_id: VMId) -> Option { + vmm::with_vm(vm_id, |vm| { + let vcpu_num = vm.vcpu_num(); + if vcpu_num >= usize::BITS as usize { + usize::MAX + } else { + // The VmmIf contract returns an active-vCPU bitmask, not a count. + (1usize << vcpu_num) - 1 + } + }) } fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector) { @@ -30,8 +38,10 @@ impl VmmIf for VmmImpl { }); } - fn inject_interrupt_to_cpus(_vm_id: VMId, _vcpu_set: VCpuSet, _vector: InterruptVector) { - todo!("inject_interrupt_to_cpus") + fn inject_interrupt_to_cpus(vm_id: VMId, vcpu_set: VCpuSet, vector: InterruptVector) { + for vcpu_id in &vcpu_set { + Self::inject_interrupt(vm_id, vcpu_id, vector); + } } fn notify_vcpu_timer_expired(_vm_id: VMId, _vcpu_id: VCpuId) { diff --git a/os/axvisor/src/hal/mod.rs b/os/axvisor/src/hal/mod.rs index 4c92940b81..97425fd0fa 100644 --- a/os/axvisor/src/hal/mod.rs +++ b/os/axvisor/src/hal/mod.rs @@ -129,6 +129,7 @@ pub(crate) fn enable_virtualization() { info!("All cores have enabled hardware virtualization support."); } +mod impl_console; mod impl_host; mod impl_memory; mod impl_time; diff --git a/os/axvisor/src/vmm/config.rs b/os/axvisor/src/vmm/config.rs index 607b0769c9..b85d78a21b 100644 --- a/os/axvisor/src/vmm/config.rs +++ b/os/axvisor/src/vmm/config.rs @@ -225,6 +225,11 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { ))] handle_fdt_operations(&mut vm_config, &mut vm_create_config)?; + #[cfg(target_arch = "x86_64")] + let skip_guest_address_adjustment = x86_linux_direct_boot_config(&vm_create_config); + #[cfg(not(target_arch = "x86_64"))] + let skip_guest_address_adjustment = false; + // info!("after parse_vm_interrupt, crate VM[{}] with config: {:#?}", vm_config.id(), vm_config); info!("Creating VM[{}] {:?}", vm_config.id(), vm_config.name()); @@ -241,7 +246,9 @@ pub fn init_guest_vm(raw_cfg: &str) -> AxResult { .cloned() .ok_or_else(|| ax_err_type!(InvalidData, "VM must have at least one memory region"))?; - config_guest_address(&vm, &main_mem); + if !skip_guest_address_adjustment { + config_guest_address(&vm, &main_mem); + } // Load corresponding images for VM. info!("VM[{}] created success, loading images...", vm.id()); @@ -272,6 +279,11 @@ fn config_guest_address(vm: &VM, main_memory: &VMMemoryRegion) { }); } +#[cfg(target_arch = "x86_64")] +fn x86_linux_direct_boot_config(config: &AxVMCrateConfig) -> bool { + crate::vmm::images::is_x86_linux_image_config(config) +} + fn vm_alloc_memory_regions(vm_create_config: &AxVMCrateConfig, vm: &VM) -> AxResult { const MB: usize = 1024 * 1024; const ALIGN: usize = 2 * MB; diff --git a/os/axvisor/src/vmm/devices/mod.rs b/os/axvisor/src/vmm/devices/mod.rs new file mode 100644 index 0000000000..06ae8e6e8a --- /dev/null +++ b/os/axvisor/src/vmm/devices/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_arch = "x86_64")] +pub mod x86; diff --git a/os/axvisor/src/vmm/devices/x86.rs b/os/axvisor/src/vmm/devices/x86.rs new file mode 100644 index 0000000000..26ed393e21 --- /dev/null +++ b/os/axvisor/src/vmm/devices/x86.rs @@ -0,0 +1,221 @@ +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + +use std::os::arceos::modules::ax_hal::{irq, time}; + +use axvcpu::InterruptTriggerMode; +use axvm::config::VMInterruptMode; + +use crate::vmm::{VCpuRef, VMRef}; + +const IOAPIC_VECTOR_BASE: usize = 0x20; +const IOAPIC_GSI_COUNT: usize = 24; +const IOAPIC_VECTOR_END: usize = IOAPIC_VECTOR_BASE + IOAPIC_GSI_COUNT; + +const PIT_TIMER_GSI: usize = 0; +const COM1_GSI: usize = 4; + +static IOAPIC_IRQ_FORWARDING_ENABLED: AtomicBool = AtomicBool::new(false); +static IOAPIC_IRQ_HOOK_REGISTERED: AtomicBool = AtomicBool::new(false); +static IOAPIC_IRQ_FORWARD_VM_ID: AtomicUsize = AtomicUsize::new(usize::MAX); +static IOAPIC_IRQ_FORWARD_VCPU_ID: AtomicUsize = AtomicUsize::new(usize::MAX); +static IOAPIC_IRQ_PENDING: AtomicUsize = AtomicUsize::new(0); + +pub fn forward_passthrough_irq_from_vmexit(vm: &VMRef, vcpu: &VCpuRef, vector: usize) { + if !IOAPIC_IRQ_HOOK_REGISTERED.load(Ordering::Acquire) { + forward_passthrough_irq(vm, vcpu, vector); + } +} + +pub fn inject_due_pit_irq0(vm: &VMRef, vcpu: &VCpuRef) { + if vm.interrupt_mode() != VMInterruptMode::Passthrough { + return; + } + + let now_ns = time::monotonic_time_nanos() as u64; + if !vm.get_devices().x86_pit_consume_irq0_if_due(now_ns) { + return; + } + + let Some(irq) = vm.get_devices().x86_ioapic_assert_gsi(PIT_TIMER_GSI) else { + trace!("x86 PIT IRQ0 due but vIOAPIC GSI0 is not ready"); + return; + }; + + trace!("Injecting x86 PIT IRQ0 vector {:#x}", irq.vector); + vcpu.inject_interrupt_with_trigger( + irq.vector as _, + if irq.level_triggered { + InterruptTriggerMode::LevelTriggered + } else { + InterruptTriggerMode::EdgeTriggered + }, + ) + .unwrap(); +} + +pub fn inject_pending_serial_irq(vm: &VMRef, vcpu: &VCpuRef) { + if vm.interrupt_mode() != VMInterruptMode::Passthrough { + return; + } + + if !vm.get_devices().x86_serial_poll_irq() { + return; + } + + let Some(irq) = vm.get_devices().x86_ioapic_assert_gsi(COM1_GSI) else { + trace!("x86 COM1 RX pending but vIOAPIC GSI4 is not ready"); + return; + }; + + trace!("Injecting x86 COM1 RX IRQ vector {:#x}", irq.vector); + vcpu.inject_interrupt_with_trigger( + irq.vector as _, + if irq.level_triggered { + InterruptTriggerMode::LevelTriggered + } else { + InterruptTriggerMode::EdgeTriggered + }, + ) + .unwrap(); +} + +pub fn inject_pending_ioapic_irq_after_eoi(vm: &VMRef, vcpu: &VCpuRef, vector: u8) { + if vm.interrupt_mode() != VMInterruptMode::Passthrough { + return; + } + + let Some(irq) = vm.get_devices().x86_ioapic_end_of_interrupt(vector) else { + return; + }; + + trace!( + "Injecting pending x86 IOAPIC level IRQ vector {:#x} after EOI {vector:#x}", + irq.vector + ); + vcpu.inject_interrupt_with_trigger( + irq.vector as _, + if irq.level_triggered { + InterruptTriggerMode::LevelTriggered + } else { + InterruptTriggerMode::EdgeTriggered + }, + ) + .unwrap(); +} + +pub fn drain_pending_ioapic_irqs(vm: &VMRef, vcpu: &VCpuRef) { + if !IOAPIC_IRQ_HOOK_REGISTERED.load(Ordering::Acquire) { + return; + } + + loop { + let pending = IOAPIC_IRQ_PENDING.swap(0, Ordering::AcqRel); + if pending == 0 { + break; + } + + for gsi in 0..IOAPIC_GSI_COUNT { + if pending & (1usize << gsi) != 0 { + forward_passthrough_irq(vm, vcpu, IOAPIC_VECTOR_BASE + gsi); + } + } + } +} + +pub fn enable_ioapic_irq_forwarding(vm: &VMRef, vcpu: &VCpuRef) { + if vm.interrupt_mode() != VMInterruptMode::Passthrough { + return; + } + + IOAPIC_IRQ_FORWARD_VM_ID.store(vm.id(), Ordering::Release); + IOAPIC_IRQ_FORWARD_VCPU_ID.store(vcpu.id(), Ordering::Release); + + if IOAPIC_IRQ_FORWARDING_ENABLED + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_err() + { + return; + } + + if irq::register_irq_hook(ioapic_irq_forwarding_hook) { + IOAPIC_IRQ_HOOK_REGISTERED.store(true, Ordering::Release); + } else { + warn!( + "x86 IOAPIC IRQ forwarding hook is already registered; VM-exit forwarding fallback remains active" + ); + } + + let mut registered = 0; + for vector in IOAPIC_VECTOR_BASE..IOAPIC_VECTOR_END { + if irq::register(vector, |_| {}) { + registered += 1; + } else { + trace!("x86 IOAPIC host vector {vector:#x} already has a host handler"); + } + } + info!( + "Enabled x86 IOAPIC IRQ forwarding for host vectors {:#x}..{:#x} ({} newly registered)", + IOAPIC_VECTOR_BASE, + IOAPIC_VECTOR_END - 1, + registered + ); +} + +pub fn disable_ioapic_irq_forwarding_for_vm(vm_id: usize) { + if IOAPIC_IRQ_FORWARD_VM_ID.load(Ordering::Acquire) != vm_id { + return; + } + + IOAPIC_IRQ_FORWARD_VM_ID.store(usize::MAX, Ordering::Release); + IOAPIC_IRQ_FORWARD_VCPU_ID.store(usize::MAX, Ordering::Release); + IOAPIC_IRQ_PENDING.store(0, Ordering::Release); +} + +fn forward_passthrough_irq(vm: &VMRef, vcpu: &VCpuRef, vector: usize) { + if vm.interrupt_mode() != VMInterruptMode::Passthrough { + return; + } + + if !(IOAPIC_VECTOR_BASE..IOAPIC_VECTOR_END).contains(&vector) { + return; + } + + let host_gsi = vector - IOAPIC_VECTOR_BASE; + let Some(guest_irq) = vm.get_devices().x86_ioapic_assert_gsi(host_gsi) else { + trace!( + "x86 passthrough IRQ vector {vector:#x} has no injectable guest vIOAPIC route for host GSI \ + {host_gsi}" + ); + return; + }; + + debug!( + "Forwarding x86 passthrough IRQ host GSI {host_gsi} vector {vector:#x} to guest vector \ + {:#x}", + guest_irq.vector + ); + vcpu.inject_interrupt_with_trigger( + guest_irq.vector as _, + if guest_irq.level_triggered { + InterruptTriggerMode::LevelTriggered + } else { + InterruptTriggerMode::EdgeTriggered + }, + ) + .unwrap(); +} + +fn ioapic_irq_forwarding_hook(vector: usize) { + if !(IOAPIC_VECTOR_BASE..IOAPIC_VECTOR_END).contains(&vector) { + return; + } + + if IOAPIC_IRQ_FORWARD_VM_ID.load(Ordering::Acquire) == usize::MAX + || IOAPIC_IRQ_FORWARD_VCPU_ID.load(Ordering::Acquire) == usize::MAX + { + return; + } + + let bit = 1usize << (vector - IOAPIC_VECTOR_BASE); + IOAPIC_IRQ_PENDING.fetch_or(bit, Ordering::AcqRel); +} diff --git a/os/axvisor/src/vmm/images/mod.rs b/os/axvisor/src/vmm/images/mod.rs index 7e5da89109..6753e00989 100644 --- a/os/axvisor/src/vmm/images/mod.rs +++ b/os/axvisor/src/vmm/images/mod.rs @@ -16,7 +16,7 @@ use ax_errno::{AxResult, ax_err, ax_err_type}; use axaddrspace::GuestPhysAddr; use axvm::VMMemoryRegion; -use axvm::config::AxVMCrateConfig; +use axvm::config::{AxVMCrateConfig, VmMemMappingType}; use byte_unit::Byte; use crate::hal::CacheOp; @@ -25,7 +25,34 @@ use crate::vmm::config::{get_vm_dtb_arc, vmcfg}; mod linux; #[cfg(target_arch = "x86_64")] -mod x86_boot; +mod x86; +#[cfg(target_arch = "x86_64")] +use x86::boot_params as x86_boot_params; +#[cfg(target_arch = "x86_64")] +use x86::linux as x86_linux; +#[cfg(target_arch = "x86_64")] +use x86::linux_boot as x86_linux_boot; +#[cfg(target_arch = "x86_64")] +use x86::mptable as x86_mptable; +#[cfg(target_arch = "x86_64")] +use x86::multiboot as x86_boot; + +#[cfg(target_arch = "x86_64")] +pub fn is_x86_linux_image_config(config: &AxVMCrateConfig) -> bool { + if config.kernel.enable_bios { + return false; + } + + match config.kernel.image_location.as_deref() { + Some("memory") => with_memory_image(config, detect_x86_linux_image).is_some(), + #[cfg(feature = "fs")] + Some("fs") => fs::kernel_read(config, x86_linux::HEADER_READ_SIZE) + .ok() + .and_then(|data| detect_x86_linux_image(&data)) + .is_some(), + _ => false, + } +} pub fn get_image_header(config: &AxVMCrateConfig) -> Option { match config.kernel.image_location.as_deref() { @@ -70,6 +97,7 @@ pub struct ImageLoader { kernel_load_gpa: GuestPhysAddr, bios_load_gpa: Option, dtb_load_gpa: Option, + ramdisk_load_gpa: Option, } impl ImageLoader { @@ -81,6 +109,7 @@ impl ImageLoader { kernel_load_gpa: GuestPhysAddr::default(), bios_load_gpa: None, dtb_load_gpa: None, + ramdisk_load_gpa: None, } } @@ -97,6 +126,7 @@ impl ImageLoader { self.kernel_load_gpa = config.image_config.kernel_load_gpa; self.dtb_load_gpa = config.image_config.dtb_load_gpa; self.bios_load_gpa = config.image_config.bios_load_gpa; + self.ramdisk_load_gpa = config.image_config.ramdisk.as_ref().map(|r| r.load_gpa); }); match self.config.kernel.image_location.as_deref() { @@ -112,11 +142,20 @@ impl ImageLoader { /// Load VM images from memory /// into the guest VM's memory space based on the VM configuration. - fn load_vm_images_from_memory(&self) -> AxResult { + fn load_vm_images_from_memory(&mut self) -> AxResult { info!("Loading VM[{}] images from memory", self.config.base.id); let vm_imags = memory_images_for_vm(&self.config)?; + #[cfg(target_arch = "x86_64")] + if let Some(header) = detect_x86_linux_image(vm_imags.kernel) { + return self.load_x86_linux_images_from_memory( + header, + vm_imags.kernel, + vm_imags.ramdisk, + ); + } + load_vm_image_from_memory(vm_imags.kernel, self.kernel_load_gpa, self.vm.clone())?; // Load Ramdisk image and record its size before regenerating the DTB. @@ -170,6 +209,41 @@ impl ImageLoader { Ok(()) } + #[cfg(target_arch = "x86_64")] + fn load_x86_linux_images_from_memory( + &mut self, + header: x86_linux::X86LinuxHeader, + kernel: &[u8], + ramdisk: Option<&[u8]>, + ) -> AxResult { + self.adjust_x86_linux_dma_identity_layout()?; + let payload = x86_linux_payload(&header, kernel)?; + let initrd = if let Some(ramdisk) = ramdisk { + Some(x86_linux::X86LinuxRange::new( + self.ramdisk_load_gpa()?.as_usize(), + ramdisk.len(), + )) + } else { + None + }; + let layout = x86_linux::X86LinuxLoadLayout::new( + &header, + self.kernel_load_gpa.as_usize(), + payload.len(), + initrd, + ) + .map_err(x86_linux_layout_error)?; + + self.load_x86_linux_layout(header, layout, kernel)?; + load_vm_image_from_memory(payload, self.kernel_load_gpa, self.vm.clone())?; + + if let Some(buffer) = ramdisk { + self.load_ramdisk_from_memory(buffer)?; + } + + Ok(()) + } + fn load_boot_image_from_memory(&self, bios: Option<&[u8]>) -> AxResult { if !self.config.kernel.enable_bios { return Ok(()); @@ -246,10 +320,7 @@ impl ImageLoader { } fn load_ramdisk_from_memory(&self, ramdisk: &[u8]) -> AxResult { - let load_gpa = self - .vm - .with_config(|config| config.image_config.ramdisk.as_ref().map(|r| r.load_gpa)) - .ok_or_else(|| ax_err_type!(NotFound, "Ramdisk load address is missing"))?; + let load_gpa = self.ramdisk_load_gpa()?; let size = ramdisk.len(); self.vm.with_config(|config| { if let Some(ref mut rd) = config.image_config.ramdisk { @@ -264,6 +335,160 @@ impl ImageLoader { load_vm_image_from_memory(ramdisk, load_gpa, self.vm.clone()) } + fn ramdisk_load_gpa(&self) -> AxResult { + self.ramdisk_load_gpa + .ok_or_else(|| ax_errno::ax_err_type!(NotFound, "Ramdisk load addr is missed")) + } + + #[cfg(target_arch = "x86_64")] + fn adjust_x86_linux_dma_identity_layout(&mut self) -> AxResult { + if !self.main_memory.is_identical() { + return Ok(()); + } + + let memory_base = self.main_memory.gpa.as_usize(); + let configured_kernel = self.config.kernel.kernel_load_addr; + let configured_ramdisk = self.config.kernel.ramdisk_load_addr; + + self.kernel_load_gpa = GuestPhysAddr::from(memory_base + configured_kernel); + if let Some(ramdisk_load_addr) = configured_ramdisk { + self.ramdisk_load_gpa = Some(GuestPhysAddr::from(memory_base + ramdisk_load_addr)); + } + + self.vm.with_config(|config| { + config.image_config.kernel_load_gpa = self.kernel_load_gpa; + if let Some(load_gpa) = self.ramdisk_load_gpa + && let Some(ref mut ramdisk) = config.image_config.ramdisk + { + ramdisk.load_gpa = load_gpa; + } + }); + + info!( + "Adjusted x86 Linux identity DMA layout for VM[{}]: memory_base={:#x}, kernel_load_gpa={:#x}, ramdisk_load_gpa={:?}", + self.vm.id(), + memory_base, + self.kernel_load_gpa.as_usize(), + self.ramdisk_load_gpa + ); + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + fn load_x86_linux_layout( + &self, + header: x86_linux::X86LinuxHeader, + layout: x86_linux::X86LinuxLoadLayout, + kernel: &[u8], + ) -> AxResult { + info!( + "x86 Linux layout for VM[{}]: header={:#x?}, payload_offset={:#x}, boot_params=[{:#x}..{:#x}), boot_stub=[{:#x}..{:#x}), kernel=[{:#x}..{:#x}), initrd={:?}", + self.config.base.id, + header, + header.payload_offset(), + layout.boot_params.start, + layout.boot_params.end().unwrap(), + layout.boot_stub.start, + layout.boot_stub.end().unwrap(), + layout.kernel.start, + layout.kernel.end().unwrap(), + layout.initrd + ); + + let boot_params = self.build_x86_boot_params(header, layout, kernel)?; + let boot_stub = self.build_x86_linux_boot_stub(&layout)?; + let mp_table = x86_mptable::build(); + load_vm_image_from_memory( + &boot_params, + layout.boot_params.start.into(), + self.vm.clone(), + )?; + load_vm_image_from_memory(&boot_stub, layout.boot_stub.start.into(), self.vm.clone())?; + load_vm_image_from_memory(&mp_table, x86_mptable::MP_TABLE_GPA.into(), self.vm.clone())?; + self.install_x86_linux_boot_entry(&layout); + Ok(()) + } + + #[cfg(target_arch = "x86_64")] + fn build_x86_linux_boot_stub( + &self, + layout: &x86_linux::X86LinuxLoadLayout, + ) -> AxResult<[u8; x86_linux::BOOT_STUB_SIZE]> { + x86_linux_boot::build_boot_image(layout).map_err(|err| { + ax_errno::ax_err_type!( + InvalidInput, + format!("failed to build x86 Linux boot stub: {err:?}") + ) + }) + } + + #[cfg(target_arch = "x86_64")] + fn install_x86_linux_boot_entry(&self, layout: &x86_linux::X86LinuxLoadLayout) { + let entry = GuestPhysAddr::from(x86_linux_boot::DEFAULT_LINUX_BOOT_LOAD_GPA); + self.vm.with_config(|config| { + config.cpu_config.bsp_entry = entry; + config.cpu_config.ap_entry = entry; + }); + info!( + "x86 Linux direct boot entry for VM[{}]: stub={:#x}, boot_params={:#x}, kernel_entry={:#x}, initrd={:?}", + self.config.base.id, + layout.boot_stub.start, + layout.boot_params.start, + layout.kernel.start, + layout.initrd + ); + } + + #[cfg(target_arch = "x86_64")] + fn build_x86_boot_params( + &self, + header: x86_linux::X86LinuxHeader, + layout: x86_linux::X86LinuxLoadLayout, + kernel: &[u8], + ) -> AxResult<[u8; x86_linux::BOOT_PARAMS_SIZE]> { + let mut builder = x86_boot_params::BootParamsBuilder::new( + kernel, + header, + layout, + x86_linux::X86LinuxRange::new(self.main_memory.gpa.as_usize(), self.main_memory.size()), + ); + if let Some(command_line) = self.config.kernel.cmdline.as_deref() { + builder.set_command_line(command_line).map_err(|err| { + ax_errno::ax_err_type!( + InvalidInput, + format!("invalid x86 Linux command line: {err:?}") + ) + })?; + } + + for memory in &self.config.kernel.memory_regions { + if memory.map_type == VmMemMappingType::MapAlloc { + builder.add_ram_range(x86_linux::X86LinuxRange::new(memory.gpa, memory.size)); + } + } + + for device in &self.config.devices.passthrough_devices { + builder.add_reserved_range(x86_linux::X86LinuxRange::new( + device.base_gpa, + device.length, + )); + } + for address in &self.config.devices.passthrough_addresses { + builder.add_reserved_range(x86_linux::X86LinuxRange::new( + address.base_gpa, + address.length, + )); + } + builder.add_reserved_range(x86_mptable::reserved_range()); + + builder.build().map_err(|err| { + ax_errno::ax_err_type!( + InvalidInput, + format!("failed to build x86 boot_params: {err:?}") + ) + }) + } + #[cfg(feature = "fs")] fn load_ramdisk_from_filesystem(&self, ramdisk_path: &str) -> AxResult { let load_gpa = self @@ -384,8 +609,21 @@ pub mod fs { /// Loads the VM image files from the filesystem /// into the guest VM's memory space based on the VM configuration. - pub(crate) fn load_vm_images_from_filesystem(loader: &ImageLoader) -> AxResult { + pub(crate) fn load_vm_images_from_filesystem(loader: &mut ImageLoader) -> AxResult { info!("Loading VM images from filesystem"); + #[cfg(target_arch = "x86_64")] + { + let kernel_probe = kernel_read(&loader.config, x86_linux::HEADER_READ_SIZE); + match kernel_probe { + Ok(data) => { + if let Some(header) = detect_x86_linux_image(&data) { + let kernel = read_image_file(&loader.config.kernel.kernel_path)?; + return loader.load_x86_linux_images_from_filesystem(header, &kernel); + } + } + Err(err) => debug!("Unable to probe x86 Linux bzImage header: {err:?}"), + } + } // Load kernel image. load_vm_image( &loader.config.kernel.kernel_path, @@ -457,6 +695,43 @@ pub mod fs { Ok(()) } + #[cfg(target_arch = "x86_64")] + impl ImageLoader { + fn load_x86_linux_images_from_filesystem( + &mut self, + header: x86_linux::X86LinuxHeader, + kernel: &[u8], + ) -> AxResult { + self.adjust_x86_linux_dma_identity_layout()?; + let payload = x86_linux_payload(&header, kernel)?; + let initrd = if let Some(ramdisk_path) = &self.config.kernel.ramdisk_path { + let (_, ramdisk_size) = open_image_file(ramdisk_path)?; + Some(x86_linux::X86LinuxRange::new( + self.ramdisk_load_gpa()?.as_usize(), + ramdisk_size, + )) + } else { + None + }; + let layout = x86_linux::X86LinuxLoadLayout::new( + &header, + self.kernel_load_gpa.as_usize(), + payload.len(), + initrd, + ) + .map_err(x86_linux_layout_error)?; + + self.load_x86_linux_layout(header, layout, kernel)?; + load_vm_image_from_memory(payload, self.kernel_load_gpa, self.vm.clone())?; + + if let Some(ramdisk_path) = &self.config.kernel.ramdisk_path { + self.load_ramdisk_from_filesystem(ramdisk_path)?; + } + + Ok(()) + } + } + pub(crate) fn load_vm_image( image_path: &str, image_load_gpa: GuestPhysAddr, @@ -528,6 +803,43 @@ pub mod fs { } } +#[cfg(target_arch = "x86_64")] +fn detect_x86_linux_image(image: &[u8]) -> Option { + match x86_linux::X86LinuxHeader::parse(image) { + Ok(header) => Some(header), + Err(err) => { + debug!("Not an x86 Linux bzImage: {err:?}"); + None + } + } +} + +#[cfg(target_arch = "x86_64")] +fn x86_linux_payload<'a>( + header: &x86_linux::X86LinuxHeader, + image: &'a [u8], +) -> AxResult<&'a [u8]> { + let payload_offset = header.payload_offset(); + image.get(payload_offset..).ok_or_else(|| { + ax_errno::ax_err_type!( + InvalidInput, + format!( + "x86 Linux bzImage payload offset {:#x} exceeds image size {:#x}", + payload_offset, + image.len() + ) + ) + }) +} + +#[cfg(target_arch = "x86_64")] +fn x86_linux_layout_error(err: x86_linux::X86LinuxLayoutError) -> ax_errno::AxError { + ax_errno::ax_err_type!( + InvalidInput, + format!("invalid x86 Linux memory layout: {err:?}") + ) +} + #[cfg(target_arch = "x86_64")] fn builtin_x86_bios_load_gpa(configured_gpa: Option) -> AxResult { let default_gpa = GuestPhysAddr::from(x86_boot::DEFAULT_BIOS_LOAD_GPA); diff --git a/os/axvisor/src/vmm/images/x86/boot_params.rs b/os/axvisor/src/vmm/images/x86/boot_params.rs new file mode 100644 index 0000000000..13509c4f41 --- /dev/null +++ b/os/axvisor/src/vmm/images/x86/boot_params.rs @@ -0,0 +1,615 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Linux x86 `boot_params` / zero page construction. + +use alloc::vec::Vec; + +use super::linux::{ + BOOT_PARAMS_SIZE, X86LinuxHeader, X86LinuxLayoutError, X86LinuxLoadLayout, X86LinuxRange, +}; + +const SETUP_HEADER_START: usize = 0x1f1; +const SETUP_HEADER_END: usize = 0x290; + +const EXT_RAMDISK_IMAGE_OFFSET: usize = 0x0c0; +const EXT_RAMDISK_SIZE_OFFSET: usize = 0x0c4; +const EXT_CMD_LINE_PTR_OFFSET: usize = 0x0c8; +const E820_ENTRIES_OFFSET: usize = 0x1e8; +const SENTINEL_OFFSET: usize = 0x1ef; +const TYPE_OF_LOADER_OFFSET: usize = 0x210; +const LOADFLAGS_OFFSET: usize = 0x211; +const CODE32_START_OFFSET: usize = 0x214; +const RAMDISK_IMAGE_OFFSET: usize = 0x218; +const RAMDISK_SIZE_OFFSET: usize = 0x21c; +const HEAP_END_PTR_OFFSET: usize = 0x224; +const CMD_LINE_PTR_OFFSET: usize = 0x228; +const SETUP_DATA_OFFSET: usize = 0x250; +const E820_TABLE_OFFSET: usize = 0x2d0; + +const COMMAND_LINE_OFFSET: usize = 0xe00; + +const TYPE_OF_LOADER_UNSPECIFIED: u8 = 0xff; +const LOADFLAG_CAN_USE_HEAP: u8 = 0x80; + +const E820_ENTRY_SIZE: usize = 20; +const E820_MAX_ENTRIES: usize = 128; +const E820_TYPE_RAM: u32 = 1; +const E820_TYPE_RESERVED: u32 = 2; + +const LEGACY_RESERVED_START: usize = 0x000a_0000; +const LEGACY_RESERVED_SIZE: usize = 0x0006_0000; + +// The x86 Linux bring-up path still relies on these default boot parameters to keep the +// guest on the currently supported surface: ACPI and MSI are disabled, x2APIC and TSC deadline +// timer are avoided, TSC is marked unstable, and AHCI/i8042 init is skipped until the x86 timer, +// IRQ routing, and device-topology support are complete. +pub const DEFAULT_COMMAND_LINE: &str = concat!( + "console=ttyS0 root=/dev/vda rw rootwait devtmpfs.mount=1 init=/sbin/getty ", + "acpi=off pci=conf1 pci=nomsi nox2apic ", + "tsc=unstable initcall_blacklist=ahci_pci_driver_init,i8042_init ", + "-- -n -l /bin/sh -L 115200 ttyS0 dumb" +); + +/// Builds a Linux x86 boot_params page for the direct-boot path. +pub struct BootParamsBuilder<'a> { + kernel_image: &'a [u8], + header: X86LinuxHeader, + layout: X86LinuxLoadLayout, + ram_ranges: Vec, + reserved_ranges: Vec, + command_line: &'a str, +} + +impl<'a> BootParamsBuilder<'a> { + pub fn new( + kernel_image: &'a [u8], + header: X86LinuxHeader, + layout: X86LinuxLoadLayout, + main_memory: X86LinuxRange, + ) -> Self { + Self { + kernel_image, + header, + layout, + ram_ranges: alloc::vec![main_memory], + reserved_ranges: alloc::vec![ + layout.boot_params, + layout.boot_stub, + X86LinuxRange::new(LEGACY_RESERVED_START, LEGACY_RESERVED_SIZE), + ], + command_line: DEFAULT_COMMAND_LINE, + } + } + + pub fn add_ram_range(&mut self, range: X86LinuxRange) { + if range.size != 0 { + self.ram_ranges.push(range); + } + } + + pub fn set_command_line(&mut self, command_line: &'a str) -> Result<(), BootParamsError> { + self.validate_command_line(command_line)?; + self.command_line = command_line; + Ok(()) + } + + pub fn add_reserved_range(&mut self, range: X86LinuxRange) { + if range.size != 0 { + self.reserved_ranges.push(range); + } + } + + pub fn build(mut self) -> Result<[u8; BOOT_PARAMS_SIZE], BootParamsError> { + let mut boot_params = [0u8; BOOT_PARAMS_SIZE]; + self.copy_setup_header(&mut boot_params)?; + self.patch_setup_header(&mut boot_params)?; + self.write_e820(&mut boot_params)?; + Ok(boot_params) + } + + fn copy_setup_header(&self, boot_params: &mut [u8]) -> Result<(), BootParamsError> { + let source = self + .kernel_image + .get(SETUP_HEADER_START..SETUP_HEADER_END) + .ok_or(BootParamsError::SetupHeaderTruncated { + image_size: self.kernel_image.len(), + required: SETUP_HEADER_END, + })?; + boot_params[SETUP_HEADER_START..SETUP_HEADER_END].copy_from_slice(source); + Ok(()) + } + + fn patch_setup_header(&self, boot_params: &mut [u8]) -> Result<(), BootParamsError> { + write_u8(boot_params, SENTINEL_OFFSET, 0xff); + write_u8( + boot_params, + TYPE_OF_LOADER_OFFSET, + TYPE_OF_LOADER_UNSPECIFIED, + ); + write_u8( + boot_params, + LOADFLAGS_OFFSET, + self.header.loadflags | LOADFLAG_CAN_USE_HEAP, + ); + write_u16(boot_params, HEAP_END_PTR_OFFSET, self.header.heap_end_ptr); + write_u32( + boot_params, + CODE32_START_OFFSET, + self.layout.kernel.start as u32, + ); + write_u64(boot_params, SETUP_DATA_OFFSET, 0); + + let cmdline_ptr = self + .layout + .boot_params + .start + .checked_add(COMMAND_LINE_OFFSET) + .ok_or(BootParamsError::AddressOverflow)?; + write_u32(boot_params, CMD_LINE_PTR_OFFSET, cmdline_ptr as u32); + write_u32( + boot_params, + EXT_CMD_LINE_PTR_OFFSET, + (cmdline_ptr >> 32) as u32, + ); + self.write_command_line(boot_params)?; + + if let Some(initrd) = self.layout.initrd { + write_u32(boot_params, RAMDISK_IMAGE_OFFSET, initrd.start as u32); + write_u32(boot_params, RAMDISK_SIZE_OFFSET, initrd.size as u32); + write_u32( + boot_params, + EXT_RAMDISK_IMAGE_OFFSET, + (initrd.start >> 32) as u32, + ); + write_u32( + boot_params, + EXT_RAMDISK_SIZE_OFFSET, + (initrd.size >> 32) as u32, + ); + } + + Ok(()) + } + + fn write_command_line(&self, boot_params: &mut [u8]) -> Result<(), BootParamsError> { + self.validate_command_line(self.command_line)?; + let bytes = self.command_line.as_bytes(); + let end = COMMAND_LINE_OFFSET + bytes.len(); + boot_params[COMMAND_LINE_OFFSET..end].copy_from_slice(bytes); + write_u8(boot_params, end, 0); + Ok(()) + } + + fn validate_command_line(&self, command_line: &str) -> Result<(), BootParamsError> { + if command_line.as_bytes().contains(&0) { + return Err(BootParamsError::CommandLineContainsNul); + } + + let max_len = self.command_line_capacity(); + if command_line.len() > max_len { + return Err(BootParamsError::CommandLineTooLong { + len: command_line.len(), + max: max_len, + }); + } + Ok(()) + } + + fn command_line_capacity(&self) -> usize { + let zero_page_capacity = BOOT_PARAMS_SIZE - COMMAND_LINE_OFFSET - 1; + if self.header.cmdline_size == 0 { + zero_page_capacity + } else { + zero_page_capacity.min(self.header.cmdline_size as usize) + } + } + + fn write_e820(&mut self, boot_params: &mut [u8]) -> Result<(), BootParamsError> { + let entries = self.e820_entries()?; + if entries.len() > E820_MAX_ENTRIES { + return Err(BootParamsError::TooManyE820Entries { + entries: entries.len(), + }); + } + + write_u8(boot_params, E820_ENTRIES_OFFSET, entries.len() as u8); + for (idx, entry) in entries.iter().enumerate() { + let offset = E820_TABLE_OFFSET + idx * E820_ENTRY_SIZE; + write_u64(boot_params, offset, entry.addr); + write_u64(boot_params, offset + 8, entry.size); + write_u32(boot_params, offset + 16, entry.entry_type); + } + + Ok(()) + } + + fn e820_entries(&mut self) -> Result, BootParamsError> { + let mut entries = Vec::new(); + let ram_ranges = normalized_ranges(&self.ram_ranges)?; + let reserved = normalized_ranges(&self.reserved_ranges)?; + + for ram in ram_ranges.iter().copied() { + let ram_end = ram.end().map_err(BootParamsError::Layout)?; + let mut cursor = ram.start; + + for range in reserved.iter().copied() { + let range_end = range.end().map_err(BootParamsError::Layout)?; + if range_end <= ram.start || range.start >= ram_end { + continue; + } + + let reserved_start = range.start.max(ram.start); + let reserved_end = range_end.min(ram_end); + if cursor < reserved_start { + entries.push(E820Entry::ram(cursor, reserved_start - cursor)?); + } + entries.push(E820Entry::reserved(X86LinuxRange::new( + reserved_start, + reserved_end - reserved_start, + ))?); + cursor = cursor.max(reserved_end); + } + + if cursor < ram_end { + entries.push(E820Entry::ram(cursor, ram_end - cursor)?); + } + } + + for range in reserved { + let overlaps_ram = ram_ranges + .iter() + .try_fold(false, |found, ram| Ok(found || range.overlaps(ram)?)) + .map_err(BootParamsError::Layout)?; + if !overlaps_ram { + entries.push(E820Entry::reserved(range)?); + } + } + + entries.sort_by_key(|entry| entry.addr); + Ok(entries) + } +} + +/// Error returned while building Linux x86 boot_params. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BootParamsError { + SetupHeaderTruncated { image_size: usize, required: usize }, + CommandLineContainsNul, + CommandLineTooLong { len: usize, max: usize }, + AddressOverflow, + Layout(X86LinuxLayoutError), + TooManyE820Entries { entries: usize }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct E820Entry { + addr: u64, + size: u64, + entry_type: u32, +} + +impl E820Entry { + fn ram(start: usize, size: usize) -> Result { + Self::new(start, size, E820_TYPE_RAM) + } + + fn reserved(range: X86LinuxRange) -> Result { + Self::new(range.start, range.size, E820_TYPE_RESERVED) + } + + fn new(start: usize, size: usize, entry_type: u32) -> Result { + Ok(Self { + addr: start as u64, + size: size as u64, + entry_type, + }) + } +} + +fn normalized_ranges(ranges: &[X86LinuxRange]) -> Result, BootParamsError> { + let mut ranges = ranges + .iter() + .copied() + .filter(|range| range.size != 0) + .collect::>(); + ranges.sort_by_key(|range| range.start); + + let mut normalized = Vec::::new(); + for range in ranges { + range.end().map_err(BootParamsError::Layout)?; + if let Some(last) = normalized.last_mut() { + let last_end = last.end().map_err(BootParamsError::Layout)?; + let range_end = range.end().map_err(BootParamsError::Layout)?; + if range.start <= last_end { + last.size = range_end.max(last_end) - last.start; + continue; + } + } + normalized.push(range); + } + + Ok(normalized) +} + +fn write_u8(buffer: &mut [u8], offset: usize, value: u8) { + buffer[offset] = value; +} + +fn write_u16(buffer: &mut [u8], offset: usize, value: u16) { + buffer[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); +} + +fn write_u32(buffer: &mut [u8], offset: usize, value: u32) { + buffer[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); +} + +fn write_u64(buffer: &mut [u8], offset: usize, value: u64) { + buffer[offset..offset + 8].copy_from_slice(&value.to_le_bytes()); +} + +#[cfg(test)] +mod tests { + use super::linux::{BOOT_PARAMS_GPA, BOOT_STUB_GPA, BOOT_STUB_SIZE}; + use super::*; + + const SETUP_SECTS_OFFSET: usize = 0x1f1; + const BOOT_FLAG_OFFSET: usize = 0x1fe; + const HEADER_OFFSET: usize = 0x202; + const VERSION_OFFSET: usize = 0x206; + const LOADFLAGS_OFFSET: usize = 0x211; + const CODE32_START_OFFSET: usize = 0x214; + const INITRD_ADDR_MAX_OFFSET: usize = 0x22c; + const KERNEL_ALIGNMENT_OFFSET: usize = 0x230; + const RELOCATABLE_KERNEL_OFFSET: usize = 0x234; + const CMDLINE_SIZE_OFFSET: usize = 0x238; + + fn read_u8(buffer: &[u8], offset: usize) -> u8 { + buffer[offset] + } + + fn read_u32(buffer: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(buffer[offset..offset + 4].try_into().unwrap()) + } + + fn read_e820_entry(buffer: &[u8], idx: usize) -> E820Entry { + let offset = E820_TABLE_OFFSET + idx * E820_ENTRY_SIZE; + E820Entry { + addr: u64::from_le_bytes(buffer[offset..offset + 8].try_into().unwrap()), + size: u64::from_le_bytes(buffer[offset + 8..offset + 16].try_into().unwrap()), + entry_type: u32::from_le_bytes(buffer[offset + 16..offset + 20].try_into().unwrap()), + } + } + + fn write_header_u16(image: &mut [u8], offset: usize, value: u16) { + image[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); + } + + fn write_header_u32(image: &mut [u8], offset: usize, value: u32) { + image[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } + + fn valid_image() -> Vec { + let mut image = alloc::vec![0u8; SETUP_HEADER_END + 0x1000]; + image[SETUP_SECTS_OFFSET] = 5; + write_header_u16(&mut image, BOOT_FLAG_OFFSET, 0xaa55); + write_header_u32(&mut image, HEADER_OFFSET, u32::from_le_bytes(*b"HdrS")); + write_header_u16(&mut image, VERSION_OFFSET, 0x020f); + image[LOADFLAGS_OFFSET] = 0x01; + write_header_u32(&mut image, CODE32_START_OFFSET, 0x100000); + write_header_u32(&mut image, INITRD_ADDR_MAX_OFFSET, 0x7fff_ffff); + write_header_u32(&mut image, KERNEL_ALIGNMENT_OFFSET, 0x20_0000); + image[RELOCATABLE_KERNEL_OFFSET] = 1; + write_header_u32(&mut image, CMDLINE_SIZE_OFFSET, 4096); + image + } + + fn valid_layout(header: &X86LinuxHeader) -> X86LinuxLoadLayout { + X86LinuxLoadLayout::new( + header, + 0x20_0000, + 0x10_0000, + Some(X86LinuxRange::new(0x40_0000, 0x20_0000)), + ) + .unwrap() + } + + #[test] + fn builds_boot_params_with_patched_header_and_initrd() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let mut builder = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x80_0000)); + builder + .set_command_line("console=ttyS0 rdinit=/init") + .unwrap(); + let params = builder.build().unwrap(); + + assert_eq!(read_u8(¶ms, SENTINEL_OFFSET), 0xff); + assert_eq!( + read_u8(¶ms, TYPE_OF_LOADER_OFFSET), + TYPE_OF_LOADER_UNSPECIFIED + ); + assert_eq!( + read_u8(¶ms, LOADFLAGS_OFFSET), + 0x01 | LOADFLAG_CAN_USE_HEAP + ); + assert_eq!(read_u32(¶ms, CODE32_START_OFFSET), 0x20_0000); + assert_eq!(read_u32(¶ms, RAMDISK_IMAGE_OFFSET), 0x40_0000); + assert_eq!(read_u32(¶ms, RAMDISK_SIZE_OFFSET), 0x20_0000); + assert_eq!( + read_u32(¶ms, CMD_LINE_PTR_OFFSET), + (BOOT_PARAMS_GPA + COMMAND_LINE_OFFSET) as u32 + ); + assert_eq!( + ¶ms[COMMAND_LINE_OFFSET..COMMAND_LINE_OFFSET + 26], + b"console=ttyS0 rdinit=/init" + ); + assert_eq!(read_u8(¶ms, COMMAND_LINE_OFFSET + 26), 0); + } + + #[test] + fn uses_default_command_line_when_not_overridden() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let params = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x80_0000)) + .build() + .unwrap(); + + assert_eq!( + ¶ms[COMMAND_LINE_OFFSET..COMMAND_LINE_OFFSET + DEFAULT_COMMAND_LINE.len()], + DEFAULT_COMMAND_LINE.as_bytes() + ); + assert_eq!( + read_u8(¶ms, COMMAND_LINE_OFFSET + DEFAULT_COMMAND_LINE.len()), + 0 + ); + } + + #[test] + fn rejects_command_line_that_does_not_fit_zero_page() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let mut builder = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)); + let long_command_line = alloc::string::String::from_utf8(alloc::vec![ + b'a'; + BOOT_PARAMS_SIZE - COMMAND_LINE_OFFSET + ]) + .unwrap(); + + assert_eq!( + builder.set_command_line(&long_command_line), + Err(BootParamsError::CommandLineTooLong { + len: BOOT_PARAMS_SIZE - COMMAND_LINE_OFFSET, + max: BOOT_PARAMS_SIZE - COMMAND_LINE_OFFSET - 1, + }) + ); + } + + #[test] + fn rejects_command_line_with_nul() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let mut builder = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)); + + assert_eq!( + builder.set_command_line("console=ttyS0\0rdinit=/init"), + Err(BootParamsError::CommandLineContainsNul) + ); + } + + #[test] + fn builds_e820_with_ram_and_reserved_low_ranges() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let params = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)) + .build() + .unwrap(); + + let entries = read_u8(¶ms, E820_ENTRIES_OFFSET) as usize; + assert!(entries >= 5); + assert_eq!( + read_e820_entry(¶ms, 0), + E820Entry::new(0, BOOT_PARAMS_GPA, 1).unwrap() + ); + assert_eq!( + read_e820_entry(¶ms, 1), + E820Entry::reserved(X86LinuxRange::new( + BOOT_PARAMS_GPA, + BOOT_STUB_GPA + BOOT_STUB_SIZE - BOOT_PARAMS_GPA + )) + .unwrap() + ); + } + + #[test] + fn builds_e820_with_multiple_ram_ranges() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let mut builder = BootParamsBuilder::new( + &image, + header, + layout, + X86LinuxRange::new(0x0960_0000, 0x0800_0000), + ); + builder.add_ram_range(X86LinuxRange::new(0, 0x10_0000)); + let params = builder.build().unwrap(); + + let entries = read_u8(¶ms, E820_ENTRIES_OFFSET) as usize; + let has_low_usable = (0..entries).any(|idx| { + read_e820_entry(¶ms, idx) == E820Entry::new(0, BOOT_PARAMS_GPA, 1).unwrap() + }); + let has_trampoline_usable = (0..entries).any(|idx| { + read_e820_entry(¶ms, idx) + == E820Entry::new(BOOT_STUB_GPA + BOOT_STUB_SIZE, 0xa0000 - 0x9000, 1).unwrap() + }); + let has_high_usable = (0..entries).any(|idx| { + read_e820_entry(¶ms, idx) == E820Entry::new(0x0960_0000, 0x0800_0000, 1).unwrap() + }); + + assert!(has_low_usable); + assert!(has_trampoline_usable); + assert!(has_high_usable); + } + + #[test] + fn records_reserved_passthrough_ranges_in_e820() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let mut builder = + BootParamsBuilder::new(&image, header, layout, X86LinuxRange::new(0, 0x20_0000)); + builder.add_reserved_range(X86LinuxRange::new(0xfec0_0000, 0x1000)); + let params = builder.build().unwrap(); + + let entries = read_u8(¶ms, E820_ENTRIES_OFFSET) as usize; + let found = (0..entries).any(|idx| { + read_e820_entry(¶ms, idx) + == E820Entry::reserved(X86LinuxRange::new(0xfec0_0000, 0x1000)).unwrap() + }); + assert!(found); + } + + #[test] + fn rejects_truncated_setup_header_copy() { + let image = valid_image(); + let header = X86LinuxHeader::parse(&image).unwrap(); + let layout = valid_layout(&header); + let short_image = &image[..SETUP_HEADER_END - 1]; + + assert_eq!( + BootParamsBuilder::new( + short_image, + header, + layout, + X86LinuxRange::new(0, 0x20_0000) + ) + .build(), + Err(BootParamsError::SetupHeaderTruncated { + image_size: SETUP_HEADER_END - 1, + required: SETUP_HEADER_END, + }) + ); + } +} diff --git a/os/axvisor/src/vmm/images/x86/linux.rs b/os/axvisor/src/vmm/images/x86/linux.rs new file mode 100644 index 0000000000..5f2d8ccd64 --- /dev/null +++ b/os/axvisor/src/vmm/images/x86/linux.rs @@ -0,0 +1,458 @@ +// Copyright 2025 The Axvisor Team +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Linux x86 boot protocol header parsing. +//! +//! This module only recognizes and parses bzImage setup header fields needed by +//! the staged direct-boot work. It deliberately does not lay out or load the +//! protected-mode payload; that starts in phase 2. + +const SETUP_SECTS_DEFAULT: usize = 4; +const SECTOR_SIZE: usize = 512; + +pub const BOOT_PARAMS_GPA: usize = 0x7000; +pub const BOOT_PARAMS_SIZE: usize = 0x1000; +pub const BOOT_STUB_GPA: usize = 0x8000; +pub const BOOT_STUB_SIZE: usize = 0x1000; + +const SETUP_SECTS_OFFSET: usize = 0x1f1; +const BOOT_FLAG_OFFSET: usize = 0x1fe; +const HEADER_OFFSET: usize = 0x202; +const VERSION_OFFSET: usize = 0x206; +const LOADFLAGS_OFFSET: usize = 0x211; +const CODE32_START_OFFSET: usize = 0x214; +const HEAP_END_PTR_OFFSET: usize = 0x224; +const INITRD_ADDR_MAX_OFFSET: usize = 0x22c; +const KERNEL_ALIGNMENT_OFFSET: usize = 0x230; +const RELOCATABLE_KERNEL_OFFSET: usize = 0x234; +const CMDLINE_SIZE_OFFSET: usize = 0x238; + +const BOOT_FLAG_MAGIC: u16 = 0xaa55; +const HEADER_MAGIC: u32 = u32::from_le_bytes(*b"HdrS"); + +/// Number of bytes needed to parse every field used by [`X86LinuxHeader`]. +#[cfg(any(feature = "fs", test))] +pub const HEADER_READ_SIZE: usize = CMDLINE_SIZE_OFFSET + core::mem::size_of::(); + +/// Parsed subset of Linux x86 setup header fields. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct X86LinuxHeader { + pub setup_sects: usize, + pub boot_protocol_version: u16, + pub code32_start: u32, + pub cmdline_size: u32, + pub initrd_addr_max: u32, + pub kernel_alignment: u32, + pub relocatable_kernel: bool, + pub loadflags: u8, + pub heap_end_ptr: u16, +} + +impl X86LinuxHeader { + pub fn parse(image: &[u8]) -> Result { + let boot_flag = read_u16(image, BOOT_FLAG_OFFSET)?; + if boot_flag != BOOT_FLAG_MAGIC { + return Err(X86LinuxHeaderError::InvalidBootFlag { value: boot_flag }); + } + + let header = read_u32(image, HEADER_OFFSET)?; + if header != HEADER_MAGIC { + return Err(X86LinuxHeaderError::InvalidHeader { value: header }); + } + + let raw_setup_sects = read_u8(image, SETUP_SECTS_OFFSET)?; + let setup_sects = match raw_setup_sects { + 0 => SETUP_SECTS_DEFAULT, + value => value as usize, + }; + + Ok(Self { + setup_sects, + boot_protocol_version: read_u16(image, VERSION_OFFSET)?, + code32_start: read_u32(image, CODE32_START_OFFSET)?, + cmdline_size: read_u32(image, CMDLINE_SIZE_OFFSET)?, + initrd_addr_max: read_u32(image, INITRD_ADDR_MAX_OFFSET)?, + kernel_alignment: read_u32(image, KERNEL_ALIGNMENT_OFFSET)?, + relocatable_kernel: read_u8(image, RELOCATABLE_KERNEL_OFFSET)? != 0, + loadflags: read_u8(image, LOADFLAGS_OFFSET)?, + heap_end_ptr: read_u16(image, HEAP_END_PTR_OFFSET)?, + }) + } + + /// Offset of the protected-mode kernel payload in the bzImage. + pub fn payload_offset(&self) -> usize { + (self.setup_sects + 1) * SECTOR_SIZE + } +} + +/// Guest physical range used by the x86 Linux direct-boot layout. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct X86LinuxRange { + pub start: usize, + pub size: usize, +} + +impl X86LinuxRange { + pub const fn new(start: usize, size: usize) -> Self { + Self { start, size } + } + + pub fn end(&self) -> Result { + self.start + .checked_add(self.size) + .ok_or(X86LinuxLayoutError::RangeOverflow { range: *self }) + } + + pub fn overlaps(&self, other: &Self) -> Result { + Ok(self.start < other.end()? && other.start < self.end()?) + } +} + +/// First direct-boot memory layout for x86 Linux. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct X86LinuxLoadLayout { + pub boot_params: X86LinuxRange, + pub boot_stub: X86LinuxRange, + pub kernel: X86LinuxRange, + pub initrd: Option, +} + +impl X86LinuxLoadLayout { + pub fn new( + header: &X86LinuxHeader, + kernel_load_gpa: usize, + kernel_payload_size: usize, + initrd: Option, + ) -> Result { + if kernel_payload_size == 0 { + return Err(X86LinuxLayoutError::EmptyKernelPayload); + } + + let layout = Self { + boot_params: X86LinuxRange::new(BOOT_PARAMS_GPA, BOOT_PARAMS_SIZE), + boot_stub: X86LinuxRange::new(BOOT_STUB_GPA, BOOT_STUB_SIZE), + kernel: X86LinuxRange::new(kernel_load_gpa, kernel_payload_size), + initrd, + }; + + layout.validate_range("boot_params", &layout.boot_params)?; + layout.validate_range("boot_stub", &layout.boot_stub)?; + layout.validate_range("kernel", &layout.kernel)?; + + layout.ensure_no_overlap("kernel", &layout.kernel, "boot_params", &layout.boot_params)?; + layout.ensure_no_overlap("kernel", &layout.kernel, "boot_stub", &layout.boot_stub)?; + layout.ensure_no_overlap( + "boot_params", + &layout.boot_params, + "boot_stub", + &layout.boot_stub, + )?; + + if let Some(initrd) = layout.initrd { + layout.validate_range("initrd", &initrd)?; + layout.ensure_no_overlap("initrd", &initrd, "kernel", &layout.kernel)?; + layout.ensure_no_overlap("initrd", &initrd, "boot_params", &layout.boot_params)?; + layout.ensure_no_overlap("initrd", &initrd, "boot_stub", &layout.boot_stub)?; + let max_end = (header.initrd_addr_max as usize).saturating_add(1); + if initrd.end()? > max_end { + return Err(X86LinuxLayoutError::InitrdExceedsMax { + end: initrd.end()?, + initrd_addr_max: header.initrd_addr_max, + }); + } + } + + Ok(layout) + } + + fn validate_range( + &self, + name: &'static str, + range: &X86LinuxRange, + ) -> Result<(), X86LinuxLayoutError> { + if range.size == 0 { + return Err(X86LinuxLayoutError::EmptyRange { name }); + } + range.end()?; + Ok(()) + } + + fn ensure_no_overlap( + &self, + first_name: &'static str, + first: &X86LinuxRange, + second_name: &'static str, + second: &X86LinuxRange, + ) -> Result<(), X86LinuxLayoutError> { + if first.overlaps(second)? { + return Err(X86LinuxLayoutError::RangeOverlap { + first_name, + first: *first, + second_name, + second: *second, + }); + } + Ok(()) + } +} + +/// Error returned while validating the first x86 Linux direct-boot layout. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum X86LinuxLayoutError { + EmptyKernelPayload, + EmptyRange { + name: &'static str, + }, + RangeOverflow { + range: X86LinuxRange, + }, + RangeOverlap { + first_name: &'static str, + first: X86LinuxRange, + second_name: &'static str, + second: X86LinuxRange, + }, + InitrdExceedsMax { + end: usize, + initrd_addr_max: u32, + }, +} + +/// Error returned while parsing a Linux x86 setup header. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum X86LinuxHeaderError { + Truncated { + offset: usize, + size: usize, + image_size: usize, + }, + InvalidBootFlag { + value: u16, + }, + InvalidHeader { + value: u32, + }, +} + +fn read_u8(image: &[u8], offset: usize) -> Result { + image + .get(offset) + .copied() + .ok_or_else(|| truncated(offset, core::mem::size_of::(), image.len())) +} + +fn read_u16(image: &[u8], offset: usize) -> Result { + let bytes = read_array::<2>(image, offset)?; + Ok(u16::from_le_bytes(bytes)) +} + +fn read_u32(image: &[u8], offset: usize) -> Result { + let bytes = read_array::<4>(image, offset)?; + Ok(u32::from_le_bytes(bytes)) +} + +fn read_array(image: &[u8], offset: usize) -> Result<[u8; N], X86LinuxHeaderError> { + let end = offset + .checked_add(N) + .ok_or_else(|| truncated(offset, N, image.len()))?; + let bytes = image + .get(offset..end) + .ok_or_else(|| truncated(offset, N, image.len()))?; + Ok(bytes.try_into().unwrap()) +} + +fn truncated(offset: usize, size: usize, image_size: usize) -> X86LinuxHeaderError { + X86LinuxHeaderError::Truncated { + offset, + size, + image_size, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const VERSION: u16 = 0x020f; + const CODE32_START: u32 = 0x0010_0000; + const CMDLINE_SIZE: u32 = 4096; + const INITRD_ADDR_MAX: u32 = 0x7fff_ffff; + const KERNEL_ALIGNMENT: u32 = 0x20_0000; + const LOADFLAGS: u8 = 0x81; + const HEAP_END_PTR: u16 = 0xe000; + + fn write_u16(image: &mut [u8], offset: usize, value: u16) { + image[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); + } + + fn write_u32(image: &mut [u8], offset: usize, value: u32) { + image[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } + + fn valid_bzimage_header() -> [u8; HEADER_READ_SIZE] { + let mut image = [0u8; HEADER_READ_SIZE]; + image[SETUP_SECTS_OFFSET] = 5; + write_u16(&mut image, BOOT_FLAG_OFFSET, BOOT_FLAG_MAGIC); + write_u32(&mut image, HEADER_OFFSET, HEADER_MAGIC); + write_u16(&mut image, VERSION_OFFSET, VERSION); + image[LOADFLAGS_OFFSET] = LOADFLAGS; + write_u32(&mut image, CODE32_START_OFFSET, CODE32_START); + write_u16(&mut image, HEAP_END_PTR_OFFSET, HEAP_END_PTR); + write_u32(&mut image, INITRD_ADDR_MAX_OFFSET, INITRD_ADDR_MAX); + write_u32(&mut image, KERNEL_ALIGNMENT_OFFSET, KERNEL_ALIGNMENT); + image[RELOCATABLE_KERNEL_OFFSET] = 1; + write_u32(&mut image, CMDLINE_SIZE_OFFSET, CMDLINE_SIZE); + image + } + + #[test] + fn parses_valid_bzimage_header() { + let header = X86LinuxHeader::parse(&valid_bzimage_header()).unwrap(); + + assert_eq!(header.setup_sects, 5); + assert_eq!(header.boot_protocol_version, VERSION); + assert_eq!(header.code32_start, CODE32_START); + assert_eq!(header.cmdline_size, CMDLINE_SIZE); + assert_eq!(header.initrd_addr_max, INITRD_ADDR_MAX); + assert_eq!(header.kernel_alignment, KERNEL_ALIGNMENT); + assert!(header.relocatable_kernel); + assert_eq!(header.loadflags, LOADFLAGS); + assert_eq!(header.heap_end_ptr, HEAP_END_PTR); + assert_eq!(header.payload_offset(), 6 * 512); + } + + #[test] + fn treats_zero_setup_sects_as_four() { + let mut image = valid_bzimage_header(); + image[SETUP_SECTS_OFFSET] = 0; + + let header = X86LinuxHeader::parse(&image).unwrap(); + + assert_eq!(header.setup_sects, 4); + assert_eq!(header.payload_offset(), 5 * 512); + } + + #[test] + fn rejects_non_linux_image_without_header_magic() { + let mut image = valid_bzimage_header(); + write_u32(&mut image, HEADER_OFFSET, 0); + + assert_eq!( + X86LinuxHeader::parse(&image), + Err(X86LinuxHeaderError::InvalidHeader { value: 0 }) + ); + } + + #[test] + fn rejects_non_bootable_image_without_boot_flag() { + let mut image = valid_bzimage_header(); + write_u16(&mut image, BOOT_FLAG_OFFSET, 0); + + assert_eq!( + X86LinuxHeader::parse(&image), + Err(X86LinuxHeaderError::InvalidBootFlag { value: 0 }) + ); + } + + #[test] + fn reports_truncated_header_offset() { + assert_eq!( + X86LinuxHeader::parse(&[0u8; 16]), + Err(X86LinuxHeaderError::Truncated { + offset: BOOT_FLAG_OFFSET, + size: 2, + image_size: 16, + }) + ); + } + + #[test] + fn layout_accepts_non_overlapping_kernel_and_initrd() { + let header = X86LinuxHeader::parse(&valid_bzimage_header()).unwrap(); + let layout = X86LinuxLoadLayout::new( + &header, + 0x20_0000, + 0x10_0000, + Some(X86LinuxRange::new(0x40_0000, 0x20_0000)), + ) + .unwrap(); + + assert_eq!( + layout.boot_params, + X86LinuxRange::new(BOOT_PARAMS_GPA, BOOT_PARAMS_SIZE) + ); + assert_eq!( + layout.boot_stub, + X86LinuxRange::new(BOOT_STUB_GPA, BOOT_STUB_SIZE) + ); + assert_eq!(layout.kernel, X86LinuxRange::new(0x20_0000, 0x10_0000)); + assert_eq!( + layout.initrd, + Some(X86LinuxRange::new(0x40_0000, 0x20_0000)) + ); + } + + #[test] + fn layout_rejects_kernel_overlapping_boot_stub() { + let header = X86LinuxHeader::parse(&valid_bzimage_header()).unwrap(); + + assert!(matches!( + X86LinuxLoadLayout::new(&header, BOOT_STUB_GPA, 0x1000, None), + Err(X86LinuxLayoutError::RangeOverlap { + first_name: "kernel", + second_name: "boot_stub", + .. + }) + )); + } + + #[test] + fn layout_rejects_initrd_overlapping_kernel() { + let header = X86LinuxHeader::parse(&valid_bzimage_header()).unwrap(); + + assert!(matches!( + X86LinuxLoadLayout::new( + &header, + 0x20_0000, + 0x20_0000, + Some(X86LinuxRange::new(0x30_0000, 0x1000)), + ), + Err(X86LinuxLayoutError::RangeOverlap { + first_name: "initrd", + second_name: "kernel", + .. + }) + )); + } + + #[test] + fn layout_rejects_initrd_above_header_limit() { + let mut image = valid_bzimage_header(); + write_u32(&mut image, INITRD_ADDR_MAX_OFFSET, 0x40_0000); + let header = X86LinuxHeader::parse(&image).unwrap(); + + assert_eq!( + X86LinuxLoadLayout::new( + &header, + 0x20_0000, + 0x10_0000, + Some(X86LinuxRange::new(0x40_0000, 0x2)), + ), + Err(X86LinuxLayoutError::InitrdExceedsMax { + end: 0x40_0002, + initrd_addr_max: 0x40_0000, + }) + ); + } +} diff --git a/os/axvisor/src/vmm/images/x86/linux_boot.rs b/os/axvisor/src/vmm/images/x86/linux_boot.rs new file mode 100644 index 0000000000..8d59bb48b1 --- /dev/null +++ b/os/axvisor/src/vmm/images/x86/linux_boot.rs @@ -0,0 +1,177 @@ +//! Built-in x86 Linux boot stub for the direct-boot path. +//! +//! Axvisor starts x86 guests in 16-bit real mode with a flat zero-based segment +//! state. This stub switches to 32-bit protected mode and enters the Linux +//! 32-bit boot protocol with `esi = boot_params`. + +use super::linux::{BOOT_STUB_GPA, BOOT_STUB_SIZE, X86LinuxLoadLayout}; + +/// Default GPA where the Linux direct-boot stub is loaded. +pub const DEFAULT_LINUX_BOOT_LOAD_GPA: usize = BOOT_STUB_GPA; + +const BOOT_PARAMS_IMM_OFFSET: usize = 0x3b; +const KERNEL_ENTRY_IMM_OFFSET: usize = 0x40; + +/// Raw Linux direct-boot stub template. +/// +/// The template assumes it is loaded at [`DEFAULT_LINUX_BOOT_LOAD_GPA`]. The two +/// immediate operands patched by [`build_boot_image`] are: +/// +/// - `mov esi, imm32`: boot_params GPA +/// - `mov ecx, imm32`: Linux protected-mode entry GPA +const LINUX_BOOT_TEMPLATE: &[u8] = &[ + // 16-bit real mode entry. + 0xfa, // cli + 0xfc, // cld + 0x31, 0xc0, // xor ax, ax + 0x8e, 0xd8, // mov ds, ax + 0x8e, 0xc0, // mov es, ax + 0x8e, 0xd0, // mov ss, ax + 0xbc, 0x00, 0x70, // mov sp, 0x7000 + 0x0f, 0x01, 0x16, 0x49, 0x80, // lgdt [0x8049] + 0x0f, 0x20, 0xc0, // mov eax, cr0 + 0x66, 0x83, 0xc8, 0x01, // or eax, 1 + 0x0f, 0x22, 0xc0, // mov cr0, eax + 0xea, 0x21, 0x80, 0x10, 0x00, // ljmp 0x10:0x8021 + // 32-bit protected mode entry. CS=0x10, flat 4G code segment. + 0x66, 0xb8, 0x18, 0x00, // mov ax, 0x18 + 0x8e, 0xd8, // mov ds, ax + 0x8e, 0xc0, // mov es, ax + 0x8e, 0xd0, // mov ss, ax + 0x8e, 0xe0, // mov fs, ax + 0x8e, 0xe8, // mov gs, ax + 0xbc, 0x00, 0x70, 0x00, 0x00, // mov esp, 0x7000 + 0x31, 0xed, // xor ebp, ebp + 0x31, 0xff, // xor edi, edi + 0x31, 0xdb, // xor ebx, ebx + 0xbe, 0x00, 0x70, 0x00, 0x00, // mov esi, boot_params + 0xb9, 0x00, 0x00, 0x20, 0x00, // mov ecx, kernel_entry + 0xff, 0xe1, // jmp ecx + 0xf4, 0xeb, 0xfd, // hlt; jmp -3 + // GDT descriptor at 0x8049. + 0x1f, 0x00, 0x4f, 0x80, 0x00, 0x00, // limit=31, base=0x804f + // GDT at 0x804f. Selectors: code=0x10, data=0x18. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // null + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved + 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, // 32-bit code + 0xff, 0xff, 0x00, 0x00, 0x00, 0x92, 0xcf, 0x00, // 32-bit data +]; + +/// Builds the Linux direct-boot stub page for the provided layout. +pub fn build_boot_image( + layout: &X86LinuxLoadLayout, +) -> Result<[u8; BOOT_STUB_SIZE], LinuxBootError> { + if layout.boot_stub.start != DEFAULT_LINUX_BOOT_LOAD_GPA { + return Err(LinuxBootError::UnexpectedLoadGpa { + expected: DEFAULT_LINUX_BOOT_LOAD_GPA, + actual: layout.boot_stub.start, + }); + } + + let mut image = [0u8; BOOT_STUB_SIZE]; + image[..LINUX_BOOT_TEMPLATE.len()].copy_from_slice(LINUX_BOOT_TEMPLATE); + write_u32( + &mut image, + BOOT_PARAMS_IMM_OFFSET, + checked_u32(layout.boot_params.start)?, + ); + write_u32( + &mut image, + KERNEL_ENTRY_IMM_OFFSET, + checked_u32(layout.kernel.start)?, + ); + Ok(image) +} + +/// Error returned while building the x86 Linux boot stub. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LinuxBootError { + UnexpectedLoadGpa { expected: usize, actual: usize }, + AddressAbove4G { address: usize }, +} + +fn checked_u32(address: usize) -> Result { + if address > u32::MAX as usize { + return Err(LinuxBootError::AddressAbove4G { address }); + } + Ok(address as u32) +} + +fn write_u32(buffer: &mut [u8], offset: usize, value: u32) { + buffer[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::vmm::images::x86::linux::{BOOT_PARAMS_GPA, X86LinuxHeader, X86LinuxRange}; + + const SETUP_SECTS_OFFSET: usize = 0x1f1; + const BOOT_FLAG_OFFSET: usize = 0x1fe; + const HEADER_OFFSET: usize = 0x202; + const VERSION_OFFSET: usize = 0x206; + const LOADFLAGS_OFFSET: usize = 0x211; + const CODE32_START_OFFSET: usize = 0x214; + const HEAP_END_PTR_OFFSET: usize = 0x224; + const INITRD_ADDR_MAX_OFFSET: usize = 0x22c; + const KERNEL_ALIGNMENT_OFFSET: usize = 0x230; + const RELOCATABLE_KERNEL_OFFSET: usize = 0x234; + const CMDLINE_SIZE_OFFSET: usize = 0x238; + + fn write_header_u16(image: &mut [u8], offset: usize, value: u16) { + image[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); + } + + fn write_header_u32(image: &mut [u8], offset: usize, value: u32) { + image[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); + } + + fn read_u32(image: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(image[offset..offset + 4].try_into().unwrap()) + } + + fn valid_header() -> X86LinuxHeader { + let mut image = alloc::vec![0u8; CMDLINE_SIZE_OFFSET + 4]; + image[SETUP_SECTS_OFFSET] = 5; + write_header_u16(&mut image, BOOT_FLAG_OFFSET, 0xaa55); + write_header_u32(&mut image, HEADER_OFFSET, u32::from_le_bytes(*b"HdrS")); + write_header_u16(&mut image, VERSION_OFFSET, 0x020f); + image[LOADFLAGS_OFFSET] = 0x01; + write_header_u32(&mut image, CODE32_START_OFFSET, 0x100000); + write_header_u16(&mut image, HEAP_END_PTR_OFFSET, 0xe000); + write_header_u32(&mut image, INITRD_ADDR_MAX_OFFSET, 0x7fff_ffff); + write_header_u32(&mut image, KERNEL_ALIGNMENT_OFFSET, 0x20_0000); + image[RELOCATABLE_KERNEL_OFFSET] = 1; + write_header_u32(&mut image, CMDLINE_SIZE_OFFSET, 4096); + X86LinuxHeader::parse(&image).unwrap() + } + + #[test] + fn builds_linux_boot_stub_with_patched_entry_registers() { + let header = valid_header(); + let layout = X86LinuxLoadLayout::new(&header, 0x30_0000, 0x1000, None).unwrap(); + let image = build_boot_image(&layout).unwrap(); + + assert_eq!(image[0], 0xfa); + assert_eq!( + read_u32(&image, BOOT_PARAMS_IMM_OFFSET), + BOOT_PARAMS_GPA as u32 + ); + assert_eq!(read_u32(&image, KERNEL_ENTRY_IMM_OFFSET), 0x30_0000); + } + + #[test] + fn rejects_stub_load_gpa_mismatch() { + let header = valid_header(); + let mut layout = X86LinuxLoadLayout::new(&header, 0x20_0000, 0x1000, None).unwrap(); + layout.boot_stub = X86LinuxRange::new(0x9000, BOOT_STUB_SIZE); + + assert_eq!( + build_boot_image(&layout), + Err(LinuxBootError::UnexpectedLoadGpa { + expected: BOOT_STUB_GPA, + actual: 0x9000, + }) + ); + } +} diff --git a/os/axvisor/src/vmm/images/x86/mod.rs b/os/axvisor/src/vmm/images/x86/mod.rs new file mode 100644 index 0000000000..5aa554161c --- /dev/null +++ b/os/axvisor/src/vmm/images/x86/mod.rs @@ -0,0 +1,8 @@ +//! x86 image loading helpers. + +pub mod linux; +pub mod linux_boot; +pub mod mptable; +pub mod multiboot; + +pub mod boot_params; diff --git a/os/axvisor/src/vmm/images/x86/mptable.rs b/os/axvisor/src/vmm/images/x86/mptable.rs new file mode 100644 index 0000000000..5232de0a80 --- /dev/null +++ b/os/axvisor/src/vmm/images/x86/mptable.rs @@ -0,0 +1,200 @@ +//! Minimal Intel MultiProcessor table for x86 Linux direct boot. + +use alloc::vec::Vec; + +use super::linux::X86LinuxRange; + +pub const MP_TABLE_GPA: usize = 0x9f800; +pub const MP_TABLE_SIZE: usize = 0x800; + +const MP_CONFIG_GPA: usize = MP_TABLE_GPA; +const MP_FLOATING_POINTER_GPA: usize = 0x9fc00; +const MP_FLOATING_POINTER_OFFSET: usize = MP_FLOATING_POINTER_GPA - MP_TABLE_GPA; + +const LOCAL_APIC_ADDR: u32 = 0xfee0_0000; +const IO_APIC_ADDR: u32 = 0xfec0_0000; + +const BSP_APIC_ID: u8 = 0; +const IO_APIC_ID: u8 = 1; +const APIC_VERSION: u8 = 0x14; +const IO_APIC_VERSION: u8 = 0x11; + +const BUS_ID_PCI: u8 = 0; +const BUS_ID_ISA: u8 = 1; + +/// Returns the reserved guest physical range occupied by the MP table. +pub const fn reserved_range() -> X86LinuxRange { + X86LinuxRange::new(MP_TABLE_GPA, MP_TABLE_SIZE) +} + +/// Builds a minimal MP floating pointer and MP config table. +pub fn build() -> [u8; MP_TABLE_SIZE] { + let mut image = [0u8; MP_TABLE_SIZE]; + let config = build_config_table(); + image[..config.len()].copy_from_slice(&config); + + let floating = build_floating_pointer(); + image[MP_FLOATING_POINTER_OFFSET..MP_FLOATING_POINTER_OFFSET + floating.len()] + .copy_from_slice(&floating); + + image +} + +fn build_floating_pointer() -> [u8; 16] { + let mut data = [0u8; 16]; + data[0..4].copy_from_slice(b"_MP_"); + data[4..8].copy_from_slice(&(MP_CONFIG_GPA as u32).to_le_bytes()); + data[8] = 1; // length in 16-byte units + data[9] = 4; // MP spec revision 1.4 + data[10] = checksum(&data); + data +} + +fn build_config_table() -> Vec { + let entries = config_entries(); + let entries_len: usize = entries.iter().map(Vec::len).sum(); + + let mut table = Vec::with_capacity(44 + entries_len); + table.extend_from_slice(b"PCMP"); + table.extend_from_slice(&0u16.to_le_bytes()); + table.push(4); // MP spec revision 1.4 + table.push(0); // checksum, patched below + table.extend_from_slice(b"AXVISOR "); + table.extend_from_slice(b"X86LINUX "); + table.extend_from_slice(&0u32.to_le_bytes()); + table.extend_from_slice(&0u16.to_le_bytes()); + table.extend_from_slice(&(entries.len() as u16).to_le_bytes()); + table.extend_from_slice(&LOCAL_APIC_ADDR.to_le_bytes()); + table.extend_from_slice(&0u16.to_le_bytes()); + table.push(0); + table.push(0); + for entry in &entries { + table.extend_from_slice(entry); + } + + let len = table.len() as u16; + table[4..6].copy_from_slice(&len.to_le_bytes()); + table[7] = checksum(&table); + table +} + +fn config_entries() -> Vec> { + let mut entries = vec![ + processor_entry(), + bus_entry(BUS_ID_PCI, b"PCI "), + bus_entry(BUS_ID_ISA, b"ISA "), + io_apic_entry(), + ]; + push_isa_interrupt_entries(&mut entries); + push_pci_interrupt_entries(&mut entries); + entries +} + +fn processor_entry() -> Vec { + let mut entry = Vec::with_capacity(20); + entry.push(0); + entry.push(BSP_APIC_ID); + entry.push(APIC_VERSION); + entry.push(0x03); // enabled + BSP + entry.extend_from_slice(&0x0000_0600u32.to_le_bytes()); + entry.extend_from_slice(&0x0000_0201u32.to_le_bytes()); + entry.extend_from_slice(&[0; 8]); + entry +} + +fn bus_entry(bus_id: u8, bus_type: &[u8; 6]) -> Vec { + let mut entry = Vec::with_capacity(8); + entry.push(1); + entry.push(bus_id); + entry.extend_from_slice(bus_type); + entry +} + +fn io_apic_entry() -> Vec { + let mut entry = Vec::with_capacity(8); + entry.push(2); + entry.push(IO_APIC_ID); + entry.push(IO_APIC_VERSION); + entry.push(0x01); // enabled + entry.extend_from_slice(&IO_APIC_ADDR.to_le_bytes()); + entry +} + +fn push_isa_interrupt_entries(entries: &mut Vec>) { + // Keep legacy ISA IRQs identity-routed to the IOAPIC. IRQ0 is timer and + // IRQ4 is COM1; both are useful during early Linux bring-up diagnostics. + for irq in 0u8..16 { + entries.push(interrupt_entry(0, BUS_ID_ISA, irq, irq)); + } +} + +fn push_pci_interrupt_entries(entries: &mut Vec>) { + // QEMU q35 exposes the host rootfs virtio-blk as 00:03.0 in the current + // smoke setup. Add enough INTx routing for Linux to build the PCI IRQ + // table before a fuller virtual PCI IRQ router exists. + for dev in 0u8..4 { + for pin in 0u8..4 { + let source_irq = (dev << 2) | pin; + let intin = pci_intx_gsi(dev, pin); + entries.push(interrupt_entry(0, BUS_ID_PCI, source_irq, intin)); + } + } +} + +const fn pci_intx_gsi(dev: u8, pin: u8) -> u8 { + // Keep the guest MP table aligned with the host q35 IOAPIC line observed + // for the current smoke device. This is intentionally narrow; a later PCI + // IRQ router should derive it from platform/device topology instead. + if dev == 3 && pin == 0 { + 23 + } else { + 16 + ((dev + pin) & 3) + } +} + +fn interrupt_entry( + interrupt_type: u8, + source_bus_id: u8, + source_bus_irq: u8, + dest_io_apic_intin: u8, +) -> Vec { + let mut entry = Vec::with_capacity(8); + entry.push(3); + entry.push(interrupt_type); + entry.extend_from_slice(&0u16.to_le_bytes()); + entry.push(source_bus_id); + entry.push(source_bus_irq); + entry.push(IO_APIC_ID); + entry.push(dest_io_apic_intin); + entry +} + +fn checksum(bytes: &[u8]) -> u8 { + 0u8.wrapping_sub(bytes.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builds_valid_mp_table_checksums() { + let image = build(); + let config_len = u16::from_le_bytes([image[4], image[5]]) as usize; + assert_eq!(&image[..4], b"PCMP"); + assert_eq!( + image[..config_len] + .iter() + .fold(0u8, |sum, byte| sum.wrapping_add(*byte)), + 0 + ); + + let fp = &image[MP_FLOATING_POINTER_OFFSET..MP_FLOATING_POINTER_OFFSET + 16]; + assert_eq!(&fp[..4], b"_MP_"); + assert_eq!(fp.iter().fold(0u8, |sum, byte| sum.wrapping_add(*byte)), 0); + assert_eq!( + u32::from_le_bytes([fp[4], fp[5], fp[6], fp[7]]) as usize, + MP_CONFIG_GPA + ); + } +} diff --git a/os/axvisor/src/vmm/images/x86_boot.rs b/os/axvisor/src/vmm/images/x86/multiboot.rs similarity index 100% rename from os/axvisor/src/vmm/images/x86_boot.rs rename to os/axvisor/src/vmm/images/x86/multiboot.rs diff --git a/os/axvisor/src/vmm/mod.rs b/os/axvisor/src/vmm/mod.rs index 9cff0be1ab..0f7ea84506 100644 --- a/os/axvisor/src/vmm/mod.rs +++ b/os/axvisor/src/vmm/mod.rs @@ -16,6 +16,7 @@ mod hvc; mod ivc; pub mod config; +pub mod devices; pub mod images; pub mod timer; pub mod vcpus; @@ -64,6 +65,27 @@ pub fn init() { for vm in vm_list::get_vm_list() { vcpus::setup_vm_primary_vcpu(vm); } + + #[cfg(all(feature = "fs", target_arch = "x86_64"))] + release_host_filesystem_for_guest_passthrough().expect( + "Failed to release host filesystem before guest passthrough devices take ownership", + ); +} + +#[cfg(all(feature = "fs", target_arch = "x86_64"))] +fn release_host_filesystem_for_guest_passthrough() -> AxResult { + use std::os::arceos::modules::ax_fs; + + let has_conflicting_guest_ownership = vm_list::get_vm_list() + .into_iter() + .any(|vm| vm.has_host_fs_passthrough_conflict()); + if !has_conflicting_guest_ownership { + return Ok(()); + } + + ax_fs::shutdown_filesystems()?; + info!("Host filesystem cleanly unmounted before guest passthrough devices start"); + Ok(()) } /// Start the VMM. diff --git a/os/axvisor/src/vmm/timer.rs b/os/axvisor/src/vmm/timer.rs index 4067c4385c..b9fb8e9f0e 100644 --- a/os/axvisor/src/vmm/timer.rs +++ b/os/axvisor/src/vmm/timer.rs @@ -59,7 +59,7 @@ static TIMER_LIST: LazyInit>> = LazyInit::new /// Registers a new timer that will execute at the specified deadline /// /// # Arguments -/// - `deadline`: The absolute time in nanoseconds when the timer should trigger +/// - `deadline`: The absolute monotonic time in nanoseconds when the timer should trigger /// - `handler`: The callback function to execute when the timer expires /// /// # Returns @@ -76,13 +76,17 @@ where ); // SAFETY: Called from a vCPU task pinned to a physical CPU. TIMER_LIST is // initialised per-CPU in init_percpu() before any timer operation is invoked. - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - let mut timers = timer_list.lock(); // The token is only an identifier used for cancellation and does not // publish any timer data, so relaxed ordering is sufficient. let token = TOKEN.fetch_add(1, Ordering::Relaxed); - let event = VmmTimerEvent::new(token, handler); - timers.set(TimeValue::from_nanos(deadline), event); + let next_deadline = { + let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; + let mut timers = timer_list.lock(); + let event = VmmTimerEvent::new(token, handler); + timers.set(TimeValue::from_nanos(deadline), event); + timers.next_deadline() + }; + rearm_host_timer(next_deadline); token } @@ -93,30 +97,40 @@ where pub fn cancel_timer(token: usize) { // SAFETY: Called from a vCPU task pinned to a physical CPU. TIMER_LIST is // initialised per-CPU in init_percpu() before any timer operation is invoked. - let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; - let mut timers = timer_list.lock(); - timers.cancel(|event| event.token == token); + let next_deadline = { + let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; + let mut timers = timer_list.lock(); + timers.cancel(|event| event.token == token); + timers.next_deadline() + }; + rearm_host_timer(next_deadline); } /// Check and process any pending timer events pub fn check_events() { - // info!("Checking timer events..."); - // info!("now is {:#?}", ax_hal::time::wall_time()); // SAFETY: Called from a vCPU task pinned to a physical CPU. TIMER_LIST is // initialised per-CPU in init_percpu() before any timer operation is invoked. let timer_list = unsafe { TIMER_LIST.current_ref_mut_raw() }; loop { - let now = ax_hal::time::wall_time(); + let now = ax_hal::time::monotonic_time(); let event = timer_list.lock().expire_one(now); if let Some((_deadline, event)) = event { trace!("pick one {_deadline:#?} to handle!!!"); event.callback(now); } else { + let next_deadline = timer_list.lock().next_deadline(); + rearm_host_timer(next_deadline); break; } } } +fn rearm_host_timer(next_deadline: Option) { + if let Some(deadline) = next_deadline { + ax_hal::time::set_oneshot_timer(deadline.as_nanos() as u64); + } +} + // /// Schedule the next timer event based on the periodic interval // pub fn scheduler_next_event() { // trace!("Scheduling next event..."); diff --git a/os/axvisor/src/vmm/vcpus.rs b/os/axvisor/src/vmm/vcpus.rs index 6ef987f27a..ffc147ca34 100644 --- a/os/axvisor/src/vmm/vcpus.rs +++ b/os/axvisor/src/vmm/vcpus.rs @@ -14,7 +14,6 @@ use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; use ax_cpumask::CpuMask; - use ax_kspin::SpinNoIrq as Mutex; use core::sync::atomic::{AtomicUsize, Ordering}; use std::os::arceos::{ @@ -411,9 +410,14 @@ fn vcpu_run() { wait_for(vm_id, || vm.running()); info!("VM[{}] VCpu[{}] running...", vm.id(), vcpu.id()); + #[cfg(target_arch = "x86_64")] + super::devices::x86::enable_ioapic_irq_forwarding(&vm, &vcpu); mark_vcpu_running(vm_id); loop { + #[cfg(target_arch = "x86_64")] + super::devices::x86::drain_pending_ioapic_irqs(&vm, &vcpu); + match vm.run_vcpu(vcpu_id) { Ok(exit_reason) => match exit_reason { AxVCpuExitReason::Hypercall { nr, args } => { @@ -449,13 +453,41 @@ fn vcpu_run() { // TODO: maybe move this irq dispatcher to lower layer to accelerate the interrupt handling ax_hal::trap::irq_handler(vector as usize); super::timer::check_events(); + #[cfg(target_arch = "x86_64")] + super::devices::x86::forward_passthrough_irq_from_vmexit( + &vm, + &vcpu, + vector as usize, + ); + #[cfg(target_arch = "x86_64")] + super::devices::x86::inject_pending_serial_irq(&vm, &vcpu); #[cfg(target_arch = "riscv64")] { vcpu.get_arch_vcpu().latch_hvip_from_hw(); } } + AxVCpuExitReason::PreemptionTimer => { + #[cfg(target_arch = "x86_64")] + super::devices::x86::inject_due_pit_irq0(&vm, &vcpu); + #[cfg(target_arch = "x86_64")] + super::devices::x86::inject_pending_serial_irq(&vm, &vcpu); + } + AxVCpuExitReason::InterruptEnd { vector } => + { + #[cfg(target_arch = "x86_64")] + if let Some(vector) = vector { + super::devices::x86::inject_pending_ioapic_irq_after_eoi( + &vm, &vcpu, vector, + ); + } + } AxVCpuExitReason::Halt => { debug!("VM[{vm_id}] run VCpu[{vcpu_id}] Halt"); + #[cfg(target_arch = "x86_64")] + super::devices::x86::inject_pending_serial_irq(&vm, &vcpu); + #[cfg(target_arch = "x86_64")] + continue; + #[cfg(not(target_arch = "x86_64"))] wait(vm_id) } AxVCpuExitReason::Nothing => {} @@ -571,6 +603,9 @@ fn vcpu_run() { vm.set_vm_status(axvm::VMStatus::Stopped); info!("VM[{}] state changed to Stopped", vm_id); + #[cfg(target_arch = "x86_64")] + super::devices::x86::disable_ioapic_irq_forwarding_for_vm(vm_id); + sub_running_vm_count(1); ax_wait_queue_wake(&super::VMM, 1); } diff --git a/platforms/ax-plat-x86-pc/src/time.rs b/platforms/ax-plat-x86-pc/src/time.rs index ddca9d7f53..fcad40475e 100644 --- a/platforms/ax-plat-x86-pc/src/time.rs +++ b/platforms/ax-plat-x86-pc/src/time.rs @@ -114,8 +114,7 @@ impl TimeIf for TimeIfImpl { unsafe { if now_ns < deadline_ns { let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); - assert!(apic_ticks <= u32::MAX as u64); - lapic.set_timer_initial(apic_ticks.max(1) as u32); + lapic.set_timer_initial(apic_ticks.clamp(1, u32::MAX as u64) as u32); } else { lapic.set_timer_initial(1); } diff --git a/platforms/ax-plat-x86-qemu-q35/src/apic.rs b/platforms/ax-plat-x86-qemu-q35/src/apic.rs index 06d7e563d8..476716611b 100644 --- a/platforms/ax-plat-x86-qemu-q35/src/apic.rs +++ b/platforms/ax-plat-x86-qemu-q35/src/apic.rs @@ -20,7 +20,7 @@ use ax_kspin::SpinNoIrq; use ax_lazyinit::LazyInit; use ax_plat::mem::{PhysAddr, pa, phys_to_virt}; use x2apic::{ - ioapic::IoApic, + ioapic::{IoApic, IrqFlags}, lapic::{LocalApic, LocalApicBuilder, xapic_base}, }; use x86_64::instructions::port::Port; @@ -34,6 +34,7 @@ pub(super) mod vectors { } const IO_APIC_BASE: PhysAddr = pa!(0xFEC0_0000); +const IO_APIC_VECTOR_OFFSET: u8 = 0x20; static mut LOCAL_APIC: MaybeUninit = MaybeUninit::uninit(); static mut IS_X2APIC: bool = false; @@ -42,18 +43,30 @@ static IO_APIC: LazyInit> = LazyInit::new(); /// Enables or disables the given IRQ. #[cfg(feature = "irq")] pub fn set_enable(vector: usize, enabled: bool) { - // should not affect LAPIC interrupts - if vector < APIC_TIMER_VECTOR as _ { + if let Some(irq) = vector_to_io_apic_irq(vector) { unsafe { + let mut io_apic = IO_APIC.lock(); + if irq > io_apic.max_table_entry() { + warn!("IO APIC IRQ {irq} is out of range for vector {vector:#x}"); + return; + } if enabled { - IO_APIC.lock().enable_irq(vector as u8); + io_apic.enable_irq(irq); } else { - IO_APIC.lock().disable_irq(vector as u8); + io_apic.disable_irq(irq); } } } } +#[cfg(feature = "irq")] +fn vector_to_io_apic_irq(vector: usize) -> Option { + if vector < IO_APIC_VECTOR_OFFSET as usize || vector >= APIC_TIMER_VECTOR as usize { + return None; + } + Some((vector - IO_APIC_VECTOR_OFFSET as usize) as u8) +} + #[cfg(any(feature = "smp", feature = "irq"))] #[allow(static_mut_refs)] pub fn local_apic<'a>() -> &'a mut LocalApic { @@ -61,7 +74,7 @@ pub fn local_apic<'a>() -> &'a mut LocalApic { unsafe { LOCAL_APIC.assume_init_mut() } } -#[cfg(feature = "smp")] +#[cfg(any(feature = "smp", feature = "irq"))] pub fn raw_apic_id(id_u8: u8) -> u32 { if unsafe { IS_X2APIC } { id_u8 as u32 @@ -109,7 +122,20 @@ pub fn init_primary() { } info!("Initialize IO APIC..."); - let io_apic = unsafe { IoApic::new(phys_to_virt(IO_APIC_BASE).as_usize() as u64) }; + let mut io_apic = unsafe { IoApic::new(phys_to_virt(IO_APIC_BASE).as_usize() as u64) }; + unsafe { + io_apic.init(IO_APIC_VECTOR_OFFSET); + let max_entry = io_apic.max_table_entry(); + for irq in 0..=max_entry { + let mut entry = io_apic.table_entry(irq); + entry.set_flags(entry.flags() | IrqFlags::MASKED); + io_apic.set_table_entry(irq, entry); + } + debug!( + "IO APIC initialized with vector offset {:#x}, entries 0..={}", + IO_APIC_VECTOR_OFFSET, max_entry + ); + } IO_APIC.init_once(SpinNoIrq::new(io_apic)); } @@ -175,14 +201,15 @@ mod irq_impl { /// Sends an inter-processor interrupt (IPI) to the specified target CPU or all CPUs. fn send_ipi(irq_num: usize, target: IpiTarget) { match target { - IpiTarget::Current { cpu_id } => { + IpiTarget::Current { cpu_id: _ } => { unsafe { - super::local_apic().send_ipi_self(cpu_id as _); + super::local_apic().send_ipi_self(irq_num as _); }; } IpiTarget::Other { cpu_id } => { + let apic_id = super::raw_apic_id(cpu_id as u8); unsafe { - super::local_apic().send_ipi(irq_num as _, cpu_id as _); + super::local_apic().send_ipi(irq_num as _, apic_id as _); }; } IpiTarget::AllExceptCurrent { diff --git a/platforms/ax-plat-x86-qemu-q35/src/time.rs b/platforms/ax-plat-x86-qemu-q35/src/time.rs index 5b76ac834a..8d5f26f814 100644 --- a/platforms/ax-plat-x86-qemu-q35/src/time.rs +++ b/platforms/ax-plat-x86-qemu-q35/src/time.rs @@ -128,8 +128,7 @@ impl TimeIf for TimeIfImpl { unsafe { if now_ns < deadline_ns { let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); - assert!(apic_ticks <= u32::MAX as u64); - lapic.set_timer_initial(apic_ticks.max(1) as u32); + lapic.set_timer_initial(apic_ticks.clamp(1, u32::MAX as u64) as u32); } else { lapic.set_timer_initial(1); } diff --git a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml index 96a1fc057a..ff0942357c 100644 --- a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml @@ -8,4 +8,4 @@ features = [ ] log = "Info" target = "x86_64-unknown-none" -vm_configs = ["os/axvisor/configs/vms/nimbos-x86_64-qemu-smp1.toml"] +vm_configs = ["os/axvisor/configs/vms/linux-x86_64-qemu-smp1.toml"] diff --git a/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64.toml b/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64.toml index 4f3e441239..1b7c3e32e7 100644 --- a/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64.toml +++ b/test-suit/axvisor/normal/qemu/smoke/qemu-x86_64.toml @@ -1,28 +1,36 @@ args = [ - "-nographic", + "-nodefaults", + "-no-user-config", + "-display", + "none", + "-serial", + "stdio", + "-monitor", + "none", "-cpu", "host", "-machine", - "q35", + "q35,sata=off,smbus=off,i8042=off,usb=off,graphics=off", "-smp", "1", "-accel", "kvm", "-device", - "virtio-blk-pci,drive=disk0", + "virtio-blk-pci,drive=disk0,addr=03.0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", "-m", - "128M", + "512M", ] +timeout = 180 fail_regex = [ "(?i)\\bpanic(?:ked)?\\b", "(?i)kernel panic", "(?i)login incorrect", "(?i)permission denied", ] -success_regex = ["Hello world from user mode program!"] -shell_prefix = ">>" -shell_init_cmd = "hello_world" +success_regex = ["(?m)^guest linux test pass!\\s*$"] +shell_prefix = "~ #" +shell_init_cmd = "pwd && echo 'guest linux test pass!'" to_bin = false uefi = false diff --git a/virtualization/axdevice/Cargo.toml b/virtualization/axdevice/Cargo.toml index 5dce2a9698..fb0a39b56d 100644 --- a/virtualization/axdevice/Cargo.toml +++ b/virtualization/axdevice/Cargo.toml @@ -29,3 +29,8 @@ axdevice_base = { workspace = true } arm_vgic = { workspace = true, features = ["vgicv3"] } [target.'cfg(target_arch = "riscv64")'.dependencies] riscv_vplic = { workspace = true } +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86_vlapic = { workspace = true } + +[dev-dependencies] +axvisor_api = { workspace = true } diff --git a/virtualization/axdevice/src/device.rs b/virtualization/axdevice/src/device.rs index b79b2dd78b..8dd28bf1c2 100644 --- a/virtualization/axdevice/src/device.rs +++ b/virtualization/axdevice/src/device.rs @@ -26,10 +26,14 @@ use axaddrspace::{ GuestPhysAddr, GuestPhysAddrRange, device::{AccessWidth, DeviceAddrRange, Port, PortRange, SysRegAddr, SysRegAddrRange}, }; +#[cfg(target_arch = "x86_64")] +use axdevice_base::map_device_of_type; use axdevice_base::{BaseDeviceOps, BaseMmioDeviceOps, BasePortDeviceOps, BaseSysRegDeviceOps}; use axvmconfig::{EmulatedDeviceConfig, EmulatedDeviceType}; #[cfg(target_arch = "riscv64")] use riscv_vplic::VPlicGlobal; +#[cfg(target_arch = "x86_64")] +use x86_vlapic::{EmulatedIoApic, EmulatedPit, EmulatedSerialPort, IoApicInterrupt}; use crate::{AxVmDeviceConfig, range_alloc::RangeAllocator}; @@ -280,6 +284,54 @@ impl AxVmDevices { ); } } + EmulatedDeviceType::Console => { + #[cfg(target_arch = "x86_64")] + { + this.add_port_dev(Arc::new(EmulatedSerialPort::new())); + info!("x86 16550 serial initialized for ports 0x3f8..=0x3ff"); + } + #[cfg(not(target_arch = "x86_64"))] + { + warn!( + "emu type: {} is not supported on this platform", + config.emu_type + ); + } + } + EmulatedDeviceType::X86IoApic => { + #[cfg(target_arch = "x86_64")] + { + this.add_mmio_dev(Arc::new(EmulatedIoApic::new( + config.base_gpa.into(), + Some(config.length), + ))); + info!( + "x86 IO APIC initialized with base GPA {:#x} and length {:#x}", + config.base_gpa, config.length + ); + } + #[cfg(not(target_arch = "x86_64"))] + { + warn!( + "emu type: {} is not supported on this platform", + config.emu_type + ); + } + } + EmulatedDeviceType::X86Pit => { + #[cfg(target_arch = "x86_64")] + { + this.add_port_dev(Arc::new(EmulatedPit::new())); + info!("x86 PIT initialized for ports 0x40..=0x43 and 0x61"); + } + #[cfg(not(target_arch = "x86_64"))] + { + warn!( + "emu type: {} is not supported on this platform", + config.emu_type + ); + } + } EmulatedDeviceType::IVCChannel => { if this.ivc_channel.is_none() { // Initialize the IVC channel range allocator @@ -385,6 +437,51 @@ impl AxVmDevices { self.emu_port_devices.iter() } + /// Returns the guest vector programmed for an x86 IOAPIC GSI. + #[cfg(target_arch = "x86_64")] + pub fn x86_ioapic_vector_for_gsi(&self, gsi: usize) -> Option { + self.emu_mmio_devices.iter().find_map(|dev| { + map_device_of_type(dev, |ioapic: &EmulatedIoApic| ioapic.vector_for_gsi(gsi)).flatten() + }) + } + + /// Assert an x86 IOAPIC GSI and return the interrupt to inject. + #[cfg(target_arch = "x86_64")] + pub fn x86_ioapic_assert_gsi(&self, gsi: usize) -> Option { + self.emu_mmio_devices.iter().find_map(|dev| { + map_device_of_type(dev, |ioapic: &EmulatedIoApic| ioapic.assert_gsi(gsi)).flatten() + }) + } + + /// Broadcast an x86 local APIC EOI to the virtual IOAPIC. + #[cfg(target_arch = "x86_64")] + pub fn x86_ioapic_end_of_interrupt(&self, vector: u8) -> Option { + self.emu_mmio_devices.iter().find_map(|dev| { + map_device_of_type(dev, |ioapic: &EmulatedIoApic| { + ioapic.end_of_interrupt(vector) + }) + .flatten() + }) + } + + /// Consume a pending x86 PIT channel 0 timer tick if the deadline is due. + #[cfg(target_arch = "x86_64")] + pub fn x86_pit_consume_irq0_if_due(&self, now_ns: u64) -> bool { + self.emu_port_devices.iter().any(|dev| { + map_device_of_type(dev, |pit: &EmulatedPit| pit.consume_irq0_if_due(now_ns)) + .unwrap_or(false) + }) + } + + /// Poll x86 COM1 and return whether it has a pending RX interrupt. + #[cfg(target_arch = "x86_64")] + pub fn x86_serial_poll_irq(&self) -> bool { + self.emu_port_devices.iter().any(|dev| { + map_device_of_type(dev, |serial: &EmulatedSerialPort| serial.poll_irq()) + .unwrap_or(false) + }) + } + /// Iterates over the MMIO devices in the set. pub fn iter_mut_mmio_dev(&mut self) -> impl Iterator> { self.emu_mmio_devices.iter_mut() diff --git a/virtualization/axdevice/tests/test.rs b/virtualization/axdevice/tests/test.rs index 68d858f91b..a24d1a18fe 100644 --- a/virtualization/axdevice/tests/test.rs +++ b/virtualization/axdevice/tests/test.rs @@ -113,3 +113,43 @@ fn test_mmio_panic_on_missing_device() { let _ = devices.handle_mmio_read(invalid_addr, width); } + +// Mock implementations for axvisor_api interfaces required by x86_vlapic +// when running `cargo test -p axdevice` on x86_64 host. + +struct MockConsoleIfImpl; + +#[axvisor_api::api_impl] +impl axvisor_api::console::ConsoleIf for MockConsoleIfImpl { + fn write_bytes(_bytes: &[u8]) {} + + fn read_bytes(_bytes: &mut [u8]) -> usize { + 0 + } +} + +struct MockTimeIfImpl; + +#[axvisor_api::api_impl] +impl axvisor_api::time::TimeIf for MockTimeIfImpl { + fn current_ticks() -> axvisor_api::time::Ticks { + 0 + } + + fn ticks_to_nanos(ticks: axvisor_api::time::Ticks) -> axvisor_api::time::Nanos { + ticks + } + + fn nanos_to_ticks(nanos: axvisor_api::time::Nanos) -> axvisor_api::time::Ticks { + nanos + } + + fn register_timer( + _deadline: axvisor_api::time::TimeValue, + _callback: Box, + ) -> axvisor_api::time::CancelToken { + 0 + } + + fn cancel_timer(_token: axvisor_api::time::CancelToken) {} +} diff --git a/virtualization/axvcpu/Cargo.toml b/virtualization/axvcpu/Cargo.toml index fd406aeb5c..a503cf2232 100644 --- a/virtualization/axvcpu/Cargo.toml +++ b/virtualization/axvcpu/Cargo.toml @@ -15,3 +15,4 @@ ax-memory-addr = { workspace = true } ax-percpu = { workspace = true } axaddrspace = { workspace = true } axvisor_api = { workspace = true } +ax-kspin = { workspace = true } diff --git a/virtualization/axvcpu/src/arch_vcpu.rs b/virtualization/axvcpu/src/arch_vcpu.rs index f843e1a9f5..325a059692 100644 --- a/virtualization/axvcpu/src/arch_vcpu.rs +++ b/virtualization/axvcpu/src/arch_vcpu.rs @@ -18,6 +18,19 @@ use axvisor_api::vmm::{VCpuId, VMId}; use crate::exit::AxVCpuExitReason; +/// Interrupt trigger mode. +/// +/// Represents the trigger mode of an interrupt in a platform-neutral way. +/// Architectures that do not distinguish between edge and level triggering +/// can ignore this parameter. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InterruptTriggerMode { + /// Edge-triggered interrupt. + EdgeTriggered, + /// Level-triggered interrupt. + LevelTriggered, +} + /// Architecture-specific virtual CPU trait definition. /// /// This trait provides an abstraction layer for implementing virtual CPUs across @@ -88,6 +101,24 @@ pub trait AxArchVCpu: Sized { /// until the VCpu is running. fn inject_interrupt(&mut self, vector: usize) -> AxResult; + /// Inject an interrupt with trigger mode metadata. + fn inject_interrupt_with_trigger( + &mut self, + vector: usize, + trigger: InterruptTriggerMode, + ) -> AxResult { + debug_assert!( + trigger == InterruptTriggerMode::EdgeTriggered, + "level-triggered interrupt injection requires an architecture-specific implementation" + ); + self.inject_interrupt(vector) + } + + /// Process a guest EOI and return the vector for an external EOI broadcast. + fn handle_eoi(&mut self) -> Option { + None + } + /// Sets the return value that will be delivered to the guest. fn set_return_value(&mut self, val: usize); } diff --git a/virtualization/axvcpu/src/exit.rs b/virtualization/axvcpu/src/exit.rs index b2e07c86da..7daf37fb8b 100644 --- a/virtualization/axvcpu/src/exit.rs +++ b/virtualization/axvcpu/src/exit.rs @@ -233,6 +233,18 @@ pub enum AxVCpuExitReason { /// The VCpu can typically be resumed immediately after these checks. Nothing, + /// A hardware virtualization preemption timer expired. + /// + /// This gives the VMM a chance to poll virtual device timers without relying on an unrelated + /// host interrupt or guest I/O exit. + PreemptionTimer, + + /// The guest completed interrupt service with EOI. + InterruptEnd { + /// The EOI vector, when an external interrupt controller needs notification. + vector: Option, + }, + /// VM entry failed due to invalid VCpu state or configuration. /// /// This corresponds to `KVM_EXIT_FAIL_ENTRY` in KVM and indicates that diff --git a/virtualization/axvcpu/src/lib.rs b/virtualization/axvcpu/src/lib.rs index cddca862f2..e182029c4f 100644 --- a/virtualization/axvcpu/src/lib.rs +++ b/virtualization/axvcpu/src/lib.rs @@ -40,7 +40,7 @@ mod test; // Unit tests for VCpu functionality mod vcpu; // Main VCpu implementation and state management // Public API exports -pub use arch_vcpu::AxArchVCpu; // Architecture-specific VCpu trait +pub use arch_vcpu::{AxArchVCpu, InterruptTriggerMode}; // Architecture-specific VCpu trait pub use exit::AxVCpuExitReason; pub use percpu::*; // Per-CPU state management types pub use vcpu::*; // Main VCpu types and functions // VM exit reasons diff --git a/virtualization/axvcpu/src/vcpu.rs b/virtualization/axvcpu/src/vcpu.rs index 5329a47237..071b96833b 100644 --- a/virtualization/axvcpu/src/vcpu.rs +++ b/virtualization/axvcpu/src/vcpu.rs @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::cell::{RefCell, UnsafeCell}; +use core::cell::UnsafeCell; use ax_errno::{AxResult, ax_err}; +use ax_kspin::SpinNoIrq as Mutex; use axaddrspace::{GuestPhysAddr, HostPhysAddr}; use axvisor_api::vmm::{VCpuId, VMId}; -use super::{AxArchVCpu, AxVCpuExitReason}; +use super::{AxArchVCpu, AxVCpuExitReason, InterruptTriggerMode}; /// Immutable configuration data for a virtual CPU. /// @@ -62,8 +63,9 @@ pub enum VCpuState { /// Mutable runtime state of a virtual CPU. /// -/// This structure contains data that changes during VCpu execution, -/// protected by RefCell for interior mutability. +/// This structure contains data that changes during VCpu execution. The state is guarded by a +/// short critical-section mutex because a vCPU can be observed by management or interrupt paths +/// while the owning vCPU task is between VM exits. pub struct AxVCpuInnerMut { /// Current execution state of the VCpu state: VCpuState, @@ -81,8 +83,8 @@ pub struct AxVCpuInnerMut { pub struct AxVCpu { /// Immutable VCpu configuration (VM ID, CPU affinity, etc.) inner_const: AxVCpuInnerConst, - /// Mutable VCpu state protected by RefCell for safe interior mutability - inner_mut: RefCell, + /// Mutable VCpu state protected by a short critical-section mutex. + inner_mut: Mutex, /// Architecture-specific VCpu implementation /// /// Uses UnsafeCell instead of RefCell because RefCell guards cannot be @@ -121,7 +123,7 @@ impl AxVCpu { favor_phys_cpu, phys_cpu_set, }, - inner_mut: RefCell::new(AxVCpuInnerMut { + inner_mut: Mutex::new(AxVCpuInnerMut { state: VCpuState::Created, }), arch_vcpu: UnsafeCell::new(A::new(vm_id, vcpu_id, arch_config)?), @@ -184,7 +186,7 @@ impl AxVCpu { /// Gets the current execution state of the VCpu. pub fn state(&self) -> VCpuState { - self.inner_mut.borrow().state + self.inner_mut.lock().state } /// Set the state of the VCpu. @@ -192,7 +194,7 @@ impl AxVCpu { /// This method is unsafe because it may break the state transition model. /// Use it with caution. pub unsafe fn set_state(&self, state: VCpuState) { - self.inner_mut.borrow_mut().state = state; + self.inner_mut.lock().state = state; } /// Execute a block with the state of the VCpu transitioned from `from` to `to`. If the current state is not `from`, return an error. @@ -204,22 +206,25 @@ impl AxVCpu { where F: FnOnce() -> AxResult, { - let mut inner_mut = self.inner_mut.borrow_mut(); - if inner_mut.state != from { - inner_mut.state = VCpuState::Invalid; - ax_err!( - BadState, - format!("VCpu state is not {:?}, but {:?}", from, inner_mut.state) - ) - } else { - let result = f(); - inner_mut.state = if result.is_err() { - VCpuState::Invalid - } else { - to - }; - result + { + let mut inner_mut = self.inner_mut.lock(); + if inner_mut.state != from { + let current_state = inner_mut.state; + inner_mut.state = VCpuState::Invalid; + return ax_err!( + BadState, + format!("VCpu state is not {from:?}, but {current_state:?}") + ); + } } + + let result = f(); + self.inner_mut.lock().state = if result.is_err() { + VCpuState::Invalid + } else { + to + }; + result } /// Execute a block with the current VCpu set to `&self`. @@ -301,6 +306,21 @@ impl AxVCpu { self.get_arch_vcpu().inject_interrupt(vector) } + /// Inject an interrupt with trigger mode metadata. + pub fn inject_interrupt_with_trigger( + &self, + vector: usize, + trigger: InterruptTriggerMode, + ) -> AxResult { + self.get_arch_vcpu() + .inject_interrupt_with_trigger(vector, trigger) + } + + /// Process a guest EOI and return the vector for an external EOI broadcast. + pub fn handle_eoi(&self) -> Option { + self.get_arch_vcpu().handle_eoi() + } + /// Sets the return value of the VCpu. pub fn set_return_value(&self, val: usize) { self.get_arch_vcpu().set_return_value(val); diff --git a/virtualization/axvisor_api/src/console.rs b/virtualization/axvisor_api/src/console.rs new file mode 100644 index 0000000000..369fb78c99 --- /dev/null +++ b/virtualization/axvisor_api/src/console.rs @@ -0,0 +1,13 @@ +//! Console APIs for virtual console devices. + +/// Console I/O interface exposed by the hypervisor host. +#[crate::api_def] +pub trait ConsoleIf { + /// Writes raw bytes to the host console. + fn write_bytes(bytes: &[u8]); + + /// Reads bytes from the host console into `bytes`. + /// + /// Returns the number of bytes read. + fn read_bytes(bytes: &mut [u8]) -> usize; +} diff --git a/virtualization/axvisor_api/src/lib.rs b/virtualization/axvisor_api/src/lib.rs index d58fa7aebb..3edfbb9229 100644 --- a/virtualization/axvisor_api/src/lib.rs +++ b/virtualization/axvisor_api/src/lib.rs @@ -147,6 +147,7 @@ pub use axvisor_api_proc::{api_def, api_impl}; pub mod arch; +pub mod console; pub mod host; pub mod memory; pub mod time; diff --git a/virtualization/axvm/src/config.rs b/virtualization/axvm/src/config.rs index fcd47dbdb8..9e9abc883d 100644 --- a/virtualization/axvm/src/config.rs +++ b/virtualization/axvm/src/config.rs @@ -63,6 +63,8 @@ pub struct RamdiskInfo { pub struct VMImageConfig { /// The load address in GPA for the kernel image. pub kernel_load_gpa: GuestPhysAddr, + /// Whether VM images are loaded from the host filesystem. + pub loaded_from_filesystem: bool, /// The load address in GPA for the BIOS image, `None` if not used. pub bios_load_gpa: Option, /// The load address in GPA for the device tree blob (DTB), `None` if not used. @@ -110,6 +112,7 @@ impl From for AxVMConfig { }, image_config: VMImageConfig { kernel_load_gpa: GuestPhysAddr::from(cfg.kernel.kernel_load_addr), + loaded_from_filesystem: cfg.kernel.image_location.as_deref() == Some("fs"), bios_load_gpa, dtb_load_gpa: cfg.kernel.dtb_load_addr.map(GuestPhysAddr::from), ramdisk: cfg.kernel.ramdisk_load_addr.map(|addr| RamdiskInfo { @@ -176,6 +179,11 @@ impl AxVMConfig { &self.image_config } + /// Returns whether VM images are loaded from the host filesystem. + pub fn images_loaded_from_filesystem(&self) -> bool { + self.image_config.loaded_from_filesystem + } + /// Returns the entry address in GPA for the Bootstrap Processor (BSP). pub fn bsp_entry(&self) -> GuestPhysAddr { // Retrieves BSP entry from the CPU configuration. diff --git a/virtualization/axvm/src/vcpu.rs b/virtualization/axvm/src/vcpu.rs index e1e0506c83..f200c4dc10 100644 --- a/virtualization/axvm/src/vcpu.rs +++ b/virtualization/axvm/src/vcpu.rs @@ -18,6 +18,7 @@ cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { pub use x86_vcpu::X86ArchVCpu as AxArchVCpuImpl; pub use x86_vcpu::X86ArchPerCpuState as AxVMArchPerCpuImpl; + pub use x86_vcpu::X86VCpuSetupConfig as AxVCpuSetupConfig; pub use x86_vcpu::has_hardware_support; #[allow(dead_code)] pub type AxVCpuCreateConfig = (); diff --git a/virtualization/axvm/src/vm.rs b/virtualization/axvm/src/vm.rs index c3350b3723..1de49ff9e1 100644 --- a/virtualization/axvm/src/vm.rs +++ b/virtualization/axvm/src/vm.rs @@ -26,13 +26,15 @@ use axdevice::{AxVmDeviceConfig, AxVmDevices}; use axvcpu::{AxVCpu, AxVCpuExitReason}; use axvisor_api::vmm::InterruptVector; use spin::Once; +#[cfg(all(target_arch = "x86_64", feature = "vmx"))] +use x86_vcpu::{X86_APIC_ACCESS_GPA, x86_apic_access_page_addr}; #[cfg(not(target_arch = "x86_64"))] use crate::vcpu::AxVCpuCreateConfig; #[cfg(target_arch = "aarch64")] use crate::vcpu::get_sysreg_device; use crate::{ - config::{AxVMConfig, PhysCpuList}, + config::{AxVMConfig, PhysCpuList, VMInterruptMode}, hal::PagingHandlerImpl, has_hardware_support, vcpu::AxArchVCpuImpl, @@ -204,6 +206,20 @@ impl AxVM { self.id } + /// Returns the configured VM interrupt mode. + pub fn interrupt_mode(&self) -> VMInterruptMode { + self.inner_mut.lock().config.interrupt_mode() + } + + /// Returns whether this VM loads its images from the host filesystem and maps passthrough + /// devices or address ranges that can require exclusive ownership after VM start. + pub fn has_host_fs_passthrough_conflict(&self) -> bool { + let inner_mut = self.inner_mut.lock(); + inner_mut.config.images_loaded_from_filesystem() + && (!inner_mut.config.pass_through_devices().is_empty() + || !inner_mut.config.pass_through_addresses().is_empty()) + } + /// Sets up the VM before booting. pub fn init(&self) -> AxResult { let mut inner_mut = self.inner_mut.lock(); @@ -311,6 +327,14 @@ impl AxVM { )?; } + #[cfg(all(target_arch = "x86_64", feature = "vmx"))] + inner_mut.address_space.map_linear( + GuestPhysAddr::from(X86_APIC_ACCESS_GPA), + x86_apic_access_page_addr(), + ax_memory_addr::PAGE_SIZE_4K, + MappingFlags::DEVICE | MappingFlags::READ | MappingFlags::WRITE, + )?; + #[cfg_attr(not(target_arch = "aarch64"), expect(unused_mut))] let mut devices = axdevice::AxVmDevices::new(AxVmDeviceConfig { emu_configs: inner_mut.config.emu_devices().to_vec(), @@ -384,9 +408,21 @@ impl AxVM { passthrough_timer: passthrough, } }; - #[cfg(not(any(target_arch = "aarch64", target_arch = "loongarch64")))] + #[cfg(not(any( + target_arch = "aarch64", + target_arch = "loongarch64", + target_arch = "x86_64" + )))] #[allow(clippy::let_unit_value)] let setup_config = ::SetupConfig::default(); + #[cfg(target_arch = "x86_64")] + let setup_config = crate::vcpu::AxVCpuSetupConfig { + emulate_com1: inner_mut + .config + .emu_devices() + .iter() + .any(|dev| dev.emu_type == axvmconfig::EmulatedDeviceType::Console), + }; let entry = if vcpu.id() == 0 { inner_mut.config.bsp_entry() diff --git a/virtualization/axvmconfig/src/lib.rs b/virtualization/axvmconfig/src/lib.rs index 7b7f743f58..e532f2a5f8 100644 --- a/virtualization/axvmconfig/src/lib.rs +++ b/virtualization/axvmconfig/src/lib.rs @@ -147,6 +147,12 @@ pub enum EmulatedDeviceType { /// ARM GIC Partial Passthrough Interrupt Translation Service device. GPPTITS = 0x22, + // 0x23 - 0x24: x86 platform devices. + /// x86 virtual IO APIC device. + X86IoApic = 0x23, + /// x86 virtual PIT/8254 timer device. + X86Pit = 0x24, + // 0x30: PPPT (PLIC Partial Passthrough) devices. /// RISC-V PLIC Partial Passthrough Global device. PPPTGlobal = 0x30, @@ -180,6 +186,8 @@ impl Display for EmulatedDeviceType { } EmulatedDeviceType::GPPTDistributor => write!(f, "gic partial passthrough distributor"), EmulatedDeviceType::GPPTITS => write!(f, "gic partial passthrough its"), + EmulatedDeviceType::X86IoApic => write!(f, "x86 io apic"), + EmulatedDeviceType::X86Pit => write!(f, "x86 pit"), EmulatedDeviceType::PPPTGlobal => write!(f, "plic partial passthrough global"), // EmulatedDeviceType::IOMMU => write!(f, "iommu"), // EmulatedDeviceType::ICCSRE => write!(f, "interrupt icc sre"), @@ -204,6 +212,8 @@ impl EmulatedDeviceType { // | EmulatedDeviceType::SGIR // | EmulatedDeviceType::ICCSRE | EmulatedDeviceType::GPPTRedistributor + | EmulatedDeviceType::X86IoApic + | EmulatedDeviceType::X86Pit | EmulatedDeviceType::VirtioBlk | EmulatedDeviceType::VirtioNet // | EmulatedDeviceType::GICR @@ -221,6 +231,8 @@ impl EmulatedDeviceType { 0x20 => EmulatedDeviceType::GPPTRedistributor, 0x21 => EmulatedDeviceType::GPPTDistributor, 0x22 => EmulatedDeviceType::GPPTITS, + 0x23 => EmulatedDeviceType::X86IoApic, + 0x24 => EmulatedDeviceType::X86Pit, 0x30 => EmulatedDeviceType::PPPTGlobal, 0xE1 => EmulatedDeviceType::VirtioBlk, 0xE2 => EmulatedDeviceType::VirtioNet, diff --git a/virtualization/x86_vcpu/src/lib.rs b/virtualization/x86_vcpu/src/lib.rs index 735acb2206..9f615f83ed 100644 --- a/virtualization/x86_vcpu/src/lib.rs +++ b/virtualization/x86_vcpu/src/lib.rs @@ -29,6 +29,13 @@ compile_error!("features `vmx` and `svm` are mutually exclusive"); #[cfg(test)] mod test_utils; +/// x86 vCPU setup configuration. +#[derive(Clone, Copy, Debug, Default)] +pub struct X86VCpuSetupConfig { + /// Intercept COM1 PIO ports and route them to an emulated serial device. + pub emulate_com1: bool, +} + pub(crate) mod msr; #[cfg(feature = "vmx")] #[macro_use] @@ -47,7 +54,7 @@ cfg_if::cfg_if! { pub use vendor::{ VmxArchPerCpuState, VmxArchPerCpuState as X86ArchPerCpuState, VmxArchVCpu, - VmxArchVCpu as X86ArchVCpu, + VmxArchVCpu as X86ArchVCpu, X86_APIC_ACCESS_GPA, x86_apic_access_page_addr, }; } else if #[cfg(feature = "svm")] { mod svm; @@ -79,3 +86,10 @@ pub(crate) fn restore_host_interrupt_flag(host_rflags: u64) { x86_64::instructions::interrupts::disable(); } } + +#[cfg(any(feature = "vmx", feature = "svm"))] +pub(crate) fn host_tsc_frequency_mhz() -> Option { + u32::try_from(axvisor_api::time::nanos_to_ticks(1_000)) + .ok() + .filter(|&freq| freq > 0) +} diff --git a/virtualization/x86_vcpu/src/svm/vcpu.rs b/virtualization/x86_vcpu/src/svm/vcpu.rs index 43053aa7d6..8307714e8d 100644 --- a/virtualization/x86_vcpu/src/svm/vcpu.rs +++ b/virtualization/x86_vcpu/src/svm/vcpu.rs @@ -22,7 +22,10 @@ use super::{ structs::{IOPm, MSRPm, VmcbFrame}, vmcb::{InterceptCrRw, InterceptExceptions, NestedCtl, VmcbTlbControl, set_vmcb_segment}, }; -use crate::{msr::Msr, regs::GeneralRegisters, restore_host_interrupt_flag, xstate::XState}; +use crate::{ + X86VCpuSetupConfig, msr::Msr, regs::GeneralRegisters, restore_host_interrupt_flag, + xstate::XState, +}; const QEMU_EXIT_PORT: u16 = 0x604; const QEMU_EXIT_MAGIC: u64 = 0x2000; @@ -615,14 +618,16 @@ impl SvmVcpu { edx: 0, }, EAX_FREQUENCY_INFO => { - const TIMER_FREQUENCY_MHZ: u32 = 3_000; + const FALLBACK_TSC_FREQUENCY_MHZ: u32 = 3_000; let mut res = cpuid!(regs_clone.rax, regs_clone.rcx); if res.eax == 0 { + let frequency_mhz = + crate::host_tsc_frequency_mhz().unwrap_or(FALLBACK_TSC_FREQUENCY_MHZ); warn!( "handle_cpuid: Failed to get TSC frequency by CPUID, default to \ - {TIMER_FREQUENCY_MHZ} MHz" + {frequency_mhz} MHz" ); - res.eax = TIMER_FREQUENCY_MHZ; + res.eax = frequency_mhz; } res } @@ -741,7 +746,7 @@ impl Debug for SvmVcpu { impl AxArchVCpu for SvmVcpu { type CreateConfig = (); - type SetupConfig = (); + type SetupConfig = X86VCpuSetupConfig; fn new(vm_id: VMId, vcpu_id: VCpuId, _config: Self::CreateConfig) -> AxResult { Self::create(vm_id, vcpu_id) diff --git a/virtualization/x86_vcpu/src/vmx/mod.rs b/virtualization/x86_vcpu/src/vmx/mod.rs index aed96224e6..7342b6ee9a 100644 --- a/virtualization/x86_vcpu/src/vmx/mod.rs +++ b/virtualization/x86_vcpu/src/vmx/mod.rs @@ -20,12 +20,14 @@ mod vcpu; mod vmcs; use ax_errno::ax_err_type; +use axaddrspace::HostPhysAddr; +use x86_vlapic::EmulatedLocalApic; use self::structs::VmxBasic; pub use self::{ definitions::VmxExitReason, percpu::VmxPerCpuState as VmxArchPerCpuState, - vcpu::VmxVcpu as VmxArchVCpu, + vcpu::{VmxVcpu as VmxArchVCpu, X86_APIC_ACCESS_GPA}, vmcs::{VmxExitInfo, VmxInterruptInfo, VmxIoExitInfo}, }; @@ -42,6 +44,10 @@ pub fn read_vmcs_revision_id() -> u32 { VmxBasic::read().revision_id } +pub fn x86_apic_access_page_addr() -> HostPhysAddr { + EmulatedLocalApic::virtual_apic_access_addr() +} + fn as_axerr(err: x86::vmx::VmFail) -> ax_errno::AxError { use x86::vmx::VmFail; match err { diff --git a/virtualization/x86_vcpu/src/vmx/vcpu.rs b/virtualization/x86_vcpu/src/vmx/vcpu.rs index ef23c69210..7b5a0e8ba7 100644 --- a/virtualization/x86_vcpu/src/vmx/vcpu.rs +++ b/virtualization/x86_vcpu/src/vmx/vcpu.rs @@ -20,13 +20,17 @@ use core::{ }; use ax_errno::{AxResult, ax_err, ax_err_type}; +use ax_memory_addr::AddrRange; use axaddrspace::{ - GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo, + GuestPhysAddr, GuestVirtAddr, HostPhysAddr, MappingFlags, NestedPageFaultInfo, device::{AccessWidth, Port, SysRegAddr, SysRegAddrRange}, }; use axdevice_base::BaseDeviceOps; use axvcpu::{AxArchVCpu, AxVCpuExitReason}; -use axvisor_api::vmm::{VCpuId, VMId}; +use axvisor_api::{ + memory::{self, PhysAddr}, + vmm::{VCpuId, VMId}, +}; use bit_field::BitField; use raw_cpuid::CpuId; use x86::{ @@ -48,14 +52,22 @@ use super::{ }, }; use crate::{ - ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters, restore_host_interrupt_flag, - xstate::XState, + X86VCpuSetupConfig, ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters, + restore_host_interrupt_flag, xstate::XState, }; -const VMX_PREEMPTION_TIMER_SET_VALUE: u32 = 1_000_000; +const VMX_PREEMPTION_TIMER_SET_VALUE: u32 = 100_000; const QEMU_EXIT_PORT: u16 = 0x604; const QEMU_EXIT_MAGIC: u64 = 0x2000; +const X86_PIT_PORT_BASE: u16 = 0x40; +const X86_PIT_PORT_COUNT: u32 = 4; +const X86_PIT_SPEAKER_PORT: u16 = 0x61; +const X86_COM1_PORT_BASE: u16 = 0x3f8; +const X86_COM1_PORT_COUNT: u32 = 8; +pub const X86_APIC_ACCESS_GPA: usize = 0xfee0_0000; +const X86_IOAPIC_BASE: usize = 0xfec0_0000; +const X86_IOAPIC_SIZE: usize = 0x1000; #[derive(PartialEq, Eq, Debug)] pub enum VmCpuMode { @@ -68,6 +80,13 @@ pub enum VmCpuMode { const MSR_IA32_EFER_LMA_BIT: u64 = 1 << 10; const CR0_PE: usize = 1 << 0; +#[derive(Clone, Copy, Debug)] +struct PendingEvent { + vector: u8, + err_code: Option, + level_triggered: bool, +} + /// A virtual CPU within a guest. #[repr(C)] pub struct VmxVcpu { @@ -104,7 +123,7 @@ pub struct VmxVcpu { // Interrupt-related fields /// Pending events to be injected to the guest. - pending_events: VecDeque<(u8, Option)>, + pending_events: VecDeque, /// Emulated Local APIC. vlapic: EmulatedLocalApic, @@ -145,7 +164,7 @@ impl VmxVcpu { /// Set the new [`VmxVcpu`] context from guest OS. pub fn setup(&mut self, ept_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { - self.setup_vmcs(entry, ept_root)?; + self.setup_vmcs(entry, ept_root, X86VCpuSetupConfig::default())?; Ok(()) } @@ -383,7 +402,25 @@ impl VmxVcpu { /// Add a virtual interrupt or exception to the pending events list, /// and try to inject it before later VM entries. pub fn queue_event(&mut self, vector: u8, err_code: Option) { - self.pending_events.push_back((vector, err_code)); + self.pending_events.push_back(PendingEvent { + vector, + err_code, + level_triggered: false, + }); + } + + /// Add a virtual interrupt or exception with trigger mode metadata. + pub fn queue_event_with_trigger( + &mut self, + vector: u8, + err_code: Option, + level_triggered: bool, + ) { + self.pending_events.push_back(PendingEvent { + vector, + err_code, + level_triggered, + }); } /// If enable, a VM exit occurs at the beginning of any instruction if @@ -417,25 +454,36 @@ impl VmxVcpu { // Implementation of private methods impl VmxVcpu { - fn setup_io_bitmap(&mut self) -> AxResult { + fn setup_io_bitmap(&mut self, config: X86VCpuSetupConfig) -> AxResult { // By default, I/O bitmap is set as `intercept_all`. // Todo: these should be combined with emulated pio device management, // in `modules/axvm/src/device/x86_64/mod.rs` somehow. - let io_to_be_intercepted = QEMU_EXIT_PORT..QEMU_EXIT_PORT + 1; // QEMU exit port + let io_to_be_intercepted = QEMU_EXIT_PORT..QEMU_EXIT_PORT + 1; // QEMU exit port. self.io_bitmap.set_intercept_of_range( io_to_be_intercepted.start as _, io_to_be_intercepted.count() as u32, true, ); + self.io_bitmap + .set_intercept_of_range(X86_PIT_PORT_BASE as u32, X86_PIT_PORT_COUNT, true); + self.io_bitmap + .set_intercept(X86_PIT_SPEAKER_PORT as u32, true); + if config.emulate_com1 { + self.io_bitmap.set_intercept_of_range( + X86_COM1_PORT_BASE as u32, + X86_COM1_PORT_COUNT, + true, + ); + } Ok(()) } #[allow(dead_code)] fn setup_msr_bitmap(&mut self) -> AxResult { // Intercept IA32_APIC_BASE MSR accesses - // let msr = x86::msr::IA32_APIC_BASE; - // self.msr_bitmap.set_read_intercept(msr, true); - // self.msr_bitmap.set_write_intercept(msr, true); + const IA32_APIC_BASE: u32 = 0x1b; + self.msr_bitmap.set_read_intercept(IA32_APIC_BASE, true); + self.msr_bitmap.set_write_intercept(IA32_APIC_BASE, true); // This is strange, guest Linux's access to `IA32_UMWAIT_CONTROL` will cause an exception. // But if we intercept it, it seems okay. @@ -453,7 +501,12 @@ impl VmxVcpu { Ok(()) } - fn setup_vmcs(&mut self, entry: GuestPhysAddr, ept_root: HostPhysAddr) -> AxResult { + fn setup_vmcs( + &mut self, + entry: GuestPhysAddr, + ept_root: HostPhysAddr, + config: X86VCpuSetupConfig, + ) -> AxResult { let paddr = self.vmcs.phys_addr().as_usize() as u64; unsafe { vmx::vmclear(paddr).map_err(as_axerr)?; @@ -461,7 +514,7 @@ impl VmxVcpu { self.bind_to_current_processor()?; self.setup_msr_bitmap()?; self.setup_vmcs_guest(entry)?; - self.setup_vmcs_control(ept_root, true)?; + self.setup_vmcs_control(ept_root, true, config)?; self.unbind_from_current_processor()?; Ok(()) } @@ -559,7 +612,12 @@ impl VmxVcpu { Ok(()) } - fn setup_vmcs_control(&mut self, ept_root: HostPhysAddr, is_guest: bool) -> AxResult { + fn setup_vmcs_control( + &mut self, + ept_root: HostPhysAddr, + is_guest: bool, + config: X86VCpuSetupConfig, + ) -> AxResult { // Intercept NMI and external interrupts. use PinbasedControls as PinCtrl; @@ -570,9 +628,10 @@ impl VmxVcpu { VmcsControl32::PINBASED_EXEC_CONTROLS, Msr::IA32_VMX_TRUE_PINBASED_CTLS, Msr::IA32_VMX_PINBASED_CTLS.read() as u32, - (PinCtrl::NMI_EXITING | PinCtrl::EXTERNAL_INTERRUPT_EXITING).bits(), - // (PinCtrl::NMI_EXITING | PinCtrl::VMX_PREEMPTION_TIMER).bits(), - // PinCtrl::NMI_EXITING.bits(), + (PinCtrl::NMI_EXITING + | PinCtrl::EXTERNAL_INTERRUPT_EXITING + | PinCtrl::VMX_PREEMPTION_TIMER) + .bits(), 0, )?; @@ -583,7 +642,10 @@ impl VmxVcpu { VmcsControl32::PRIMARY_PROCBASED_EXEC_CONTROLS, Msr::IA32_VMX_TRUE_PROCBASED_CTLS, Msr::IA32_VMX_PROCBASED_CTLS.read() as u32, - (CpuCtrl::USE_IO_BITMAPS | CpuCtrl::USE_MSR_BITMAPS | CpuCtrl::SECONDARY_CONTROLS) + (CpuCtrl::USE_IO_BITMAPS + | CpuCtrl::USE_MSR_BITMAPS + | CpuCtrl::USE_TPR_SHADOW + | CpuCtrl::SECONDARY_CONTROLS) .bits(), (CpuCtrl::CR3_LOAD_EXITING | CpuCtrl::CR3_STORE_EXITING @@ -594,9 +656,10 @@ impl VmxVcpu { // Enable EPT, RDTSCP, INVPCID, and unrestricted guest. use SecondaryControls as CpuCtrl2; - let mut val = - // CpuCtrl2::VIRTUALIZE_APIC | - CpuCtrl2::ENABLE_EPT | CpuCtrl2::UNRESTRICTED_GUEST; + let mut val = CpuCtrl2::VIRTUALIZE_APIC + | CpuCtrl2::VIRTUAL_INTERRUPT_DELIVERY + | CpuCtrl2::ENABLE_EPT + | CpuCtrl2::UNRESTRICTED_GUEST; if let Some(features) = raw_cpuid.get_extended_processor_and_feature_identifiers() && features.has_rdtscp() { @@ -668,16 +731,20 @@ impl VmxVcpu { // Pass-through exceptions (except #UD(6)), don't use I/O bitmap, set MSR bitmaps. let exception_bitmap: u32 = 1 << 6; - self.setup_io_bitmap()?; + self.setup_io_bitmap(config)?; VmcsControl32::EXCEPTION_BITMAP.write(exception_bitmap)?; VmcsControl64::IO_BITMAP_A_ADDR.write(self.io_bitmap.phys_addr().0.as_usize() as _)?; VmcsControl64::IO_BITMAP_B_ADDR.write(self.io_bitmap.phys_addr().1.as_usize() as _)?; VmcsControl64::MSR_BITMAPS_ADDR.write(self.msr_bitmap.phys_addr().as_usize() as _)?; - // VmcsControl64::APIC_ACCESS_ADDR.write( - // EmulatedLocalApic::::virtual_apic_access_addr().as_usize() as _, - // )?; + VmcsControl64::VIRT_APIC_ADDR.write(self.vlapic.virtual_apic_page_addr().as_usize() as _)?; + VmcsControl64::APIC_ACCESS_ADDR + .write(EmulatedLocalApic::virtual_apic_access_addr().as_usize() as _)?; + VmcsControl64::EOI_EXIT0.write(u64::MAX)?; + VmcsControl64::EOI_EXIT1.write(u64::MAX)?; + VmcsControl64::EOI_EXIT2.write(u64::MAX)?; + VmcsControl64::EOI_EXIT3.write(u64::MAX)?; Ok(()) } @@ -762,6 +829,15 @@ impl VmxVcpu { } } +// The current VMX APIC-access decode path is used only with Axvisor's +// identity-mapped guest RAM layout, so the guest physical address is also +// the host physical address. A non-identity guest memory backend should +// replace this helper with an explicit GPA-to-HVA translation. +fn read_guest_phys_u64(gpa: usize) -> u64 { + let hva = memory::phys_to_virt(PhysAddr::from(gpa)); + unsafe { core::ptr::read_unaligned(hva.as_ptr() as *const u64) } +} + /// Get ready then vmlaunch or vmresume. macro_rules! vmx_entry_with { ($instr:literal) => { @@ -838,12 +914,16 @@ impl VmxVcpu { if let Some(event) = self.pending_events.front() { // trace!( // "pending event vector {:#x} allow_int {}", - // event.0, + // event.vector, // self.allow_interrupt() // ); - if event.0 < 32 || self.allow_interrupt() { + if event.vector < 32 || self.allow_interrupt() { // if it's an exception, or an interrupt that is not blocked, inject it directly. - vmcs::inject_event(event.0, event.1)?; + vmcs::inject_event(event.vector, event.err_code)?; + if event.vector >= 32 { + self.vlapic + .accept_interrupt(event.vector, event.level_triggered); + } self.pending_events.pop_front(); } else { // interrupts are blocked, enable interrupt-window exiting. @@ -853,22 +933,33 @@ impl VmxVcpu { Ok(()) } + fn handle_interrupt_window(&mut self) -> AxResult { + self.set_interrupt_window(false)?; + self.inject_pending_events() + } + /// Handle vm-exits than can and should be handled by [`VmxVcpu`] itself. /// /// Return the result or None if the vm-exit was not handled. fn builtin_vmexit_handler(&mut self, exit_info: &VmxExitInfo) -> Option { + const APIC_BASE_MSR: u32 = 0x1b; const X2APIC_MSR_BASE: u32 = 0x800; const X2APIC_MSR_END: u32 = 0x8ff; // SDM says 0x8ff, but actually 0x83f, we respect the SDM here. + const AMD64_DE_CFG: u32 = 0xc001_1029; // Following vm-exits are handled here: // - interrupt window: turn off interrupt window; // - xsetbv: set guest xcr; // - cr access: just panic; match exit_info.exit_reason { - VmxExitReason::INTERRUPT_WINDOW => Some(self.set_interrupt_window(false)), - VmxExitReason::PREEMPTION_TIMER => Some(self.handle_vmx_preemption_timer()), + VmxExitReason::INTERRUPT_WINDOW => Some(self.handle_interrupt_window()), VmxExitReason::XSETBV => Some(self.handle_xsetbv()), VmxExitReason::CR_ACCESS => Some(self.handle_cr()), VmxExitReason::CPUID => Some(self.handle_cpuid()), + msr_rw @ (VmxExitReason::MSR_READ | VmxExitReason::MSR_WRITE) + if self.regs().rcx as u32 == APIC_BASE_MSR => + { + Some(self.handle_apic_base_msr_access(msr_rw == VmxExitReason::MSR_WRITE)) + } msr_rw @ (VmxExitReason::MSR_READ | VmxExitReason::MSR_WRITE) if { let msr = self.regs().rcx as u32; @@ -880,6 +971,11 @@ impl VmxVcpu { self.regs().rcx as u32, )) } + msr_rw @ (VmxExitReason::MSR_READ | VmxExitReason::MSR_WRITE) + if self.regs().rcx as u32 == AMD64_DE_CFG => + { + Some(self.handle_amd64_de_cfg_msr_access(msr_rw == VmxExitReason::MSR_WRITE)) + } VmxExitReason::APIC_ACCESS => Some(self.handle_apic_access(exit_info)), _ => None, } @@ -896,6 +992,23 @@ impl VmxVcpu { self.regs_mut().rdx = val >> 32; } + fn handle_apic_base_msr_access(&mut self, write: bool) -> AxResult { + const VMEXIT_INSTR_LEN_RDMSR_WRMSR: u8 = 2; + + self.advance_rip(VMEXIT_INSTR_LEN_RDMSR_WRMSR)?; + + if write { + let value = self.read_edx_eax(); + trace!("handle_vlapic_apic_base_write: value={value:#x}"); + self.vlapic.set_apic_base(value) + } else { + let value = self.vlapic.apic_base(); + trace!("handle_vlapic_apic_base_read: value={value:#x}"); + self.write_edx_eax(value); + Ok(()) + } + } + fn handle_apic_msr_access(&mut self, write: bool, msr: u32) -> AxResult { const VMEXIT_INSTR_LEN_RDMSR_WRMSR: u8 = 2; @@ -927,6 +1040,16 @@ impl VmxVcpu { } } + fn handle_amd64_de_cfg_msr_access(&mut self, write: bool) -> AxResult { + const VMEXIT_INSTR_LEN_RDMSR_WRMSR: u8 = 2; + + self.advance_rip(VMEXIT_INSTR_LEN_RDMSR_WRMSR)?; + if !write { + self.write_edx_eax(0); + } + Ok(()) + } + fn handle_apic_access(&mut self, exit_info: &VmxExitInfo) -> AxResult { let apic_access_exit_info = self.apic_access_exit_info()?; @@ -942,13 +1065,233 @@ impl VmxVcpu { } }; - // TODO: handle APIC access. - let _ = write; + let reg = apic_access_exit_info.offset as usize; + let addr = GuestPhysAddr::from(X86_APIC_ACCESS_GPA + reg); + if write { + let value = self.decode_apic_mmio_write_value(exit_info)?; + >>::handle_write( + &self.vlapic, + addr, + AccessWidth::Dword, + value, + )?; + } else { + let value = + >>::handle_read( + &self.vlapic, + addr, + AccessWidth::Dword, + )?; + self.regs_mut().rax = value as u64; + } - self.advance_rip(exit_info.exit_instruction_length as _)?; + self.advance_rip(exit_info.exit_instruction_length as _) + } - unimplemented!("apic access"); - // TODO + fn decode_apic_mmio_write_value(&self, exit_info: &VmxExitInfo) -> AxResult { + let mut rip = self.gla2gva(GuestVirtAddr::from(exit_info.guest_rip)); + let mut rex = 0u8; + + Self::skip_simple_prefixes(self, &mut rip, &mut rex)?; + + let opcode = self.read_guest_u8(rip)?; + rip += 1; + let modrm = self.read_guest_u8(rip)?; + rip += 1; + let mode = modrm >> 6; + if mode == 0b11 { + return ax_err!(Unsupported, "APIC MMIO write destination is not memory"); + } + + if opcode == 0x89 { + let reg = ((modrm >> 3) & 0x7) | ((rex & 0x4) << 1); + return Ok(self.guest_regs.get_reg_of_index(reg) as u32 as usize); + } + + if opcode == 0xc7 && (modrm >> 3) & 0x7 == 0 { + let imm_addr = self.skip_modrm_memory_operand(rip, modrm, rex)?; + let mut value = 0u32; + for i in 0..size_of::() { + value |= (self.read_guest_u8(imm_addr + i)? as u32) << (i * 8); + } + return Ok(value as usize); + } + + ax_err!( + Unsupported, + format_args!("unsupported APIC MMIO write opcode {opcode:#x}") + ) + } + + fn decode_ept_mmio_access( + &self, + exit_info: &VmxExitInfo, + addr: GuestPhysAddr, + write: bool, + ) -> Option { + // Keep EPT-violation MMIO decoding scoped to the PC IOAPIC window used + // by the current x86 Linux direct-boot path. The VMX exit qualification + // alone does not tell us whether an unmapped GPA is an emulated device + // or a genuine missing memory mapping. + if !(X86_IOAPIC_BASE..X86_IOAPIC_BASE + X86_IOAPIC_SIZE).contains(&addr.as_usize()) { + return None; + } + + let mut rip = self.gla2gva(GuestVirtAddr::from(exit_info.guest_rip)); + let mut rex = 0u8; + if let Err(err) = Self::skip_simple_prefixes(self, &mut rip, &mut rex) { + debug!("failed to decode EPT MMIO prefixes: {err:?}"); + return None; + } + + let opcode = self.read_guest_u8(rip).ok()?; + rip += 1; + let modrm = self.read_guest_u8(rip).ok()?; + rip += 1; + if modrm >> 6 == 0b11 { + debug!("EPT MMIO access did not use a memory operand"); + return None; + } + + match (write, opcode) { + (true, 0x89) => { + let reg = ((modrm >> 3) & 0x7) | ((rex & 0x4) << 1); + Some(AxVCpuExitReason::MmioWrite { + addr, + width: AccessWidth::Dword, + data: self.guest_regs.get_reg_of_index(reg) as u32 as u64, + }) + } + (true, 0xc7) if (modrm >> 3) & 0x7 == 0 => { + let imm_addr = self.skip_modrm_memory_operand(rip, modrm, rex).ok()?; + let mut data = 0u32; + for i in 0..size_of::() { + data |= (self.read_guest_u8(imm_addr + i).ok()? as u32) << (i * 8); + } + Some(AxVCpuExitReason::MmioWrite { + addr, + width: AccessWidth::Dword, + data: data as u64, + }) + } + (false, 0x8b) => { + let reg = (((modrm >> 3) & 0x7) | ((rex & 0x4) << 1)) as usize; + Some(AxVCpuExitReason::MmioRead { + addr, + width: AccessWidth::Dword, + reg, + reg_width: AccessWidth::Dword, + signed_ext: false, + }) + } + _ => { + debug!("unsupported EPT MMIO opcode {opcode:#x}, write={write}"); + None + } + } + } + + fn skip_simple_prefixes(&self, rip: &mut GuestVirtAddr, rex: &mut u8) -> AxResult { + loop { + let byte = self.read_guest_u8(*rip)?; + if byte == 0x66 { + *rip += 1; + } else if (0x40..=0x4f).contains(&byte) { + *rex = byte; + *rip += 1; + } else { + return Ok(()); + } + } + } + + fn skip_modrm_memory_operand( + &self, + mut cursor: GuestVirtAddr, + modrm: u8, + rex: u8, + ) -> AxResult { + let mode = modrm >> 6; + let rm = modrm & 0x7; + + if rm == 0b100 { + let sib = self.read_guest_u8(cursor)?; + cursor += 1; + let base = sib & 0x7; + if mode == 0 && base == 0b101 { + cursor += size_of::(); + } + } else if mode == 0 && rm == 0b101 && rex & 0x1 == 0 { + cursor += size_of::(); + } + + match mode { + 0 => {} + 1 => cursor += size_of::(), + 2 => cursor += size_of::(), + _ => return ax_err!(InvalidInput, "ModRM register operand is not memory"), + } + + Ok(cursor) + } + + fn read_guest_u8(&self, gva: GuestVirtAddr) -> AxResult { + let gpa = self.translate_guest_linear(gva)?; + let hva = memory::phys_to_virt(PhysAddr::from(gpa.as_usize())); + Ok(unsafe { core::ptr::read_volatile(hva.as_ptr()) }) + } + + fn translate_guest_linear(&self, gva: GuestVirtAddr) -> AxResult { + let addr = gva.as_usize(); + match self.get_paging_level() { + 0 => Ok(GuestPhysAddr::from(addr)), + 4 => self.walk_guest_page_table_4level(addr), + level => ax_err!( + Unsupported, + format_args!("unsupported APIC MMIO write decode paging level {level}") + ), + } + } + + fn walk_guest_page_table_4level(&self, gva: usize) -> AxResult { + const PRESENT: u64 = 1 << 0; + const HUGE_PAGE: u64 = 1 << 7; + const ADDR_MASK: u64 = 0x000f_ffff_ffff_f000; + const PAGE_4K_MASK: usize = 0xfff; + const PAGE_2M_MASK: usize = 0x1f_ffff; + const PAGE_1G_MASK: usize = 0x3fff_ffff; + + let mut table = VmcsGuestNW::CR3.read()? & ADDR_MASK as usize; + let indexes = [ + (gva >> 39) & 0x1ff, + (gva >> 30) & 0x1ff, + (gva >> 21) & 0x1ff, + (gva >> 12) & 0x1ff, + ]; + + for (level, index) in indexes.into_iter().enumerate() { + let entry = read_guest_phys_u64(table + index * size_of::()); + if entry & PRESENT == 0 { + return ax_err!( + InvalidInput, + format_args!("guest RIP page table entry is not present at level {level}") + ); + } + + let paddr = (entry & ADDR_MASK) as usize; + match level { + 1 if entry & HUGE_PAGE != 0 => { + return Ok(GuestPhysAddr::from(paddr + (gva & PAGE_1G_MASK))); + } + 2 if entry & HUGE_PAGE != 0 => { + return Ok(GuestPhysAddr::from(paddr + (gva & PAGE_2M_MASK))); + } + 3 => return Ok(GuestPhysAddr::from(paddr + (gva & PAGE_4K_MASK))), + _ => table = paddr, + } + } + + ax_err!(InvalidInput, "failed to translate guest RIP") } fn handle_vmx_preemption_timer(&mut self) -> AxResult { @@ -1016,12 +1359,28 @@ impl VmxVcpu { const FEATURE_VMX: u32 = 1 << 5; const FEATURE_HYPERVISOR: u32 = 1 << 31; const FEATURE_MCE: u32 = 1 << 7; + const FEATURE_X2APIC: u32 = 1 << 21; + const FEATURE_TSC_DEADLINE: u32 = 1 << 24; + const FEATURE_APIC: u32 = 1 << 9; + const MAX_LOGICAL_PROCESSORS_MASK: u32 = 0xff << 16; + const INITIAL_APIC_ID_MASK: u32 = 0xff << 24; let mut res = cpuid!(regs_clone.rax, regs_clone.rcx); res.ecx &= !FEATURE_VMX; + res.ecx |= FEATURE_X2APIC; + res.ecx &= !FEATURE_TSC_DEADLINE; res.ecx |= FEATURE_HYPERVISOR; - res.eax &= !FEATURE_MCE; + res.edx &= !FEATURE_MCE; + res.edx |= FEATURE_APIC; + res.ebx &= !(MAX_LOGICAL_PROCESSORS_MASK | INITIAL_APIC_ID_MASK); + res.ebx |= 1 << 16; res } + 0xb | 0x1f => CpuIdResult { + eax: 0, + ebx: 0, + ecx: regs_clone.rcx as u32, + edx: 0, + }, // See SDM Table 3-8. Information Returned by CPUID Instruction (Contd.) LEAF_STRUCTURED_EXTENDED_FEATURE_FLAGS_ENUMERATION => { let mut res = cpuid!(regs_clone.rax, regs_clone.rcx); @@ -1054,16 +1413,16 @@ impl VmxVcpu { edx: 0, }, EAX_FREQUENCY_INFO => { - /// Timer interrupt frequencyin Hz. - /// Todo: this should be the same as `ax_config::TIMER_FREQUENCY` defined in ArceOS's config file. - const TIMER_FREQUENCY_MHZ: u32 = 3_000; + const FALLBACK_TSC_FREQUENCY_MHZ: u32 = 3_000; let mut res = cpuid!(regs_clone.rax, regs_clone.rcx); if res.eax == 0 { + let frequency_mhz = + crate::host_tsc_frequency_mhz().unwrap_or(FALLBACK_TSC_FREQUENCY_MHZ); warn!( "handle_cpuid: Failed to get TSC frequency by CPUID, default to \ - {TIMER_FREQUENCY_MHZ} MHz" + {frequency_mhz} MHz" ); - res.eax = TIMER_FREQUENCY_MHZ; + res.eax = frequency_mhz; } res } @@ -1108,20 +1467,23 @@ impl VmxVcpu { return None; } - if x.contains(Xcr0::XCR0_OPMASK_STATE) + let avx512_state = x.contains(Xcr0::XCR0_OPMASK_STATE) || x.contains(Xcr0::XCR0_ZMM_HI256_STATE) - || x.contains(Xcr0::XCR0_HI16_ZMM_STATE) - || !x.contains(Xcr0::XCR0_AVX_STATE) - || !x.contains(Xcr0::XCR0_OPMASK_STATE) - || !x.contains(Xcr0::XCR0_ZMM_HI256_STATE) - || !x.contains(Xcr0::XCR0_HI16_ZMM_STATE) + || x.contains(Xcr0::XCR0_HI16_ZMM_STATE); + let avx512_state_complete = x.contains(Xcr0::XCR0_OPMASK_STATE) + && x.contains(Xcr0::XCR0_ZMM_HI256_STATE) + && x.contains(Xcr0::XCR0_HI16_ZMM_STATE); + if avx512_state + && (!avx512_state_complete + || !x.contains(Xcr0::XCR0_AVX_STATE) + || !x.contains(Xcr0::XCR0_SSE_STATE)) { return None; } Some(x) }) - .ok_or(ax_err_type!(InvalidInput)) + .ok_or_else(|| ax_err_type!(InvalidInput)) .and_then(|x| { self.xstate.guest_xcr0 = x.bits(); self.advance_rip(VM_EXIT_INSTR_LEN_XSETBV) @@ -1188,7 +1550,7 @@ impl Debug for VmxVcpu { impl AxArchVCpu for VmxVcpu { type CreateConfig = (); - type SetupConfig = (); + type SetupConfig = X86VCpuSetupConfig; fn new(vm_id: VMId, vcpu_id: VCpuId, _config: Self::CreateConfig) -> AxResult { Self::new(vm_id, vcpu_id) @@ -1204,8 +1566,8 @@ impl AxArchVCpu for VmxVcpu { Ok(()) } - fn setup(&mut self, _config: Self::SetupConfig) -> AxResult { - self.setup_vmcs(self.entry.unwrap(), self.ept_root.unwrap()) + fn setup(&mut self, config: Self::SetupConfig) -> AxResult { + self.setup_vmcs(self.entry.unwrap(), self.ept_root.unwrap(), config) } fn run(&mut self) -> AxResult { @@ -1277,6 +1639,42 @@ impl AxArchVCpu for VmxVcpu { vector: int_info.vector as _, } } + VmxExitReason::PREEMPTION_TIMER => { + self.handle_vmx_preemption_timer()?; + AxVCpuExitReason::PreemptionTimer + } + VmxExitReason::VIRTUALIZED_EOI => AxVCpuExitReason::InterruptEnd { + vector: self.vlapic.handle_eoi(), + }, + VmxExitReason::APIC_WRITE => { + let offset = self.apic_access_exit_info()?.offset as usize; + if offset == 0xb0 { + let vector = self.vlapic.handle_eoi(); + AxVCpuExitReason::InterruptEnd { vector } + } else { + AxVCpuExitReason::Nothing + } + } + VmxExitReason::EPT_VIOLATION => { + let info = self.nested_page_fault_info()?; + let write = info.access_flags.contains(MappingFlags::WRITE); + let read = info.access_flags.contains(MappingFlags::READ); + if (read || write) + && let Some(mmio_exit) = self.decode_ept_mmio_access( + &exit_info, + info.fault_guest_paddr, + write, + ) + { + self.advance_rip(exit_info.exit_instruction_length as _)?; + mmio_exit + } else { + AxVCpuExitReason::NestedPageFault { + addr: info.fault_guest_paddr, + access_flags: info.access_flags, + } + } + } VmxExitReason::MSR_READ => { // `reg` is unused here. AxVCpuExitReason::SysRegRead { @@ -1327,6 +1725,27 @@ impl AxArchVCpu for VmxVcpu { Ok(()) } + fn inject_interrupt_with_trigger( + &mut self, + vector: usize, + trigger: axvcpu::InterruptTriggerMode, + ) -> AxResult { + if vector == 0 { + warn!("interrupt queued in inject_interrupt_with_trigger: vector 0"); + panic!() + } + self.queue_event_with_trigger( + vector as u8, + None, + trigger == axvcpu::InterruptTriggerMode::LevelTriggered, + ); + Ok(()) + } + + fn handle_eoi(&mut self) -> Option { + self.vlapic.handle_eoi() + } + fn set_return_value(&mut self, val: usize) { self.regs_mut().rax = val as u64; } @@ -1376,7 +1795,7 @@ mod tests { #[test] fn test_constants() { // Test that constants have expected values - assert_eq!(VMX_PREEMPTION_TIMER_SET_VALUE, 1_000_000); + assert_eq!(VMX_PREEMPTION_TIMER_SET_VALUE, 100_000); assert_eq!(QEMU_EXIT_PORT, 0x604); assert_eq!(QEMU_EXIT_MAGIC, 0x2000); assert_eq!(MSR_IA32_EFER_LMA_BIT, 1 << 10); diff --git a/virtualization/x86_vlapic/Cargo.toml b/virtualization/x86_vlapic/Cargo.toml index 04ce4effc2..f22189bf77 100644 --- a/virtualization/x86_vlapic/Cargo.toml +++ b/virtualization/x86_vlapic/Cargo.toml @@ -20,6 +20,7 @@ bit = "0.1.1" ax-memory-addr = { workspace = true } ax-errno = { workspace = true } +ax-kspin = { workspace = true } axaddrspace = { workspace = true } axdevice_base = { workspace = true } axvisor_api = { workspace = true } diff --git a/virtualization/x86_vlapic/src/lib.rs b/virtualization/x86_vlapic/src/lib.rs index a06e9a2df9..b744397919 100644 --- a/virtualization/x86_vlapic/src/lib.rs +++ b/virtualization/x86_vlapic/src/lib.rs @@ -22,9 +22,12 @@ extern crate alloc; extern crate log; mod consts; +mod pit; mod regs; +mod serial; mod timer; mod utils; +mod vioapic; mod vlapic; use core::cell::UnsafeCell; @@ -56,6 +59,10 @@ pub struct EmulatedLocalApic { vlapic_regs: UnsafeCell, } +pub use pit::EmulatedPit; +pub use serial::EmulatedSerialPort; +pub use vioapic::{EmulatedIoApic, IoApicInterrupt}; + impl EmulatedLocalApic { /// Create a new `EmulatedLocalApic`. pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { @@ -68,7 +75,19 @@ impl EmulatedLocalApic { unsafe { &*self.vlapic_regs.get() } } - #[allow(clippy::mut_from_ref)] // SAFETY: get_mut_vlapic_regs is never called concurrently. + /// Returns mutable access to the virtual APIC register state. + /// + /// # Safety + /// + /// `vlapic_regs` is stored in an [`UnsafeCell`] because the vLAPIC MMIO/MSR + /// handlers are exposed through shared device references. Callers must + /// guarantee that no two execution contexts call this method, or otherwise + /// mutate/read the same [`VirtualApicRegs`], concurrently. In the current + /// Axvisor x86 path each `EmulatedLocalApic` is owned by one vCPU and vLAPIC + /// register accesses are handled synchronously on that vCPU's run path; any + /// cross-vCPU interrupt requests are funneled through the vCPU task instead + /// of directly mutating another vCPU's local APIC registers. + #[allow(clippy::mut_from_ref)] fn get_mut_vlapic_regs(&self) -> &mut VirtualApicRegs { unsafe { &mut *self.vlapic_regs.get() } } @@ -93,6 +112,27 @@ impl EmulatedLocalApic { pub fn virtual_apic_page_addr(&self) -> HostPhysAddr { self.get_vlapic_regs().virtual_apic_page_addr() } + + /// Returns the current IA32_APIC_BASE MSR value. + pub fn apic_base(&self) -> u64 { + self.get_vlapic_regs().apic_base() + } + + /// Sets the IA32_APIC_BASE MSR value. + pub fn set_apic_base(&self, value: u64) -> AxResult { + self.get_mut_vlapic_regs().set_apic_base(value) + } + + /// Record that the local APIC accepted an interrupt. + pub fn accept_interrupt(&self, vector: u8, level_triggered: bool) { + self.get_mut_vlapic_regs() + .accept_interrupt(vector, level_triggered); + } + + /// Process a guest EOI and return the vector that needs an IO APIC EOI broadcast. + pub fn handle_eoi(&self) -> Option { + self.get_mut_vlapic_regs().handle_eoi() + } } impl BaseDeviceOps> for EmulatedLocalApic { diff --git a/virtualization/x86_vlapic/src/pit.rs b/virtualization/x86_vlapic/src/pit.rs new file mode 100644 index 0000000000..e30eacf529 --- /dev/null +++ b/virtualization/x86_vlapic/src/pit.rs @@ -0,0 +1,228 @@ +use ax_errno::{AxResult, ax_err}; +use ax_kspin::SpinNoIrq as Mutex; +use axaddrspace::device::{AccessWidth, Port, PortRange}; +use axdevice_base::{BaseDeviceOps, EmuDeviceType}; +use axvisor_api::time; + +const PIT_CHANNEL0: u16 = 0x40; +const PIT_CHANNEL2: u16 = 0x42; +const PIT_COMMAND: u16 = 0x43; +const PIT_SPEAKER_CONTROL: u16 = 0x61; +const PIT_PORT_END: u16 = PIT_SPEAKER_CONTROL; + +const PIT_BASE_FREQUENCY_HZ: u64 = 1_193_182; +const NANOSECONDS_PER_SECOND: u64 = 1_000_000_000; +const MIN_PERIOD_NS: u64 = 1_000; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum AccessMode { + LatchCount, + LowByte, + HighByte, + LowThenHigh, +} + +impl AccessMode { + fn from_command(command: u8) -> Self { + match (command >> 4) & 0b11 { + 0 => Self::LatchCount, + 1 => Self::LowByte, + 2 => Self::HighByte, + _ => Self::LowThenHigh, + } + } +} + +#[derive(Clone, Copy, Debug)] +struct PitChannel { + access_mode: AccessMode, + reload_value: u16, + write_low_latched: Option, + read_high_next: bool, + period_ns: Option, + next_deadline_ns: u64, +} + +impl PitChannel { + const fn new() -> Self { + Self { + access_mode: AccessMode::LowThenHigh, + reload_value: 0, + write_low_latched: None, + read_high_next: false, + period_ns: None, + next_deadline_ns: 0, + } + } + + fn program_reload(&mut self, reload_value: u16, now_ns: u64) { + self.reload_value = reload_value; + let divisor = if reload_value == 0 { + 0x1_0000 + } else { + reload_value as u64 + }; + let period_ns = + ((divisor * NANOSECONDS_PER_SECOND) / PIT_BASE_FREQUENCY_HZ).max(MIN_PERIOD_NS); + self.period_ns = Some(period_ns); + self.next_deadline_ns = now_ns.saturating_add(period_ns); + self.read_high_next = false; + trace!("x86 PIT channel 0 programmed: reload={reload_value:#x}, period_ns={period_ns}"); + } + + fn write_count(&mut self, value: u8, now_ns: u64) { + match self.access_mode { + AccessMode::LatchCount => {} + AccessMode::LowByte => self.program_reload(value as u16, now_ns), + AccessMode::HighByte => self.program_reload((value as u16) << 8, now_ns), + AccessMode::LowThenHigh => { + if let Some(low) = self.write_low_latched.take() { + self.program_reload(((value as u16) << 8) | low as u16, now_ns); + } else { + self.write_low_latched = Some(value); + } + } + } + } + + fn read_count(&mut self) -> u8 { + let value = self.reload_value; + match self.access_mode { + AccessMode::HighByte => (value >> 8) as u8, + AccessMode::LowThenHigh => { + if self.read_high_next { + self.read_high_next = false; + (value >> 8) as u8 + } else { + self.read_high_next = true; + value as u8 + } + } + AccessMode::LatchCount | AccessMode::LowByte => value as u8, + } + } +} + +#[derive(Debug)] +struct PitState { + channel0: PitChannel, + channel2: PitChannel, + speaker_control: u8, +} + +impl PitState { + const fn new() -> Self { + Self { + channel0: PitChannel::new(), + channel2: PitChannel::new(), + speaker_control: 0, + } + } +} + +/// A minimal emulated x86 PIT/8254 device. +pub struct EmulatedPit { + state: Mutex, +} + +impl EmulatedPit { + /// Create a new PIT device. + pub const fn new() -> Self { + Self { + state: Mutex::new(PitState::new()), + } + } + + /// Return whether channel 0 has reached its next IRQ0 deadline. + /// + /// When a deadline is reached, this advances the deadline by whole periods so the timer + /// remains periodic without queueing a burst of missed ticks. + pub fn consume_irq0_if_due(&self, now_ns: u64) -> bool { + let mut state = self.state.lock(); + let channel = &mut state.channel0; + let Some(period_ns) = channel.period_ns else { + return false; + }; + if now_ns < channel.next_deadline_ns { + return false; + } + + let elapsed = now_ns.saturating_sub(channel.next_deadline_ns); + let missed_periods = elapsed / period_ns; + channel.next_deadline_ns = channel + .next_deadline_ns + .saturating_add((missed_periods + 1).saturating_mul(period_ns)); + true + } + + fn write_command(state: &mut PitState, command: u8) { + let channel = (command >> 6) & 0b11; + let access_mode = AccessMode::from_command(command); + if access_mode == AccessMode::LatchCount { + return; + } + + match channel { + 0 => { + state.channel0.access_mode = access_mode; + state.channel0.write_low_latched = None; + state.channel0.read_high_next = false; + } + 2 => { + state.channel2.access_mode = access_mode; + state.channel2.write_low_latched = None; + state.channel2.read_high_next = false; + } + _ => debug!("x86 PIT command for unsupported channel {channel}: {command:#x}"), + } + } +} + +impl Default for EmulatedPit { + fn default() -> Self { + Self::new() + } +} + +impl BaseDeviceOps for EmulatedPit { + fn emu_type(&self) -> EmuDeviceType { + EmuDeviceType::X86Pit + } + + fn address_range(&self) -> PortRange { + PortRange::new(Port(PIT_CHANNEL0), Port(PIT_PORT_END)) + } + + fn handle_read(&self, port: Port, width: AccessWidth) -> AxResult { + if width != AccessWidth::Byte { + return ax_err!(Unsupported, "x86 PIT only supports byte port reads"); + } + + let mut state = self.state.lock(); + let value = match port.0 { + PIT_CHANNEL0 => state.channel0.read_count(), + PIT_CHANNEL2 => state.channel2.read_count(), + PIT_COMMAND => 0, + PIT_SPEAKER_CONTROL => state.speaker_control, + _ => return ax_err!(Unsupported, "unsupported x86 PIT read port"), + }; + Ok(value as usize) + } + + fn handle_write(&self, port: Port, width: AccessWidth, val: usize) -> AxResult { + if width != AccessWidth::Byte { + return ax_err!(Unsupported, "x86 PIT only supports byte port writes"); + } + + let now_ns = time::current_time_nanos() as u64; + let mut state = self.state.lock(); + match port.0 { + PIT_CHANNEL0 => state.channel0.write_count(val as u8, now_ns), + PIT_CHANNEL2 => state.channel2.write_count(val as u8, now_ns), + PIT_COMMAND => Self::write_command(&mut state, val as u8), + PIT_SPEAKER_CONTROL => state.speaker_control = val as u8, + _ => return ax_err!(Unsupported, "unsupported x86 PIT write port"), + } + Ok(()) + } +} diff --git a/virtualization/x86_vlapic/src/regs/mod.rs b/virtualization/x86_vlapic/src/regs/mod.rs index 9e21d62260..695b9a69d7 100644 --- a/virtualization/x86_vlapic/src/regs/mod.rs +++ b/virtualization/x86_vlapic/src/regs/mod.rs @@ -44,7 +44,7 @@ register_structs! { (0x20 => pub ID: ReadWrite), (0x24 => _reserved1), /// Local APIC Version register (VVER): the 32-bit field located at offset 030H on the virtual-APIC page. - (0x30 => pub VERSION: ReadOnly), + (0x30 => pub VERSION: ReadWrite), (0x34 => _reserved2), /// Virtual task-priority register (VTPR): the 32-bit field located at offset 080H on the virtual-APIC page. (0x80 => pub TPR: ReadWrite), @@ -77,7 +77,7 @@ register_structs! { /// Virtual trigger-mode register (VTMR): /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets /// 180H, 190H, 1A0H, 1B0H, 1C0H, 1D0H, 1E0H, and 1F0H on the virtual-APIC page. - (0x180 => pub TMR: [ReadOnly; 8]), + (0x180 => pub TMR: [ReadWrite; 8]), /// Virtual interrupt-request register (VIRR): /// the 256-bit value comprising eight non-contiguous 32-bit fields at offsets /// 200H, 210H, 220H, 230H, 240H, 250H, 260H, and 270H on the virtual-APIC page. diff --git a/virtualization/x86_vlapic/src/serial.rs b/virtualization/x86_vlapic/src/serial.rs new file mode 100644 index 0000000000..2e2d8b29db --- /dev/null +++ b/virtualization/x86_vlapic/src/serial.rs @@ -0,0 +1,211 @@ +use ax_errno::{AxResult, ax_err}; +use ax_kspin::SpinNoIrq as Mutex; +use axaddrspace::device::{AccessWidth, Port, PortRange}; +use axdevice_base::{BaseDeviceOps, EmuDeviceType}; +use axvisor_api::console; + +const COM1_BASE: u16 = 0x3f8; +const COM1_END: u16 = COM1_BASE + 7; + +const REG_RBR_THR_DLL: u16 = 0; +const REG_IER_DLM: u16 = 1; +const REG_IIR_FCR: u16 = 2; +const REG_LCR: u16 = 3; +const REG_MCR: u16 = 4; +const REG_LSR: u16 = 5; +const REG_MSR: u16 = 6; +const REG_SCR: u16 = 7; + +const IER_RX_AVAILABLE: u8 = 1 << 0; +const IER_THR_EMPTY: u8 = 1 << 1; + +const IIR_NO_INTERRUPT: u8 = 0x01; +const IIR_THR_EMPTY: u8 = 0x02; +const IIR_RX_AVAILABLE: u8 = 0x04; +const IIR_FIFO_16550A: u8 = 0xc0; + +const LCR_DLAB: u8 = 1 << 7; + +const LSR_DATA_READY: u8 = 1 << 0; +const LSR_THR_EMPTY: u8 = 1 << 5; +const LSR_TRANSMITTER_EMPTY: u8 = 1 << 6; + +const MSR_DCD: u8 = 1 << 7; +const MSR_DSR: u8 = 1 << 5; +const MSR_CTS: u8 = 1 << 4; + +const FIFO_CAPACITY: usize = 128; + +#[derive(Debug)] +struct SerialState { + ier: u8, + fcr: u8, + lcr: u8, + mcr: u8, + scr: u8, + dll: u8, + dlm: u8, + rx_fifo: [u8; FIFO_CAPACITY], + rx_head: usize, + rx_len: usize, +} + +impl SerialState { + const fn new() -> Self { + Self { + ier: 0, + fcr: 0, + lcr: 0x03, + mcr: 0, + scr: 0, + dll: 1, + dlm: 0, + rx_fifo: [0; FIFO_CAPACITY], + rx_head: 0, + rx_len: 0, + } + } + + fn dlab(&self) -> bool { + self.lcr & LCR_DLAB != 0 + } + + fn push_rx(&mut self, byte: u8) { + if self.rx_len == self.rx_fifo.len() { + return; + } + let tail = (self.rx_head + self.rx_len) % self.rx_fifo.len(); + self.rx_fifo[tail] = byte; + self.rx_len += 1; + } + + fn pop_rx(&mut self) -> Option { + if self.rx_len == 0 { + return None; + } + let byte = self.rx_fifo[self.rx_head]; + self.rx_head = (self.rx_head + 1) % self.rx_fifo.len(); + self.rx_len -= 1; + Some(byte) + } + + fn clear_rx(&mut self) { + self.rx_head = 0; + self.rx_len = 0; + } + + fn lsr(&self) -> u8 { + let mut value = LSR_THR_EMPTY | LSR_TRANSMITTER_EMPTY; + if self.rx_len != 0 { + value |= LSR_DATA_READY; + } + value + } + + fn iir(&self) -> u8 { + if self.ier & IER_RX_AVAILABLE != 0 && self.rx_len != 0 { + IIR_FIFO_16550A | IIR_RX_AVAILABLE + } else if self.ier & IER_THR_EMPTY != 0 { + IIR_FIFO_16550A | IIR_THR_EMPTY + } else { + IIR_FIFO_16550A | IIR_NO_INTERRUPT + } + } +} + +/// Minimal 16550-compatible COM1 UART backed by the host console. +pub struct EmulatedSerialPort { + state: Mutex, +} + +impl EmulatedSerialPort { + /// Create a new COM1 UART. + pub const fn new() -> Self { + Self { + state: Mutex::new(SerialState::new()), + } + } + + fn poll_host_input(state: &mut SerialState) { + let mut buf = [0u8; 32]; + let read = console::read_bytes(&mut buf); + for &byte in &buf[..read] { + state.push_rx(byte); + } + } + + /// Poll host console input and return whether the UART should assert IRQ4. + pub fn poll_irq(&self) -> bool { + let mut state = self.state.lock(); + Self::poll_host_input(&mut state); + state.ier & IER_RX_AVAILABLE != 0 && state.rx_len != 0 + } +} + +impl Default for EmulatedSerialPort { + fn default() -> Self { + Self::new() + } +} + +impl BaseDeviceOps for EmulatedSerialPort { + fn emu_type(&self) -> EmuDeviceType { + EmuDeviceType::Console + } + + fn address_range(&self) -> PortRange { + PortRange::new(Port(COM1_BASE), Port(COM1_END)) + } + + fn handle_read(&self, port: Port, width: AccessWidth) -> AxResult { + if width != AccessWidth::Byte { + return ax_err!(Unsupported, "x86 serial only supports byte port reads"); + } + + let mut state = self.state.lock(); + Self::poll_host_input(&mut state); + let offset = port.0 - COM1_BASE; + let value = match offset { + REG_RBR_THR_DLL if state.dlab() => state.dll, + REG_RBR_THR_DLL => state.pop_rx().unwrap_or(0), + REG_IER_DLM if state.dlab() => state.dlm, + REG_IER_DLM => state.ier, + REG_IIR_FCR => state.iir(), + REG_LCR => state.lcr, + REG_MCR => state.mcr, + REG_LSR => state.lsr(), + REG_MSR => MSR_DCD | MSR_DSR | MSR_CTS, + REG_SCR => state.scr, + _ => return ax_err!(Unsupported, "unsupported x86 serial read port"), + }; + Ok(value as usize) + } + + fn handle_write(&self, port: Port, width: AccessWidth, val: usize) -> AxResult { + if width != AccessWidth::Byte { + return ax_err!(Unsupported, "x86 serial only supports byte port writes"); + } + + let mut state = self.state.lock(); + let offset = port.0 - COM1_BASE; + let value = val as u8; + match offset { + REG_RBR_THR_DLL if state.dlab() => state.dll = value, + REG_RBR_THR_DLL => console::write_bytes(&[value]), + REG_IER_DLM if state.dlab() => state.dlm = value, + REG_IER_DLM => state.ier = value & 0x0f, + REG_IIR_FCR => { + state.fcr = value; + if value & (1 << 1) != 0 { + state.clear_rx(); + } + } + REG_LCR => state.lcr = value, + REG_MCR => state.mcr = value, + REG_LSR | REG_MSR => {} + REG_SCR => state.scr = value, + _ => return ax_err!(Unsupported, "unsupported x86 serial write port"), + } + Ok(()) + } +} diff --git a/virtualization/x86_vlapic/src/timer.rs b/virtualization/x86_vlapic/src/timer.rs index 6b4184c629..badfb939b9 100644 --- a/virtualization/x86_vlapic/src/timer.rs +++ b/virtualization/x86_vlapic/src/timer.rs @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloc::boxed::Box; +use alloc::{boxed::Box, sync::Arc}; +use core::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering}; use ax_errno::{AxResult, ax_err}; use axvisor_api::{ - time::{self, current_ticks, register_timer, ticks_to_nanos, ticks_to_time}, + time::{self, register_timer}, vmm::{VCpuId, VMId, inject_interrupt}, }; @@ -28,6 +29,8 @@ use crate::{ }, }; +const APIC_TIMER_TICKS_PER_NANO: u64 = 1; + /// A virtual local APIC timer. (SDM Vol. 3C, Section 11.5.4) /// /// This struct virtualizes the access to 4 registers in the Local APIC: @@ -69,10 +72,18 @@ pub struct ApicTimer { // temporary fields untils we find a permanent place for apic and its timer cancel_token: Option, where_am_i: (VMId, VCpuId), // (vm_id, vcpu_id) + shared: Arc, +} + +struct ApicTimerShared { + generation: AtomicUsize, + lvt_timer_register: AtomicU32, + interval_ns: AtomicU64, + deadline_ns: AtomicU64, } impl ApicTimer { - pub(crate) const fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { + pub(crate) fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { Self { lvt_timer_register: LvtTimerRegisterLocal::new(RESET_LVT_REG), /* masked, one-shot, vector 0 */ initial_count_register: 0, // 0 (stopped) @@ -83,6 +94,12 @@ impl ApicTimer { deadline_ns: 0, cancel_token: None, where_am_i: (vm_id, vcpu_id), + shared: Arc::new(ApicTimerShared { + generation: AtomicUsize::new(0), + lvt_timer_register: AtomicU32::new(RESET_LVT_REG), + interval_ns: AtomicU64::new(0), + deadline_ns: AtomicU64::new(0), + }), } } @@ -113,6 +130,9 @@ impl ApicTimer { value &= LVT_MASK; self.lvt_timer_register.set(value); + self.shared + .lvt_timer_register + .store(value, Ordering::Release); Ok(()) } @@ -167,8 +187,25 @@ impl ApicTimer { if !self.is_started() { return 0; } - let remaining_ns = self.deadline_ns.wrapping_sub(time::current_time_nanos()); - let remaining_ticks = time::nanos_to_ticks(remaining_ns); + let mut deadline_ns = self.shared.deadline_ns.load(Ordering::Acquire); + let now_ns = time::current_time_nanos(); + if now_ns >= deadline_ns { + if !self.is_periodic() { + return 0; + } + + let interval_ns = self.shared.interval_ns.load(Ordering::Acquire); + if interval_ns == 0 { + return 0; + } + + deadline_ns = next_periodic_deadline_ns(deadline_ns, interval_ns, now_ns); + self.shared + .deadline_ns + .store(deadline_ns, Ordering::Release); + } + let remaining_ns = deadline_ns - now_ns; + let remaining_ticks = remaining_ns * APIC_TIMER_TICKS_PER_NANO; (remaining_ticks >> self.divide_shift) as _ } @@ -185,11 +222,6 @@ impl ApicTimer { self.lvt_timer_register.is_set(LVT_TIMER::Mask) } - /// The timer interrupt vector number. - pub fn vector(&self) -> u8 { - self.lvt_timer_register.read(LVT_TIMER::Vector) as u8 - } - /// Check whether the timer is started. pub fn is_started(&self) -> bool { // these two conditions are equivalent actually, we check both for clarity and robustness @@ -212,30 +244,33 @@ impl ApicTimer { return ax_err!(BadState, "Timer already started"); } - let current_ticks = current_ticks(); - let deadline_ticks = - current_ticks + ((self.initial_count_register as u64) << self.divide_shift); + let current_ns = time::current_time_nanos(); + let interval_ticks = (self.initial_count_register as u64) << self.divide_shift; + let interval_ns = interval_ticks / APIC_TIMER_TICKS_PER_NANO; + let deadline_ns = current_ns + interval_ns; let (vm_id, vcpu_id) = self.where_am_i; - let vector = self.vector(); + let generation = self.next_generation(); trace!( - "vlapic @ (vm {vm_id}, vcpu {vcpu_id}) starts timer @ tick {current_ticks:?}, \ - deadline tick {deadline_ticks:?}" + "vlapic @ (vm {vm_id}, vcpu {vcpu_id}) starts timer @ ns {current_ns:?}, deadline ns \ + {deadline_ns:?}" ); - self.last_start_ticks = current_ticks; - self.deadline_ns = ticks_to_nanos(deadline_ticks); - - self.cancel_token = Some(register_timer( - ticks_to_time(deadline_ticks), - Box::new(move |_| { - // TODO: read the LVT Timer Register here - trace!( - "vlapic @ (vm {vm_id}, vcpu {vcpu_id}) timer expired, inject interrupt \ - {vector}" - ); - inject_interrupt(vm_id, vcpu_id, vector); - }), + self.last_start_ticks = current_ns; + self.deadline_ns = deadline_ns; + self.shared + .interval_ns + .store(interval_ns, Ordering::Release); + self.shared + .deadline_ns + .store(self.deadline_ns, Ordering::Release); + + self.cancel_token = Some(schedule_apic_timer( + time::current_time() + core::time::Duration::from_nanos(interval_ns), + Arc::clone(&self.shared), + generation, + vm_id, + vcpu_id, )); Ok(()) @@ -243,13 +278,14 @@ impl ApicTimer { pub fn stop_timer(&mut self) -> AxResult { // TODO: maybe disable irq here? - if self.is_started() { - self.last_start_ticks = 0; - self.deadline_ns = 0; - - time::cancel_timer(self.cancel_token.take().unwrap()); - } else { - warn!("`stop_timer` called when timer is not started, bad operation tolerated"); + self.next_generation(); + self.last_start_ticks = 0; + self.deadline_ns = 0; + self.shared.interval_ns.store(0, Ordering::Release); + self.shared.deadline_ns.store(0, Ordering::Release); + + if let Some(token) = self.cancel_token.take() { + time::cancel_timer(token); } Ok(()) @@ -260,6 +296,10 @@ impl ApicTimer { self.timer_mode() == TimerMode::Periodic } + fn next_generation(&self) -> usize { + self.shared.generation.fetch_add(1, Ordering::AcqRel) + 1 + } + // /// Set LVT Timer Register. // pub fn set_lvt_timer(&mut self, bits: u32) -> RvmResult { // let timer_mode = bits.get_bits(17..19); @@ -302,6 +342,64 @@ impl ApicTimer { // } } +fn schedule_apic_timer( + deadline: time::TimeValue, + shared: Arc, + generation: usize, + vm_id: VMId, + vcpu_id: VCpuId, +) -> usize { + register_timer( + deadline, + Box::new(move |_| { + if shared.generation.load(Ordering::Acquire) != generation { + return; + } + + let lvt = shared.lvt_timer_register.load(Ordering::Acquire); + let vector = (lvt & 0xff) as u8; + let masked = (lvt & LVT_TIMER::Mask::SET.mask()) != 0; + let mode = (lvt & LVT_TIMER::TimerMode::SET.mask()) >> 17; + + if !masked { + trace!( + "vlapic @ (vm {vm_id}, vcpu {vcpu_id}) timer expired, inject interrupt \ + {vector}" + ); + inject_interrupt(vm_id, vcpu_id, vector); + } + + if mode == TimerMode::Periodic as u32 + && shared.generation.load(Ordering::Acquire) == generation + { + let interval_ns = shared.interval_ns.load(Ordering::Acquire); + if interval_ns != 0 { + let old_deadline = shared.deadline_ns.load(Ordering::Acquire); + let next_deadline_ns = next_periodic_deadline_ns( + old_deadline, + interval_ns, + time::current_time_nanos(), + ); + let next_deadline = core::time::Duration::from_nanos(next_deadline_ns); + shared + .deadline_ns + .store(next_deadline_ns, Ordering::Release); + let _ = schedule_apic_timer(next_deadline, shared, generation, vm_id, vcpu_id); + } + } + }), + ) +} + +fn next_periodic_deadline_ns(deadline_ns: u64, interval_ns: u64, now_ns: u64) -> u64 { + if deadline_ns > now_ns { + return deadline_ns; + } + + let missed_intervals = (now_ns - deadline_ns) / interval_ns + 1; + deadline_ns.saturating_add(interval_ns.saturating_mul(missed_intervals)) +} + #[cfg(test)] mod tests { use axvisor_api::vmm::{VCpuId, VMId}; @@ -320,7 +418,7 @@ mod tests { // assert_eq!(timer.read_ccr(), 0); assert!(timer.is_masked()); assert_eq!(timer.timer_mode(), TimerMode::OneShot); - assert_eq!(timer.vector(), 0); + assert_eq!(timer.read_lvt() & 0xff, 0); } #[test] @@ -339,7 +437,7 @@ mod tests { // Test vector number assert!(timer.write_lvt(0x50).is_ok()); // vector 0x50 - assert_eq!(timer.vector(), 0x50); + assert_eq!(timer.read_lvt() & 0xff, 0x50); } #[test] diff --git a/virtualization/x86_vlapic/src/vioapic.rs b/virtualization/x86_vlapic/src/vioapic.rs new file mode 100644 index 0000000000..01f2dc1c7a --- /dev/null +++ b/virtualization/x86_vlapic/src/vioapic.rs @@ -0,0 +1,266 @@ +use ax_errno::{AxResult, ax_err}; +use ax_kspin::SpinNoIrq as Mutex; +use ax_memory_addr::AddrRange; +use axaddrspace::{GuestPhysAddr, GuestPhysAddrRange, device::AccessWidth}; +use axdevice_base::{BaseDeviceOps, EmuDeviceType}; + +const IOAPIC_BASE: usize = 0xfec0_0000; +const IOAPIC_SIZE: usize = 0x1000; + +const IOREGSEL: usize = 0x00; +const IOWIN: usize = 0x10; + +const IOAPIC_ID: u32 = 0x00; +const IOAPIC_VER: u32 = 0x01; +const IOAPIC_ARB: u32 = 0x02; +const IOREDTBL_BASE: u32 = 0x10; + +const IOAPIC_ID_VALUE: u32 = 1 << 24; +const IOAPIC_VERSION_VALUE: u32 = 0x11 | ((MAX_REDIRECTION_ENTRY as u32) << 16); +const MAX_REDIRECTION_ENTRY: usize = 23; +const REDIRECTION_ENTRY_COUNT: usize = MAX_REDIRECTION_ENTRY + 1; +const REDIRECTION_ENTRY_MASKED: u64 = 1 << 16; +const REDIRECTION_ENTRY_TRIGGER_MODE: u64 = 1 << 15; +const REDIRECTION_ENTRY_REMOTE_IRR: u64 = 1 << 14; +const REDIRECTION_ENTRY_DELIVERY_MODE_MASK: u64 = 0b111 << 8; + +#[derive(Debug)] +struct IoApicState { + selector: u32, + redirection_table: [u64; REDIRECTION_ENTRY_COUNT], + pending_level: [bool; REDIRECTION_ENTRY_COUNT], +} + +impl IoApicState { + const fn new() -> Self { + Self { + selector: 0, + redirection_table: [REDIRECTION_ENTRY_MASKED; REDIRECTION_ENTRY_COUNT], + pending_level: [false; REDIRECTION_ENTRY_COUNT], + } + } + + fn interrupt_for_entry(&mut self, gsi: usize) -> Option { + let entry = self.redirection_table.get_mut(gsi)?; + if *entry & REDIRECTION_ENTRY_MASKED != 0 { + return None; + } + + if *entry & REDIRECTION_ENTRY_DELIVERY_MODE_MASK != 0 { + debug!("vIOAPIC GSI {gsi} uses unsupported delivery mode entry {entry:#x}"); + return None; + } + + let vector = (*entry & 0xff) as u8; + if vector < 16 { + return None; + } + + let level_triggered = *entry & REDIRECTION_ENTRY_TRIGGER_MODE != 0; + if level_triggered { + if *entry & REDIRECTION_ENTRY_REMOTE_IRR != 0 { + self.pending_level[gsi] = true; + return None; + } + *entry |= REDIRECTION_ENTRY_REMOTE_IRR; + } + + Some(IoApicInterrupt { + vector, + level_triggered, + }) + } +} + +/// A routed interrupt from the virtual IO APIC. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct IoApicInterrupt { + /// Guest interrupt vector. + pub vector: u8, + /// Whether the redirection entry is level-triggered. + pub level_triggered: bool, +} + +/// A minimal emulated x86 IO APIC. +pub struct EmulatedIoApic { + base: GuestPhysAddr, + size: usize, + state: Mutex, +} + +impl EmulatedIoApic { + /// Create a new `EmulatedIoApic`. + pub fn new(base: GuestPhysAddr, size: Option) -> Self { + Self { + base, + size: size.unwrap_or(IOAPIC_SIZE), + state: Mutex::new(IoApicState::new()), + } + } + + /// Create an IO APIC at the default PC-compatible GPA. + pub fn new_default() -> Self { + Self::new(GuestPhysAddr::from_usize(IOAPIC_BASE), Some(IOAPIC_SIZE)) + } + + /// Return the guest interrupt vector programmed for a GSI. + pub fn vector_for_gsi(&self, gsi: usize) -> Option { + let state = self.state.lock(); + let entry = *state.redirection_table.get(gsi)?; + if entry & REDIRECTION_ENTRY_MASKED != 0 { + return None; + } + + if entry & REDIRECTION_ENTRY_DELIVERY_MODE_MASK != 0 { + debug!("vIOAPIC GSI {gsi} uses unsupported delivery mode entry {entry:#x}"); + return None; + } + + let vector = (entry & 0xff) as u8; + if vector < 16 { + return None; + } + + Some(vector) + } + + /// Assert an IO APIC input line and return the interrupt to inject. + pub fn assert_gsi(&self, gsi: usize) -> Option { + let mut state = self.state.lock(); + state.interrupt_for_entry(gsi) + } + + /// Process an EOI broadcast from the local APIC. + pub fn end_of_interrupt(&self, vector: u8) -> Option { + let mut state = self.state.lock(); + for gsi in 0..REDIRECTION_ENTRY_COUNT { + let entry = &mut state.redirection_table[gsi]; + if (*entry & 0xff) as u8 != vector + || *entry & REDIRECTION_ENTRY_TRIGGER_MODE == 0 + || *entry & REDIRECTION_ENTRY_REMOTE_IRR == 0 + { + continue; + } + + *entry &= !REDIRECTION_ENTRY_REMOTE_IRR; + if core::mem::take(&mut state.pending_level[gsi]) { + return state.interrupt_for_entry(gsi); + } + } + + None + } + + fn offset(&self, addr: GuestPhysAddr) -> usize { + addr.as_usize() - self.base.as_usize() + } + + fn read_selected_register(state: &IoApicState) -> AxResult { + match state.selector { + IOAPIC_ID => Ok(IOAPIC_ID_VALUE), + IOAPIC_VER => Ok(IOAPIC_VERSION_VALUE), + IOAPIC_ARB => Ok(IOAPIC_ID_VALUE), + reg @ IOREDTBL_BASE..=0x3f => { + let index = ((reg - IOREDTBL_BASE) / 2) as usize; + if index >= REDIRECTION_ENTRY_COUNT { + return ax_err!(InvalidInput, "IOAPIC redirection index out of range"); + } + let entry = state.redirection_table[index]; + if (reg - IOREDTBL_BASE) & 1 == 0 { + Ok(entry as u32) + } else { + Ok((entry >> 32) as u32) + } + } + reg => { + debug!("vIOAPIC read from unsupported register {reg:#x}"); + Ok(0) + } + } + } + + fn write_selected_register(state: &mut IoApicState, value: u32) -> AxResult { + match state.selector { + IOAPIC_ID | IOAPIC_VER | IOAPIC_ARB => Ok(()), + reg @ IOREDTBL_BASE..=0x3f => { + let index = ((reg - IOREDTBL_BASE) / 2) as usize; + if index >= REDIRECTION_ENTRY_COUNT { + return ax_err!(InvalidInput, "IOAPIC redirection index out of range"); + } + let entry = &mut state.redirection_table[index]; + if (reg - IOREDTBL_BASE) & 1 == 0 { + let remote_irr = *entry & REDIRECTION_ENTRY_REMOTE_IRR; + *entry = (*entry & !0xffff_ffff) + | ((value as u64) & !REDIRECTION_ENTRY_REMOTE_IRR) + | remote_irr; + if *entry & REDIRECTION_ENTRY_MASKED != 0 { + state.pending_level[index] = false; + } + } else { + *entry = (*entry & 0xffff_ffff) | ((value as u64) << 32); + } + Ok(()) + } + reg => { + debug!("vIOAPIC write to unsupported register {reg:#x} = {value:#x}"); + Ok(()) + } + } + } +} + +impl Default for EmulatedIoApic { + fn default() -> Self { + Self::new_default() + } +} + +impl BaseDeviceOps for EmulatedIoApic { + fn emu_type(&self) -> EmuDeviceType { + EmuDeviceType::X86IoApic + } + + fn address_range(&self) -> GuestPhysAddrRange { + AddrRange::new( + self.base, + GuestPhysAddr::from_usize(self.base.as_usize() + self.size), + ) + } + + fn handle_read(&self, addr: GuestPhysAddr, width: AccessWidth) -> AxResult { + if !matches!(width, AccessWidth::Dword | AccessWidth::Qword) { + return ax_err!(Unsupported, "unsupported IOAPIC read width"); + } + + let offset = self.offset(addr); + let state = self.state.lock(); + match offset { + IOREGSEL => Ok(state.selector as usize), + IOWIN => Ok(Self::read_selected_register(&state)? as usize), + _ => { + debug!("vIOAPIC read from unsupported offset {offset:#x}"); + Ok(0) + } + } + } + + fn handle_write(&self, addr: GuestPhysAddr, width: AccessWidth, val: usize) -> AxResult { + if !matches!(width, AccessWidth::Dword | AccessWidth::Qword) { + return ax_err!(Unsupported, "unsupported IOAPIC write width"); + } + + let offset = self.offset(addr); + let mut state = self.state.lock(); + match offset { + IOREGSEL => { + state.selector = val as u32; + Ok(()) + } + IOWIN => Self::write_selected_register(&mut state, val as u32), + _ => { + debug!("vIOAPIC write to unsupported offset {offset:#x} = {val:#x}"); + Ok(()) + } + } + } +} diff --git a/virtualization/x86_vlapic/src/vlapic.rs b/virtualization/x86_vlapic/src/vlapic.rs index 76865f50d4..9e28c4124c 100644 --- a/virtualization/x86_vlapic/src/vlapic.rs +++ b/virtualization/x86_vlapic/src/vlapic.rs @@ -28,7 +28,7 @@ pub use crate::regs::lvt::LVT_TIMER::TimerMode::Value as TimerMode; use crate::{ consts::{ APIC_LVT_DS, APIC_LVT_M, APIC_LVT_VECTOR, ApicRegOffset, LAPIC_TRIG_EDGE, - RESET_SPURIOUS_INTERRUPT_VECTOR, + RESET_SPURIOUS_INTERRUPT_VECTOR, xapic::DEFAULT_APIC_BASE, }, regs::{ APIC_BASE, ApicBaseRegisterMsr, @@ -49,6 +49,12 @@ use crate::{ utils::fls32, }; +const APIC_BASE_ENABLE: u64 = 1 << 11; +const APIC_BASE_X2APIC_ENABLE: u64 = 1 << 10; +const APIC_BASE_BSP: u64 = 1 << 8; +const APIC_VERSION_INTEGRATED: u32 = 0x14; +const APIC_VERSION_MAX_LVT_ENTRIES: u32 = 6 << 16; + /// Virtual-APIC Registers. pub struct VirtualApicRegs { /// The virtual-APIC page is a 4-KByte region of memory @@ -82,7 +88,7 @@ impl VirtualApicRegs { /// Create new virtual-APIC registers by allocating a 4-KByte page for the virtual-APIC page. pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> Self { let apic_frame = PhysFrame::alloc_zero().expect("allocate virtual-APIC page failed"); - Self { + let regs = Self { // virtual-APIC ID is the same as the VCPU ID. vapic_id: vcpu_id as _, esr_pending: ErrorStatusRegisterLocal::new(0), @@ -92,9 +98,18 @@ impl VirtualApicRegs { svr_last: SpuriousInterruptVectorRegisterLocal::new(RESET_SPURIOUS_INTERRUPT_VECTOR), lvt_last: LocalVectorTable::default(), isrv: 0, - apic_base: ApicBaseRegisterMsr::new(0), + apic_base: ApicBaseRegisterMsr::new( + DEFAULT_APIC_BASE as u64 + | APIC_BASE_ENABLE + | if vcpu_id == 0 { APIC_BASE_BSP } else { 0 }, + ), virtual_timer: ApicTimer::new(vm_id, vcpu_id), - } + }; + regs.regs().ID.set((vcpu_id as u32) << 24); + regs.regs() + .VERSION + .set(APIC_VERSION_INTEGRATED | APIC_VERSION_MAX_LVT_ENTRIES); + regs } const fn regs(&self) -> &LocalAPICRegs { @@ -115,6 +130,19 @@ impl VirtualApicRegs { self.apic_base.get() } + /// Sets the APIC base MSR value. + pub fn set_apic_base(&mut self, value: u64) -> AxResult { + if value & APIC_BASE_X2APIC_ENABLE != 0 && value & APIC_BASE_ENABLE == 0 { + return Err(ax_err_type!( + InvalidInput, + "x2APIC cannot be enabled while xAPIC is disabled" + )); + } + + self.apic_base.set(value); + Ok(()) + } + /// Returns whether the x2APIC mode is enabled. pub fn is_x2apic_enabled(&self) -> bool { self.apic_base.is_set(APIC_BASE::XAPIC_ENABLED) @@ -172,11 +200,11 @@ impl VirtualApicRegs { /// Process the EOI operation triggered by a write to the EOI register. /// 11.8.5 Signaling Interrupt Servicing Completion /// 30.1.4 EOI Virtualization - fn process_eoi(&mut self) { + fn process_eoi(&mut self) -> Option { let vector = self.isrv; if vector == 0 { - return; + return None; } let (idx, bitpos) = extract_index_and_bitpos_u32(vector); @@ -201,18 +229,14 @@ impl VirtualApicRegs { // Upon acceptance of an interrupt into the IRR, the corresponding TMR bit is cleared for edge-triggered interrupts and set for leveltriggered interrupts. // If a TMR bit is set when an EOI cycle for its corresponding interrupt vector is generated, an EOI message is sent to all I/O APICs. // (see 11.8.4 Interrupt Acceptance for Fixed Interrupts) - if (self.regs().TMR[idx].get() as u32).bit(bitpos) { - // Send EOI to all I/O APICs - // Per Intel SDM 10.8.5, Software can inhibit the broadcast of - // EOI by setting bit 12 of the Spurious Interrupt Vector - // Register of the LAPIC. - // TODO: Check if the bit 12 "Suppress EOI Broadcasts" is set. - unimplemented!("vioapic_broadcast_eoi(vlapic2vcpu(vlapic)->vm, vector);") - } + let broadcast_eoi = (self.regs().TMR[idx].get() as u32).bit(bitpos) + && !self + .regs() + .SVR + .is_set(SPURIOUS_INTERRUPT_VECTOR::EOIBroadcastSuppression); debug!("Gratuitous EOI vector: {vector:#010X}"); - - unimplemented!("vcpu_make_request(vlapic2vcpu(vlapic), ACRN_REQUEST_EVENT);") + broadcast_eoi.then_some(vector as u8) } /// Post an interrupt to the vcpu running on 'hostcpu'. @@ -343,21 +367,44 @@ impl VirtualApicRegs { Ok(dmask) } - fn handle_self_ipi(&mut self) { - unimplemented!("x2apic handle_self_ipi"); + fn handle_self_ipi(&mut self, vector: u32) { + if vector < 16 { + self.set_err(ERROR_STATUS::SendIllegalVector::SET); + warn!("[VLAPIC] ignoring illegal self IPI vector {vector:#x}"); + return; + } + + self.set_intr(self.vapic_id, vector, LAPIC_TRIG_EDGE); } - fn set_intr(&mut self, vcpu_id: u32, vector: u32, level: bool) { - unimplemented!( - "set_intr, vcpu_id: {}, vector: {}, level: {}", - vcpu_id, - vector, - level - ); + fn set_intr(&mut self, vcpu_id: u32, vector: u32, _level: bool) { + if vector > u8::MAX as u32 { + warn!("[VLAPIC] ignoring out-of-range vector {vector:#x}"); + return; + } + + debug!("[VLAPIC] injecting vector {vector:#x} to vcpu {vcpu_id}"); + vmm::inject_interrupt(vmm::current_vm_id(), vcpu_id as usize, vector as u8); + } + + /// Record interrupt acceptance in the virtual APIC page. + pub fn accept_interrupt(&mut self, vector: u8, level_triggered: bool) { + let vector = vector as u32; + let (idx, bitpos) = extract_index_and_bitpos_u32(vector); + let bit = 1 << bitpos; + + self.regs().ISR[idx].set(self.regs().ISR[idx].get() | bit); + self.regs().TMR[idx].set(if level_triggered { + self.regs().TMR[idx].get() | bit + } else { + self.regs().TMR[idx].get() & !bit + }); + self.isrv = self.find_isrv(); + self.update_ppr(); } fn inject_nmi(&mut self, vcpu_id: u32) { - unimplemented!("inject_nmi vcpu_id: {}", vcpu_id); + warn!("[VLAPIC] ignoring NMI IPI to vcpu {vcpu_id}: NMI injection is not implemented yet"); } fn process_init_sipi( @@ -366,10 +413,11 @@ impl VirtualApicRegs { mode: APICDeliveryMode, icr_low: InterruptCommandRegisterLowLocal, ) { - unimplemented!( - "process_init_sipi, vcpu_id: {}, mode: {:?} icr_low: {:#010X}", - vcpu_id, + debug!( + "[VLAPIC] ignoring {:?} IPI to vcpu {} in current single-vCPU bring-up path, icr_low: \ + {:#010X}", mode, + vcpu_id, icr_low.get() ); } @@ -473,7 +521,7 @@ impl VirtualApicRegs { let mode = icr_low .read_as_enum::(INTERRUPT_COMMAND_LOW::DeliveryMode) .ok_or(AxError::InvalidData)?; - let is_phys = icr_low.is_set(INTERRUPT_COMMAND_LOW::DestinationMode); + let is_phys = !icr_low.is_set(INTERRUPT_COMMAND_LOW::DestinationMode); let shorthand = icr_low .read_as_enum::(INTERRUPT_COMMAND_LOW::DestinationShorthand) .ok_or(AxError::InvalidData)?; @@ -545,14 +593,6 @@ impl VirtualApicRegs { fn write_lvt(&mut self, offset: ApicRegOffset) -> AxResult { let mut val = self.extract_lvt_val(offset); - if self - .regs() - .SVR - .is_set(SPURIOUS_INTERRUPT_VECTOR::APICSoftwareEnableDisable) - { - val |= APIC_LVT_M; - } - // Mask::Masked, Delivery Status:SendPending, Vector::SET(0xff) let mut mask = APIC_LVT_M | APIC_LVT_DS | APIC_LVT_VECTOR; @@ -817,8 +857,11 @@ impl VirtualApicRegs { // Force APIC ID to be read-only. // self.regs().ID.set(val as _); } + ApicRegOffset::TPR => { + self.regs().TPR.set(data32 & 0xff); + } ApicRegOffset::EOI => { - self.process_eoi(); + let _ = self.process_eoi(); } ApicRegOffset::LDR => { self.regs().LDR.set(data32); @@ -856,6 +899,15 @@ impl VirtualApicRegs { self.regs().ICR_LO.set(data32); self.write_icr()?; } + ApicRegOffset::ICRHi => { + if self.is_x2apic_enabled() { + warn!("[VLAPIC] write ICRHi register: unsupported in x2APIC mode"); + return Err(AxError::InvalidInput); + } + self.regs() + .ICR_HI + .set(data32 & INTERRUPT_COMMAND_HIGH::Destination::SET.mask()); + } // Local Vector Table registers. ApicRegOffset::LvtCMCI => { self.regs().LVT_CMCI.set(data32); @@ -905,7 +957,7 @@ impl VirtualApicRegs { ApicRegOffset::SelfIPI => { if self.is_x2apic_enabled() { self.regs().SELF_IPI.set(data32); - self.handle_self_ipi(); + self.handle_self_ipi(data32 & 0xff); } else { warn!("[VLAPIC] write SelfIPI register: unsupported in xAPIC mode"); return Err(AxError::InvalidInput); @@ -921,4 +973,9 @@ impl VirtualApicRegs { Ok(()) } + + /// Process a guest EOI and return the vector that needs an IO APIC EOI broadcast. + pub fn handle_eoi(&mut self) -> Option { + self.process_eoi() + } } From 8045fae5ffa326cac0ee1b55f7bdbb26340c4a3b Mon Sep 17 00:00:00 2001 From: Long Weili <121844090+LetsWalkInLine@users.noreply.github.com> Date: Thu, 28 May 2026 11:20:07 +0800 Subject: [PATCH 68/71] feat(starry-kernel): add initial cgroup2 support (#989) Add the Slice 0 cgroup-basic StarryOS QEMU case across x86_64, aarch64, riscv64, and loongarch64. Implement the Slice 1 minimum cgroup2 root filesystem: explicit cgroup2 mount support, root cgroup.procs, empty cgroup.controllers, empty cgroup.subtree_control, and errno-preserving write failures for unsupported operations. Keep cgroup v1 mounts unsupported with ENODEV and avoid hierarchy, controller, procfs cgroup, and clone cgroup semantics. --- os/StarryOS/kernel/src/cgroup/mod.rs | 39 ++++ os/StarryOS/kernel/src/lib.rs | 1 + os/StarryOS/kernel/src/pseudofs/cgroup.rs | 84 +++++++++ os/StarryOS/kernel/src/pseudofs/mod.rs | 1 + os/StarryOS/kernel/src/syscall/fs/mount.rs | 8 + .../qemu-smp1/cgroup-basic/c/CMakeLists.txt | 10 ++ .../qemu-smp1/cgroup-basic/c/src/main.c | 166 ++++++++++++++++++ .../qemu-smp1/cgroup-basic/qemu-aarch64.toml | 22 +++ .../cgroup-basic/qemu-loongarch64.toml | 24 +++ .../qemu-smp1/cgroup-basic/qemu-riscv64.toml | 22 +++ .../qemu-smp1/cgroup-basic/qemu-x86_64.toml | 14 ++ 11 files changed, 391 insertions(+) create mode 100644 os/StarryOS/kernel/src/cgroup/mod.rs create mode 100644 os/StarryOS/kernel/src/pseudofs/cgroup.rs create mode 100644 test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-aarch64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-loongarch64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-riscv64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-x86_64.toml diff --git a/os/StarryOS/kernel/src/cgroup/mod.rs b/os/StarryOS/kernel/src/cgroup/mod.rs new file mode 100644 index 0000000000..9b5fd77275 --- /dev/null +++ b/os/StarryOS/kernel/src/cgroup/mod.rs @@ -0,0 +1,39 @@ +use alloc::{string::String, vec::Vec}; +use core::fmt::Write; + +use ax_errno::{AxError, AxResult, LinuxError}; + +/// Returns the process list for the root cgroup. +pub fn root_procs_text() -> String { + let mut pids: Vec<_> = crate::task::processes() + .into_iter() + .map(|proc_data| proc_data.proc.pid()) + .collect(); + pids.sort_unstable(); + + let mut text = String::new(); + for pid in pids { + let _ = writeln!(text, "{pid}"); + } + text +} + +/// Returns the controllers visible at the root cgroup. +pub fn root_controllers_text() -> &'static str { + "" +} + +/// Returns enabled subtree controllers at the root cgroup. +pub fn root_subtree_control_text() -> &'static str { + "" +} + +/// Writes to cgroup.procs are not implemented in this slice. +pub fn write_root_procs(_data: &[u8]) -> AxResult<()> { + Err(AxError::from(LinuxError::EOPNOTSUPP)) +} + +/// Writes to cgroup.subtree_control cannot enable unavailable controllers. +pub fn write_root_subtree_control(_data: &[u8]) -> AxResult<()> { + Err(AxError::from(LinuxError::EINVAL)) +} diff --git a/os/StarryOS/kernel/src/lib.rs b/os/StarryOS/kernel/src/lib.rs index 7010d5b688..cf4324314a 100644 --- a/os/StarryOS/kernel/src/lib.rs +++ b/os/StarryOS/kernel/src/lib.rs @@ -17,6 +17,7 @@ pub mod dyn_debug; // Re-export debug macros for use in other modules. It will o pub mod entry; +mod cgroup; mod config; #[cfg(feature = "ebpf")] mod ebpf; diff --git a/os/StarryOS/kernel/src/pseudofs/cgroup.rs b/os/StarryOS/kernel/src/pseudofs/cgroup.rs new file mode 100644 index 0000000000..4cdb196739 --- /dev/null +++ b/os/StarryOS/kernel/src/pseudofs/cgroup.rs @@ -0,0 +1,84 @@ +use alloc::{sync::Arc, vec::Vec}; + +use ax_errno::LinuxError; +use axfs_ng_vfs::{Filesystem, NodePermission, VfsError, VfsResult}; + +use super::{DirMaker, DirMapping, DirectRwFsFileOps, SimpleDir, SimpleFs, SpecialFsFile}; + +const CGROUP2_SUPER_MAGIC: u32 = 0x6367_7270; + +enum RootFile { + Procs, + Controllers, + SubtreeControl, +} + +impl RootFile { + fn read_content(&self) -> Vec { + match self { + Self::Procs => crate::cgroup::root_procs_text().into_bytes(), + Self::Controllers => crate::cgroup::root_controllers_text().as_bytes().to_vec(), + Self::SubtreeControl => crate::cgroup::root_subtree_control_text() + .as_bytes() + .to_vec(), + } + } +} + +impl DirectRwFsFileOps for RootFile { + fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult { + let content = self.read_content(); + let offset = offset as usize; + if offset >= content.len() { + return Ok(0); + } + + let content = &content[offset..]; + let read = content.len().min(buf.len()); + buf[..read].copy_from_slice(&content[..read]); + Ok(read) + } + + fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult { + match self { + Self::Procs => crate::cgroup::write_root_procs(buf)?, + Self::Controllers => return Err(VfsError::from(LinuxError::EACCES)), + Self::SubtreeControl => crate::cgroup::write_root_subtree_control(buf)?, + } + Ok(buf.len()) + } +} + +/// Creates a minimal cgroup v2 pseudo filesystem. +pub(crate) fn new_cgroup2fs() -> Filesystem { + SimpleFs::new_with("cgroup2".into(), CGROUP2_SUPER_MAGIC, cgroup2fs_builder) +} + +fn cgroup2fs_builder(fs: Arc) -> DirMaker { + let mut root = DirMapping::new(); + root.add( + "cgroup.procs", + SpecialFsFile::new_regular_with_perm( + fs.clone(), + RootFile::Procs, + NodePermission::from_bits_truncate(0o644), + ), + ); + root.add( + "cgroup.controllers", + SpecialFsFile::new_regular_with_perm( + fs.clone(), + RootFile::Controllers, + NodePermission::from_bits_truncate(0o444), + ), + ); + root.add( + "cgroup.subtree_control", + SpecialFsFile::new_regular_with_perm( + fs.clone(), + RootFile::SubtreeControl, + NodePermission::from_bits_truncate(0o644), + ), + ); + SimpleDir::new_maker(fs, Arc::new(root)) +} diff --git a/os/StarryOS/kernel/src/pseudofs/mod.rs b/os/StarryOS/kernel/src/pseudofs/mod.rs index 7c62b472bb..f996f2cf3b 100644 --- a/os/StarryOS/kernel/src/pseudofs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/mod.rs @@ -1,5 +1,6 @@ //! Basic virtual filesystem support +pub(crate) mod cgroup; pub mod debug; pub mod dev; mod device; diff --git a/os/StarryOS/kernel/src/syscall/fs/mount.rs b/os/StarryOS/kernel/src/syscall/fs/mount.rs index 4c1432b90d..7dfb47b451 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mount.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mount.rs @@ -139,6 +139,14 @@ pub fn sys_mount( mp.set_readonly(true); } } + "cgroup2" => { + let fs = crate::pseudofs::cgroup::new_cgroup2fs(); + let target = FS_CONTEXT.lock().resolve(target)?; + let mp = target.mount(&fs)?; + if (flags & MS_RDONLY) != 0 { + mp.set_readonly(true); + } + } #[cfg(feature = "ext4")] "ext4" => { mount_ext4(&source, &target, (flags & MS_RDONLY) != 0)?; diff --git a/test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/CMakeLists.txt new file mode 100644 index 0000000000..22a5e93f7f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.20) +project(cgroup-basic C) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(cgroup-basic src/main.c) +target_include_directories(cgroup-basic PRIVATE src) +target_compile_options(cgroup-basic PRIVATE -Wall -Wextra -Werror) +install(TARGETS cgroup-basic RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/src/main.c new file mode 100644 index 0000000000..07de64cef1 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/c/src/main.c @@ -0,0 +1,166 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int __pass = 0; +static int __fail = 0; + +#define CHECK(cond, msg) do { \ + if (cond) { \ + printf(" PASS | %s:%d | %s\n", __FILE__, __LINE__, msg); \ + __pass++; \ + } else { \ + printf(" FAIL | %s:%d | %s | errno=%d (%s)\n", \ + __FILE__, __LINE__, msg, errno, strerror(errno)); \ + __fail++; \ + } \ +} while (0) + +#define TEST_START(name) \ + printf("================================================\n"); \ + printf(" TEST: %s\n", name); \ + printf(" FILE: %s\n", __FILE__); \ + printf("================================================\n") + +#define TEST_DONE() \ + printf("------------------------------------------------\n"); \ + printf(" DONE: %d pass, %d fail\n", __pass, __fail); \ + printf("================================================\n\n"); \ + return __fail > 0 ? 1 : 0 + +#define CGROUP2_PATH "/tmp/cg" +#define CGROUP_V1_PATH "/tmp/cg-v1" + +static void check_mkdir(const char *path, const char *msg) +{ + errno = 0; + int ret = mkdir(path, 0755); + int saved_errno = errno; + errno = saved_errno; + CHECK(ret == 0 || saved_errno == EEXIST, msg); +} + +static ssize_t read_text_file(const char *path, char *buf, size_t cap) +{ + if (cap == 0) { + errno = EINVAL; + return -1; + } + + int fd = open(path, O_RDONLY); + if (fd < 0) { + return -1; + } + + errno = 0; + ssize_t nread = read(fd, buf, cap - 1); + int saved_errno = errno; + if (nread >= 0) { + buf[nread] = '\0'; + } + + close(fd); + errno = saved_errno; + return nread; +} + +static int buffer_contains_pid(const char *buf, pid_t pid) +{ + const char *cursor = buf; + + while (*cursor != '\0') { + char *end = NULL; + errno = 0; + long value = strtol(cursor, &end, 10); + if (cursor != end && errno == 0 && value == (long)pid) { + return 1; + } + + while (*cursor != '\0' && *cursor != '\n') { + cursor++; + } + while (*cursor == '\n' || *cursor == '\r') { + cursor++; + } + } + + return 0; +} + +static void expect_write_errno(const char *path, const char *data, + int expected_errno, const char *msg) +{ + int fd = open(path, O_WRONLY); + if (fd < 0) { + CHECK(0, msg); + return; + } + + errno = 0; + ssize_t written = write(fd, data, strlen(data)); + int saved_errno = errno; + close(fd); + errno = saved_errno; + CHECK(written == -1 && saved_errno == expected_errno, msg); +} + +int main(void) +{ + TEST_START("cgroup-basic"); + + check_mkdir(CGROUP2_PATH, "mkdir cgroup2 mountpoint"); + + errno = 0; + int ret = mount("none", CGROUP2_PATH, "cgroup2", 0, NULL); + int mount_errno = errno; + errno = mount_errno; + CHECK(ret == 0, "mount cgroup2 succeeds"); + if (ret != 0) { + printf(" OBSERVE | mount cgroup2 failed errno=%d (%s)\n", + mount_errno, strerror(mount_errno)); + TEST_DONE(); + } + + char buf[4096]; + ssize_t nread = read_text_file(CGROUP2_PATH "/cgroup.procs", buf, sizeof(buf)); + CHECK(nread >= 0, "read root cgroup.procs"); + if (nread >= 0) { + CHECK(buffer_contains_pid(buf, getpid()), + "root cgroup.procs contains current process"); + } + + nread = read_text_file(CGROUP2_PATH "/cgroup.controllers", buf, sizeof(buf)); + CHECK(nread >= 0, "read root cgroup.controllers"); + if (nread >= 0) { + CHECK(nread == 0, "root cgroup.controllers is empty before controllers exist"); + } + + nread = read_text_file(CGROUP2_PATH "/cgroup.subtree_control", buf, sizeof(buf)); + CHECK(nread >= 0, "read root cgroup.subtree_control"); + if (nread >= 0) { + CHECK(nread == 0, "root cgroup.subtree_control is initially empty"); + } + + expect_write_errno(CGROUP2_PATH "/cgroup.subtree_control", "+pids", + EINVAL, "writing +pids to subtree_control fails with EINVAL"); + + check_mkdir(CGROUP_V1_PATH, "mkdir cgroup v1 mountpoint"); + + errno = 0; + ret = mount("none", CGROUP_V1_PATH, "cgroup", 0, NULL); + int v1_errno = errno; + errno = v1_errno; + CHECK(ret == -1 && v1_errno == ENODEV, "mount cgroup v1 fails with ENODEV"); + + TEST_DONE(); +} diff --git a/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-aarch64.toml b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-aarch64.toml new file mode 100644 index 0000000000..dafede9d3f --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-aarch64.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "cortex-a53", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-aarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/cgroup-basic" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-loongarch64.toml b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-loongarch64.toml new file mode 100644 index 0000000000..28de78f6b3 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-loongarch64.toml @@ -0,0 +1,24 @@ +args = [ + "-machine", + "virt", + "-cpu", + "la464", + "-nographic", + "-m", + "512M", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-loongarch64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/cgroup-basic" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-riscv64.toml b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-riscv64.toml new file mode 100644 index 0000000000..f279cd65e2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-riscv64.toml @@ -0,0 +1,22 @@ +args = [ + "-nographic", + "-m", + "512M", + "-cpu", + "rv64", + "-device", + "virtio-blk-pci,drive=disk0", + "-drive", + "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-riscv64-alpine.img", + "-device", + "virtio-net-pci,netdev=net0", + "-netdev", + "user,id=net0", +] +uefi = false +to_bin = true +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/cgroup-basic" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 diff --git a/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-x86_64.toml new file mode 100644 index 0000000000..3d4125fce2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/cgroup-basic/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/cgroup-basic" +success_regex = ["(?m)DONE: \\d+ pass, 0 fail"] +fail_regex = ['(?i)\bpanic(?:ked)?\b', '(?m)FAIL'] +timeout = 60 From a3bedbc762f3f8b0e621d14297f2a6c73331ffea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=96=E5=9C=B0=E5=9D=80=E7=AC=A6?= <108524916+zyc107109102@users.noreply.github.com> Date: Thu, 28 May 2026 11:22:13 +0800 Subject: [PATCH 69/71] fix(rsext4): use physical byte offset in readdir to fix rm -rf skipping entries (#1001) * fix(rsext4): use physical byte offset in readdir to fix rm -rf skipping entries * test ci * test ci * test ci --------- Co-authored-by: zyc107109102 --- components/rsext4/src/file/delete.rs | 33 +--- .../axfs-ng/src/fs/ext4/rsext4/inode.rs | 43 +++-- .../bugfix/bug-ext4-dir-ops/c/CMakeLists.txt | 2 +- .../bugfix/bug-ext4-dir-ops/c/src/main.c | 166 +++++++++++++++++- 4 files changed, 207 insertions(+), 37 deletions(-) diff --git a/components/rsext4/src/file/delete.rs b/components/rsext4/src/file/delete.rs index b0e57be666..f2d9396403 100644 --- a/components/rsext4/src/file/delete.rs +++ b/components/rsext4/src/file/delete.rs @@ -154,8 +154,6 @@ fn remove_dentry_in_dir_block( ) -> bool { let block_bytes = BLOCK_SIZE; let mut offset: usize = 0; - let mut prev_off: Option = None; - let mut prev_rec_len: u16 = 0; while offset + 8 <= block_bytes { let inode = u32::from_le_bytes([ data[offset], @@ -178,27 +176,14 @@ fn remove_dentry_in_dir_block( if name_len > 0 && offset + 8 + name_len <= entry_end { let name = &data[offset + 8..offset + 8 + name_len]; if inode != 0 && name == name_bytes { - if let Some(poff) = prev_off { - // Merge current entry's space into previous entry. - let new_len = prev_rec_len.saturating_add(rec_len); - let bytes = new_len.to_le_bytes(); - data[poff + 4] = bytes[0]; - data[poff + 5] = bytes[1]; - - // Clear current entry inode so it will be treated as free. - let zero = 0u32.to_le_bytes(); - data[offset] = zero[0]; - data[offset + 1] = zero[1]; - data[offset + 2] = zero[2]; - data[offset + 3] = zero[3]; - } else { - // No previous entry in this block: mark this entry free. - let zero = 0u32.to_le_bytes(); - data[offset] = zero[0]; - data[offset + 1] = zero[1]; - data[offset + 2] = zero[2]; - data[offset + 3] = zero[3]; - } + // Mark entry as deleted by zeroing inode. Do NOT merge rec_len + // into the previous entry — keeping rec_len unchanged preserves + // stable byte offsets for readdir (getdents64) across deletions. + let zero = 0u32.to_le_bytes(); + data[offset] = zero[0]; + data[offset + 1] = zero[1]; + data[offset + 2] = zero[2]; + data[offset + 3] = zero[3]; update_ext4_dirblock_csum32( superblock, parent_ino_num.raw(), @@ -211,8 +196,6 @@ fn remove_dentry_in_dir_block( if entry_end >= block_bytes { break; } - prev_off = Some(offset); - prev_rec_len = rec_len; offset = entry_end; } false diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs b/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs index 0ab1565a79..27861ac2a1 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs +++ b/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs @@ -422,7 +422,7 @@ impl DirNodeOps for Inode { let blocks = rsext4::loopfile::resolve_inode_block_allextend(fs, dev, &mut inode) .map_err(into_vfs_err)?; - let mut idx = 0u64; + let mut byte_offset: u64 = 0; let mut count = 0usize; for &phys in blocks.values() { let cached = fs @@ -430,21 +430,44 @@ impl DirNodeOps for Inode { .get_or_load(dev, phys) .map_err(into_vfs_err)?; let data = &cached.data[..BLOCK_SIZE]; - let iter = rsext4::entries::DirEntryIterator::new(data); - for (entry, _) in iter { - if entry.inode == 0 { + + // Manually iterate entries, tracking byte_offset for ALL entries + // (including inode==0 deleted ones) so offset stays physical. + let mut pos = 0usize; + while pos + 8 <= data.len() { + let entry_inode = + u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]); + let rec_len = u16::from_le_bytes([data[pos + 4], data[pos + 5]]); + if rec_len < 8 { + break; + } + let rec_usize = rec_len as usize; + if pos + rec_usize > data.len() { + break; + } + + let entry_offset = byte_offset; + byte_offset += rec_len as u64; + pos += rec_usize; + + if entry_inode == 0 { + continue; + } + if entry_offset < offset { continue; } - if idx < offset { - idx += 1; + + let name_len = data[pos - rec_usize + 6] as usize; + let file_type = data[pos - rec_usize + 7]; + let name_start = pos - rec_usize + 8; + if name_len > rec_usize - 8 { continue; } - let name = core::str::from_utf8(entry.name) + let name = core::str::from_utf8(&data[name_start..name_start + name_len]) .map_err(|_| VfsError::InvalidData)? .to_owned(); - let node_type = dir_entry_type_to_vfs(entry.file_type); - idx += 1; - if !sink.accept(&name, entry.inode as u64, node_type, idx) { + let node_type = dir_entry_type_to_vfs(file_type); + if !sink.accept(&name, entry_inode as u64, node_type, byte_offset) { return Ok(count); } count += 1; diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/CMakeLists.txt index 09c8e28dec..4e5049e140 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/CMakeLists.txt +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/CMakeLists.txt @@ -4,5 +4,5 @@ set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) add_executable(bug-ext4-dir-ops src/main.c) -target_compile_options(bug-ext4-dir-ops PRIVATE -Wall -Wextra -Werror) +target_compile_options(bug-ext4-dir-ops PRIVATE -Wall -Wextra -Werror -Wno-format-truncation) install(TARGETS bug-ext4-dir-ops RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c index 20f11a88f1..dd96ec213d 100644 --- a/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c +++ b/test-suit/starryos/normal/qemu-smp1/bugfix/bug-ext4-dir-ops/c/src/main.c @@ -1,6 +1,6 @@ /*! * bug-ext4-dir-ops.c - * + *! * Verifies POSIX rmdir() and rename() semantics on ext4 (rsext4), * plus VFS dentry cache correctness after rename (stale parent bug). * @@ -13,8 +13,18 @@ #include #include #include +#include #include +/* linux_dirent64 for getdents64 syscall */ +struct linux_dirent64 { + unsigned long long d_ino; + long long d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + #define BASE "/root/bug-ext4-dir-ops-test" /* ========== 辅助函数 ========== */ @@ -571,6 +581,156 @@ static void test_rename_many_files(void) force_remove(dir); } +/* ========== readdir offset after delete ========== */ + +/* + * Reproduce the rm -rf bug: read entries with getdents64 using a tiny buffer + * (1 entry per call), delete each entry immediately, then continue reading. + * On correct kernel (byte offsets), the fd position advances past the deleted + * entry and lands on the next one. On buggy kernel (logical indices), the + * index shifts after deletion and entries get skipped. + */ +static void test_readdir_offset_after_delete(void) +{ + char dir[256], path[256], content[64]; + + snprintf(dir, sizeof(dir), "%s/readdir_del", BASE); + CHECK(mkdir(dir, 0755) == 0, "mkdir readdir_del"); + + /* Create 30 files */ + int ok = 1; + for (int i = 0; i < 30; i++) { + snprintf(path, sizeof(path), "%s/f%02d", dir, i); + snprintf(content, sizeof(content), "%d\n", i); + if (write_file(path, content) < 0) + ok = 0; + } + CHECK(ok, "created 30 files"); + + /* + * Read-delete loop: tiny buffer forces 1 entry per getdents64 call. + * After reading each entry, delete it immediately. If offset tracking + * is correct, we'll see all 30 entries. If buggy, entries get skipped. + */ + int dfd = open(dir, O_RDONLY | O_DIRECTORY); + CHECK(dfd >= 0, "open dir for read-delete loop"); + + char buf[32]; /* minimum buffer: exactly 1 entry */ + int total_deleted = 0; + int rounds = 0; + + for (;;) { + int nread = syscall(SYS_getdents64, dfd, buf, sizeof(buf)); + if (nread <= 0) + break; + + int pos = 0; + while (pos < nread) { + struct linux_dirent64 *ent = + (struct linux_dirent64 *)(buf + pos); + if (strcmp(ent->d_name, ".") != 0 && + strcmp(ent->d_name, "..") != 0) { + snprintf(path, sizeof(path), "%s/%s", dir, ent->d_name); + if (unlink(path) == 0) + total_deleted++; + } + pos += ent->d_reclen; + } + rounds++; + } + close(dfd); + + CHECK(total_deleted == 30, + "read-delete loop: all 30 files deleted"); + CHECK(rounds > 1, + "read-delete loop: needed multiple getdents calls (bug trigger)"); + + /* If bug exists, some files were skipped and rmdir fails */ + int rmdir_ret = rmdir(dir); + CHECK_RET(rmdir_ret, 0, + "rmdir after read-delete loop succeeds (no skipped entries)"); + if (rmdir_ret != 0) + force_remove(dir); +} + +/* + * Variant: read-readdir-delete-readdir pattern (simulates rm -rf). + * Read all entries via getdents64, delete each batch before reading next. + */ +static void test_rm_rf_pattern(void) +{ + char dir[256], path[256], content[64]; + + snprintf(dir, sizeof(dir), "%s/rmrf", BASE); + CHECK(mkdir(dir, 0755) == 0, "mkdir rmrf"); + + /* Create 30 files */ + for (int i = 0; i < 30; i++) { + snprintf(path, sizeof(path), "%s/f%02d", dir, i); + snprintf(content, sizeof(content), "%d\n", i); + write_file(path, content); + } + + /* + * Simulate rm -rf: open dir, read small batch, delete those files, + * repeat until empty. If offset is logical and entries are deleted + * between getdents calls, files get skipped and rmdir fails. + */ + int dfd = open(dir, O_RDONLY | O_DIRECTORY); + CHECK(dfd >= 0, "open dir for rm-rf pattern"); + + char buf[80]; + int total_deleted = 0; + int rounds = 0; + + for (;;) { + /* Read one batch */ + int nread = syscall(SYS_getdents64, dfd, buf, sizeof(buf)); + if (nread <= 0) + break; + + /* Collect names from this batch */ + char names[4][32]; + int name_count = 0; + int pos = 0; + while (pos < nread && name_count < 4) { + struct linux_dirent64 *ent = + (struct linux_dirent64 *)(buf + pos); + if (strcmp(ent->d_name, ".") != 0 && + strcmp(ent->d_name, "..") != 0) { + strncpy(names[name_count], ent->d_name, 31); + names[name_count][31] = '\0'; + name_count++; + } + pos += ent->d_reclen; + } + + /* Delete this batch */ + for (int i = 0; i < name_count; i++) { + snprintf(path, sizeof(path), "%s/%s", dir, names[i]); + if (unlink(path) == 0) + total_deleted++; + } + rounds++; + } + close(dfd); + + CHECK(total_deleted == 30, + "rm-rf pattern: all 30 files deleted"); + CHECK(rounds > 1, + "rm-rf pattern: needed multiple getdents calls"); + + /* + * If the bug exists, some files were skipped by getdents and remain. + * rmdir should succeed only if all files were deleted. + */ + int rmdir_ret = rmdir(dir); + CHECK_RET(rmdir_ret, 0, + "rmdir after rm-rf pattern succeeds (no leftover files)"); + if (rmdir_ret != 0) + force_remove(dir); +} + /* ========== main ========== */ int main(void) @@ -611,6 +771,10 @@ int main(void) test_pip_upgrade_pattern(); test_rename_many_files(); + /* readdir offset correctness after deletion (rm -rf bug) */ + test_readdir_offset_after_delete(); + test_rm_rf_pattern(); + manual_rmdir_recursive(BASE); TEST_DONE(); From 67b3b0f7076bb28061eee960018311b6b4d94b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= <34859362+ZR233@users.noreply.github.com> Date: Thu, 28 May 2026 12:26:18 +0800 Subject: [PATCH 70/71] refactor(ax-alloc): remove ax-allocator dependency, simplify to TLSF/buddy-slab backends (#987) * refactor(ax-alloc): remove ax-allocator dependency, simplify to TLSF/buddy-slab backends Remove the two-level allocation architecture (byte allocator + bitmap page allocator from ax-allocator). TLSF now uses rlsf directly as a single-level allocator. buddy-slab remains unchanged. - Remove ax-allocator dependency, add rlsf directly - Remove slab/buddy/page-alloc-4g/page-alloc-64g features - tlsf and buddy-slab are mutually exclusive, neither enabled by default - Add build.rs for cfg(tlsf)/cfg(buddy_slab) generation - Add stub_impl.rs with unimplemented!() when no backend is selected - Clean up downstream Cargo.toml feature propagation * fix(starry): separate allocator feature selection from static features Remove hardcoded `alloc-tlsf` from starry-kernel's static ax-feat features to avoid conflicting with buddy-slab. Add `tlsf` feature to starry-kernel and starryos crates. Add `starryos/tlsf` to all non-buddy-slab StarryOS test configurations. * Refactor memory allocator configuration and implementation - Updated `Cargo.toml` files to remove unused allocator features and streamline dependencies. - Replaced `alloc-slab` and `alloc-buddy` with `alloc-tlsf` in the kernel and API configurations. - Removed the default implementation of the memory allocator and introduced a stub implementation for cases where no backend is enabled. - Implemented a TLSF (Two-Level Segregate Fit) memory allocator using the `rlsf` crate, providing efficient memory management. - Added a build script to enforce mutually exclusive feature selection between `tlsf` and `buddy-slab`. - Cleaned up the allocator's API to ensure proper usage statistics tracking and memory management. * fix(ax-alloc): select allocator backend from platforms * fix(starryos): give x86 dhcp test enough memory * refactor: remove optional dependency on axklib for paging feature --- Cargo.lock | 9 +- components/axklib/Cargo.toml | 6 + drivers/ax-driver/Cargo.toml | 2 +- os/StarryOS/kernel/Cargo.toml | 6 +- os/StarryOS/starryos/Cargo.toml | 1 - os/arceos/api/arceos_api/Cargo.toml | 2 +- os/arceos/api/arceos_posix_api/Cargo.toml | 2 +- os/arceos/api/axfeat/Cargo.toml | 8 +- os/arceos/api/axfeat/src/lib.rs | 3 - os/arceos/modules/axalloc/Cargo.toml | 20 +- os/arceos/modules/axalloc/build.rs | 16 + os/arceos/modules/axalloc/src/default_impl.rs | 449 ------------------ os/arceos/modules/axalloc/src/lib.rs | 29 +- os/arceos/modules/axalloc/src/stub_impl.rs | 227 +++++++++ os/arceos/modules/axalloc/src/tlsf_impl.rs | 355 ++++++++++++++ os/arceos/modules/axdma/Cargo.toml | 6 +- os/arceos/modules/axfs-ng/Cargo.toml | 2 +- os/arceos/modules/axhal/Cargo.toml | 2 +- os/arceos/modules/axmm/Cargo.toml | 2 +- os/arceos/modules/axruntime/Cargo.toml | 4 +- os/arceos/modules/axruntime/src/lib.rs | 2 +- os/arceos/modules/axruntime/src/mp.rs | 2 +- os/arceos/ulib/arceos-rust/Cargo.toml | 5 - os/arceos/ulib/arceos-rust/lib/Cargo.toml | 5 - os/arceos/ulib/axstd/Cargo.toml | 6 - os/arceos/ulib/axstd/src/lib.rs | 4 - os/axvisor/Cargo.toml | 5 +- .../ax-plat-aarch64-bsta1000b/Cargo.toml | 1 + .../ax-plat-aarch64-phytium-pi/Cargo.toml | 1 + .../ax-plat-aarch64-qemu-virt/Cargo.toml | 4 +- platforms/ax-plat-aarch64-raspi/Cargo.toml | 1 + .../ax-plat-loongarch64-qemu-virt/Cargo.toml | 4 +- .../ax-plat-riscv64-qemu-virt/Cargo.toml | 4 +- platforms/ax-plat-riscv64-sg2002/Cargo.toml | 2 +- .../ax-plat-riscv64-visionfive2/Cargo.toml | 1 + platforms/ax-plat-x86-pc/Cargo.toml | 2 +- platforms/ax-plat-x86-qemu-q35/Cargo.toml | 2 +- platforms/axplat-dyn/Cargo.toml | 2 +- .../arceos/c/httpclient/qemu-aarch64.toml | 2 +- .../arceos/c/httpclient/qemu-riscv64.toml | 2 +- test-suit/arceos/c/memtest/qemu-aarch64.toml | 2 +- test-suit/arceos/c/memtest/qemu-riscv64.toml | 2 +- .../arceos/c/pthread-pipe/qemu-aarch64.toml | 2 +- .../arceos/c/pthread-pipe/qemu-riscv64.toml | 2 +- .../arceos/c/pthread-rr/qemu-aarch64.toml | 2 +- .../arceos/c/pthread-rr/qemu-riscv64.toml | 2 +- .../arceos/c/pthread-sleep/qemu-aarch64.toml | 2 +- .../arceos/c/pthread-sleep/qemu-riscv64.toml | 2 +- .../c/pthread/pthread-basic/qemu-aarch64.toml | 2 +- .../c/pthread/pthread-basic/qemu-riscv64.toml | 2 +- .../c/pthread/pthread-fifo/qemu-aarch64.toml | 2 +- .../c/pthread/pthread-fifo/qemu-riscv64.toml | 2 +- test-suit/arceos/c/smp1/qemu-aarch64.toml | 2 +- test-suit/arceos/c/smp1/qemu-riscv64.toml | 2 +- test-suit/arceos/c/smp4/qemu-aarch64.toml | 2 +- test-suit/arceos/c/smp4/qemu-riscv64.toml | 2 +- .../backtrace-raw-basic/qemu-aarch64.toml | 2 +- .../backtrace-raw-basic/qemu-riscv64.toml | 2 +- .../arceos/rust/backtrace/qemu-aarch64.toml | 2 +- .../arceos/rust/backtrace/qemu-riscv64.toml | 2 +- .../arceos/rust/display/qemu-aarch64.toml | 2 +- .../arceos/rust/display/qemu-riscv64.toml | 2 +- .../arceos/rust/exception/qemu-aarch64.toml | 2 +- .../arceos/rust/exception/qemu-riscv64.toml | 2 +- .../arceos/rust/fs/shell/qemu-aarch64.toml | 2 +- .../arceos/rust/fs/shell/qemu-riscv64.toml | 2 +- .../arceos/rust/memtest/qemu-aarch64.toml | 2 +- .../arceos/rust/memtest/qemu-riscv64.toml | 2 +- .../rust/net/echoserver/qemu-aarch64.toml | 2 +- .../rust/net/echoserver/qemu-riscv64.toml | 2 +- .../rust/net/httpclient/qemu-aarch64.toml | 2 +- .../rust/net/httpclient/qemu-riscv64.toml | 2 +- .../rust/net/httpserver/qemu-aarch64.toml | 2 +- .../rust/net/httpserver/qemu-riscv64.toml | 2 +- .../rust/net/udpserver/qemu-aarch64.toml | 2 +- .../rust/net/udpserver/qemu-riscv64.toml | 2 +- .../rust/task/affinity/qemu-aarch64.toml | 2 +- .../rust/task/affinity/qemu-riscv64.toml | 2 +- .../arceos/rust/task/ipi/qemu-aarch64.toml | 2 +- .../arceos/rust/task/ipi/qemu-riscv64.toml | 2 +- .../arceos/rust/task/irq/qemu-aarch64.toml | 2 +- .../arceos/rust/task/irq/qemu-riscv64.toml | 2 +- .../rust/task/lockdep/qemu-aarch64.toml | 2 +- .../rust/task/lockdep/qemu-base-aarch64.toml | 2 +- .../rust/task/lockdep/qemu-base-riscv64.toml | 2 +- .../rust/task/lockdep/qemu-riscv64.toml | 2 +- .../rust/task/parallel/qemu-aarch64.toml | 2 +- .../rust/task/parallel/qemu-riscv64.toml | 2 +- .../rust/task/priority/qemu-aarch64.toml | 2 +- .../rust/task/priority/qemu-riscv64.toml | 2 +- .../arceos/rust/task/sleep/qemu-aarch64.toml | 2 +- .../arceos/rust/task/sleep/qemu-riscv64.toml | 2 +- .../task/stack_guard_page/qemu-aarch64.toml | 2 +- .../task/stack_guard_page/qemu-riscv64.toml | 2 +- .../arceos/rust/task/tls/qemu-aarch64.toml | 2 +- .../arceos/rust/task/tls/qemu-riscv64.toml | 2 +- .../rust/task/wait_queue/qemu-aarch64.toml | 2 +- .../rust/task/wait_queue/qemu-riscv64.toml | 2 +- .../wait_queue_remote_wake/qemu-aarch64.toml | 2 +- .../wait_queue_remote_wake/qemu-riscv64.toml | 2 +- .../arceos/rust/task/yield/qemu-aarch64.toml | 2 +- .../arceos/rust/task/yield/qemu-riscv64.toml | 2 +- .../qemu-smp1/arce_agent/qemu-riscv64.toml | 2 +- .../qemu-smp1/helloworld/qemu-riscv64.toml | 2 +- .../qemu-smp1/httpclient/qemu-riscv64.toml | 2 +- .../qemu-smp1/httpserver/qemu-riscv64.toml | 2 +- .../std/qemu-smp1/io_test/qemu-riscv64.toml | 2 +- .../qemu-smp1/thread_test/qemu-riscv64.toml | 2 +- .../qemu-smp1/tokio_test/qemu-riscv64.toml | 2 +- .../normal/qemu-dhcp/dhcp/qemu-x86_64.toml | 2 + .../build-aarch64-unknown-none-softfloat.toml | 1 - 111 files changed, 732 insertions(+), 617 deletions(-) create mode 100644 os/arceos/modules/axalloc/build.rs delete mode 100644 os/arceos/modules/axalloc/src/default_impl.rs create mode 100644 os/arceos/modules/axalloc/src/stub_impl.rs create mode 100644 os/arceos/modules/axalloc/src/tlsf_impl.rs diff --git a/Cargo.lock b/Cargo.lock index 5aeecab1cc..3b4b99b53d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -694,7 +694,6 @@ dependencies = [ name = "ax-alloc" version = "0.7.2" dependencies = [ - "ax-allocator", "ax-errno", "ax-kspin", "ax-memory-addr", @@ -704,6 +703,7 @@ dependencies = [ "buddy-slab-allocator", "cfg-if", "log", + "rlsf", "strum 0.27.2", ] @@ -1336,6 +1336,7 @@ dependencies = [ "ax-page-table-entry", "ax-plat", "ax-plat-aarch64-peripherals", + "axklib", "log", "some-serial", ] @@ -1366,6 +1367,7 @@ dependencies = [ "ax-page-table-entry", "ax-plat", "ax-plat-aarch64-peripherals", + "axklib", "log", ] @@ -1394,6 +1396,7 @@ dependencies = [ "ax-page-table-entry", "ax-plat", "ax-plat-aarch64-peripherals", + "axklib", "log", ] @@ -1477,6 +1480,7 @@ dependencies = [ "ax-lazyinit", "ax-plat", "ax-riscv-plic", + "axklib", "log", "rdrive", "riscv 0.14.0", @@ -1848,6 +1852,7 @@ dependencies = [ name = "axklib" version = "0.5.9" dependencies = [ + "ax-alloc", "ax-errno", "ax-memory-addr", "dma-api", @@ -1975,8 +1980,6 @@ dependencies = [ "ax-page-table-entry", "ax-page-table-multiarch", "ax-percpu", - "ax-plat-loongarch64-qemu-virt", - "ax-plat-x86-qemu-q35", "ax-std", "ax-timer-list", "axaddrspace", diff --git a/components/axklib/Cargo.toml b/components/axklib/Cargo.toml index 1f5698ab86..fa919f07a1 100644 --- a/components/axklib/Cargo.toml +++ b/components/axklib/Cargo.toml @@ -12,8 +12,14 @@ documentation = "https://arceos-hypervisor.github.io/axklib" license = "Apache-2.0" [dependencies] +ax-alloc = { workspace = true } ax-errno = { workspace = true } ax-memory-addr = { workspace = true } dma-api = { workspace = true } mmio-api = { workspace = true } trait-ffi = "0.2" + +[features] +default = [] +tlsf = ["ax-alloc/tlsf"] +buddy-slab = ["ax-alloc/buddy-slab"] diff --git a/drivers/ax-driver/Cargo.toml b/drivers/ax-driver/Cargo.toml index 1f05376de1..a23d8dbf09 100644 --- a/drivers/ax-driver/Cargo.toml +++ b/drivers/ax-driver/Cargo.toml @@ -90,7 +90,7 @@ list-pci-devices = [] [dependencies] anyhow = { workspace = true } arm-scmi-rs = { workspace = true, optional = true } -ax-alloc = { workspace = true, optional = true, features = ["default"] } +ax-alloc = { workspace = true, optional = true } ax-crate-interface = { workspace = true, optional = true } ax-errno = { workspace = true, optional = true } ax-arm-pl031 = { workspace = true, optional = true } diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index c002da5516..a3ac6d926f 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -38,7 +38,6 @@ sg2002 = [ "dep:tock-registers", ] stack-guard-page = ["ax-feat/stack-guard-page"] -buddy-slab = ["ax-feat/buddy-slab"] [dependencies] ax-feat = { workspace = true, features = [ @@ -46,9 +45,6 @@ ax-feat = { workspace = true, features = [ "irq", "uspace", - "page-alloc-4g", - "alloc-slab", - "multitask", "task-ext", "sched-rr", @@ -59,7 +55,7 @@ ax-feat = { workspace = true, features = [ "net-ng", ] } -ax-alloc = { workspace = true, features = ["default"] } +ax-alloc = { workspace = true } ax-config.workspace = true ax-display.workspace = true ax-fs = { version = "0.5.15", path = "../../arceos/modules/axfs-ng", package = "ax-fs-ng" } diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index 36f14f6c0c..66aa264810 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -57,7 +57,6 @@ aarch64-hvf = ["gic-v3", "cntv-timer"] rknpu = ["ax-driver/rknpu", "starry-kernel/rknpu"] vf2 = ["ax-hal/riscv64-visionfive2"] -buddy-slab = ["starry-kernel/buddy-slab"] [[bin]] name = "starryos" diff --git a/os/arceos/api/arceos_api/Cargo.toml b/os/arceos/api/arceos_api/Cargo.toml index 8ff1536b04..05db0568ab 100644 --- a/os/arceos/api/arceos_api/Cargo.toml +++ b/os/arceos/api/arceos_api/Cargo.toml @@ -26,7 +26,7 @@ display = ["dep:ax-display", "ax-feat/display"] dummy-if-not-enabled = [] [dependencies] -ax-alloc = { workspace = true, optional = true, features = ["default"] } +ax-alloc = { workspace = true, optional = true } ax-config.workspace = true ax-display = { workspace = true, optional = true } ax-dma = { workspace = true, optional = true } diff --git a/os/arceos/api/arceos_posix_api/Cargo.toml b/os/arceos/api/arceos_posix_api/Cargo.toml index 73efef989e..077d4d68b6 100644 --- a/os/arceos/api/arceos_posix_api/Cargo.toml +++ b/os/arceos/api/arceos_posix_api/Cargo.toml @@ -35,7 +35,7 @@ epoll = ["fd"] [dependencies] # ArceOS modules -ax-alloc = { workspace = true, optional = true, features = ["default"] } +ax-alloc = { workspace = true, optional = true } ax-config.workspace = true ax-feat.workspace = true ax-fs = { workspace = true, optional = true } diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index 05657df2ec..212b137d33 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -44,12 +44,6 @@ aarch64-cntv-timer = ["ax-hal/aarch64-cntv-timer"] # Memory alloc = ["ax-alloc"] -alloc-tlsf = ["ax-alloc/tlsf"] -alloc-slab = ["ax-alloc/slab"] -alloc-buddy = ["ax-alloc/buddy"] -buddy-slab = ["ax-runtime/buddy-slab"] -page-alloc-64g = ["ax-alloc/page-alloc-64g"] # up to 64G memory capacity -page-alloc-4g = ["ax-alloc/page-alloc-4g"] # up to 4G memory capacity paging = ["ax-hal/paging", "ax-runtime/paging"] tls = ["ax-hal/tls", "ax-runtime/tls", "ax-task?/tls"] dma = ["paging"] @@ -117,7 +111,7 @@ backtrace = ["axbacktrace/alloc"] dwarf = ["axbacktrace/dwarf"] [dependencies] -ax-alloc = { workspace = true, optional = true, features = ["default"] } +ax-alloc = { workspace = true, optional = true } axbacktrace.workspace = true ax-config = { workspace = true, optional = true } ax-driver = { workspace = true, optional = true } diff --git a/os/arceos/api/axfeat/src/lib.rs b/os/arceos/api/axfeat/src/lib.rs index 17bc22c7f6..e32d3570e5 100644 --- a/os/arceos/api/axfeat/src/lib.rs +++ b/os/arceos/api/axfeat/src/lib.rs @@ -10,9 +10,6 @@ //! - `ipi`: Enable Inter-Processor Interrupts (IPIs). //! - Memory //! - `alloc`: Enable dynamic memory allocation. -//! - `alloc-tlsf`: Use the TLSF allocator. -//! - `alloc-slab`: Use the slab allocator. -//! - `alloc-buddy`: Use the buddy system allocator. //! - `paging`: Enable page table manipulation. //! - `tls`: Enable thread-local storage. //! - Task management diff --git a/os/arceos/modules/axalloc/Cargo.toml b/os/arceos/modules/axalloc/Cargo.toml index 22d7a335a2..1f5fa22be6 100644 --- a/os/arceos/modules/axalloc/Cargo.toml +++ b/os/arceos/modules/axalloc/Cargo.toml @@ -8,29 +8,17 @@ description = "ArceOS global memory allocator" license.workspace = true [features] -default = ["tlsf", "ax-allocator/page-alloc-4g"] +default = [] +tlsf = [] buddy-slab = ["dep:buddy-slab-allocator", "dep:ax-percpu", "dep:ax-plat"] -buddy = ["dep:ax-allocator", "ax-allocator/buddy"] -slab = ["dep:ax-allocator", "ax-allocator/slab"] -tlsf = ["dep:ax-allocator", "ax-allocator/tlsf"] -page-alloc-4g = [ - "dep:ax-allocator", - "tlsf", - "ax-allocator/page-alloc-4g", -] # Support up to 4GB memory capacity (default: 256MB) -page-alloc-64g = [ - "dep:ax-allocator", - "tlsf", - "ax-allocator/page-alloc-64g", -] # Support up to 64GB memory capacity (default: 256MB) -tracking = ["dep:ax-percpu", "dep:axbacktrace", "tlsf"] +tracking = ["dep:ax-percpu", "dep:axbacktrace"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "doc_cfg"] [dependencies] -ax-allocator = { workspace = true, features = ["bitmap"], optional = true } +rlsf = "0.2" ax-errno.workspace = true ax-kspin.workspace = true ax-memory-addr.workspace = true diff --git a/os/arceos/modules/axalloc/build.rs b/os/arceos/modules/axalloc/build.rs new file mode 100644 index 0000000000..115e2bf39d --- /dev/null +++ b/os/arceos/modules/axalloc/build.rs @@ -0,0 +1,16 @@ +fn main() { + println!("cargo:rustc-check-cfg=cfg(tlsf)"); + println!("cargo:rustc-check-cfg=cfg(buddy_slab)"); + + let tlsf = std::env::var("CARGO_FEATURE_TLSF").is_ok(); + let buddy_slab = std::env::var("CARGO_FEATURE_BUDDY_SLAB").is_ok(); + if tlsf && buddy_slab { + panic!("Features \"tlsf\" and \"buddy-slab\" are mutually exclusive"); + } + if tlsf { + println!("cargo:rustc-cfg=tlsf"); + } + if buddy_slab { + println!("cargo:rustc-cfg=buddy_slab"); + } +} diff --git a/os/arceos/modules/axalloc/src/default_impl.rs b/os/arceos/modules/axalloc/src/default_impl.rs deleted file mode 100644 index f827d17901..0000000000 --- a/os/arceos/modules/axalloc/src/default_impl.rs +++ /dev/null @@ -1,449 +0,0 @@ -//! Default memory allocator implementation using axallocator crate. -//! -//! This is the standard ArceOS memory allocator implementation that uses -//! the axallocator crate with support for different byte allocator algorithms -//! (TLSF, slab, buddy) and page allocation. - -#![allow(dead_code)] - -use core::{ - alloc::{GlobalAlloc, Layout}, - ptr::NonNull, -}; - -use ax_allocator::{BaseAllocator, BitmapPageAllocator, ByteAllocator, PageAllocator}; -use ax_kspin::SpinNoIrq; - -use super::{AllocResult, AllocatorOps, UsageKind, Usages}; - -/// The global allocator instance for standard mode. -#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] -static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); - -const PAGE_SIZE: usize = 0x1000; -const MIN_HEAP_SIZE: usize = 0x8000; // 32 K - -cfg_if::cfg_if! { - if #[cfg(feature = "slab")] { - /// The default byte allocator. - pub type DefaultByteAllocator = ax_allocator::SlabByteAllocator; - } else if #[cfg(feature = "buddy")] { - /// The default byte allocator. - pub type DefaultByteAllocator = ax_allocator::BuddyByteAllocator; - } else if #[cfg(feature = "tlsf")] { - /// The default byte allocator. - pub type DefaultByteAllocator = ax_allocator::TlsfByteAllocator; - } -} - -/// The global allocator used by ArceOS. -/// -/// It combines a [`ByteAllocator`] and a [`PageAllocator`] into a simple -/// two-level allocator: firstly tries allocate from the byte allocator, if -/// there is no memory, asks the page allocator for more memory and adds it to -/// the byte allocator. -pub struct GlobalAllocator { - balloc: SpinNoIrq, - palloc: SpinNoIrq>, - usages: SpinNoIrq, -} - -impl Default for GlobalAllocator { - fn default() -> Self { - Self::new() - } -} - -impl GlobalAllocator { - /// Creates an empty [`GlobalAllocator`]. - pub const fn new() -> Self { - Self { - balloc: SpinNoIrq::new(DefaultByteAllocator::new()), - palloc: SpinNoIrq::new(BitmapPageAllocator::new()), - usages: SpinNoIrq::new(Usages::new()), - } - } - - /// Returns the name of the allocator. - pub const fn name(&self) -> &'static str { - cfg_if::cfg_if! { - if #[cfg(feature = "slab")] { - "slab" - } else if #[cfg(feature = "buddy")] { - "buddy" - } else if #[cfg(feature = "tlsf")] { - "TLSF" - } else { - "unknown" - } - } - } - - /// Initializes the allocator with the given region. - /// - /// It firstly adds the whole region to the page allocator, then allocates - /// a small region (32 KB) to initialize the byte allocator. Therefore, - /// the given region must be larger than 32 KB. - pub fn init(&self, start_vaddr: usize, size: usize) -> AllocResult { - if size <= MIN_HEAP_SIZE { - return Err(crate::AllocError::InvalidParam); - } - let init_heap_size = MIN_HEAP_SIZE; - self.palloc.lock().init(start_vaddr, size); - let heap_ptr = - self.alloc_pages(init_heap_size / PAGE_SIZE, PAGE_SIZE, UsageKind::RustHeap)?; - - self.balloc.lock().init(heap_ptr, init_heap_size); - Ok(()) - } - - /// Add the given region to the allocator. - /// - /// It will add the whole region to the byte allocator. - pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { - self.balloc - .lock() - .add_memory(start_vaddr, size) - .map_err(Into::into) - } - - /// Allocate arbitrary number of bytes. Returns the left bound of the - /// allocated region. - /// - /// It firstly tries to allocate from the byte allocator. If there is no - /// memory, it asks the page allocator for more memory and adds it to the - /// byte allocator. - pub fn alloc(&self, layout: Layout) -> AllocResult> { - // simple two-level allocator: if no heap memory, allocate from the page allocator. - let mut balloc = self.balloc.lock(); - loop { - if let Ok(ptr) = balloc.alloc(layout) { - self.usages.lock().alloc(UsageKind::RustHeap, layout.size()); - return Ok(ptr); - } else { - let old_size = balloc.total_bytes(); - let expand_size = old_size - .max(layout.size()) - .next_power_of_two() - .max(PAGE_SIZE); - - let mut try_size = expand_size; - let min_size = PAGE_SIZE.max(layout.size()); - loop { - let heap_ptr = match self.alloc_pages( - try_size / PAGE_SIZE, - PAGE_SIZE, - UsageKind::RustHeap, - ) { - Ok(ptr) => ptr, - Err(err) => { - try_size /= 2; - if try_size < min_size { - return Err(err); - } - continue; - } - }; - debug!( - "expand heap memory: [{:#x}, {:#x})", - heap_ptr, - heap_ptr + try_size - ); - balloc - .add_memory(heap_ptr, try_size) - .map_err(crate::AllocError::from)?; - break; - } - } - } - } - - /// Gives back the allocated region to the byte allocator. - /// - /// The region should be allocated by [`alloc`] with the same `layout`. - /// Otherwise, the behavior is undefined. - pub fn dealloc(&self, pos: NonNull, layout: Layout) { - self.usages - .lock() - .dealloc(UsageKind::RustHeap, layout.size()); - self.balloc.lock().dealloc(pos, layout) - } - - /// Allocates contiguous pages. - /// - /// It allocates `num_pages` pages from the page allocator. - /// - /// `align` is the requested alignment in bytes, not a log2/exponent. - /// It must be a power-of-two byte alignment accepted by the page allocator. - pub fn alloc_pages( - &self, - num_pages: usize, - alignment: usize, - kind: UsageKind, - ) -> AllocResult { - let addr = self - .palloc - .lock() - .alloc_pages(num_pages, alignment) - .map_err(crate::AllocError::from)?; - if !matches!(kind, UsageKind::RustHeap) { - self.usages.lock().alloc(kind, num_pages * PAGE_SIZE); - } - Ok(addr) - } - - /// Allocates contiguous low-memory pages (physical address < 4 GiB). - pub fn alloc_dma32_pages( - &self, - _num_pages: usize, - _alignment: usize, - _kind: UsageKind, - ) -> AllocResult { - unimplemented!("default allocator does not support alloc_dma32_pages") - } - - /// Allocates contiguous pages starting from the given address. - /// - /// It allocates `num_pages` pages from the page allocator starting from the - /// given address. - /// - /// `align` is the requested alignment in bytes, not a log2/exponent. - /// It must be a power-of-two byte alignment accepted by the page allocator. - pub fn alloc_pages_at( - &self, - start: usize, - num_pages: usize, - alignment: usize, - kind: UsageKind, - ) -> AllocResult { - let addr = self - .palloc - .lock() - .alloc_pages_at(start, num_pages, alignment) - .map_err(crate::AllocError::from)?; - if !matches!(kind, UsageKind::RustHeap) { - self.usages.lock().alloc(kind, num_pages * PAGE_SIZE); - } - Ok(addr) - } - - /// Gives back the allocated pages starts from `pos` to the page allocator. - /// - /// The pages should be allocated by [`alloc_pages`] or [`alloc_pages_at`]. - /// Otherwise, the behavior is undefined. - pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { - self.usages.lock().dealloc(kind, num_pages * PAGE_SIZE); - self.palloc.lock().dealloc_pages(pos, num_pages); - } - - /// Returns the number of allocated bytes in the byte allocator. - pub fn used_bytes(&self) -> usize { - self.balloc.lock().used_bytes() - } - - /// Returns the number of available bytes in the byte allocator. - pub fn available_bytes(&self) -> usize { - self.balloc.lock().available_bytes() - } - - /// Returns the number of allocated pages in the page allocator. - pub fn used_pages(&self) -> usize { - self.palloc.lock().used_pages() - } - - /// Returns the number of available pages in the page allocator. - pub fn available_pages(&self) -> usize { - self.palloc.lock().available_pages() - } - - /// Returns the usage statistics of the allocator. - pub fn usages(&self) -> Usages { - *self.usages.lock() - } -} - -impl AllocatorOps for GlobalAllocator { - fn name(&self) -> &'static str { - GlobalAllocator::name(self) - } - - fn init(&self, start_vaddr: usize, size: usize) -> AllocResult { - GlobalAllocator::init(self, start_vaddr, size) - } - - fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { - GlobalAllocator::add_memory(self, start_vaddr, size) - } - - fn alloc(&self, layout: Layout) -> AllocResult> { - GlobalAllocator::alloc(self, layout) - } - - fn dealloc(&self, pos: NonNull, layout: Layout) { - GlobalAllocator::dealloc(self, pos, layout) - } - - fn alloc_pages( - &self, - num_pages: usize, - alignment: usize, - kind: UsageKind, - ) -> AllocResult { - GlobalAllocator::alloc_pages(self, num_pages, alignment, kind) - } - - fn alloc_dma32_pages( - &self, - num_pages: usize, - alignment: usize, - kind: UsageKind, - ) -> AllocResult { - GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind) - } - - fn alloc_pages_at( - &self, - start: usize, - num_pages: usize, - alignment: usize, - kind: UsageKind, - ) -> AllocResult { - GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind) - } - - fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { - GlobalAllocator::dealloc_pages(self, pos, num_pages, kind) - } - - fn used_bytes(&self) -> usize { - GlobalAllocator::used_bytes(self) - } - - fn available_bytes(&self) -> usize { - GlobalAllocator::available_bytes(self) - } - - fn used_pages(&self) -> usize { - GlobalAllocator::used_pages(self) - } - - fn available_pages(&self) -> usize { - GlobalAllocator::available_pages(self) - } - - fn usages(&self) -> Usages { - GlobalAllocator::usages(self) - } -} - -/// Returns the reference to the global allocator. -pub fn global_allocator() -> &'static GlobalAllocator { - &GLOBAL_ALLOCATOR -} - -/// Initializes the global allocator with the given memory region. -/// -/// Note that the memory region bounds are just numbers, and the allocator -/// does not actually access the region. Users should ensure that the region -/// is valid and not being used by others, so that the allocated memory is also -/// valid. -/// -/// This function should be called only once, and before any allocation. -/// -/// # Arguments -/// -/// - `start_vaddr`: The starting virtual address of the memory region. -/// - `size`: The size of the memory region in bytes. -pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult { - debug!( - "initialize global allocator at: [{:#x}, {:#x})", - start_vaddr, - start_vaddr + size - ); - GLOBAL_ALLOCATOR.init(start_vaddr, size) -} - -/// Add the given memory region to the global allocator. -/// -/// Users should ensure that the region is valid and not being used by others, -/// so that the allocated memory is also valid. -/// -/// It's similar to [`global_init`], but can be called multiple times. -/// -/// # Arguments -/// -/// - `start_vaddr`: The starting virtual address of the memory region. -/// - `size`: The size of the memory region in bytes. -pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult { - debug!( - "add a memory region to global allocator: [{:#x}, {:#x})", - start_vaddr, - start_vaddr + size - ); - GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) -} - -unsafe impl GlobalAlloc for GlobalAllocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let inner = move || { - if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { - ptr.as_ptr() - } else { - alloc::alloc::handle_alloc_error(layout) - } - }; - - #[cfg(feature = "tracking")] - { - crate::tracking::with_state(|state| match state { - None => inner(), - Some(state) => { - let ptr = inner(); - let generation = state.generation; - state.generation += 1; - state.map.insert( - ptr as usize, - crate::tracking::AllocationInfo { - layout, - backtrace: axbacktrace::Backtrace::capture(), - generation, - }, - ); - ptr - } - }) - } - - #[cfg(not(feature = "tracking"))] - inner() - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - let ptr = NonNull::new(ptr).expect("dealloc null ptr"); - let inner = || GlobalAllocator::dealloc(self, ptr, layout); - - #[cfg(feature = "tracking")] - crate::tracking::with_state(|state| match state { - None => inner(), - Some(state) => { - let address = ptr.as_ptr() as usize; - state.map.remove(&address); - inner() - } - }); - - #[cfg(not(feature = "tracking"))] - inner(); - } -} - -impl From for super::AllocError { - fn from(value: ax_allocator::AllocError) -> Self { - match value { - ax_allocator::AllocError::InvalidParam => Self::InvalidParam, - ax_allocator::AllocError::MemoryOverlap => Self::MemoryOverlap, - ax_allocator::AllocError::NoMemory => Self::NoMemory, - ax_allocator::AllocError::NotAllocated => Self::NotAllocated, - } - } -} diff --git a/os/arceos/modules/axalloc/src/lib.rs b/os/arceos/modules/axalloc/src/lib.rs index f324b8fcfe..661fda1200 100644 --- a/os/arceos/modules/axalloc/src/lib.rs +++ b/os/arceos/modules/axalloc/src/lib.rs @@ -7,6 +7,7 @@ #![no_std] +#[allow(unused_imports)] #[macro_use] extern crate log; extern crate alloc; @@ -52,10 +53,12 @@ impl Usages { Self([0; UsageKind::VARIANTS.len()]) } + #[allow(dead_code)] fn alloc(&mut self, kind: UsageKind, size: usize) { self.0[kind as usize] += size; } + #[allow(dead_code)] fn dealloc(&mut self, kind: UsageKind, size: usize) { self.0[kind as usize] -= size; } @@ -175,19 +178,23 @@ pub trait AllocatorOps { fn usages(&self) -> Usages; } -// Select implementation based on features. -#[cfg(feature = "buddy-slab")] +// Select implementation based on build.rs-generated cfg flags. +#[cfg(buddy_slab)] mod buddy_slab; -#[cfg(feature = "buddy-slab")] -use buddy_slab as imp; +#[cfg(not(any(tlsf, buddy_slab)))] +mod stub_impl; +#[cfg(tlsf)] +mod tlsf_impl; -#[cfg(not(feature = "buddy-slab"))] -mod default_impl; -#[cfg(not(feature = "buddy-slab"))] -use default_impl as imp; -#[cfg(feature = "buddy-slab")] -pub use imp::init_percpu_slab; -pub use imp::{DefaultByteAllocator, GlobalAllocator, global_add_memory, global_init}; +#[cfg(buddy_slab)] +use buddy_slab as imp; +pub use imp::{ + DefaultByteAllocator, GlobalAllocator, global_add_memory, global_init, init_percpu_slab, +}; +#[cfg(not(any(tlsf, buddy_slab)))] +use stub_impl as imp; +#[cfg(tlsf)] +use tlsf_impl as imp; /// Returns the reference to the global allocator. pub fn global_allocator() -> &'static GlobalAllocator { diff --git a/os/arceos/modules/axalloc/src/stub_impl.rs b/os/arceos/modules/axalloc/src/stub_impl.rs new file mode 100644 index 0000000000..53c7b6d8e2 --- /dev/null +++ b/os/arceos/modules/axalloc/src/stub_impl.rs @@ -0,0 +1,227 @@ +//! Stub allocator implementation when no backend is enabled. + +use core::{ + alloc::{GlobalAlloc, Layout}, + ptr::NonNull, +}; + +use ax_kspin::SpinNoIrq; + +use super::{AllocResult, AllocatorOps, UsageKind, Usages}; + +/// The global allocator instance (stub). +#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); + +/// Placeholder byte allocator type when no backend is enabled. +pub type DefaultByteAllocator = (); + +/// The global allocator stub when no backend is enabled. +pub struct GlobalAllocator { + usages: SpinNoIrq, +} + +impl Default for GlobalAllocator { + fn default() -> Self { + Self::new() + } +} + +impl GlobalAllocator { + /// Creates a new empty stub allocator. + pub const fn new() -> Self { + Self { + usages: SpinNoIrq::new(Usages::new()), + } + } + + /// Returns the name of the allocator. + pub const fn name(&self) -> &'static str { + "stub" + } + + /// Initializes the allocator (stub). + pub fn init(&self, _start_vaddr: usize, _size: usize) -> AllocResult { + unimplemented!("no allocator backend enabled, enable 'tlsf' or 'buddy-slab' feature") + } + + /// Add memory (stub). + pub fn add_memory(&self, _start_vaddr: usize, _size: usize) -> AllocResult { + unimplemented!("no allocator backend enabled") + } + + /// Allocate bytes (stub). + pub fn alloc(&self, _layout: Layout) -> AllocResult> { + unimplemented!("no allocator backend enabled") + } + + /// Deallocate bytes (stub). + pub fn dealloc(&self, _pos: NonNull, _layout: Layout) { + unimplemented!("no allocator backend enabled") + } + + /// Allocate pages (stub). + pub fn alloc_pages( + &self, + _num_pages: usize, + _align: usize, + _kind: UsageKind, + ) -> AllocResult { + unimplemented!("no allocator backend enabled") + } + + /// Allocate DMA32 pages (stub). + pub fn alloc_dma32_pages( + &self, + _num_pages: usize, + _alignment: usize, + _kind: UsageKind, + ) -> AllocResult { + unimplemented!("no allocator backend enabled") + } + + /// Allocate pages at address (stub). + pub fn alloc_pages_at( + &self, + _start: usize, + _num_pages: usize, + _alignment: usize, + _kind: UsageKind, + ) -> AllocResult { + unimplemented!("no allocator backend enabled") + } + + /// Deallocate pages (stub). + pub fn dealloc_pages(&self, _pos: usize, _num_pages: usize, _kind: UsageKind) { + unimplemented!("no allocator backend enabled") + } + + /// Returns used bytes (stub). + pub fn used_bytes(&self) -> usize { + 0 + } + + /// Returns available bytes (stub). + pub fn available_bytes(&self) -> usize { + 0 + } + + /// Returns used pages (stub). + pub fn used_pages(&self) -> usize { + 0 + } + + /// Returns available pages (stub). + pub fn available_pages(&self) -> usize { + 0 + } + + /// Returns usage statistics. + pub fn usages(&self) -> Usages { + *self.usages.lock() + } +} + +impl AllocatorOps for GlobalAllocator { + fn name(&self) -> &'static str { + GlobalAllocator::name(self) + } + + fn init(&self, start_vaddr: usize, size: usize) -> AllocResult { + GlobalAllocator::init(self, start_vaddr, size) + } + + fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { + GlobalAllocator::add_memory(self, start_vaddr, size) + } + + fn alloc(&self, layout: Layout) -> AllocResult> { + GlobalAllocator::alloc(self, layout) + } + + fn dealloc(&self, pos: NonNull, layout: Layout) { + GlobalAllocator::dealloc(self, pos, layout) + } + + fn alloc_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + GlobalAllocator::alloc_pages(self, num_pages, alignment, kind) + } + + fn alloc_dma32_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind) + } + + fn alloc_pages_at( + &self, + start: usize, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind) + } + + fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { + GlobalAllocator::dealloc_pages(self, pos, num_pages, kind) + } + + fn used_bytes(&self) -> usize { + GlobalAllocator::used_bytes(self) + } + + fn available_bytes(&self) -> usize { + GlobalAllocator::available_bytes(self) + } + + fn used_pages(&self) -> usize { + GlobalAllocator::used_pages(self) + } + + fn available_pages(&self) -> usize { + GlobalAllocator::available_pages(self) + } + + fn usages(&self) -> Usages { + GlobalAllocator::usages(self) + } +} + +/// Returns the reference to the global allocator. +pub fn global_allocator() -> &'static GlobalAllocator { + &GLOBAL_ALLOCATOR +} + +/// Initializes per-CPU allocator state. +/// +/// The stub backend has no per-CPU state. +pub fn init_percpu_slab(_cpu_id: usize) {} + +/// Initializes the global allocator (stub). +pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult { + GLOBAL_ALLOCATOR.init(start_vaddr, size) +} + +/// Add the given memory region to the global allocator (stub). +pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult { + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) +} + +unsafe impl GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { + unimplemented!("no allocator backend enabled") + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + unimplemented!("no allocator backend enabled") + } +} diff --git a/os/arceos/modules/axalloc/src/tlsf_impl.rs b/os/arceos/modules/axalloc/src/tlsf_impl.rs new file mode 100644 index 0000000000..7cbf68f925 --- /dev/null +++ b/os/arceos/modules/axalloc/src/tlsf_impl.rs @@ -0,0 +1,355 @@ +//! TLSF memory allocator implementation using the `rlsf` crate. + +use core::{ + alloc::{GlobalAlloc, Layout}, + ptr::NonNull, +}; + +use ax_kspin::SpinNoIrq; +use rlsf::Tlsf; + +use super::{AllocResult, AllocatorOps, UsageKind, Usages}; + +/// The global allocator instance for TLSF mode. +#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); + +const PAGE_SIZE: usize = 0x1000; + +/// The default byte allocator for TLSF mode. +pub type DefaultByteAllocator = Tlsf<'static, u32, u32, 28, 32>; + +struct TlsfInfo { + tlsf: Tlsf<'static, u32, u32, 28, 32>, + total_bytes: usize, + used_bytes: usize, +} + +impl TlsfInfo { + const fn new() -> Self { + Self { + tlsf: Tlsf::new(), + total_bytes: 0, + used_bytes: 0, + } + } +} + +/// The global allocator used by ArceOS when TLSF is enabled. +pub struct GlobalAllocator { + inner: SpinNoIrq, + usages: SpinNoIrq, +} + +impl Default for GlobalAllocator { + fn default() -> Self { + Self::new() + } +} + +impl GlobalAllocator { + /// Creates an empty [`GlobalAllocator`]. + pub const fn new() -> Self { + Self { + inner: SpinNoIrq::new(TlsfInfo::new()), + usages: SpinNoIrq::new(Usages::new()), + } + } + + /// Returns the name of the allocator. + pub const fn name(&self) -> &'static str { + "TLSF" + } + + /// Initializes the allocator with the given region. + pub fn init(&self, start_vaddr: usize, size: usize) -> AllocResult { + let mut inner = self.inner.lock(); + unsafe { + let pool = core::slice::from_raw_parts_mut(start_vaddr as *mut u8, size); + inner + .tlsf + .insert_free_block_ptr(NonNull::new(pool).unwrap()) + .unwrap(); + } + inner.total_bytes = size; + Ok(()) + } + + /// Add the given region to the allocator. + pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { + let mut inner = self.inner.lock(); + unsafe { + let pool = core::slice::from_raw_parts_mut(start_vaddr as *mut u8, size); + inner + .tlsf + .insert_free_block_ptr(NonNull::new(pool).unwrap()) + .ok_or(crate::AllocError::InvalidParam)?; + } + inner.total_bytes += size; + Ok(()) + } + + /// Allocate arbitrary number of bytes. + pub fn alloc(&self, layout: Layout) -> AllocResult> { + let ptr = self + .inner + .lock() + .tlsf + .allocate(layout) + .ok_or(crate::AllocError::NoMemory)?; + self.inner.lock().used_bytes += layout.size(); + self.usages.lock().alloc(UsageKind::RustHeap, layout.size()); + Ok(ptr) + } + + /// Gives back the allocated region. + pub fn dealloc(&self, pos: NonNull, layout: Layout) { + unsafe { + self.inner.lock().tlsf.deallocate(pos, layout.align()); + } + self.inner.lock().used_bytes -= layout.size(); + self.usages + .lock() + .dealloc(UsageKind::RustHeap, layout.size()); + } + + /// Allocates contiguous pages by allocating page-aligned bytes from TLSF. + pub fn alloc_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + let size = num_pages * PAGE_SIZE; + let align = alignment.max(PAGE_SIZE); + let layout = + Layout::from_size_align(size, align).map_err(|_| crate::AllocError::InvalidParam)?; + let ptr = self + .inner + .lock() + .tlsf + .allocate(layout) + .ok_or(crate::AllocError::NoMemory)?; + self.inner.lock().used_bytes += size; + if !matches!(kind, UsageKind::RustHeap) { + self.usages.lock().alloc(kind, size); + } + Ok(ptr.as_ptr() as usize) + } + + /// Allocates contiguous low-memory pages (physical address < 4 GiB). + pub fn alloc_dma32_pages( + &self, + _num_pages: usize, + _alignment: usize, + _kind: UsageKind, + ) -> AllocResult { + unimplemented!("TLSF allocator does not support alloc_dma32_pages") + } + + /// Allocates contiguous pages starting from the given address. + pub fn alloc_pages_at( + &self, + _start: usize, + _num_pages: usize, + _alignment: usize, + _kind: UsageKind, + ) -> AllocResult { + unimplemented!("TLSF allocator does not support alloc_pages_at") + } + + /// Gives back the allocated pages. + pub fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { + let size = num_pages * PAGE_SIZE; + let ptr = NonNull::new(pos as *mut u8).expect("dealloc_pages null ptr"); + unsafe { + self.inner.lock().tlsf.deallocate(ptr, PAGE_SIZE); + } + self.inner.lock().used_bytes -= size; + self.usages.lock().dealloc(kind, size); + } + + /// Returns the number of allocated bytes. + pub fn used_bytes(&self) -> usize { + self.inner.lock().used_bytes + } + + /// Returns the number of available bytes. + pub fn available_bytes(&self) -> usize { + let inner = self.inner.lock(); + inner.total_bytes.saturating_sub(inner.used_bytes) + } + + /// Returns the number of allocated pages. + pub fn used_pages(&self) -> usize { + self.used_bytes() / PAGE_SIZE + } + + /// Returns the number of available pages. + pub fn available_pages(&self) -> usize { + self.available_bytes() / PAGE_SIZE + } + + /// Returns the usage statistics. + pub fn usages(&self) -> Usages { + *self.usages.lock() + } +} + +impl AllocatorOps for GlobalAllocator { + fn name(&self) -> &'static str { + GlobalAllocator::name(self) + } + + fn init(&self, start_vaddr: usize, size: usize) -> AllocResult { + GlobalAllocator::init(self, start_vaddr, size) + } + + fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { + GlobalAllocator::add_memory(self, start_vaddr, size) + } + + fn alloc(&self, layout: Layout) -> AllocResult> { + GlobalAllocator::alloc(self, layout) + } + + fn dealloc(&self, pos: NonNull, layout: Layout) { + GlobalAllocator::dealloc(self, pos, layout) + } + + fn alloc_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + GlobalAllocator::alloc_pages(self, num_pages, alignment, kind) + } + + fn alloc_dma32_pages( + &self, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + GlobalAllocator::alloc_dma32_pages(self, num_pages, alignment, kind) + } + + fn alloc_pages_at( + &self, + start: usize, + num_pages: usize, + alignment: usize, + kind: UsageKind, + ) -> AllocResult { + GlobalAllocator::alloc_pages_at(self, start, num_pages, alignment, kind) + } + + fn dealloc_pages(&self, pos: usize, num_pages: usize, kind: UsageKind) { + GlobalAllocator::dealloc_pages(self, pos, num_pages, kind) + } + + fn used_bytes(&self) -> usize { + GlobalAllocator::used_bytes(self) + } + + fn available_bytes(&self) -> usize { + GlobalAllocator::available_bytes(self) + } + + fn used_pages(&self) -> usize { + GlobalAllocator::used_pages(self) + } + + fn available_pages(&self) -> usize { + GlobalAllocator::available_pages(self) + } + + fn usages(&self) -> Usages { + GlobalAllocator::usages(self) + } +} + +/// Returns the reference to the global allocator. +pub fn global_allocator() -> &'static GlobalAllocator { + &GLOBAL_ALLOCATOR +} + +/// Initializes per-CPU allocator state. +/// +/// TLSF does not use per-CPU slabs, so this is intentionally a no-op. +pub fn init_percpu_slab(_cpu_id: usize) {} + +/// Initializes the global allocator with the given memory region. +pub fn global_init(start_vaddr: usize, size: usize) -> AllocResult { + debug!( + "initialize global allocator at: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.init(start_vaddr, size) +} + +/// Add the given memory region to the global allocator. +pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult { + debug!( + "add a memory region to global allocator: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) +} + +unsafe impl GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let inner = move || { + if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { + ptr.as_ptr() + } else { + alloc::alloc::handle_alloc_error(layout) + } + }; + + #[cfg(feature = "tracking")] + { + crate::tracking::with_state(|state| match state { + None => inner(), + Some(state) => { + let ptr = inner(); + let generation = state.generation; + state.generation += 1; + state.map.insert( + ptr as usize, + crate::tracking::AllocationInfo { + layout, + backtrace: axbacktrace::Backtrace::capture(), + generation, + }, + ); + ptr + } + }) + } + + #[cfg(not(feature = "tracking"))] + inner() + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let ptr = NonNull::new(ptr).expect("dealloc null ptr"); + let inner = || GlobalAllocator::dealloc(self, ptr, layout); + + #[cfg(feature = "tracking")] + crate::tracking::with_state(|state| match state { + None => inner(), + Some(state) => { + let address = ptr.as_ptr() as usize; + state.map.remove(&address); + inner() + } + }); + + #[cfg(not(feature = "tracking"))] + inner(); + } +} diff --git a/os/arceos/modules/axdma/Cargo.toml b/os/arceos/modules/axdma/Cargo.toml index 8899f1112b..2500946bc8 100644 --- a/os/arceos/modules/axdma/Cargo.toml +++ b/os/arceos/modules/axdma/Cargo.toml @@ -9,12 +9,8 @@ keywords.workspace = true categories.workspace = true license.workspace = true -[features] -default = [] -buddy-slab = ["ax-alloc/buddy-slab"] - [dependencies] -ax-alloc = { workspace = true, features = ["default"] } +ax-alloc = { workspace = true } ax-allocator = { workspace = true, features = ["slab"] } ax-config.workspace = true ax-hal.workspace = true diff --git a/os/arceos/modules/axfs-ng/Cargo.toml b/os/arceos/modules/axfs-ng/Cargo.toml index 868e4c9695..2262c6790c 100644 --- a/os/arceos/modules/axfs-ng/Cargo.toml +++ b/os/arceos/modules/axfs-ng/Cargo.toml @@ -16,7 +16,7 @@ times = [] std = ["lwext4_rust?/std"] [dependencies] -ax-alloc = { workspace = true, features = ["default"] } +ax-alloc = { workspace = true } ax-errno = { workspace = true } axfs-ng-vfs = { workspace = true } ax-hal = { workspace = true } diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index ec775a64c8..a401f3308e 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -115,7 +115,7 @@ default = [] ipi = ["irq"] [dependencies] -ax-alloc = { workspace = true, optional = true, features = ["default"] } +ax-alloc = { workspace = true, optional = true } ax-config.workspace = true ax-cpu.workspace = true ax-plat.workspace = true diff --git a/os/arceos/modules/axmm/Cargo.toml b/os/arceos/modules/axmm/Cargo.toml index 86b0745d84..d8b7efa7e4 100644 --- a/os/arceos/modules/axmm/Cargo.toml +++ b/os/arceos/modules/axmm/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true copy = ["ax-page-table-multiarch/copy-from"] [dependencies] -ax-alloc = { workspace = true, features = ["default"] } +ax-alloc = { workspace = true } ax-errno.workspace = true ax-hal = { workspace = true, features = ["paging"] } ax-kspin.workspace = true diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 65230c060e..8c4729fda0 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -10,7 +10,6 @@ version = "0.5.16" [features] default = [] -buddy-slab = ["ax-alloc/buddy-slab"] dma = ["paging"] ipi = ["dep:ax-ipi"] irq = ["ax-hal/irq", "ax-task?/irq", "dep:ax-percpu"] @@ -25,7 +24,6 @@ plat-dyn = [ "ax-driver/plat-dyn", "ax-hal/plat-dyn", "paging", - "buddy-slab", "irq", "rtc", "smp", @@ -56,7 +54,7 @@ net-ng = ["dep:ax-net-ng", "dep:rd-net", "dep:spin", "dep:axklib", "ax-driver/ne vsock = ["net-ng", "ax-net-ng/vsock", "ax-driver/vsock"] [dependencies] -ax-alloc = {workspace = true, features = ["default"]} +ax-alloc = {workspace = true} ax-config = {workspace = true} ax-crate-interface = {workspace = true} ax-ctor-bare = {workspace = true} diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 61cba02dee..99bb2ce06f 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -156,8 +156,8 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { ax_hal::mem::clear_bss() }; ax_hal::percpu::init_primary(cpu_id); - #[cfg(feature = "buddy-slab")] // After per-CPU init, before scheduler/IPI/IRQ paths can allocate. + // This is a no-op for allocator backends that do not need per-CPU state. ax_alloc::init_percpu_slab(cpu_id); ax_hal::init_early(cpu_id, arg); let log_level = option_env!("AX_LOG").unwrap_or("info"); diff --git a/os/arceos/modules/axruntime/src/mp.rs b/os/arceos/modules/axruntime/src/mp.rs index 7194510827..2ea46b4a46 100644 --- a/os/arceos/modules/axruntime/src/mp.rs +++ b/os/arceos/modules/axruntime/src/mp.rs @@ -137,8 +137,8 @@ pub fn rust_main_secondary(cpu_id: usize) -> ! { } } ax_hal::percpu::init_secondary(cpu_id); - #[cfg(feature = "buddy-slab")] // After per-CPU init, before scheduler/IPI/IRQ paths can allocate. + // This is a no-op for allocator backends that do not need per-CPU state. ax_alloc::init_percpu_slab(cpu_id); ax_hal::init_early_secondary(cpu_id); diff --git a/os/arceos/ulib/arceos-rust/Cargo.toml b/os/arceos/ulib/arceos-rust/Cargo.toml index 4617ea6f19..3ad69bbcd8 100644 --- a/os/arceos/ulib/arceos-rust/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/Cargo.toml @@ -31,11 +31,6 @@ defplat = [] # Memory alloc = [] -alloc-tlsf = [] -alloc-slab = [] -alloc-buddy = [] -page-alloc-64g = [] # Support up to 64G memory capacity -page-alloc-4g = [] # Support up to 4G memory capacity paging = [] dma = [] tls = [] diff --git a/os/arceos/ulib/arceos-rust/lib/Cargo.toml b/os/arceos/ulib/arceos-rust/lib/Cargo.toml index a9a5e45157..a402fcc0b1 100644 --- a/os/arceos/ulib/arceos-rust/lib/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/lib/Cargo.toml @@ -42,12 +42,7 @@ myplat = ["ax-feat/myplat"] # Memory alloc = ["ax-api/alloc", "ax-feat/alloc", "ax-io/alloc"] -alloc-buddy = ["ax-feat/alloc-buddy"] -alloc-slab = ["ax-feat/alloc-slab"] -alloc-tlsf = ["ax-feat/alloc-tlsf"] dma = ["ax-api/dma", "ax-feat/dma"] -page-alloc-4g = ["ax-feat/page-alloc-4g"] # Support up to 4G memory capacity -page-alloc-64g = ["ax-feat/page-alloc-64g"] # Support up to 64G memory capacity paging = ["ax-feat/paging"] tls = ["ax-feat/tls"] diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index d97b65ee1b..2d1ad12606 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -40,12 +40,6 @@ plat-dyn = ["ax-feat/plat-dyn"] # Memory alloc = ["ax-api/alloc", "ax-feat/alloc", "ax-io/alloc"] -alloc-tlsf = ["ax-feat/alloc-tlsf"] -alloc-slab = ["ax-feat/alloc-slab"] -alloc-buddy = ["ax-feat/alloc-buddy"] -buddy-slab = ["ax-feat/buddy-slab"] -page-alloc-64g = ["ax-feat/page-alloc-64g"] # Support up to 64G memory capacity -page-alloc-4g = ["ax-feat/page-alloc-4g"] # Support up to 4G memory capacity paging = ["ax-feat/paging", "alloc"] dma = ["ax-api/dma", "ax-feat/dma"] tls = ["ax-feat/tls"] diff --git a/os/arceos/ulib/axstd/src/lib.rs b/os/arceos/ulib/axstd/src/lib.rs index 8a2e3cdf07..40830faf1e 100644 --- a/os/arceos/ulib/axstd/src/lib.rs +++ b/os/arceos/ulib/axstd/src/lib.rs @@ -17,10 +17,6 @@ //! - `irq`: Enable interrupt handling support. //! - Memory //! - `alloc`: Enable dynamic memory allocation. -//! - `alloc-tlsf`: Use the TLSF allocator. -//! - `alloc-slab`: Use the slab allocator. -//! - `alloc-buddy`: Use the buddy system allocator. -//! - `buddy-slab`: Use the buddy-slab allocator. //! - `paging`: Enable page table manipulation. //! - `tls`: Enable thread-local storage. //! - Task management diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index ca265eb539..2dc8d04429 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -67,7 +67,7 @@ ax-timer-list = { workspace = true } hashbrown = "0.14" # System dependent modules provided by ArceOS. -ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv", "buddy-slab"] } +ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv"] } ax-hal = { workspace = true, features = ["paging", "irq", "smp", "hv", "axvisor-linker"] } # System dependent modules provided by ArceOS-Hypervisor (bare-metal only) axaddrspace = { workspace = true } @@ -96,14 +96,11 @@ ax-driver.workspace = true axplat-dyn = { workspace = true, optional = true, default-features = false } [target.'cfg(target_arch = "x86_64")'.dependencies] -ax-plat-x86-qemu-q35 = { workspace = true, default-features = false, features = ["reboot-on-system-off", "irq", "smp"] } ax-config = { workspace = true, features = ["plat-dyn"] } [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu-ext = "0.1" arm-gic-driver = { workspace = true, features = ["rdif"] } -[target.'cfg(target_arch = "loongarch64")'.dependencies] -ax-plat-loongarch64-qemu-virt = { workspace = true, default-features = false, features = ["irq", "smp"] } [target.'cfg(target_arch = "riscv64")'.dependencies] riscv_vcpu = { workspace = true } riscv_vplic = { workspace = true } diff --git a/platforms/ax-plat-aarch64-bsta1000b/Cargo.toml b/platforms/ax-plat-aarch64-bsta1000b/Cargo.toml index 91115c6650..5a86d57ab3 100644 --- a/platforms/ax-plat-aarch64-bsta1000b/Cargo.toml +++ b/platforms/ax-plat-aarch64-bsta1000b/Cargo.toml @@ -25,6 +25,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +axklib = { workspace = true, features = ["tlsf"] } [package.metadata.axplat] platform = "aarch64-bsta1000b" diff --git a/platforms/ax-plat-aarch64-phytium-pi/Cargo.toml b/platforms/ax-plat-aarch64-phytium-pi/Cargo.toml index 4101728dbc..a1d7af3f00 100644 --- a/platforms/ax-plat-aarch64-phytium-pi/Cargo.toml +++ b/platforms/ax-plat-aarch64-phytium-pi/Cargo.toml @@ -22,6 +22,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +axklib = { workspace = true, features = ["tlsf"] } [package.metadata.axplat] platform = "aarch64-phytium-pi" diff --git a/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml b/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml index 1eb9dbd453..b02861d0d1 100644 --- a/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-aarch64-qemu-virt/Cargo.toml @@ -12,7 +12,7 @@ license = "Apache-2.0" [features] fp-simd = ["ax-cpu/fp-simd"] irq = ["ax-plat/irq", "ax-plat-aarch64-peripherals/irq"] -paging = ["dep:axklib"] +paging = [] rtc = [] smp = ["ax-plat/smp"] # Enable the GICv3 distributor + redistributor + system-register CPU @@ -32,7 +32,7 @@ ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-driver = { workspace = true, features = ["plat-static", "virtio"] } ax-plat = { workspace = true } -axklib = { workspace = true, optional = true } +axklib = { workspace = true, features = ["tlsf"] } mmio-api.workspace = true [package.metadata.axplat] diff --git a/platforms/ax-plat-aarch64-raspi/Cargo.toml b/platforms/ax-plat-aarch64-raspi/Cargo.toml index 21ff757392..ca00342f5e 100644 --- a/platforms/ax-plat-aarch64-raspi/Cargo.toml +++ b/platforms/ax-plat-aarch64-raspi/Cargo.toml @@ -23,6 +23,7 @@ ax-config-macros = { workspace = true } ax-plat-aarch64-peripherals = { workspace = true } ax-cpu = { workspace = true } ax-plat = { workspace = true } +axklib = { workspace = true, features = ["tlsf"] } [package.metadata.axplat] platform = "aarch64-raspi" diff --git a/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml b/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml index bbef34aa55..72658a2b99 100644 --- a/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-loongarch64-qemu-virt/Cargo.toml @@ -12,7 +12,7 @@ license = "Apache-2.0" [features] fp-simd = ["ax-cpu/fp-simd"] irq = ["ax-plat/irq"] -paging = ["dep:axklib"] +paging = [] rtc = ["dep:chrono"] smp = ["ax-plat/smp", "ax-kspin/smp", "irq"] @@ -29,7 +29,7 @@ ax-config-macros = { workspace = true } ax-cpu = { workspace = true } ax-driver = { workspace = true, features = ["plat-static"] } ax-plat = { workspace = true } -axklib = { workspace = true, optional = true } +axklib = { workspace = true, features = ["tlsf"] } mmio-api.workspace = true [package.metadata.axplat] diff --git a/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml b/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml index a66a880d29..352fc4c56c 100644 --- a/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml +++ b/platforms/ax-plat-riscv64-qemu-virt/Cargo.toml @@ -13,7 +13,7 @@ license = "Apache-2.0" fp-simd = ["ax-cpu/fp-simd"] hypervisor = ["irq", "smp"] irq = ["ax-plat/irq", "dep:ax-riscv-plic"] -paging = ["dep:axklib"] +paging = [] rtc = ["dep:riscv_goldfish"] smp = ["ax-plat/smp"] @@ -31,7 +31,7 @@ ax-config-macros = { workspace = true } ax-cpu = { workspace = true } ax-driver = { workspace = true, features = ["plat-static", "virtio"] } ax-plat = { workspace = true } -axklib = { workspace = true, optional = true } +axklib = { workspace = true, features = ["tlsf"] } mmio-api.workspace = true [package.metadata.axplat] diff --git a/platforms/ax-plat-riscv64-sg2002/Cargo.toml b/platforms/ax-plat-riscv64-sg2002/Cargo.toml index b76056eb81..346c3d8333 100644 --- a/platforms/ax-plat-riscv64-sg2002/Cargo.toml +++ b/platforms/ax-plat-riscv64-sg2002/Cargo.toml @@ -28,7 +28,7 @@ ax-driver = { workspace = true, features = ["plat-static", "block"] } ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } ax-plat = { workspace = true } -axklib.workspace = true +axklib = { workspace = true, features = ["tlsf"] } rd-block.workspace = true sg200x-bsp.workspace = true some-serial.workspace = true diff --git a/platforms/ax-plat-riscv64-visionfive2/Cargo.toml b/platforms/ax-plat-riscv64-visionfive2/Cargo.toml index 23b6dabb83..7257e6cbe1 100644 --- a/platforms/ax-plat-riscv64-visionfive2/Cargo.toml +++ b/platforms/ax-plat-riscv64-visionfive2/Cargo.toml @@ -21,6 +21,7 @@ ax-cpu = { workspace = true } ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } ax-plat = { workspace = true } +axklib = { workspace = true, features = ["tlsf"] } rdrive.workspace = true ax-riscv-plic = { workspace = true, optional = true } log = "0.4" diff --git a/platforms/ax-plat-x86-pc/Cargo.toml b/platforms/ax-plat-x86-pc/Cargo.toml index b544c201c8..745e8efc8d 100644 --- a/platforms/ax-plat-x86-pc/Cargo.toml +++ b/platforms/ax-plat-x86-pc/Cargo.toml @@ -28,7 +28,7 @@ ax-config-macros = { workspace = true } ax-cpu = { workspace = true } ax-driver = { workspace = true, features = ["plat-static"] } ax-plat = { workspace = true } -axklib.workspace = true +axklib = { workspace = true, features = ["tlsf"] } x86 = "0.52" x86_64 = "0.15.2" diff --git a/platforms/ax-plat-x86-qemu-q35/Cargo.toml b/platforms/ax-plat-x86-qemu-q35/Cargo.toml index 484bbdb6db..7aa5a36140 100644 --- a/platforms/ax-plat-x86-qemu-q35/Cargo.toml +++ b/platforms/ax-plat-x86-qemu-q35/Cargo.toml @@ -28,7 +28,7 @@ ax-config-macros = { workspace = true } ax-cpu = { workspace = true, features = ["arm-el2"] } ax-driver = { workspace = true, features = ["plat-static"] } ax-plat.workspace = true -axklib.workspace = true +axklib = { workspace = true, features = ["tlsf"] } bitflags = "2.6" heapless = "0.9" ax-int-ratio = { workspace = true } diff --git a/platforms/axplat-dyn/Cargo.toml b/platforms/axplat-dyn/Cargo.toml index d7e9fdcea1..596b8f79b7 100644 --- a/platforms/axplat-dyn/Cargo.toml +++ b/platforms/axplat-dyn/Cargo.toml @@ -25,7 +25,7 @@ ax-config-macros = { workspace = true } ax-cpu.workspace = true ax-driver = { workspace = true, features = ["plat-dyn"] } ax-errno.workspace = true -axklib.workspace = true +axklib = { workspace = true, features = ["buddy-slab"] } ax-plat.workspace = true heapless = "0.9" log.workspace = true diff --git a/test-suit/arceos/c/httpclient/qemu-aarch64.toml b/test-suit/arceos/c/httpclient/qemu-aarch64.toml index af7010138a..81eb3f1a7c 100644 --- a/test-suit/arceos/c/httpclient/qemu-aarch64.toml +++ b/test-suit/arceos/c/httpclient/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-nographic", "-device", "virtio-net-pci,netdev=net0", diff --git a/test-suit/arceos/c/httpclient/qemu-riscv64.toml b/test-suit/arceos/c/httpclient/qemu-riscv64.toml index f267f31cb1..e4ca40d28d 100644 --- a/test-suit/arceos/c/httpclient/qemu-riscv64.toml +++ b/test-suit/arceos/c/httpclient/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-nographic", "-device", "virtio-net-pci,netdev=net0", diff --git a/test-suit/arceos/c/memtest/qemu-aarch64.toml b/test-suit/arceos/c/memtest/qemu-aarch64.toml index 54ef643774..dece6fdd66 100644 --- a/test-suit/arceos/c/memtest/qemu-aarch64.toml +++ b/test-suit/arceos/c/memtest/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-nographic", "-serial", "mon:stdio", diff --git a/test-suit/arceos/c/memtest/qemu-riscv64.toml b/test-suit/arceos/c/memtest/qemu-riscv64.toml index ad0634c293..3746f1c560 100644 --- a/test-suit/arceos/c/memtest/qemu-riscv64.toml +++ b/test-suit/arceos/c/memtest/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-nographic", "-serial", "mon:stdio", diff --git a/test-suit/arceos/c/pthread-pipe/qemu-aarch64.toml b/test-suit/arceos/c/pthread-pipe/qemu-aarch64.toml index 4860a6ad1f..7be792ea03 100644 --- a/test-suit/arceos/c/pthread-pipe/qemu-aarch64.toml +++ b/test-suit/arceos/c/pthread-pipe/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread-pipe/qemu-riscv64.toml b/test-suit/arceos/c/pthread-pipe/qemu-riscv64.toml index 62485e3c3d..000b4aa5fd 100644 --- a/test-suit/arceos/c/pthread-pipe/qemu-riscv64.toml +++ b/test-suit/arceos/c/pthread-pipe/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread-rr/qemu-aarch64.toml b/test-suit/arceos/c/pthread-rr/qemu-aarch64.toml index 2590bf5f48..6da57410ea 100644 --- a/test-suit/arceos/c/pthread-rr/qemu-aarch64.toml +++ b/test-suit/arceos/c/pthread-rr/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread-rr/qemu-riscv64.toml b/test-suit/arceos/c/pthread-rr/qemu-riscv64.toml index 047ff1f228..64db90569b 100644 --- a/test-suit/arceos/c/pthread-rr/qemu-riscv64.toml +++ b/test-suit/arceos/c/pthread-rr/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread-sleep/qemu-aarch64.toml b/test-suit/arceos/c/pthread-sleep/qemu-aarch64.toml index ad77250f57..dabc6b49ee 100644 --- a/test-suit/arceos/c/pthread-sleep/qemu-aarch64.toml +++ b/test-suit/arceos/c/pthread-sleep/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread-sleep/qemu-riscv64.toml b/test-suit/arceos/c/pthread-sleep/qemu-riscv64.toml index b9e311a4d7..2800b059df 100644 --- a/test-suit/arceos/c/pthread-sleep/qemu-riscv64.toml +++ b/test-suit/arceos/c/pthread-sleep/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread/pthread-basic/qemu-aarch64.toml b/test-suit/arceos/c/pthread/pthread-basic/qemu-aarch64.toml index 8aa6008418..c7f1ce7e83 100644 --- a/test-suit/arceos/c/pthread/pthread-basic/qemu-aarch64.toml +++ b/test-suit/arceos/c/pthread/pthread-basic/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread/pthread-basic/qemu-riscv64.toml b/test-suit/arceos/c/pthread/pthread-basic/qemu-riscv64.toml index 3e62faebf4..22a3bf90e0 100644 --- a/test-suit/arceos/c/pthread/pthread-basic/qemu-riscv64.toml +++ b/test-suit/arceos/c/pthread/pthread-basic/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread/pthread-fifo/qemu-aarch64.toml b/test-suit/arceos/c/pthread/pthread-fifo/qemu-aarch64.toml index 2590bf5f48..6da57410ea 100644 --- a/test-suit/arceos/c/pthread/pthread-fifo/qemu-aarch64.toml +++ b/test-suit/arceos/c/pthread/pthread-fifo/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/pthread/pthread-fifo/qemu-riscv64.toml b/test-suit/arceos/c/pthread/pthread-fifo/qemu-riscv64.toml index 047ff1f228..64db90569b 100644 --- a/test-suit/arceos/c/pthread/pthread-fifo/qemu-riscv64.toml +++ b/test-suit/arceos/c/pthread/pthread-fifo/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/smp1/qemu-aarch64.toml b/test-suit/arceos/c/smp1/qemu-aarch64.toml index 9b5935468d..b928f1a601 100644 --- a/test-suit/arceos/c/smp1/qemu-aarch64.toml +++ b/test-suit/arceos/c/smp1/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-nographic", "-serial", "mon:stdio", diff --git a/test-suit/arceos/c/smp1/qemu-riscv64.toml b/test-suit/arceos/c/smp1/qemu-riscv64.toml index 236c2f3de7..4f163b5584 100644 --- a/test-suit/arceos/c/smp1/qemu-riscv64.toml +++ b/test-suit/arceos/c/smp1/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-nographic", "-serial", "mon:stdio", diff --git a/test-suit/arceos/c/smp4/qemu-aarch64.toml b/test-suit/arceos/c/smp4/qemu-aarch64.toml index 14a3163f43..1b626cc04e 100644 --- a/test-suit/arceos/c/smp4/qemu-aarch64.toml +++ b/test-suit/arceos/c/smp4/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/c/smp4/qemu-riscv64.toml b/test-suit/arceos/c/smp4/qemu-riscv64.toml index 3df3436cae..50fdbd8559 100644 --- a/test-suit/arceos/c/smp4/qemu-riscv64.toml +++ b/test-suit/arceos/c/smp4/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/backtrace-raw-basic/qemu-aarch64.toml b/test-suit/arceos/rust/backtrace-raw-basic/qemu-aarch64.toml index 6f51310971..540fbff9c8 100644 --- a/test-suit/arceos/rust/backtrace-raw-basic/qemu-aarch64.toml +++ b/test-suit/arceos/rust/backtrace-raw-basic/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/backtrace-raw-basic/qemu-riscv64.toml b/test-suit/arceos/rust/backtrace-raw-basic/qemu-riscv64.toml index c01e95b152..f74e16f4a5 100644 --- a/test-suit/arceos/rust/backtrace-raw-basic/qemu-riscv64.toml +++ b/test-suit/arceos/rust/backtrace-raw-basic/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/backtrace/qemu-aarch64.toml b/test-suit/arceos/rust/backtrace/qemu-aarch64.toml index b47e5fad58..e7b560ea05 100644 --- a/test-suit/arceos/rust/backtrace/qemu-aarch64.toml +++ b/test-suit/arceos/rust/backtrace/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/backtrace/qemu-riscv64.toml b/test-suit/arceos/rust/backtrace/qemu-riscv64.toml index bd61353a0b..04abcc8c28 100644 --- a/test-suit/arceos/rust/backtrace/qemu-riscv64.toml +++ b/test-suit/arceos/rust/backtrace/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/display/qemu-aarch64.toml b/test-suit/arceos/rust/display/qemu-aarch64.toml index 606c91cbee..715badd521 100644 --- a/test-suit/arceos/rust/display/qemu-aarch64.toml +++ b/test-suit/arceos/rust/display/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/display/qemu-riscv64.toml b/test-suit/arceos/rust/display/qemu-riscv64.toml index c49c71e3e6..899dd5246d 100644 --- a/test-suit/arceos/rust/display/qemu-riscv64.toml +++ b/test-suit/arceos/rust/display/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/exception/qemu-aarch64.toml b/test-suit/arceos/rust/exception/qemu-aarch64.toml index 21b73fb6a0..aec544abae 100644 --- a/test-suit/arceos/rust/exception/qemu-aarch64.toml +++ b/test-suit/arceos/rust/exception/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/exception/qemu-riscv64.toml b/test-suit/arceos/rust/exception/qemu-riscv64.toml index b04c04753d..9b4ea65e52 100644 --- a/test-suit/arceos/rust/exception/qemu-riscv64.toml +++ b/test-suit/arceos/rust/exception/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/fs/shell/qemu-aarch64.toml b/test-suit/arceos/rust/fs/shell/qemu-aarch64.toml index adddb3af2f..db7a79b06f 100644 --- a/test-suit/arceos/rust/fs/shell/qemu-aarch64.toml +++ b/test-suit/arceos/rust/fs/shell/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/fs/shell/qemu-riscv64.toml b/test-suit/arceos/rust/fs/shell/qemu-riscv64.toml index 95cd407653..38599c35f0 100644 --- a/test-suit/arceos/rust/fs/shell/qemu-riscv64.toml +++ b/test-suit/arceos/rust/fs/shell/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-bios", "default", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/memtest/qemu-aarch64.toml b/test-suit/arceos/rust/memtest/qemu-aarch64.toml index ae6e9f681c..6694eccd03 100644 --- a/test-suit/arceos/rust/memtest/qemu-aarch64.toml +++ b/test-suit/arceos/rust/memtest/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/memtest/qemu-riscv64.toml b/test-suit/arceos/rust/memtest/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/memtest/qemu-riscv64.toml +++ b/test-suit/arceos/rust/memtest/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/echoserver/qemu-aarch64.toml b/test-suit/arceos/rust/net/echoserver/qemu-aarch64.toml index 31b468fa62..96307b4cfa 100644 --- a/test-suit/arceos/rust/net/echoserver/qemu-aarch64.toml +++ b/test-suit/arceos/rust/net/echoserver/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/echoserver/qemu-riscv64.toml b/test-suit/arceos/rust/net/echoserver/qemu-riscv64.toml index 56d9fc2737..ba49f78213 100644 --- a/test-suit/arceos/rust/net/echoserver/qemu-riscv64.toml +++ b/test-suit/arceos/rust/net/echoserver/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/httpclient/qemu-aarch64.toml b/test-suit/arceos/rust/net/httpclient/qemu-aarch64.toml index cfe65cb291..033c4e418d 100644 --- a/test-suit/arceos/rust/net/httpclient/qemu-aarch64.toml +++ b/test-suit/arceos/rust/net/httpclient/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/httpclient/qemu-riscv64.toml b/test-suit/arceos/rust/net/httpclient/qemu-riscv64.toml index 720c88bacf..643a13de4d 100644 --- a/test-suit/arceos/rust/net/httpclient/qemu-riscv64.toml +++ b/test-suit/arceos/rust/net/httpclient/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/httpserver/qemu-aarch64.toml b/test-suit/arceos/rust/net/httpserver/qemu-aarch64.toml index a942c480f3..bb3facb60e 100644 --- a/test-suit/arceos/rust/net/httpserver/qemu-aarch64.toml +++ b/test-suit/arceos/rust/net/httpserver/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/httpserver/qemu-riscv64.toml b/test-suit/arceos/rust/net/httpserver/qemu-riscv64.toml index 3e768298a5..e976dfda74 100644 --- a/test-suit/arceos/rust/net/httpserver/qemu-riscv64.toml +++ b/test-suit/arceos/rust/net/httpserver/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/udpserver/qemu-aarch64.toml b/test-suit/arceos/rust/net/udpserver/qemu-aarch64.toml index 31b468fa62..96307b4cfa 100644 --- a/test-suit/arceos/rust/net/udpserver/qemu-aarch64.toml +++ b/test-suit/arceos/rust/net/udpserver/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/net/udpserver/qemu-riscv64.toml b/test-suit/arceos/rust/net/udpserver/qemu-riscv64.toml index 56d9fc2737..ba49f78213 100644 --- a/test-suit/arceos/rust/net/udpserver/qemu-riscv64.toml +++ b/test-suit/arceos/rust/net/udpserver/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/affinity/qemu-aarch64.toml b/test-suit/arceos/rust/task/affinity/qemu-aarch64.toml index 90888bce1b..cf12e2693b 100644 --- a/test-suit/arceos/rust/task/affinity/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/affinity/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/affinity/qemu-riscv64.toml b/test-suit/arceos/rust/task/affinity/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/affinity/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/affinity/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/ipi/qemu-aarch64.toml b/test-suit/arceos/rust/task/ipi/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/ipi/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/ipi/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/ipi/qemu-riscv64.toml b/test-suit/arceos/rust/task/ipi/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/ipi/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/ipi/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/irq/qemu-aarch64.toml b/test-suit/arceos/rust/task/irq/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/irq/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/irq/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/irq/qemu-riscv64.toml b/test-suit/arceos/rust/task/irq/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/irq/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/irq/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/lockdep/qemu-aarch64.toml b/test-suit/arceos/rust/task/lockdep/qemu-aarch64.toml index f190834686..67cbbaa5d2 100644 --- a/test-suit/arceos/rust/task/lockdep/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/lockdep/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/lockdep/qemu-base-aarch64.toml b/test-suit/arceos/rust/task/lockdep/qemu-base-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/lockdep/qemu-base-aarch64.toml +++ b/test-suit/arceos/rust/task/lockdep/qemu-base-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/lockdep/qemu-base-riscv64.toml b/test-suit/arceos/rust/task/lockdep/qemu-base-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/lockdep/qemu-base-riscv64.toml +++ b/test-suit/arceos/rust/task/lockdep/qemu-base-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/lockdep/qemu-riscv64.toml b/test-suit/arceos/rust/task/lockdep/qemu-riscv64.toml index d62b8a5314..dfb258112a 100644 --- a/test-suit/arceos/rust/task/lockdep/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/lockdep/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/parallel/qemu-aarch64.toml b/test-suit/arceos/rust/task/parallel/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/parallel/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/parallel/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/parallel/qemu-riscv64.toml b/test-suit/arceos/rust/task/parallel/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/parallel/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/parallel/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/priority/qemu-aarch64.toml b/test-suit/arceos/rust/task/priority/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/priority/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/priority/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/priority/qemu-riscv64.toml b/test-suit/arceos/rust/task/priority/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/priority/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/priority/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/sleep/qemu-aarch64.toml b/test-suit/arceos/rust/task/sleep/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/sleep/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/sleep/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/sleep/qemu-riscv64.toml b/test-suit/arceos/rust/task/sleep/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/sleep/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/sleep/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/stack_guard_page/qemu-aarch64.toml b/test-suit/arceos/rust/task/stack_guard_page/qemu-aarch64.toml index 7891a54b15..1c5e29fcea 100644 --- a/test-suit/arceos/rust/task/stack_guard_page/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/stack_guard_page/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "1", "-nographic", diff --git a/test-suit/arceos/rust/task/stack_guard_page/qemu-riscv64.toml b/test-suit/arceos/rust/task/stack_guard_page/qemu-riscv64.toml index cb5fce00a9..0c34b32392 100644 --- a/test-suit/arceos/rust/task/stack_guard_page/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/stack_guard_page/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/tls/qemu-aarch64.toml b/test-suit/arceos/rust/task/tls/qemu-aarch64.toml index 518c35c7fb..241a481bde 100644 --- a/test-suit/arceos/rust/task/tls/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/tls/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/tls/qemu-riscv64.toml b/test-suit/arceos/rust/task/tls/qemu-riscv64.toml index ae545841ca..7ab91a8686 100644 --- a/test-suit/arceos/rust/task/tls/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/tls/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/wait_queue/qemu-aarch64.toml b/test-suit/arceos/rust/task/wait_queue/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/wait_queue/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/wait_queue/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/wait_queue/qemu-riscv64.toml b/test-suit/arceos/rust/task/wait_queue/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/wait_queue/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/wait_queue/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml index da79509f7c..4a9272b4a6 100644 --- a/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/wait_queue_remote_wake/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-accel", diff --git a/test-suit/arceos/rust/task/yield/qemu-aarch64.toml b/test-suit/arceos/rust/task/yield/qemu-aarch64.toml index 479157ca39..677fa44018 100644 --- a/test-suit/arceos/rust/task/yield/qemu-aarch64.toml +++ b/test-suit/arceos/rust/task/yield/qemu-aarch64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "cortex-a72", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/rust/task/yield/qemu-riscv64.toml b/test-suit/arceos/rust/task/yield/qemu-riscv64.toml index d23465c6ba..373b46f596 100644 --- a/test-suit/arceos/rust/task/yield/qemu-riscv64.toml +++ b/test-suit/arceos/rust/task/yield/qemu-riscv64.toml @@ -4,7 +4,7 @@ args = [ "-cpu", "rv64", "-m", - "128M", + "512M", "-smp", "4", "-nographic", diff --git a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml index a9379d53e2..a4da00d694 100644 --- a/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/arce_agent/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/arce_agent/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["ArceAgent smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml index 5569600ea9..fc5c21b66e 100644 --- a/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/helloworld/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/helloworld/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/helloworld/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["Hello, world!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml index c1871988a9..1a59e34041 100644 --- a/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpclient/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpclient/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpclient/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP client tests run OK!"] diff --git a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml index 2b096111af..b050649857 100644 --- a/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/httpserver/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpserver/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/httpserver/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["HTTP server smoke test ready"] diff --git a/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml index cf72d2902f..bb2a43ab7a 100644 --- a/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/io_test/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/io_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml index 951abda65e..da4c566425 100644 --- a/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/thread_test/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/thread_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/thread_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有测试完成 ==="] diff --git a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml index b46ea08b21..636ba19b54 100644 --- a/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml +++ b/test-suit/arceos/std/qemu-smp1/tokio_test/qemu-riscv64.toml @@ -1,4 +1,4 @@ -args = ["-machine", "virt", "-cpu", "rv64", "-m", "128M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/tokio_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] +args = ["-machine", "virt", "-cpu", "rv64", "-m", "512M", "-smp", "1", "-nographic", "-device", "virtio-blk-pci,drive=disk0", "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/runtime-assets/arceos/std/qemu-smp1/tokio_test/disk.img", "-device", "virtio-net-pci,netdev=net0", "-netdev", "user,id=net0", "-serial", "mon:stdio"] uefi = false to_bin = false success_regex = ["=== 所有 Tokio 测试完成 ==="] diff --git a/test-suit/starryos/normal/qemu-dhcp/dhcp/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-dhcp/dhcp/qemu-x86_64.toml index 7ce73d425c..8c595ffabb 100644 --- a/test-suit/starryos/normal/qemu-dhcp/dhcp/qemu-x86_64.toml +++ b/test-suit/starryos/normal/qemu-dhcp/dhcp/qemu-x86_64.toml @@ -1,5 +1,7 @@ args = [ "-nographic", + "-m", + "512M", "-device", "virtio-blk-pci,drive=disk0", "-drive", diff --git a/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml b/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml index 8b785b5ae5..3f72491b2b 100644 --- a/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/starryos/normal/qemu-smp4-buddy-slab/build-aarch64-unknown-none-softfloat.toml @@ -10,7 +10,6 @@ features = [ "ax-driver/virtio-socket", "starry-kernel/input", "starry-kernel/vsock", - "starryos/buddy-slab", ] max_cpu_num = 4 log = "Warn" From 97348e1d045515e260749227daee1d109c274899 Mon Sep 17 00:00:00 2001 From: 54dK3n Date: Sun, 31 May 2026 21:03:41 +1000 Subject: [PATCH 71/71] feat(starry-kernel): x86_64 ptrace register r/w and PTRACE_SINGLESTEP support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐 x86_64 ptrace 主干功能: ## Kernel changes (os/StarryOS/kernel) ### ptrace.rs: x86_64 register read/write + singlestep - 新增 `X8664UserRegs` 结构体 (repr(C)),对应 Linux `user_regs_struct`,含 r15..rsp/ss/fs_base/gs_base 全部 27 个字段 - 实现 `From for X8664UserRegs` 和 `write_to()` - 补齐 x86_64 上之前缺失的 ptrace opcode: GETREGS / SETREGS / GETREGSET(NT_PRSTATUS) / SETREGSET(NT_PRSTATUS) / GETSIGINFO / SETSIGINFO - 将 `ptrace_getregset_prstatus` 中 `regs` 局部变量从内部 block 提升到 函数级作用域,修复 reg_bytes slice 的 use-after-free bug(regs 出 block 后被栈复用覆盖,导致用户态读到随机值,表现为 RSP=0x1) - 新增 `ptrace_setup_singlestep` for x86_64:设置 RFLAGS 的 TF (bit 8) 触发 CPU 单步执行 ### user.rs: SINGLESTEP + #DB 异常路由 - 为 x86_64 添加 singlestep setup 调用(之前仅 riscv64) - 新增 `ExceptionKind::Debug` 处理分支:命中 traced task 的 #DB 异常时,清除 TF 并调用 ptrace_stop_current(SIGTRAP) - Intel SDM 规定 CPU 在交付 TF 诱导的 #DB 时会清除 pushed RFLAGS 中的 TF,但 QEMU 不完全遵守,故显式清除防止 resume 后再次单步 ## Test cases (test-suit/starryos/normal/qemu-smp1) | 测试 | 验证内容 | |------|----------| | test-ptrace-x86-regs | TRACEME -> GETREGS/GETREGSET/SETREGS/SETREGSET 闭环 | | test-ptrace-exec-stop | TRACEME + execve -> SIGTRAP + DETACH | | test-ptrace-x86-breakpoint | 双测试:software int3 最小闭环 + SINGLESTEP 基础验证 | | test-ptrace-x86-singlestep | PTRACE_SINGLESTEP 执行一条指令后以 SIGTRAP stop | | test-ptrace-x86-breakpoint-reinsert | gdb 风格断点恢复:rewind RIP -> 恢复原字节 -> SINGLESTEP -> 重新插入断点 -> 再次命中 | | test-gdb-native-batch | gdb -q -batch 端到端:break native_marker / run / bt / info registers / continue | 所有 6 个 x86_64 ptrace case 全部通过。 --- os/StarryOS/kernel/src/syscall/task/ptrace.rs | 249 +++++++++-- os/StarryOS/kernel/src/task/user.rs | 20 + .../test-gdb-native-batch/c/CMakeLists.txt | 13 + .../c/gdb-native-batch.gdb | 10 + .../c/run-gdb-native-batch.sh | 4 + .../test-gdb-native-batch/c/src/main.c | 13 + .../test-gdb-native-batch/qemu-x86_64.toml | 23 ++ .../test-ptrace-exec-stop/qemu-x86_64.toml | 14 + .../c/CMakeLists.txt | 11 + .../c/src/main.c | 264 ++++++++++++ .../qemu-x86_64.toml | 14 + .../c/CMakeLists.txt | 5 + .../test-ptrace-x86-breakpoint/c/src/main.c | 389 ++++++++++++++++++ .../qemu-x86_64.toml | 14 + .../test-ptrace-x86-regs/c/CMakeLists.txt | 15 + .../test-ptrace-x86-regs/c/src/main.c | 269 ++++++++++++ .../test-ptrace-x86-regs/qemu-x86_64.toml | 14 + .../c/CMakeLists.txt | 11 + .../test-ptrace-x86-singlestep/c/src/main.c | 223 ++++++++++ .../qemu-x86_64.toml | 14 + 20 files changed, 1562 insertions(+), 27 deletions(-) create mode 100644 test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/gdb-native-batch.gdb create mode 100644 test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/run-gdb-native-batch.sh create mode 100644 test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/qemu-x86_64.toml create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/CMakeLists.txt create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/src/main.c create mode 100644 test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/qemu-x86_64.toml diff --git a/os/StarryOS/kernel/src/syscall/task/ptrace.rs b/os/StarryOS/kernel/src/syscall/task/ptrace.rs index ca3903eb72..ad356c48a5 100644 --- a/os/StarryOS/kernel/src/syscall/task/ptrace.rs +++ b/os/StarryOS/kernel/src/syscall/task/ptrace.rs @@ -1,6 +1,6 @@ use alloc::{sync::Arc, vec, vec::Vec}; use core::mem::{MaybeUninit, size_of}; -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", target_arch = "x86_64"))] use core::slice; use ax_errno::{AxError, AxResult, LinuxError}; @@ -108,6 +108,40 @@ struct RiscvFpRegs { fcsr: usize, } +/// Linux `user_regs_struct` for x86_64 (`arch/x86/include/uapi/asm/user.h`). +#[cfg(target_arch = "x86_64")] +#[repr(C)] +#[derive(Clone, Copy)] +struct X8664UserRegs { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + rbp: u64, + rbx: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rax: u64, + rcx: u64, + rdx: u64, + rsi: u64, + rdi: u64, + orig_rax: u64, + rip: u64, + cs: u64, + eflags: u64, + rsp: u64, + ss: u64, + fs_base: u64, + gs_base: u64, + ds: u64, + es: u64, + fs: u64, + gs: u64, +} + pub fn sys_ptrace(request: u32, pid: usize, addr: usize, data: usize) -> AxResult { info!("sys_ptrace <= request: {request}, pid: {pid}, addr: {addr:#x}, data: {data:#x}"); @@ -268,7 +302,7 @@ fn ptrace_getsiginfo(pid: usize, data: usize) -> AxResult { .ptrace_stop_siginfo() .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; - #[cfg(target_arch = "riscv64")] + #[cfg(any(target_arch = "riscv64", target_arch = "x86_64"))] { let bytes = unsafe { slice::from_raw_parts( @@ -280,14 +314,14 @@ fn ptrace_getsiginfo(pid: usize, data: usize) -> AxResult { Ok(0) } - #[cfg(not(target_arch = "riscv64"))] + #[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] { let _ = (data, siginfo); Err(AxError::Unsupported) } } -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", target_arch = "x86_64"))] fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); @@ -301,7 +335,7 @@ fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { Ok(0) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] fn ptrace_setsiginfo(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) @@ -341,7 +375,23 @@ fn ptrace_getregs(pid: usize, data: usize) -> AxResult { Ok(0) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(target_arch = "x86_64")] +fn ptrace_getregs(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_stopped_user_regs_x86_64(pid)?; + let bytes = unsafe { + slice::from_raw_parts( + (®s as *const X8664UserRegs).cast::(), + size_of::(), + ) + }; + vm_write_slice(data as *mut u8, bytes)?; + Ok(0) +} + +#[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] fn ptrace_getregs(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) @@ -356,7 +406,16 @@ fn ptrace_setregs(pid: usize, data: usize) -> AxResult { ptrace_write_stopped_user_regs(pid, regs) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(target_arch = "x86_64")] +fn ptrace_setregs(pid: usize, data: usize) -> AxResult { + if data == 0 { + return Err(AxError::InvalidInput); + } + let regs = ptrace_read_user_regs_x86_64_from_user(data)?; + ptrace_write_stopped_user_regs_x86_64(pid, regs) +} + +#[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] fn ptrace_setregs(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) @@ -435,48 +494,80 @@ fn ptrace_interrupt(pid: usize) -> AxResult { Ok(0) } -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", target_arch = "x86_64"))] fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); } + #[cfg(target_arch = "riscv64")] let regs = ptrace_read_stopped_user_regs(pid)?; - let mut iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < 0 { - return Err(AxError::InvalidInput); - } - - let bytes = unsafe { + #[cfg(target_arch = "riscv64")] + let reg_bytes = unsafe { slice::from_raw_parts( (®s as *const RiscvUserRegs).cast::(), size_of::(), ) }; - let copy_len = (iov.iov_len as usize).min(bytes.len()); - vm_write_slice(iov.iov_base, &bytes[..copy_len])?; + #[cfg(target_arch = "x86_64")] + let regs = ptrace_read_stopped_user_regs_x86_64(pid)?; + #[cfg(target_arch = "x86_64")] + let reg_bytes = unsafe { + slice::from_raw_parts( + (®s as *const X8664UserRegs).cast::(), + size_of::(), + ) + }; + + let mut iov = (data as *const IoVec).vm_read()?; + if iov.iov_len < 0 { + return Err(AxError::InvalidInput); + } + + let copy_len = (iov.iov_len as usize).min(reg_bytes.len()); + vm_write_slice(iov.iov_base, ®_bytes[..copy_len])?; iov.iov_len = copy_len as isize; (data as *mut IoVec).vm_write(iov)?; Ok(0) } -#[cfg(not(target_arch = "riscv64"))] +#[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] fn ptrace_getregset_prstatus(pid: usize, data: usize) -> AxResult { let _ = (pid, data); Err(AxError::Unsupported) } -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", target_arch = "x86_64"))] fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult { if data == 0 { return Err(AxError::InvalidInput); } + + #[cfg(target_arch = "riscv64")] + let reg_size = size_of::() as isize; + #[cfg(target_arch = "x86_64")] + let reg_size = size_of::() as isize; + let iov = (data as *const IoVec).vm_read()?; - if iov.iov_len < size_of::() as isize { + if iov.iov_len < reg_size { return Err(AxError::InvalidInput); } - let regs = ptrace_read_user_regs(iov.iov_base as usize)?; - ptrace_write_stopped_user_regs(pid, regs) + #[cfg(target_arch = "riscv64")] + { + let regs = ptrace_read_user_regs(iov.iov_base as usize)?; + ptrace_write_stopped_user_regs(pid, regs) + } + #[cfg(target_arch = "x86_64")] + { + let regs = ptrace_read_user_regs_x86_64_from_user(iov.iov_base as usize)?; + ptrace_write_stopped_user_regs_x86_64(pid, regs) + } +} + +#[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] +fn ptrace_setregset_prstatus(pid: usize, data: usize) -> AxResult { + let _ = (pid, data); + Err(AxError::Unsupported) } #[cfg(target_arch = "riscv64")] @@ -514,10 +605,39 @@ fn ptrace_write_stopped_user_regs(pid: usize, regs: RiscvUserRegs) -> AxResult AxResult { - let _ = (pid, data); - Err(AxError::Unsupported) +#[cfg(target_arch = "x86_64")] +fn ptrace_read_stopped_user_regs_x86_64(pid: usize) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let uctx = tracee + .ptrace_stop_user_context() + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + Ok(X8664UserRegs::from(&uctx)) +} + +#[cfg(target_arch = "x86_64")] +fn ptrace_read_user_regs_x86_64_from_user(data: usize) -> AxResult { + let mut regs = MaybeUninit::::uninit(); + let bytes = unsafe { + slice::from_raw_parts_mut( + regs.as_mut_ptr().cast::>(), + size_of::(), + ) + }; + starry_vm::vm_read_slice(data as *const u8, bytes)?; + Ok(unsafe { regs.assume_init() }) +} + +#[cfg(target_arch = "x86_64")] +fn ptrace_write_stopped_user_regs_x86_64(pid: usize, regs: X8664UserRegs) -> AxResult { + let tracee = ptrace_stopped_tracee(pid)?; + let mut uctx = tracee + .ptrace_stop_user_context() + .ok_or_else(|| AxError::from(LinuxError::ESRCH))?; + regs.write_to(&mut uctx); + if !tracee.set_ptrace_stop_user_context(uctx) { + return Err(AxError::from(LinuxError::ESRCH)); + } + Ok(0) } #[cfg(target_arch = "riscv64")] @@ -580,7 +700,7 @@ fn ptrace_read_user_fpregs(data: usize) -> AxResult { Ok(unsafe { regs.assume_init() }) } -#[cfg(target_arch = "riscv64")] +#[cfg(any(target_arch = "riscv64", target_arch = "x86_64"))] fn ptrace_read_user_siginfo(data: usize) -> AxResult { let mut siginfo = MaybeUninit::::uninit(); let bytes = unsafe { @@ -593,7 +713,7 @@ fn ptrace_read_user_siginfo(data: usize) -> AxResult AxResult { let signo = unsafe { siginfo.__bindgen_anon_1.__bindgen_anon_1.si_signo }; Signo::from_repr(signo as u8).ok_or(AxError::InvalidInput) @@ -822,6 +942,18 @@ fn ptrace_stopped_tracee(pid: usize) -> AxResult> { Ok(tracee) } +#[cfg(target_arch = "x86_64")] +pub fn ptrace_setup_singlestep( + _tracee: &ProcessData, + uctx: &mut ax_runtime::hal::cpu::uspace::UserContext, +) { + // Set Trap Flag (TF, bit 8) in RFLAGS. + // The CPU will generate a #DB debug exception after executing one + // instruction and will automatically clear TF before pushing RFLAGS + // onto the exception stack frame (Intel SDM Vol 3A §17.3.2). + uctx.rflags |= 1 << 8; +} + #[cfg(target_arch = "riscv64")] pub fn ptrace_setup_singlestep( tracee: &ProcessData, @@ -1226,3 +1358,66 @@ impl RiscvUserRegs { r.t6 = self.t6; } } + +#[cfg(target_arch = "x86_64")] +impl From<&ax_runtime::hal::cpu::uspace::UserContext> for X8664UserRegs { + fn from(uctx: &ax_runtime::hal::cpu::uspace::UserContext) -> Self { + Self { + r15: uctx.r15, + r14: uctx.r14, + r13: uctx.r13, + r12: uctx.r12, + rbp: uctx.rbp, + rbx: uctx.rbx, + r11: uctx.r11, + r10: uctx.r10, + r9: uctx.r9, + r8: uctx.r8, + rax: uctx.rax, + rcx: uctx.rcx, + rdx: uctx.rdx, + rsi: uctx.rsi, + rdi: uctx.rdi, + orig_rax: u64::MAX, + rip: uctx.rip, + cs: uctx.cs, + eflags: uctx.rflags, + rsp: uctx.rsp, + ss: uctx.ss, + fs_base: uctx.fs_base, + gs_base: uctx.gs_base, + ds: 0, + es: 0, + fs: 0, + gs: 0, + } + } +} + +#[cfg(target_arch = "x86_64")] +impl X8664UserRegs { + fn write_to(&self, uctx: &mut ax_runtime::hal::cpu::uspace::UserContext) { + uctx.r15 = self.r15; + uctx.r14 = self.r14; + uctx.r13 = self.r13; + uctx.r12 = self.r12; + uctx.rbp = self.rbp; + uctx.rbx = self.rbx; + uctx.r11 = self.r11; + uctx.r10 = self.r10; + uctx.r9 = self.r9; + uctx.r8 = self.r8; + uctx.rax = self.rax; + uctx.rcx = self.rcx; + uctx.rdx = self.rdx; + uctx.rsi = self.rsi; + uctx.rdi = self.rdi; + uctx.rip = self.rip; + uctx.cs = self.cs; + uctx.rflags = self.eflags; + uctx.rsp = self.rsp; + uctx.ss = self.ss; + uctx.fs_base = self.fs_base; + uctx.gs_base = self.gs_base; + } +} diff --git a/os/StarryOS/kernel/src/task/user.rs b/os/StarryOS/kernel/src/task/user.rs index 423ce48ad4..928df6b6e5 100644 --- a/os/StarryOS/kernel/src/task/user.rs +++ b/os/StarryOS/kernel/src/task/user.rs @@ -34,6 +34,8 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> { #[cfg(target_arch = "riscv64")] crate::syscall::ptrace_setup_singlestep(&thr.proc_data, &mut uctx); + #[cfg(target_arch = "x86_64")] + crate::syscall::ptrace_setup_singlestep(&thr.proc_data, &mut uctx); } let reason = uctx.run(); @@ -123,6 +125,24 @@ pub fn new_user_task(name: &str, mut uctx: UserContext, set_child_tid: usize) -> break 'exc; } } + // On x86_64, PTRACE_SINGLESTEP sets TF in RFLAGS; + // the resulting #DB exception arrives here. + if matches!(kind, ExceptionKind::Debug) + && (thr.proc_data.is_ptrace_traceme() + || thr.proc_data.is_ptrace_attached()) + { + // Clear TF (bit 8) in the saved RFLAGS. The Intel + // SDM (Vol 3A §17.3.2) states the CPU clears TF + // when delivering a TF-induced #DB, but QEMU may + // not always honour this. Clearing explicitly + // prevents an unwanted extra single-step on resume. + uctx.rflags &= !(1u64 << 8); + if let Some(_resume_sig) = + ptrace_stop_current(thr, Signo::SIGTRAP, &mut uctx) + { + break 'exc; + } + } warn!( "user exception: ip={:#x}, fault_addr={:#x}, kind={:?}, esr={:#x}, \ ec={:#x}, iss={:#x}, info={:?}", diff --git a/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/CMakeLists.txt new file mode 100644 index 0000000000..3cac231efc --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.20) +project(test-gdb-native-batch C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-gdb-native-batch-target src/main.c) +target_compile_options(test-gdb-native-batch-target PRIVATE -Wall -Wextra -Werror -O0 -g) + +install(TARGETS test-gdb-native-batch-target RUNTIME DESTINATION usr/bin) +install(FILES gdb-native-batch.gdb DESTINATION usr/bin) +install(PROGRAMS run-gdb-native-batch.sh DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/gdb-native-batch.gdb b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/gdb-native-batch.gdb new file mode 100644 index 0000000000..57dcfb28e9 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/gdb-native-batch.gdb @@ -0,0 +1,10 @@ +set pagination off +set confirm off +set debuginfod enabled off +break native_marker +run +bt +info registers +continue +echo GDB_NATIVE_BATCH_DONE\n +quit diff --git a/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/run-gdb-native-batch.sh b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/run-gdb-native-batch.sh new file mode 100644 index 0000000000..33124ace86 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/run-gdb-native-batch.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +/usr/bin/gdb -q -batch -x /usr/bin/gdb-native-batch.gdb /usr/bin/test-gdb-native-batch-target diff --git a/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/src/main.c new file mode 100644 index 0000000000..5f0806e1bf --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/c/src/main.c @@ -0,0 +1,13 @@ +#include + +__attribute__((noinline)) static int native_marker(int value) +{ + return value + 1; +} + +int main(void) +{ + int value = native_marker(41); + printf("gdb-native-batch-target value=%d\n", value); + return value == 42 ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/qemu-x86_64.toml new file mode 100644 index 0000000000..4b5ae0d6f5 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-gdb-native-batch/qemu-x86_64.toml @@ -0,0 +1,23 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "apk add gdb && /usr/bin/run-gdb-native-batch.sh" +success_regex = ["GDB_NATIVE_BATCH_DONE"] +fail_regex = [ + '(?i)\\bpanic(?:ked)?\\b', + '(?m)FAIL', + 'gdb: not found', + 'Python Exception', + 'Python initialization failed', + 'Cannot insert breakpoint', + 'ptrace: Function not implemented', + 'No stack', +] +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-x86_64.toml new file mode 100644 index 0000000000..517bdbf7e2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-exec-stop/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-exec-stop" +success_regex = ["DONE: 3 pass, 0 fail"] +fail_regex = ['(?i)\\bpanic(?:ked)?\\b', '(?m)FAIL'] +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/CMakeLists.txt new file mode 100644 index 0000000000..52983e2687 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ptrace-x86-breakpoint-reinsert C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-ptrace-x86-breakpoint-reinsert src/main.c) +target_compile_options(test-ptrace-x86-breakpoint-reinsert PRIVATE -Wall -Wextra -Werror -O0 -g) + +install(TARGETS test-ptrace-x86-breakpoint-reinsert RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/src/main.c new file mode 100644 index 0000000000..9b560f5cda --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/c/src/main.c @@ -0,0 +1,264 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef PTRACE_SETREGSET +#define PTRACE_SETREGSET 0x4205 +#endif +#ifndef PTRACE_TRACEME +#define PTRACE_TRACEME 0 +#endif +#ifndef PTRACE_SINGLESTEP +#define PTRACE_SINGLESTEP 9 +#endif +#ifndef PTRACE_CONT +#define PTRACE_CONT 7 +#endif +#ifndef PTRACE_PEEKDATA +#define PTRACE_PEEKDATA 2 +#endif +#ifndef PTRACE_POKEDATA +#define PTRACE_POKEDATA 5 +#endif +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif + +struct x86_64_user_regs { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t orig_rax; + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + uint64_t fs_base; + uint64_t gs_base; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +}; + +__attribute__((noinline)) static int native_marker(int value) +{ + return value + 1; +} + +static int fail(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +static long ptrace_call(int request, pid_t pid, void *addr, void *data) +{ +#ifdef __APPLE__ + return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); +#else + return ptrace(request, pid, addr, data); +#endif +} + +static int wait_stopped(pid_t pid, int *status, int expected_sig) +{ + if (waitpid(pid, status, WUNTRACED) != pid) { + return fail("waitpid"); + } + if (!WIFSTOPPED(*status) || WSTOPSIG(*status) != expected_sig) { + printf("FAIL: expected stop signal %d, status=%#x\n", expected_sig, *status); + return 1; + } + return 0; +} + +static int getregset(pid_t pid, struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; + if (ptrace_call(PTRACE_GETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { + return -1; + } + if ((size_t)iov.iov_len != sizeof(*regs)) { + errno = EIO; + return -1; + } + return 0; +} + +static int setregset(pid_t pid, const struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; + if (ptrace_call(PTRACE_SETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { + return -1; + } + return 0; +} + +static int test_breakpoint_reinsert(void) +{ + printf("test 1: x86_64 breakpoint restore + singlestep + reinsert\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + int value = native_marker(40); + value = native_marker(value); + _exit(value == 42 ? 42 : 1); + } + + int status = 0; + if (wait_stopped(pid, &status, SIGSTOP) != 0) { + return 1; + } + printf(" ok: child stopped with SIGSTOP\n"); + + uintptr_t bp_addr = (uintptr_t)native_marker; + errno = 0; + long orig_word = ptrace_call(PTRACE_PEEKDATA, pid, (void *)bp_addr, NULL); + if (orig_word == -1 && errno != 0) { + return fail("PTRACE_PEEKDATA original instruction"); + } + unsigned long bp_word = ((unsigned long)orig_word & ~0xffUL) | 0xccUL; + if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)bp_word) != 0) { + return fail("PTRACE_POKEDATA install breakpoint"); + } + printf(" ok: installed int3 at native_marker\n"); + + if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("PTRACE_CONT first call"); + } + if (wait_stopped(pid, &status, SIGTRAP) != 0) { + return 1; + } + printf(" ok: first breakpoint hit\n"); + + struct x86_64_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset at first breakpoint"); + } + if (regs.rip != bp_addr + 1 && regs.rip != bp_addr) { + printf("FAIL: RIP=%#llx not at breakpoint %#llx\n", + (unsigned long long)regs.rip, (unsigned long long)bp_addr); + return 1; + } + + if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)orig_word) != 0) { + return fail("PTRACE_POKEDATA restore original instruction"); + } + regs.rip = bp_addr; + if (setregset(pid, ®s) != 0) { + return fail("setregset rewind RIP to breakpoint"); + } + printf(" ok: restored original byte and rewound RIP\n"); + + if (ptrace_call(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { + return fail("PTRACE_SINGLESTEP over original instruction"); + } + if (wait_stopped(pid, &status, SIGTRAP) != 0) { + return 1; + } + printf(" ok: single-step completed original instruction\n"); + + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset after singlestep"); + } + if (regs.rip == bp_addr) { + printf("FAIL: RIP did not advance after single-step: %#llx\n", + (unsigned long long)regs.rip); + return 1; + } + + if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)bp_word) != 0) { + return fail("PTRACE_POKEDATA reinsert breakpoint"); + } + printf(" ok: reinserted breakpoint at native_marker\n"); + + if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("PTRACE_CONT second call"); + } + if (wait_stopped(pid, &status, SIGTRAP) != 0) { + return 1; + } + printf(" ok: second breakpoint hit after reinsertion\n"); + + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset at second breakpoint"); + } + if (regs.rip != bp_addr + 1 && regs.rip != bp_addr) { + printf("FAIL: second-hit RIP=%#llx not at breakpoint %#llx\n", + (unsigned long long)regs.rip, (unsigned long long)bp_addr); + return 1; + } + + if (ptrace_call(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)orig_word) != 0) { + return fail("PTRACE_POKEDATA restore after second hit"); + } + regs.rip = bp_addr; + if (setregset(pid, ®s) != 0) { + return fail("setregset rewind RIP after second hit"); + } + + if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("PTRACE_CONT final exit"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid final exit"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { + printf("FAIL: expected exit 42, status=%#x\n", status); + return 1; + } + printf(" ok: child exited with 42 after breakpoint reinsertion flow\n"); + return 0; +} + +int main(void) +{ + int pass = 0; + int fail_count = 0; + + if (test_breakpoint_reinsert() == 0) { + pass++; + } else { + fail_count++; + } + + printf("DONE: %d pass, %d fail\n", pass, fail_count); + return fail_count > 0 ? 1 : 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/qemu-x86_64.toml new file mode 100644 index 0000000000..3320ce3293 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint-reinsert/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-x86-breakpoint-reinsert" +success_regex = ["DONE: 1 pass, 0 fail"] +fail_regex = ['(?i)\\bpanic(?:ked)?\\b', '(?m)FAIL'] +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/CMakeLists.txt new file mode 100644 index 0000000000..4b8c403ecd --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) +project(test-ptrace-x86-breakpoint C) +set(CMAKE_C_STANDARD 11) +add_executable(test-ptrace-x86-breakpoint src/main.c) +install(TARGETS test-ptrace-x86-breakpoint RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/src/main.c new file mode 100644 index 0000000000..39aad2ad8e --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/c/src/main.c @@ -0,0 +1,389 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef PTRACE_SETREGSET +#define PTRACE_SETREGSET 0x4205 +#endif +#ifndef PTRACE_SINGLESTEP +#define PTRACE_SINGLESTEP 9 +#endif +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif + +struct x86_64_user_regs { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t orig_rax; + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + uint64_t fs_base; + uint64_t gs_base; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +}; + +__attribute__((noinline)) static int target_function(void) +{ + return 42; +} + + +static int fail(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +static int getregset(pid_t pid, struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; + if (ptrace(PTRACE_GETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { + return -1; + } + if ((size_t)iov.iov_len != sizeof(*regs)) { + errno = EIO; + return -1; + } + return 0; +} + +static int setregset(pid_t pid, const struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; + if (ptrace(PTRACE_SETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { + return -1; + } + return 0; +} + +/* Test 1: minimum closed-loop software breakpoint. + * + * tracer writes 0xCC; child hits breakpoint; tracer restores original + * byte and continues from the pre-int3 address. This validates the + * basic breakpoint stop and register read-back but does NOT exercise + * PTRACE_SINGLESTEP. */ +static int test_breakpoint(void) +{ + printf("test 1: x86_64 software breakpoint (int3)\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + /* Trigger initial ptrace-stop via int3 instead of raise(SIGSTOP). + * The #BP exception path preserves the CPU exception frame with + * valid segment selectors (CS/SS) and stack pointer. */ + __asm__ volatile ("int3" ::: "memory"); + target_function(); + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid) { + return fail("waitpid initial stop"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf(" note: unexpected initial stop signal=%d status=%#x\n", + WSTOPSIG(status), status); + } + printf(" ok: child stopped (signal=%d)\n", WSTOPSIG(status)); + + struct x86_64_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset at initial stop"); + } + printf(" ok: initial RIP=%#llx RSP=%#llx\n", + (unsigned long long)regs.rip, (unsigned long long)regs.rsp); + + if (regs.rsp == 0 || regs.rsp == 1) { + printf("FAIL: bogus RSP=%#llx (expected valid stack pointer)\n", + (unsigned long long)regs.rsp); + return 1; + } + + /* x86 int3 is 1 byte (0xCC). CPU pushes RIP of next instruction, so + * the saved RIP in the exception frame points to int3_addr + 1. + * StarryOS does not adjust RIP backward for ptrace breakpoint stops, + * so we observe RIP = int3 + 1 here. Save the pre-int3 RIP as the + * "continue point" for later. */ + uintptr_t continue_rip = regs.rip; + + uintptr_t bp_addr = (uintptr_t)target_function; + printf(" target_function at %p\n", (void *)bp_addr); + + errno = 0; + long orig_word = ptrace(PTRACE_PEEKDATA, pid, (void *)bp_addr, NULL); + if (orig_word == -1 && errno != 0) { + return fail("PEEKDATA target_function"); + } + + unsigned long bp_word = ((unsigned long)orig_word & ~0xffUL) | 0xccUL; + if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)bp_word) != 0) { + return fail("POKEDATA write int3"); + } + printf(" ok: wrote int3 (0xCC) at target_function\n"); + + regs.rip = bp_addr; + if (setregset(pid, ®s) != 0) { + return fail("setregset redirect RIP to target_function"); + } + printf(" ok: redirected RIP=%#llx -> %#llx\n", + (unsigned long long)continue_rip, (unsigned long long)bp_addr); + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("PTRACE_CONT to breakpoint"); + } + + if (waitpid(pid, &status, WUNTRACED) != pid) { + return fail("waitpid breakpoint"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected SIGTRAP after breakpoint, status=%#x\n", status); + return 1; + } + printf(" ok: child stopped with SIGTRAP\n"); + + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset at breakpoint"); + } + + /* After int3, RIP = bp_addr + 1. Verify it's near the breakpoint. */ + if (regs.rip != bp_addr && regs.rip != bp_addr + 1) { + printf("FAIL: RIP=%#llx not near breakpoint addr %#llx\n", + (unsigned long long)regs.rip, (unsigned long long)bp_addr); + return 1; + } + printf(" ok: RIP=%#llx at breakpoint (offset +%lld)\n", + (unsigned long long)regs.rip, (unsigned long long)bp_addr, + (long long)(regs.rip - bp_addr)); + + if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)orig_word) != 0) { + return fail("POKEDATA restore original byte"); + } + printf(" ok: restored original instruction byte\n"); + + /* Rewind RIP to the original continue point (after the initial int3). + * From there the child will call target_function() normally via the + * call instruction, then _exit(42). */ + regs.rip = continue_rip; + if (setregset(pid, ®s) != 0) { + return fail("setregset rewind RIP"); + } + printf(" ok: rewound RIP to continue point %#llx\n", + (unsigned long long)continue_rip); + + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("PTRACE_CONT after breakpoint restore"); + } + + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid exit after restore"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { + printf("FAIL: expected exit code 42, status=%#x\n", status); + return 1; + } + printf(" ok: child exited with 42\n"); + + return 0; +} + +/* Test 2: x86_64 PTRACE_SINGLESTEP validation. + * + * Uses the initial `int3` in the child as the breakpoint target: + * + * 1. child hits initial int3, stops with SIGTRAP + * 2. tracer replaces int3 (0xCC) with NOP (0x90) at that address + * 3. tracer rewinds RIP to the int3 address + * 4. tracer issues PTRACE_SINGLESTEP + * 5. child executes the NOP and stops with SIGTRAP (#DB) + * 6. tracer verifies RIP advanced past the int3 site + * 7. tracer restores int3, then CONT + * 8. child continues to completion (target_function → _exit(42)) + * + * This validates the x86_64 Trap-flag-based single-step mechanism. */ +static int test_breakpoint_singlestep_restore(void) +{ + printf("test 2: x86_64 PTRACE_SINGLESTEP\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + /* Initial stop via int3 — also serves as the breakpoint + * the tracer will single-step over. */ + __asm__ volatile ( + ".global test_bp_addr\n" + "test_bp_addr:\n" + " int3\n" + ::: "memory" + ); + target_function(); + _exit(42); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid) { + return fail("waitpid initial int3 stop"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected SIGTRAP, status=%#x\n", status); + return 1; + } + printf(" ok: initial int3 SIGTRAP stop\n"); + + struct x86_64_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset at initial stop"); + } + + extern char test_bp_addr; + uintptr_t bp_addr = (uintptr_t)&test_bp_addr; + /* int3 is 1 byte; after #BP the saved RIP = bp_addr + 1. */ + if (regs.rip != bp_addr + 1) { + printf("FAIL: expected RIP=%#llx (bp+1), got %#llx\n", + (unsigned long long)(bp_addr + 1), (unsigned long long)regs.rip); + return 1; + } + printf(" ok: RIP=%#llx (bp+1)\n", (unsigned long long)regs.rip); + + /* Step 1: replace int3 (0xCC) with NOP (0x90) at the breakpoint site, + * preserving the surrounding bytes (POKEDATA is word-sized). */ + errno = 0; + long orig_word = ptrace(PTRACE_PEEKDATA, pid, (void *)bp_addr, NULL); + if (orig_word == -1 && errno != 0) { + return fail("PEEKDATA at bp"); + } + unsigned long nop_word = ((unsigned long)orig_word & ~0xffUL) | 0x90UL; + if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)nop_word) != 0) { + return fail("POKEDATA write NOP"); + } + printf(" ok: replaced int3 with NOP at %#llx\n", (unsigned long long)bp_addr); + + /* Step 2: rewind RIP to the int3/NOP address. */ + regs.rip = bp_addr; + if (setregset(pid, ®s) != 0) { + return fail("setregset rewind RIP"); + } + + /* Step 3: PTRACE_SINGLESTEP — the CPU executes the NOP (1 byte) + * and then fires #DB because TF was set in RFLAGS. */ + if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { + return fail("PTRACE_SINGLESTEP"); + } + printf(" ok: PTRACE_SINGLESTEP issued\n"); + + /* Step 4: wait for the #DB stop. */ + if (waitpid(pid, &status, WUNTRACED) != pid) { + return fail("waitpid singlestep stop"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP) { + printf("FAIL: expected SIGTRAP after singlestep, status=%#x signal=%d\n", + status, WSTOPSIG(status)); + return 1; + } + printf(" ok: singlestep SIGTRAP stop\n"); + + /* Step 5: verify RIP advanced past the breakpoint (NOP is 1 byte). */ + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset after singlestep"); + } + if (regs.rip != bp_addr + 1) { + printf("FAIL: RIP=%#llx, expected %#llx (bp+1)\n", + (unsigned long long)regs.rip, (unsigned long long)(bp_addr + 1)); + return 1; + } + printf(" ok: RIP=%#llx advanced past breakpoint (offset +1)\n", + (unsigned long long)regs.rip); + + /* Step 6: restore int3 (for correctness; doesn't affect the test + * since execution has already passed this point). */ + unsigned long cc_word = ((unsigned long)orig_word & ~0xffUL) | 0xccUL; + if (ptrace(PTRACE_POKEDATA, pid, (void *)bp_addr, (void *)cc_word) != 0) { + return fail("POKEDATA re-insert int3"); + } + + /* Step 7: CONT — child continues through target_function → _exit(42). */ + if (ptrace(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("PTRACE_CONT"); + } + + /* Step 8: child exits normally. */ + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid exit"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { + printf("FAIL: expected exit code 42, status=%#x\n", status); + return 1; + } + printf(" ok: child exited with 42\n"); + + return 0; +} + +int main(void) +{ + int pass = 0; + int fail_count = 0; + + if (test_breakpoint() == 0) { + pass++; + } else { + fail_count++; + } + + if (test_breakpoint_singlestep_restore() == 0) { + pass++; + } else { + fail_count++; + } + + printf("DONE: %d pass, %d fail\n", pass, fail_count); + return fail_count > 0 ? 1 : 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/qemu-x86_64.toml new file mode 100644 index 0000000000..90b4ff17d2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-breakpoint/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-x86-breakpoint" +success_regex = ["DONE: 2 pass, 0 fail"] +fail_regex = ['(?i)\\bpanic(?:ked)?\\b', '(?m)FAIL'] +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/CMakeLists.txt new file mode 100644 index 0000000000..365b18d693 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ptrace-x86-regs C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-ptrace-x86-regs src/main.c) +target_compile_options(test-ptrace-x86-regs PRIVATE + -Wall + -Wextra + -Werror +) + +install(TARGETS test-ptrace-x86-regs RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/src/main.c new file mode 100644 index 0000000000..a35cb84857 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/c/src/main.c @@ -0,0 +1,269 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PTRACE_GETREGS +#define PTRACE_GETREGS 12 +#endif +#ifndef PTRACE_TRACEME +#define PTRACE_TRACEME 0 +#endif +#ifndef PTRACE_SETREGS +#define PTRACE_SETREGS 13 +#endif +#ifndef PTRACE_CONT +#define PTRACE_CONT 7 +#endif +#ifndef PTRACE_GETSIGINFO +#define PTRACE_GETSIGINFO 0x4202 +#endif +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef PTRACE_SETREGSET +#define PTRACE_SETREGSET 0x4205 +#endif +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif + +struct x86_64_user_regs { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t orig_rax; + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + uint64_t fs_base; + uint64_t gs_base; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +}; + +static long ptrace_call(int request, pid_t pid, void *addr, void *data) +{ +#ifdef __APPLE__ + return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); +#else + return ptrace(request, pid, addr, data); +#endif +} + +static int fail_errno(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +static int fail_msg(const char *msg) +{ + printf("FAIL: %s\n", msg); + return 1; +} + +static int getregs(pid_t pid, struct x86_64_user_regs *regs) +{ + if (ptrace_call(PTRACE_GETREGS, pid, NULL, regs) != 0) { + return -1; + } + return 0; +} + +static int setregs(pid_t pid, const struct x86_64_user_regs *regs) +{ + if (ptrace_call(PTRACE_SETREGS, pid, NULL, (void *)regs) != 0) { + return -1; + } + return 0; +} + +static int getregset(pid_t pid, struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; + if (ptrace_call(PTRACE_GETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return -1; + } + if (iov.iov_len != (long)sizeof(*regs)) { + errno = EIO; + return -1; + } + return 0; +} + +static int setregset(pid_t pid, const struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; + if (ptrace_call(PTRACE_SETREGSET, pid, (void *)NT_PRSTATUS, &iov) != 0) { + return -1; + } + return 0; +} + +static int test_ptrace_x86_regs(void) +{ + printf("test 1: x86_64 ptrace regs/siginfo\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail_errno("fork"); + } + + if (pid == 0) { + if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(0); + } + + int status = 0; + if (waitpid(pid, &status, WUNTRACED) != pid) { + return fail_errno("waitpid stop"); + } + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) { + printf("FAIL: expected SIGSTOP stop, status=%#x\n", status); + return 1; + } + printf(" ok: child stopped with SIGSTOP\n"); + + siginfo_t si; + memset(&si, 0, sizeof(si)); + if (ptrace_call(PTRACE_GETSIGINFO, pid, NULL, &si) != 0) { + return fail_errno("PTRACE_GETSIGINFO"); + } + if (si.si_signo != SIGSTOP) { + printf("FAIL: si_signo=%d, expected %d\n", si.si_signo, SIGSTOP); + return 1; + } + printf(" ok: GETSIGINFO reports SIGSTOP\n"); + + struct x86_64_user_regs regs_getregs; + memset(®s_getregs, 0, sizeof(regs_getregs)); + if (getregs(pid, ®s_getregs) != 0) { + return fail_errno("PTRACE_GETREGS"); + } + + struct x86_64_user_regs regs_getregset; + memset(®s_getregset, 0, sizeof(regs_getregset)); + if (getregset(pid, ®s_getregset) != 0) { + return fail_errno("PTRACE_GETREGSET"); + } + + if (regs_getregs.rip == 0 || regs_getregs.rsp == 0) { + return fail_msg("GETREGS returned zero RIP/RSP"); + } + if (regs_getregs.rip != regs_getregset.rip || + regs_getregs.rsp != regs_getregset.rsp || + regs_getregs.rax != regs_getregset.rax || + regs_getregs.r15 != regs_getregset.r15) { + printf("FAIL: GETREGS/GETREGSET mismatch: rip=%#llx/%#llx rsp=%#llx/%#llx rax=%#llx/%#llx r15=%#llx/%#llx\n", + (unsigned long long)regs_getregs.rip, + (unsigned long long)regs_getregset.rip, + (unsigned long long)regs_getregs.rsp, + (unsigned long long)regs_getregset.rsp, + (unsigned long long)regs_getregs.rax, + (unsigned long long)regs_getregset.rax, + (unsigned long long)regs_getregs.r15, + (unsigned long long)regs_getregset.r15); + return 1; + } + printf(" ok: GETREGS and GETREGSET agree on core registers\n"); + + struct x86_64_user_regs modified = regs_getregset; + modified.r15 ^= 0x13579bdfULL; + if (setregset(pid, &modified) != 0) { + return fail_errno("PTRACE_SETREGSET"); + } + + struct x86_64_user_regs check_after_setregset; + memset(&check_after_setregset, 0, sizeof(check_after_setregset)); + if (getregs(pid, &check_after_setregset) != 0) { + return fail_errno("GETREGS after SETREGSET"); + } + if (check_after_setregset.r15 != modified.r15) { + printf("FAIL: SETREGSET did not update r15 (%#llx != %#llx)\n", + (unsigned long long)check_after_setregset.r15, + (unsigned long long)modified.r15); + return 1; + } + printf(" ok: SETREGSET updated r15\n"); + + modified = check_after_setregset; + modified.r14 ^= 0x2468ace0ULL; + if (setregs(pid, &modified) != 0) { + return fail_errno("PTRACE_SETREGS"); + } + + struct x86_64_user_regs check_after_setregs; + memset(&check_after_setregs, 0, sizeof(check_after_setregs)); + if (getregset(pid, &check_after_setregs) != 0) { + return fail_errno("GETREGSET after SETREGS"); + } + if (check_after_setregs.r14 != modified.r14) { + printf("FAIL: SETREGS did not update r14 (%#llx != %#llx)\n", + (unsigned long long)check_after_setregs.r14, + (unsigned long long)modified.r14); + return 1; + } + printf(" ok: SETREGS updated r14\n"); + + if (setregs(pid, ®s_getregs) != 0) { + return fail_errno("restore original regs"); + } + + if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail_errno("PTRACE_CONT"); + } + + if (waitpid(pid, &status, 0) != pid) { + return fail_errno("waitpid exit"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("FAIL: expected child exit 0, status=%#x\n", status); + return 1; + } + printf(" ok: child continued and exited cleanly\n"); + return 0; +} + +int main(void) +{ + int pass = 0; + int fail = 0; + + if (test_ptrace_x86_regs() == 0) { + pass++; + } else { + fail++; + } + + printf("DONE: %d pass, %d fail\n", pass, fail); + return fail == 0 ? 0 : 1; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/qemu-x86_64.toml new file mode 100644 index 0000000000..76573850a6 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-regs/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-x86-regs" +success_regex = ["DONE: 1 pass, 0 fail"] +fail_regex = ['(?i)\\bpanic(?:ked)?\\b', '(?m)FAIL'] +timeout = 120 diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/CMakeLists.txt b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/CMakeLists.txt new file mode 100644 index 0000000000..cdfdc154b4 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.20) +project(test-ptrace-x86-singlestep C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +add_executable(test-ptrace-x86-singlestep src/main.c) +target_compile_options(test-ptrace-x86-singlestep PRIVATE -Wall -Wextra -Werror) + +install(TARGETS test-ptrace-x86-singlestep RUNTIME DESTINATION usr/bin) diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/src/main.c b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/src/main.c new file mode 100644 index 0000000000..efa74b9aa2 --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/c/src/main.c @@ -0,0 +1,223 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PTRACE_GETREGSET +#define PTRACE_GETREGSET 0x4204 +#endif +#ifndef PTRACE_SETREGSET +#define PTRACE_SETREGSET 0x4205 +#endif +#ifndef PTRACE_TRACEME +#define PTRACE_TRACEME 0 +#endif +#ifndef PTRACE_SINGLESTEP +#define PTRACE_SINGLESTEP 9 +#endif +#ifndef PTRACE_CONT +#define PTRACE_CONT 7 +#endif +#ifndef NT_PRSTATUS +#define NT_PRSTATUS 1 +#endif + +struct x86_64_user_regs { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; + uint64_t orig_rax; + uint64_t rip; + uint64_t cs; + uint64_t eflags; + uint64_t rsp; + uint64_t ss; + uint64_t fs_base; + uint64_t gs_base; + uint64_t ds; + uint64_t es; + uint64_t fs; + uint64_t gs; +}; + +__attribute__((naked, noinline, aligned(1))) static void singlestep_target(void) +{ + __asm__ volatile( + "mov $123, %rax\n" + "int3\n" + "jmp singlestep_landing\n"); +} + +__attribute__((naked, noinline, aligned(1), used)) static void singlestep_landing(void) +{ + __asm__ volatile( + "mov $42, %rdi\n" + "mov $60, %rax\n" + "syscall\n"); +} + +static int fail(const char *msg) +{ + printf("FAIL: %s: errno=%d (%s)\n", msg, errno, strerror(errno)); + return 1; +} + +static long ptrace_call(int request, pid_t pid, void *addr, void *data) +{ +#ifdef __APPLE__ + return ptrace(request, pid, (caddr_t)addr, (int)(intptr_t)data); +#else + return ptrace(request, pid, addr, data); +#endif +} + +static int wait_stopped(pid_t pid, int *status, int expected_sig) +{ + if (waitpid(pid, status, WUNTRACED) != pid) { + return fail("waitpid"); + } + if (!WIFSTOPPED(*status) || WSTOPSIG(*status) != expected_sig) { + printf("FAIL: expected stop signal %d, status=%#x\n", expected_sig, *status); + return 1; + } + return 0; +} + +static int getregset(pid_t pid, struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = regs, .iov_len = sizeof(*regs)}; + if (ptrace_call(PTRACE_GETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { + return -1; + } + if ((size_t)iov.iov_len != sizeof(*regs)) { + errno = EIO; + return -1; + } + return 0; +} + +static int setregset(pid_t pid, const struct x86_64_user_regs *regs) +{ + struct iovec iov = {.iov_base = (void *)regs, .iov_len = sizeof(*regs)}; + if (ptrace_call(PTRACE_SETREGSET, pid, (void *)(long)NT_PRSTATUS, &iov) != 0) { + return -1; + } + return 0; +} + +static int test_singlestep(void) +{ + printf("test 1: x86_64 PTRACE_SINGLESTEP\n"); + + pid_t pid = fork(); + if (pid < 0) { + return fail("fork"); + } + + if (pid == 0) { + if (ptrace_call(PTRACE_TRACEME, 0, NULL, NULL) != 0) { + _exit(100); + } + raise(SIGSTOP); + _exit(99); + } + + int status = 0; + if (wait_stopped(pid, &status, SIGSTOP) != 0) { + return 1; + } + printf(" ok: child stopped with SIGSTOP\n"); + + struct x86_64_user_regs regs; + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset initial"); + } + + regs.rip = (uintptr_t)singlestep_target; + regs.rax = 0; + if (setregset(pid, ®s) != 0) { + return fail("setregset target rip"); + } + + if (ptrace_call(PTRACE_SINGLESTEP, pid, NULL, NULL) != 0) { + return fail("ptrace singlestep"); + } + if (wait_stopped(pid, &status, SIGTRAP) != 0) { + return 1; + } + printf(" ok: single-step trap delivered\n"); + + memset(®s, 0, sizeof(regs)); + if (getregset(pid, ®s) != 0) { + return fail("getregset after singlestep"); + } + if (regs.rax != 123) { + printf("FAIL: expected RAX=123 after single-step, got %#llx\n", + (unsigned long long)regs.rax); + return 1; + } + if (regs.rip == (uintptr_t)singlestep_target) { + printf("FAIL: RIP did not advance after single-step: %#llx\n", + (unsigned long long)regs.rip); + return 1; + } + printf(" ok: single-step executed one instruction (RAX=%#llx RIP=%#llx)\n", + (unsigned long long)regs.rax, (unsigned long long)regs.rip); + + if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("ptrace cont to int3"); + } + if (wait_stopped(pid, &status, SIGTRAP) != 0) { + return 1; + } + printf(" ok: child reached the following int3\n"); + + if (ptrace_call(PTRACE_CONT, pid, NULL, NULL) != 0) { + return fail("ptrace cont to exit"); + } + if (waitpid(pid, &status, 0) != pid) { + return fail("waitpid exit"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 42) { + printf("FAIL: expected exit 42, status=%#x\n", status); + return 1; + } + printf(" ok: child exited with 42\n"); + return 0; +} + +int main(void) +{ + int pass = 0; + int fail_count = 0; + + if (test_singlestep() == 0) { + pass++; + } else { + fail_count++; + } + + printf("DONE: %d pass, %d fail\n", pass, fail_count); + return fail_count > 0 ? 1 : 0; +} diff --git a/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/qemu-x86_64.toml b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/qemu-x86_64.toml new file mode 100644 index 0000000000..b14aaff8be --- /dev/null +++ b/test-suit/starryos/normal/qemu-smp1/test-ptrace-x86-singlestep/qemu-x86_64.toml @@ -0,0 +1,14 @@ +args = [ + "-nographic", + "-device", "virtio-blk-pci,drive=disk0", + "-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-alpine.img", + "-device", "virtio-net-pci,netdev=net0", + "-netdev", "user,id=net0", +] +uefi = false +to_bin = false +shell_prefix = "root@starry:" +shell_init_cmd = "/usr/bin/test-ptrace-x86-singlestep" +success_regex = ["DONE: 1 pass, 0 fail"] +fail_regex = ['(?i)\\bpanic(?:ked)?\\b', '(?m)FAIL'] +timeout = 120