feat(drm): per-buffer dumb allocation with GEM-refcounted mmap pages#514
Conversation
ZR233
left a comment
There was a problem hiding this comment.
本地 fmt 和新 DRM 用例发现/执行基本可跑,但 starry-kernel clippy 当前失败;同时 dumb buffer 的 backing 生命周期和初始清零还会影响 DRM/GEM 语义与用户态内存安全,需要修正后再合入。
72335e1 to
657a0d5
Compare
ZR233
left a comment
There was a problem hiding this comment.
请求修改。
本地 cargo fmt --check 和 cargo xtask clippy --package starry-kernel 通过,cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb 也通过了,说明 per-buffer dumb allocation 主路径和新增用例已经接上了 runner。
但当前还有两个阻塞的 DRM 语义问题:
ADDFB2只记录了fb_id -> handle,没有让 framebuffer 自己持有 dumb backing 的强引用。用户在ADDFB2之后执行DESTROY_DUMB,self.dumbs.remove()会把 backing 从对象表移除,后续PAGE_FLIP/ atomic commit 再走present_fb时就找不到 buffer。这和 Linux DRM/GEM 的引用计数语义不一致。IN_FORMATS是驱动内部生成的不可变属性 blob,但当前复用了用户 blob 表,并且DESTROYPROPBLOB可以把它删掉;in_formats_blob缓存还不会同步清零,后续属性查询会继续返回一个已经失效的 blob id。
建议先修正这两处生命周期语义,并补对应回归测试后再合入。
ZR233
left a comment
There was a problem hiding this comment.
本轮复查了 per-buffer dumb allocation、DeviceMmap::Physical retainer、LinearBackend 引用保持、MAP_DUMB offset key、atomic/page-flip 提交路径,以及新增 Starry QEMU DRM 用例。mmap VMA 通过 retainer 持有 Arc 这一部分方向是对的,CREATE_DUMB 也已经清零 backing。
当前仍有三个阻塞问题:framebuffer 生命周期仍没有按 GEM 语义建模,fb 只保存 handle 而不持有 backing,DESTROY_DUMB 后 fb 仍存在但无法再 present;IN_FORMATS 内置 blob 仍可被用户删除,缓存 id 会变成悬空;此外新增 mod rtc 后没有注册 Rtc,导致 starry-kernel 全部 8 个 clippy feature 检查失败。
已验证:cargo fmt --check 通过;cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb 通过,输出 DONE: 26 pass, 0 fail。失败项:cargo xtask clippy --package starry-kernel,8 个检查均因 RTC0_DEVICE_ID never used 和 Rtc never constructed 失败。当前测试覆盖了双 dumb buffer、不同 mmap offset、两个 fb_id 和 fb1/fb2 atomic 翻页,但还没有覆盖 ADDFB2 后 DESTROY_DUMB 再提交,也没有覆盖删除 IN_FORMATS blob 后再次查询属性。
48d9682 to
dbfcd14
Compare
ZR233
left a comment
There was a problem hiding this comment.
这版已经把 dumb mmap 的 VMA retainer、初始清零、per-buffer 分配和 x86_64 QEMU 覆盖补上了,starry-kernel clippy 也通过。PR 仍是 draft。
本地验证:cargo fmt --check 通过,cargo xtask clippy --package starry-kernel 通过,cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb --list 能发现用例,实际运行 test-drm-perbuf-dumb 通过。远端检查快照里仍有 clippy/sync-lint/Starry QEMU container jobs pending。
目前还有两个阻塞的 DRM/GEM 生命周期问题:framebuffer 没有持有 backing,DESTROY_DUMB 后 fb commit 会静默失效;内部 IN_FORMATS blob 仍可被用户删除并破坏后续属性查询。建议修复这两处所有权问题并补 destroy 后继续使用的回归测试。
ZR233
left a comment
There was a problem hiding this comment.
本轮复查的是新 head dbfcd14624ff0bcae4e27674e88cd3e84f67fdc6。之前两个 DRM/GEM 生命周期阻塞点已经从实现上修复:framebuffer 现在持有 Arc<GlobalPage>,DESTROY_DUMB 不再让已创建 fb 的 backing 失效;IN_FORMATS 也已拆到 system_blobs 并禁止用户 DESTROYPROPBLOB 删除。x86_64 的 test-drm-perbuf-dumb 本地通过。
但当前 starry-kernel clippy 8 个配置全部失败,仍不满足项目的 crate 修改后 clippy 通过要求,需要修正后再合入。验证结果:cargo fmt --check 通过,cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb --list 能发现用例,实际运行 test-drm-perbuf-dumb 通过;cargo xtask clippy --package starry-kernel 失败,8/8 检查都卡在 os/StarryOS/kernel/src/pseudofs/dev/card0.rs:550 的 manual_is_multiple_of。
ZR233
left a comment
There was a problem hiding this comment.
per-buffer dumb 的本地 DRM case 能跑通,但 DRM blob/GEM 生命周期语义仍有阻塞问题,部分新增测试还把错误行为写成了期望。
验证通过:cargo fmt --check,cargo xtask clippy --package starry-kernel,test-drm-perbuf-dumb --list,test-drm-atomic --list,以及 x86_64 test-drm-perbuf-dumb。远端 gh pr checks 仍有 loongarch64 normal qemu busybox 失败,且多个 Starry container jobs 被取消。
ZR233
left a comment
There was a problem hiding this comment.
这版把上轮指出的 MODE_ID blob pinning 和 GEM destroy 后用例方向补上了,IN_FORMATS / framebuffer backing 的生命周期实现也比前几版完整;但当前 head 仍有两个阻塞问题,需要修改后再合入。
主要问题:
- 新增的
test-drm-perbuf-dumb当前编译失败,远端 x86_64 Starry QEMU 失败也包含这个 case;本地单跑cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb复现为uint8_t写入/比较 32-bit magic 常量触发-Werror。 - legacy
SETCRTC/GETCRTC路径没有维护 CRTC/fb/connector state,也不校验 fb/connector 数组,导致无效 fb/connector 可以成功,且drmModeSetCrtc()后drmModeGetCrtc()读不回绑定状态。
已验证:git diff --check origin/dev...HEAD 通过;git merge-tree --write-tree origin/dev HEAD 通过;cargo fmt --check 通过;cargo xtask clippy --package starry-kernel 8/8 通过。失败项:cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb 在 C 用例编译阶段失败。远端 Test starry x86_64 qemu / run_container 失败包含 busybox 和 test-drm-perbuf-dumb,其中 busybox 看起来是既有/环境项,但 test-drm-perbuf-dumb 是本 PR 新增用例的确定性失败。
ZR233
left a comment
There was a problem hiding this comment.
这版确认修掉了上一轮的 C 用例编译问题:test-drm-perbuf-dumb 现在本地 x86_64 QEMU 能完整跑过,并且 destroy 后 mmap/fb backing 生命周期覆盖也在用例里了。starry-kernel clippy 也已经恢复全绿。
仍然需要 request changes 的 blocker 是 legacy modeset 路径还没有修:SETCRTC 目前只校验 crtc_id,fb_id != 0 时直接 present_fb(c.fb_id) 后返回成功;它没有校验 fb 是否存在,也没有读取/校验 count_connectors 和 set_connectors_ptr。GETCRTC 仍然固定返回 fb_id = 0、count_connectors = 0,没有从当前 modeset state 读回 legacy SETCRTC 建立的绑定。这样无效 fb/connector 会被静默接受,drmModeSetCrtc() 后 drmModeGetCrtc() 也读不回当前 fb/connector 状态,和 Linux DRM legacy KMS 语义不一致。
建议复用现有 ModesetState 或单独维护 legacy CRTC state:SETCRTC 校验 fb 和 connector 数组,成功后写入当前 fb/connector/mode;GETCRTC 按 state 返回 fb_id、connector id 和 count_connectors。最好在 test-drm-modeset 里补一段:SETCRTC 后 GETCRTC 应读回 fb/connector;不存在的 fb 或错误 connector 应返回失败。
本地验证记录:
git diff --check origin/dev...HEAD通过git merge-tree --write-tree origin/dev HEAD通过cargo fmt --check通过cargo xtask clippy --package starry-kernel8/8 通过cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb通过,DONE: 30 pass, 0 fail
PR 当前仍是 draft。
8e844f6 to
d5cf564
Compare
ZR233
left a comment
There was a problem hiding this comment.
这轮复查的是新 head d5cf5640d2275452134a74b3341b76e7bfbfd6bf。前面几轮提到的 dumb backing 引用保持、初始清零、IN_FORMATS / MODE_ID blob 生命周期、C 用例编译问题和 clippy 问题这次都确认已经修掉,我也把对应旧线程 resolve 掉了。
仍然需要 request changes 的 blocker 还是 legacy modeset:handle_get_crtc() 仍固定返回 fb_id = 0、count_connectors = 0,handle_set_crtc() 仍只校验 crtc_id,fb_id != 0 时直接 present_fb(c.fb_id) 后返回成功;没有校验 fb 是否存在,也没有读取/校验 count_connectors / set_connectors_ptr,更没有把 legacy SETCRTC 建立的 fb/connector/mode state 记录下来供 GETCRTC 读回。这样不存在或已经 RMFB 的 fb、错误 connector id 都会被静默接受,drmModeSetCrtc() 后 drmModeGetCrtc() 也读不回当前绑定,和 Linux DRM legacy KMS 语义不一致。当前 test-drm-modeset 只覆盖 SETCRTC 成功和后续 page flip/event,没有覆盖 SETCRTC -> GETCRTC readback 或 invalid fb/connector reject,所以这个问题仍会漏掉。
建议复用现有 ModesetState 或单独维护 legacy CRTC state:SETCRTC 校验 fb 和 connector 数组,成功后写入当前 fb/connector/mode;GETCRTC 按 state 返回 fb_id、connector id 和 count_connectors。同时在 test-drm-modeset 里补:SETCRTC 后 GETCRTC 应读回 fb/connector;不存在/已删除 fb 或错误 connector 应返回失败。
本地验证记录:
git diff --check origin/dev...HEAD通过git merge-tree --write-tree origin/dev HEAD通过cargo fmt --check通过cargo xtask clippy --package starry-kernel9/9 通过cargo xtask starry test qemu --arch x86_64 -l能发现test-drm-modeset/test-drm-perbuf-dumbcargo xtask starry test qemu --arch x86_64 -c test-drm-modeset通过,DONE: 32 pass, 0 failcargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb通过,DONE: 30 pass, 0 fail
远端 CI 快照里大量 container/self-hosted job 仍在 pending;PR 当前仍是 draft。
ZR233
left a comment
There was a problem hiding this comment.
本轮复查确认之前的 legacy SETCRTC / GETCRTC 阻塞项已经修复:当前实现会校验 framebuffer 和 connector 数组,保存 legacy CRTC binding,并在 GETCRTC 中读回;test-drm-modeset 也覆盖了正常读回、坏 connector、RMFB 后清空绑定和已删除 fb 拒绝路径。因此旧线程已标记 resolved。
但当前 head 仍有 PR 相关 CI 失败,需要先修复后再合入。远端 CI run 25873747415 的 Test starry loongarch64 qemu / run_container 在现有 busybox case 中失败,具体为 FAIL: busybox_hwclock。这和本 PR 新增注册 /dev/rtc0 直接相关:busybox hwclock -r 不再走旧的错误输出路径,导致现有 busybox 测试的判定失败。建议同步修正 RTC 行为或更新 busybox hwclock 测试,使新增 /dev/rtc0 后 CI 能稳定通过,并补上能表达预期输出/返回值的覆盖。
本地验证:git diff --check origin/dev...HEAD 通过,cargo fmt --check 通过,cargo xtask clippy --package starry-kernel 的 9 个检查全部通过。
Resolutions: - linear.rs / mmap.rs: adopt dev's anchor naming convention (LinearBackend.anchor, Backend::new_linear_anchored). PR rcore-os#514's retain field/method renamed to match. - ion.rs, ion/device.rs: pass None as the new second argument to DeviceMmap::Physical now that the variant carries an Option<Arc>. - card0.rs, fb.rs, test-drm-{atomic,modeset}/main.c: keep PR rcore-os#514's per-buffer dumb allocation and blob-lifecycle test versions; they supersede the shared-scanout / simple-blob variants on dev. - dev/mod.rs: keep both mod rtc; (PR rcore-os#514) and pub mod tpu; (dev).
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR strengthens StarryOS’s DRM /dev/dri/card0 behavior to more closely match Linux semantics around dumb buffers, framebuffer lifetime, and modeset blob lifetimes, and adds supporting test coverage and device plumbing.
Changes:
- Add a new DRM regression test (
test-drm-perbuf-dumb) and QEMU runners for multiple architectures. - Update the DRM card0 implementation to allocate per-buffer backing pages, retain mmap backing across
DESTROY_DUMB, keep fb backing alive across handle destruction, and pin committedMODE_IDblobs pastDESTROYPROPBLOB. - Add
/dev/rtc0support and adjust BusyBox hwclock test expectations.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/qemu-x86_64.toml | Adds QEMU launch config to run the new DRM per-buffer dumb test on x86_64. |
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/qemu-riscv64.toml | Adds QEMU launch config to run the new DRM per-buffer dumb test on riscv64. |
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/qemu-loongarch64.toml | Adds QEMU launch config to run the new DRM per-buffer dumb test on loongarch64. |
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/qemu-aarch64.toml | Adds QEMU launch config to run the new DRM per-buffer dumb test on aarch64. |
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/c/src/test_framework.h | Introduces a small C test framework for consistent PASS/FAIL accounting and output. |
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/c/src/main.c | New userspace DRM test validating per-buffer dumb allocation + lifecycle semantics. |
| test-suit/starryos/normal/qemu-smp1/test-drm-perbuf-dumb/c/CMakeLists.txt | Builds/installs the new test-drm-perbuf-dumb binary. |
| test-suit/starryos/normal/qemu-smp1/drm/test-drm-modeset/c/src/main.c | Refactors/extends modeset tests to validate GETCRTC readback and RMFB effects. |
| test-suit/starryos/normal/qemu-smp1/drm/test-drm-atomic/c/src/main.c | Updates atomic tests to validate Linux-like propblob lifetime behavior after commit. |
| test-suit/starryos/normal/qemu-smp1/busybox/sh/busybox-tests.sh | Makes busybox hwclock test more tolerant while still checking for plausible output. |
| os/StarryOS/kernel/src/syscall/mm/mmap.rs | Extends device-mmap handling to optionally retain backing objects for VMA lifetime. |
| os/StarryOS/kernel/src/pseudofs/device.rs | Extends DeviceMmap::Physical to carry an optional retainer Arc. |
| os/StarryOS/kernel/src/pseudofs/dev/rtc.rs | Adds a minimal /dev/rtc0 device implementing RTC_RD_TIME. |
| os/StarryOS/kernel/src/pseudofs/dev/mod.rs | Registers /dev/rtc0 in the pseudo device filesystem. |
| os/StarryOS/kernel/src/pseudofs/dev/ion/device.rs | Adapts ION mmap to the new DeviceMmap::Physical(range, retain) signature. |
| os/StarryOS/kernel/src/pseudofs/dev/fb.rs | Adapts framebuffer mmap to the new DeviceMmap::Physical(range, retain) signature. |
| os/StarryOS/kernel/src/pseudofs/dev/card1.rs | Adapts card1 mmap paths/tests to the new DeviceMmap::Physical(range, retain) signature. |
| os/StarryOS/kernel/src/pseudofs/dev/card0.rs | Major DRM behavior changes: per-buffer pages, fb retention, legacy GETCRTC state, blob pinning. |
| os/StarryOS/kernel/src/file/ion.rs | Adapts ION buffer file mmap to the new DeviceMmap::Physical(range, retain) signature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn handle_get_plane(arg: usize) -> VfsResult<usize> { | ||
| let ptr = arg as *mut DrmModeGetPlane; | ||
| let mut p: DrmModeGetPlane = ptr.vm_read().map_err(|_| VfsError::BadAddress)?; | ||
| if p.plane_id != PLANE_ID { | ||
| return Err(VfsError::InvalidInput); | ||
| } | ||
| p.crtc_id = CRTC_ID; | ||
| p.fb_id = 0; | ||
| p.possible_crtcs = 1; | ||
| p.gamma_size = 0; | ||
| p.count_format_types = | ||
| report_user_array(p.format_type_ptr, p.count_format_types, SUPPORTED_FORMATS)?; | ||
| ptr.vm_write(p).map_err(|_| VfsError::BadAddress)?; | ||
| Ok(0) | ||
| } |
| fn handle_get_connector(arg: usize) -> VfsResult<usize> { | ||
| let ptr = arg as *mut DrmModeGetConnector; | ||
| let mut c: DrmModeGetConnector = ptr.vm_read().map_err(|_| VfsError::BadAddress)?; | ||
| if c.connector_id != CONNECTOR_ID { | ||
| return Err(VfsError::InvalidInput); | ||
| } | ||
| c.encoder_id = ENCODER_ID; | ||
| c.connector_type = DRM_MODE_CONNECTOR_VIRTUAL; | ||
| c.connector_type_id = 1; | ||
| c.connection = DRM_MODE_CONNECTED; | ||
| let (w, h) = display_resolution(); | ||
| c.mm_width = w; | ||
| c.mm_height = h; | ||
| c.subpixel = 0; | ||
|
|
||
| let state = *self.state.lock(); | ||
| let prop_vals = conn_prop_values(&state); | ||
| report_user_array(c.props_ptr, c.count_props, CONN_PROPS)?; | ||
| report_user_array(c.prop_values_ptr, c.count_props, &prop_vals)?; | ||
| c.count_props = CONN_PROPS.len() as u32; | ||
| c.count_encoders = report_user_array(c.encoders_ptr, c.count_encoders, &[ENCODER_ID])?; | ||
|
|
||
| ptr.vm_write(c).map_err(|_| VfsError::BadAddress)?; | ||
| Ok(0) | ||
| if c.modes_ptr != 0 && c.count_modes > 0 { | ||
| let p = c.modes_ptr as *mut DrmModeModeInfo; | ||
| p.vm_write(current_mode()) | ||
| .map_err(|_| VfsError::BadAddress)?; | ||
| } | ||
| c.count_modes = 1; | ||
| c.count_props = 0; | ||
|
|
||
| ptr.vm_write(c).map_err(|_| VfsError::BadAddress)?; | ||
| Ok(0) | ||
| } |
| let size = (pitch as u64) | ||
| .checked_mul(c.height as u64) | ||
| .ok_or(VfsError::InvalidInput)?; | ||
| if size > 256 * 1024 * 1024 { | ||
| return Err(VfsError::InvalidInput); | ||
| if size as usize > DUMB_BUFFER_MAX_SIZE { | ||
| return Err(VfsError::NoMemory); | ||
| } | ||
| c.pitch = pitch; | ||
| c.size = size; | ||
| let handle = self.next_dumb_handle.fetch_add(1, Ordering::Relaxed); | ||
|
|
||
| let alloc_size = | ||
| ((size as usize) + PageSize::Size4K as usize - 1) & !((PageSize::Size4K as usize) - 1); | ||
| let paddr = self.alloc_dumb_pages(alloc_size); | ||
| // Each buffer gets its own page-aligned `GlobalPage`. No shared | ||
| // pool, so we don't fail on early-boot fragmentation on arches | ||
| // whose allocator can't satisfy one large contiguous request | ||
| // after driver probe. | ||
| let size_aligned = (size as usize).next_multiple_of(PAGE_SIZE_4K); | ||
| let pages = size_aligned / PAGE_SIZE_4K; | ||
| let mut backing = | ||
| GlobalPage::alloc_contiguous(pages, PAGE_SIZE_4K).map_err(|_| VfsError::NoMemory)?; |
| let dumbs = self.dumbs.lock(); | ||
| for (_, buf) in dumbs.iter() { | ||
| if offset == buf.offset | ||
| && let Some(paddr) = buf.paddr | ||
| { | ||
| return DeviceMmap::Physical(PhysAddrRange::from_start_size( | ||
| paddr, | ||
| (length.min(buf.size)) as usize, | ||
| )); | ||
| } | ||
| } | ||
| DeviceMmap::None | ||
| let Some(b) = dumbs.values().find(|b| b.offset == offset) else { | ||
| return DeviceMmap::None; | ||
| }; |
There was a problem hiding this comment.
本轮复查的是 head bca534db626ea5e8db3842517e8cb372f52e65ed(2 commits)。
PR 概述
本 PR 将 DRM /dev/dri/card0 的 dumb buffer 模型从「所有 buffer 共享同一段 axdisplay scanout」改为 per-buffer GlobalPage 独立分配,并引入 GEM 风格的引用计数语义。同时新增 /dev/rtc0、LegacyCrtcState、MODE_ID blob pinning、IN_FORMATS 拆分到 system_blobs,以及完整的 test-drm-perbuf-dumb C 测试。
之前阻塞项确认已修复
ZR233 在之前 8 轮 review 中提出的所有阻塞问题在当前 head 均已修复:
- framebuffer backing 生命周期:
ADDFB2现在创建Framebuffer { size, pages }结构体,持有Arc<GlobalPage>克隆。DESTROY_DUMB仅移除 handle 表中的强引用,fb 持有的引用继续存活。✅ - dumb buffer 未清零:
handle_create_dumb在alloc_contiguous后调用backing.zero()。✅ - IN_FORMATS blob 可被用户删除:已拆分到
system_blobs,handle_destroy_blob拒绝删除系统 blob。✅ - MODE_ID blob 生命周期:
mode_id_blob_ref持有已提交 blob 的Arc,DESTROYPROPBLOB后仍可通过该引用访问。✅ - Clippy 失败:
cargo xtask clippy --package starry-kernel14/14 通过。✅ - legacy SETCRTC/GETCRTC 语义:新增
LegacyCrtcState,SETCRTC校验 fb/connector 数组,GETCRTC读回绑定状态,RMFB清除绑定。✅ - C 测试编译问题:当前编译运行正常。✅
- busybox hwclock CI 失败:hwclock 测试改为容错匹配,接受成功读取年份或含
hwclock的错误消息。✅
代码审查
实现质量良好:
DumbBuffer使用Arc<GlobalPage>正确建模 GEM refcountFramebuffer持有独立Arc克隆,确保DESTROY_DUMB后 fb backing 仍存活LegacyCrtcState将 legacy modeset 与 atomic state 分离present_fb快照模式避免在framebuffer_flush期间持有锁mode_id_blob_ref正确 pin 住已提交的 MODE_ID blobBackend::new_linear_anchored路径通过 retainer 正确保持 VMA 中的页面存活GETCONNECTOR的count_props从报告 CONN_PROPS 改为 0,但 libdrm 通过OBJ_GETPROPERTIES获取属性表面,不影响功能
验证结果
| 验证项 | 结果 |
|---|---|
cargo fmt --check |
✅ 通过 |
cargo xtask clippy --package starry-kernel |
✅ 14/14 通过 |
cargo xtask starry test qemu --arch x86_64 -c test-drm-perbuf-dumb |
✅ 30 pass, 0 fail |
cargo xtask starry test qemu --arch x86_64 -c drm(已有 DRM 测试) |
✅ 14 pass, 0 fail |
QEMU 验证详情:test-drm-perbuf-dumb 覆盖了完整的 per-buffer dumb 路径:
- 两次
CREATE_DUMB拿到独立 handle - 两次
MAP_DUMB返回不同 offset - 两个 mmap 区域物理隔离(写 buf2 不影响 buf1)
- 梯度像素写入验证
- 两次
ADDFB2拿到不同 fb_id - 两次 atomic commit 在 fb1/fb2 之间翻页
DESTROY_DUMB后 mmap 仍然可读写DESTROY_DUMB后 atomic commit 仍能翻页(GEM 生命周期验证)
重复/重叠分析
搜索了 open PR,未发现与 DRM dumb buffer、card0 或相关模块重叠的 PR。PR #1038(nginx CI)和 PR #1036(K230 KPU)均与本 PR 无关。
注意事项
- PR 当前
mergeable_state=dirty,需要 rebase 到最新 dev 以解决冲突后才能合入。 /dev/rtc0的RTC_RD_TIME只返回 wall time,tm_wday/tm_yday固定为 0,后续可扩展。- PRIME 路径 (
HANDLE_TO_FD/FD_TO_HANDLE) 当前为占位实现,留给后续 PR。
结论
所有之前阻塞的问题均已修复,验证通过,无新增阻塞问题。建议 APPROVE(待 rebase 解决冲突后)。
Powered by mimo-v2.5-pro
Rebase drm-perbuf onto upstream dev: - ax_hal:: -> ax_runtime::hal:: import renames - Drop kcov DeviceMmap variants removed upstream - Incorporate upstream PROT_NONE/USER flag logic - Adapt busybox hwclock test for /dev/rtc0 presence
There was a problem hiding this comment.
本轮复查的是 head 34c97fb9ac13dcad7ce64744b9d8e4c545cd009a(2 commits)。
PR 概述
本 PR 将 DRM /dev/dri/card0 的 dumb buffer 模型从「所有 buffer 共享同一段 axdisplay scanout」改为 per-buffer GlobalPage 独立分配,并引入 GEM 风格的引用计数语义。同时新增 /dev/rtc0、LegacyCrtcState、MODE_ID blob pinning、IN_FORMATS 拆分到 system_blobs,以及完整的 test-drm-perbuf-dumb C 测试。
之前阻塞项确认已修复
ZR233 在之前多轮 review 中提出的所有阻塞问题在当前 head 均已修复:
- framebuffer backing 生命周期:
ADDFB2创建Framebuffer { size, pages }结构体,持有Arc<GlobalPage>克隆。DESTROY_DUMB仅移除 handle 表中的强引用,fb 持有的引用继续存活。✅ - dumb buffer 未清零:
handle_create_dumb在alloc_contiguous后调用backing.zero()。✅ - IN_FORMATS blob 可被用户删除:已拆分到
system_blobs,handle_destroy_blob拒绝删除系统 blob。✅ - MODE_ID blob 生命周期:
mode_id_blob_ref持有已提交 blob 的Arc,DESTROYPROPBLOB后仍可通过该引用访问。✅ - Clippy 失败:
cargo xtask clippy --package starry-kernel13/13 通过。✅ - legacy SETCRTC/GETCRTC 语义:
LegacyCrtcState正确维护,SETCRTC校验 fb/connector 数组,GETCRTC读回绑定状态,RMFB清除绑定。✅ - C 测试编译问题:当前编译运行正常。✅
- busybox hwclock CI 失败:hwclock 测试改为容错匹配,接受成功读取年份或含
hwclock的错误消息。✅
代码审查
实现质量良好:
DumbBuffer使用Arc<GlobalPage>正确建模 GEM refcountFramebuffer持有独立Arc克隆,确保DESTROY_DUMB后 fb backing 仍存活LegacyCrtcState将 legacy modeset 与 atomic state 分离present_fb快照模式避免在framebuffer_flush期间持有锁mode_id_blob_ref正确 pin 住已提交的 MODE_ID blobBackend::new_linear_anchored路径通过 retainer 正确保持 VMA 中的页面存活DeviceMmap::Physical增加可选 retainer 参数,fb.rs / card1.rs / ion 等调用点填None,行为不变mmap正确将length限制在 buffer size 范围内
验证结果
| 验证项 | 结果 |
|---|---|
git diff --check origin/dev...HEAD |
✅ 通过 |
git merge-tree --write-tree origin/dev HEAD |
✅ 通过(无冲突) |
cargo fmt --check |
✅ 通过 |
cargo xtask clippy --package starry-kernel |
✅ 13/13 通过 |
test-drm-perbuf-dumb x86_64 QEMU |
✅ 30 pass, 0 fail |
drm 已有 DRM 测试 x86_64 QEMU |
✅ 14 pass, 0 fail |
QEMU 验证详情:test-drm-perbuf-dumb 覆盖了完整的 per-buffer dumb 路径:
- 两次
CREATE_DUMB拿到独立 handle - 两次
MAP_DUMB返回不同 offset - 两个 mmap 区域物理隔离(写 buf2 不影响 buf1)
- 梯度像素写入验证
- 两次
ADDFB2拿到不同 fb_id - 两次 atomic commit 在 fb1/fb2 之间翻页
DESTROY_DUMB后 mmap 仍然可读写DESTROY_DUMB后 atomic commit 仍能翻页(GEM 生命周期验证)
注意事项
- PR 当前
mergeable_state=blocked,可能需要 rebase 到最新 dev。 /dev/rtc0的RTC_RD_TIME只返回 wall time,tm_wday/tm_yday固定为 0,后续可扩展。- PRIME 路径 (
HANDLE_TO_FD/FD_TO_HANDLE) 当前为占位实现,留给后续 PR。
结论
所有之前阻塞的问题均已修复,本地验证通过,QEMU 测试全绿。建议 APPROVE。
Powered by mimo-v2.5-pro
Problem
F+G+H+I 留下的 DUMB 模型是 "所有 dumb buffer 都指向同一段 axdisplay scanout"。两个 DUMB 写出来会互相覆盖,weston pixman 的双缓冲翻页跑不起来。同时
DESTROY_DUMB一旦释放底层物理页,已经 mmap 到用户空间的 PTE 就指向被回收的内存,使用-after-free 待发。Changes
Per-buffer 物理内存 + 初始化清零
CREATE_DUMB用GlobalPage::alloc_contiguous单独拿一段页对齐的物理内存 (上限 8 MiB)。GlobalPage::zero()之后再交付,避免把内核残留页内容直接 mmap 给 userspace。GEM-style 引用计数
DumbBuffer.pages用Arc<GlobalPage>持有。Card0 的 handle 表是其中一个强引用;DESTROY_DUMB只是丢掉这个强引用,物理页是否真正释放取决于是否还有用户映射在引用。DeviceMmap::Physical(PhysAddrRange, Option<Arc<dyn Any + Send + Sync>>)多带一个 retainer 槽。Card0::mmap把Arc<GlobalPage>克隆出来填进去。LinearBackend接住 retainer:每条 VMA 自己持一份强引用,被munmap或 aspace 整体丢弃时引用计数才会真正落到零。这条链路对齐 Linux DRM GEM 的 refcount 语义。DeviceMmap::Physical调用点 (fb.rs / card1.rs) 对应 long-lived 驱动 buffer,retainer 填None,行为不变。mmap offset key + 提交链路
MAP_DUMB返回单调递增的 offset key 作为mmap查询入口。mmap(offset)在dumbs里反查到对应 buffer 把PhysAddrRange加 retainer 一起返回。SETCRTC / PAGE_FLIP / atomic非TEST_ONLYcommit 都走present_fb(fb_id):按fb_id找到对应 dumb handle 和 pages,memcpy 到 axdisplay scanout,再framebuffer_flush推给 virtio-gpu。Tests
新增
test_drm_perbuf_dumb(4 架构 toml) 覆盖完整路径:CREATE_DUMB×2拿不同 handleMAP_DUMB×2拿不同 offsetADDFB2×2拿不同 fb_idfb1 / fb2之间翻页Dependencies
依赖 F+G+H+I 的 DRM 基线 (
feat(drm): /dev/dri/card0 with legacy SETCRTC and atomic KMS)。本 PR 在 F+G+H+I 合入后 rebase 到 dev 即可编译。Notes
PRIME (
HANDLE_TO_FD / FD_TO_HANDLE)、hardware cursor plane、virtio-gpu 零拷贝资源 (resource_create_2d / set_scanout / transfer_to_host) 走后续 PR。Sync with dev
card0.rs、device.rs、mmap.rs的冲突。DeviceMmap::NotConfigured/SharedPages、kcov/SG2002/Ion mmap 和 DRM ioctl 支持,同时保留本 PR 的 per-bufferArc<GlobalPage>生命周期语义。DeviceMmap::Physical增加可选 retainer;sys_mmap在存在 retainer 时使用 anchored linear backend,让 mmap 后的 dumb buffer 页在DESTROY_DUMB后仍由 VMA 持有。cargo fmt --check与cargo xtask clippy --package starry-kernel。