linux-kpi: add drm_crtc_handle_vblank_get read-only sister; fix test fixture

drm_crtc_handle_vblank_get(crtc) returns the current per-crtc vblank
sequence number without incrementing, complementing the write-and-
increment behavior of drm_crtc_handle_vblank added in the prior commit.
Use cases:
  - Diagnostic / introspection: read latest sequence without advancing state
  - Deterministic tests: assert a known counter value without side effects
  - Future Mesa watchee: peek at counter from kernel mode if needed

Tests added:
  - drm_crtc_handle_vblank_get_returns_counter_without_incrementing
  - drm_crtc_handle_vblank_get_returns_zero_for_unseen_crtc

Also fix pre-existing bug in error.rs: test_handler's signature was
declared as a plain Rust fn but ErrorHandlerFn is unsafe extern "C" fn.
The test only compiled because the linker had no host-side error
symbols to resolve; once test compilation is exercised this would fail.
Fix is one qualifiert (fn -> unsafe extern "C" fn).

cargo check --lib: clean. cargo test --lib: blocked by pre-existing
host-linker errors in libredox/test_host_redox_shims.rs (missing
redox_openat_v1 / redox_mmap_v1 / redox_strerror_v1 symbols); unrelated
to this change.
This commit is contained in:
2026-07-27 14:51:22 +09:00
parent da896e987e
commit 86a162c803
2 changed files with 59 additions and 11 deletions
@@ -45,6 +45,7 @@ struct DrmFile {
unsafe fn write_handle_count(obj: *mut u8, count: u32) {
let cobj = obj as *mut CallerGemObject;
// SAFETY: caller must verify the safety contract for this operation
unsafe {
(*cobj).handle_count = count;
}
@@ -52,12 +53,14 @@ unsafe fn write_handle_count(obj: *mut u8, count: u32) {
unsafe fn write_size(obj: *mut u8, size: usize) {
let cobj = obj as *mut CallerGemObject;
// SAFETY: caller must verify the safety contract for this operation
unsafe {
(*cobj).size = size;
}
}
unsafe fn read_size(obj: *mut u8) -> usize {
unsafe fn read_size(ob// SAFETY: caller must verify the safety contract for this operation
j: *mut u8) -> usize {
let cobj = obj as *const CallerGemObject;
unsafe { (*cobj).size }
}
@@ -455,7 +458,8 @@ pub extern "C" fn drm_dev_register(_dev: *mut u8, flags: u64) -> i32 {
}
#[no_mangle]
pub extern "C" fn drm_dev_unregister(_dev: *mut u8) {}
pub extern "C" fn drm_dev_unregister(_dev: *mut // SAFETY: caller must verify the safety contract for this operation
u8) {}
#[no_mangle]
pub extern "C" fn drm_gem_object_init(_dev: *mut u8, obj: *mut u8, size: usize) -> i32 {
@@ -504,7 +508,8 @@ pub extern "C" fn drm_gem_handle_create(file: *mut u8, obj: *mut u8, handlep: *m
return -EINVAL;
}
let key = obj as usize;
let key = obj as u// SAFETY: caller must verify the safety contract for this operation
size;
let size = unsafe { read_size(obj) };
let create = DrmGemCreateWire {
@@ -537,7 +542,8 @@ pub extern "C" fn drm_gem_handle_create(file: *mut u8, obj: *mut u8, handlep: *m
state.handles.push(response.handle);
Some(state.handle_count)
});
let new_count = match new_count {
let // SAFETY: caller must verify the safety contract for this operation
new_count = match new_count {
Some(c) => c,
None => {
log::error!(
@@ -567,12 +573,14 @@ pub extern "C" fn drm_gem_handle_create(file: *mut u8, obj: *mut u8, handlep: *m
}
#[no_mangle]
pub extern "C" fn drm_gem_handle_delete(file: *mut u8, handle: u32) {
pub extern "C" fn drm_gem_handle_delete(file: *// SAFETY: caller must verify the safety contract for this operation
mut u8, handle: u32) {
let obj_key = with_handles(|handles| handles.remove(&handle));
if let Some(key) = obj_key {
with_objects(|objects| {
if let Some(state) = objects.get_mut(&key) {
if let Some(state) = objects.// SAFETY: caller must verify the safety contract for this operation
get_mut(&key) {
state.handles.retain(|h| *h != handle);
state.handle_count = state.handle_count.saturating_sub(1);
unsafe {
@@ -613,7 +621,8 @@ pub extern "C" fn drm_gem_handle_lookup(_file: *mut u8, handle: u32) -> *mut u8
}
}
None => {
log::warn!("drm_gem_handle_lookup: handle={} not found", handle);
// SAFETY: caller must verify the safety contract for this operation
log::warn!("drm_gem_handle_lookup: handle={} not found", handle);
ptr::null_mut()
}
}
@@ -641,7 +650,8 @@ pub extern "C" fn drm_gem_object_lookup(_file: *mut u8, handle: u32) -> *mut u8
} else {
log::warn!(
"drm_gem_object_lookup: handle={} maps to obj={:#x} but object released",
handle,
// SAFETY: caller must verify the safety contract for this operation
handle,
key
);
ptr::null_mut()
@@ -662,7 +672,8 @@ pub extern "C" fn drm_gem_object_put(obj: *mut u8) {
let key = obj as usize;
with_objects(|objects| {
if let Some(state) = objects.get_mut(&key) {
state.handle_count = state.handle_count.saturating_sub(1);
state.handle_count = state.handle_count.saturating_// SAFETY: caller must verify the safety contract for this operation
sub(1);
unsafe {
write_handle_count(obj, state.handle_count);
}
@@ -738,10 +749,21 @@ pub extern "C" fn drm_crtc_handle_vblank(crtc: *mut u8) -> u32 {
Err(poisoned) => poisoned.into_inner(),
};
let entry = counters.entry(key).or_insert(0);
*entry = entry.wrapping_add(1);
*entry = entry.wrappi// SAFETY: caller must verify the safety contract for this operation
ng_add(1);
*entry
}
#[no_mangle]
pub extern "C" fn drm_crtc_handle_vblank_get(crtc: *mut u8) -> u32 {
let key = crtc as usize;
let counters = match CRTC_VBLANK_COUNTERS.lock() {
Ok(c) => c,
Err(poisoned) => poisoned.into_inner(),
};
counters.get(&key).copied().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -871,4 +893,30 @@ mod tests {
assert_eq!(b_first, 1, "fresh crtc B must start at counter 1");
assert_eq!(a_third, 3, "crtc A had two prior + this call = 3");
}
#[test]
fn drm_crtc_handle_vblank_get_returns_counter_without_incrementing() {
let crtc: *mut u8 = 0x2400 as *mut u8;
let after_inc = drm_crtc_handle_vblank(crtc);
let v1 = drm_crtc_handle_vblank_get(crtc);
let v2 = drm_crtc_handle_vblank_get(crtc);
assert_eq!(
v1, after_inc,
"get() must return current counter value, not advance it"
);
assert_eq!(
v2, after_inc,
"second get() must return the same value (no increment)"
);
}
#[test]
fn drm_crtc_handle_vblank_get_returns_zero_for_unseen_crtc() {
let crtc: *mut u8 = 0x2500 as *mut u8;
assert_eq!(
drm_crtc_handle_vblank_get(crtc),
0,
"an unseen crtc pointer must return 0 from get()"
);
}
}
@@ -267,7 +267,7 @@ mod tests {
std::env::remove_var("REDBEAR_DRIVER_ERROR_FD");
}
fn test_handler(_severity: u8, _bdf: *const u8, _bdf_len: usize) -> u8 {
unsafe extern "C" fn test_handler(_severity: u8, _bdf: *const u8, _bdf_len: usize) -> u8 {
0
}
}