Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion bindings/rust/libdrmtap-sys/csrc/drm_grab.c
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,10 @@ void drmtap_fast_cleanup(drmtap_ctx *ctx) {
ctx->fast_last_fb_id = 0;
ctx->fast_initialized = 0;
ctx->fast_no_cpu_map = 0;
/* fast_no_privilege is deliberately NOT cleared: it records a property of the
* PROCESS (no CAP_SYS_ADMIN), not of the plane, the framebuffer or the device,
* so re-discovering it after every teardown would repeat the whole GetFB2 dance
* to reach the same verdict. */
}

// Find or allocate a slot for the given fb_id
Expand Down Expand Up @@ -1354,6 +1358,17 @@ int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame) {
return -ENOTSUP;
}

/* Established on an earlier call that this process cannot export the scanout and
* that this entry point has no helper fallback. Answer immediately and quietly:
* the caller has the reason in drmtap_error() from the first failure, and a
* capture loop calling this at frame rate must not produce a line per frame. */
if (ctx->fast_no_privilege) {
drmtap_set_error(ctx,
"drmtap_grab_mapped_fast needs CAP_SYS_ADMIN in this process and has no "
"helper fallback; use drmtap_grab_mapped()");
return -EACCES;
}

int ret;

/* Step 1: Find plane on first call only */
Expand Down Expand Up @@ -1496,8 +1511,30 @@ int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame) {
}

if (fb2->handles[0] == 0) {
/* drmModeGetFB2 zeroes the GEM handles for a caller without CAP_SYS_ADMIN.
* drmtap_grab_mapped treats that as "go through the helper"; this entry point
* has no such branch, so it cannot serve an unprivileged caller on ANY gpu.
* Say that, name the call that does work, and latch it so the next frame is
* answered at the top of the function instead of repeating this whole dance.
* Reported as endless cache-miss lines with the cause buried, which sent both
* the reporter and me after a tiling bug that was not there (issue #36). */
drmModeFreeFB2(fb2);
drmtap_set_error(ctx, "fast2: handles[0]==0, no CAP_SYS_ADMIN");
ctx->fast_no_privilege = 1;
/* Nothing on this path can be served again, so release whatever the slots hold
* (prime fds, mappings, GEM handles) now instead of at drmtap_close. Normally
* there is nothing: an unprivileged process never populated a slot in the first
* place. It matters for the one case that can, a process that captured with
* CAP_SYS_ADMIN and then dropped it, where the slots are live and unusable.
* Runs AFTER the flag is set, since cleanup deliberately leaves it alone. */
drmtap_fast_cleanup(ctx);
drmtap_debug_log(ctx,
"fast2: this process has no CAP_SYS_ADMIN, and the fast path has no helper "
"fallback (it caches a CPU mapping per framebuffer; a helper hands over a "
"fresh fd per grab, so there is nothing to cache). Use drmtap_grab_mapped(), "
"which falls back to the helper. Not repeated per frame.");
drmtap_set_error(ctx,
"drmtap_grab_mapped_fast needs CAP_SYS_ADMIN in this process and has no "
"helper fallback; use drmtap_grab_mapped()");
return -EACCES;
}

Expand Down
26 changes: 25 additions & 1 deletion bindings/rust/libdrmtap-sys/csrc/drmtap.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,14 @@ int drmtap_displays_changed(drmtap_ctx *ctx);

/** Captured frame metadata and pixel data. */
typedef struct {
void *data; /**< Pixel data (mapped path) or NULL (zero-copy) */
/** Pixel data. Always set on the mapped paths (drmtap_grab_mapped,
* drmtap_grab_mapped_fast). On the zero-copy path (drmtap_grab,
* drmtap_grab_desc) it is NOT guaranteed: it holds the raw, UNCONVERTED
* scanout mapping where one was possible and NULL where the scanout cannot
* be CPU-mapped (amdgpu GFX9+, discrete VRAM, nvidia). Do not read it after
* a zero-copy grab -- that it happens to work on one GPU and returns NULL on
* another is exactly how issue #36 was mistaken for a driver bug. */
void *data;
int dma_buf_fd; /**< DMA-BUF fd (zero-copy) or -1 (mapped) */
uint32_t width; /**< Frame width in pixels */
uint32_t height; /**< Frame height in pixels */
Expand All @@ -151,6 +158,16 @@ typedef struct {
* Returns a DMA-BUF file descriptor in frame->dma_buf_fd that can be
* passed directly to VAAPI/V4L2 encoders without copying pixel data.
*
* THERE ARE NO USABLE CPU PIXELS HERE. This call does no conversion and no detiling.
* `frame->data` is whatever the raw scanout mapping happened to give: still tiled where
* the scanout is tiled, and NULL on a GPU whose scanout cannot be CPU-mapped at all
* (amdgpu GFX9+, discrete VRAM, nvidia) -- and the call still returns 0 in both cases.
* Reading `frame->data` after a successful `drmtap_grab` is
* therefore not portable and renders black on those GPUs -- it was reported as a
* capture bug (issue #36). For pixels in this process use `drmtap_grab_mapped()`;
* to hand the buffer to another process use `drmtap_grab_desc()` plus
* `drmtap_convert_dmabuf()` on the receiving side.
*
* @param ctx Capture context
* @param frame Output frame info (caller-allocated)
* @return 0 on success, negative errno on error
Expand Down Expand Up @@ -198,9 +215,16 @@ int drmtap_grab_mapped(drmtap_ctx *ctx, drmtap_frame_info *frame);
* function OR until drmtap_close(). Do NOT call drmtap_frame_release()
* on frames from this function — cleanup is automatic.
*
* REQUIRES CAP_SYS_ADMIN IN THE CALLING PROCESS. Unlike `drmtap_grab_mapped()`, this
* entry point has NO helper fallback: caching a persistent CPU mapping per framebuffer
* is the whole point of it, and the helper hands over a fresh fd per grab, so there is
* nothing stable to cache. Without the capability it returns -EACCES on every call, on
* any GPU, and `drmtap_error()` names `drmtap_grab_mapped()` as the call to use instead.
*
* @param ctx Capture context
* @param frame Output frame info (caller-allocated, reused between calls)
* @return 0 = frame captured, negative errno on error
* @retval -EACCES The process lacks CAP_SYS_ADMIN (no helper fallback on this path)
*/
int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame);

Expand Down
11 changes: 11 additions & 0 deletions bindings/rust/libdrmtap-sys/csrc/drmtap_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ struct drmtap_ctx {
* the per-frame "miss" honest in the log: nothing was ever cached to hit. */
int fast_no_cpu_map;

/* Set once drmtap_grab_mapped_fast has established that this process cannot
* export the scanout itself (drmModeGetFB2 returns handles[0]==0 without
* CAP_SYS_ADMIN). Unlike drmtap_grab_mapped, the fast path has no helper
* fallback -- caching a persistent CPU mapping per framebuffer is its whole
* purpose, and a helper hands over a fresh fd per grab, so there is nothing
* stable to cache. It therefore cannot work for an unprivileged caller on
* any GPU. Sticky so the diagnosis is stated once instead of once per frame:
* reported as pages of "CACHE MISS ... cold start" with the real reason only
* in drmtap_error(), it read as a broken cache (issue #36). */
int fast_no_privilege;

/* Deswizzle shadow buffer (for read-only mmap'd DMA-BUFs).
* Grow-once and reused across grabs; capped at DRMTAP_MAX_FB_BYTES;
* freed in drmtap_close(). */
Expand Down
26 changes: 25 additions & 1 deletion include/drmtap.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,14 @@ int drmtap_displays_changed(drmtap_ctx *ctx);

/** Captured frame metadata and pixel data. */
typedef struct {
void *data; /**< Pixel data (mapped path) or NULL (zero-copy) */
/** Pixel data. Always set on the mapped paths (drmtap_grab_mapped,
* drmtap_grab_mapped_fast). On the zero-copy path (drmtap_grab,
* drmtap_grab_desc) it is NOT guaranteed: it holds the raw, UNCONVERTED
* scanout mapping where one was possible and NULL where the scanout cannot
* be CPU-mapped (amdgpu GFX9+, discrete VRAM, nvidia). Do not read it after
* a zero-copy grab -- that it happens to work on one GPU and returns NULL on
* another is exactly how issue #36 was mistaken for a driver bug. */
void *data;
int dma_buf_fd; /**< DMA-BUF fd (zero-copy) or -1 (mapped) */
uint32_t width; /**< Frame width in pixels */
uint32_t height; /**< Frame height in pixels */
Expand All @@ -151,6 +158,16 @@ typedef struct {
* Returns a DMA-BUF file descriptor in frame->dma_buf_fd that can be
* passed directly to VAAPI/V4L2 encoders without copying pixel data.
*
* THERE ARE NO USABLE CPU PIXELS HERE. This call does no conversion and no detiling.
* `frame->data` is whatever the raw scanout mapping happened to give: still tiled where
* the scanout is tiled, and NULL on a GPU whose scanout cannot be CPU-mapped at all
* (amdgpu GFX9+, discrete VRAM, nvidia) -- and the call still returns 0 in both cases.
* Reading `frame->data` after a successful `drmtap_grab` is
* therefore not portable and renders black on those GPUs -- it was reported as a
* capture bug (issue #36). For pixels in this process use `drmtap_grab_mapped()`;
* to hand the buffer to another process use `drmtap_grab_desc()` plus
* `drmtap_convert_dmabuf()` on the receiving side.
*
* @param ctx Capture context
* @param frame Output frame info (caller-allocated)
* @return 0 on success, negative errno on error
Expand Down Expand Up @@ -198,9 +215,16 @@ int drmtap_grab_mapped(drmtap_ctx *ctx, drmtap_frame_info *frame);
* function OR until drmtap_close(). Do NOT call drmtap_frame_release()
* on frames from this function — cleanup is automatic.
*
* REQUIRES CAP_SYS_ADMIN IN THE CALLING PROCESS. Unlike `drmtap_grab_mapped()`, this
* entry point has NO helper fallback: caching a persistent CPU mapping per framebuffer
* is the whole point of it, and the helper hands over a fresh fd per grab, so there is
* nothing stable to cache. Without the capability it returns -EACCES on every call, on
* any GPU, and `drmtap_error()` names `drmtap_grab_mapped()` as the call to use instead.
*
* @param ctx Capture context
* @param frame Output frame info (caller-allocated, reused between calls)
* @return 0 = frame captured, negative errno on error
* @retval -EACCES The process lacks CAP_SYS_ADMIN (no helper fallback on this path)
*/
int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame);

Expand Down
39 changes: 38 additions & 1 deletion src/drm_grab.c
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,10 @@ void drmtap_fast_cleanup(drmtap_ctx *ctx) {
ctx->fast_last_fb_id = 0;
ctx->fast_initialized = 0;
ctx->fast_no_cpu_map = 0;
/* fast_no_privilege is deliberately NOT cleared: it records a property of the
* PROCESS (no CAP_SYS_ADMIN), not of the plane, the framebuffer or the device,
* so re-discovering it after every teardown would repeat the whole GetFB2 dance
* to reach the same verdict. */
}

// Find or allocate a slot for the given fb_id
Expand Down Expand Up @@ -1354,6 +1358,17 @@ int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame) {
return -ENOTSUP;
}

/* Established on an earlier call that this process cannot export the scanout and
* that this entry point has no helper fallback. Answer immediately and quietly:
* the caller has the reason in drmtap_error() from the first failure, and a
* capture loop calling this at frame rate must not produce a line per frame. */
if (ctx->fast_no_privilege) {
drmtap_set_error(ctx,
"drmtap_grab_mapped_fast needs CAP_SYS_ADMIN in this process and has no "
"helper fallback; use drmtap_grab_mapped()");
return -EACCES;
}

int ret;

/* Step 1: Find plane on first call only */
Expand Down Expand Up @@ -1496,8 +1511,30 @@ int drmtap_grab_mapped_fast(drmtap_ctx *ctx, drmtap_frame_info *frame) {
}

if (fb2->handles[0] == 0) {
/* drmModeGetFB2 zeroes the GEM handles for a caller without CAP_SYS_ADMIN.
* drmtap_grab_mapped treats that as "go through the helper"; this entry point
* has no such branch, so it cannot serve an unprivileged caller on ANY gpu.
* Say that, name the call that does work, and latch it so the next frame is
* answered at the top of the function instead of repeating this whole dance.
* Reported as endless cache-miss lines with the cause buried, which sent both
* the reporter and me after a tiling bug that was not there (issue #36). */
drmModeFreeFB2(fb2);
drmtap_set_error(ctx, "fast2: handles[0]==0, no CAP_SYS_ADMIN");
ctx->fast_no_privilege = 1;
/* Nothing on this path can be served again, so release whatever the slots hold
* (prime fds, mappings, GEM handles) now instead of at drmtap_close. Normally
* there is nothing: an unprivileged process never populated a slot in the first
* place. It matters for the one case that can, a process that captured with
* CAP_SYS_ADMIN and then dropped it, where the slots are live and unusable.
* Runs AFTER the flag is set, since cleanup deliberately leaves it alone. */
drmtap_fast_cleanup(ctx);
drmtap_debug_log(ctx,
"fast2: this process has no CAP_SYS_ADMIN, and the fast path has no helper "
"fallback (it caches a CPU mapping per framebuffer; a helper hands over a "
"fresh fd per grab, so there is nothing to cache). Use drmtap_grab_mapped(), "
"which falls back to the helper. Not repeated per frame.");
drmtap_set_error(ctx,
"drmtap_grab_mapped_fast needs CAP_SYS_ADMIN in this process and has no "
"helper fallback; use drmtap_grab_mapped()");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return -EACCES;
}

Expand Down
11 changes: 11 additions & 0 deletions src/drmtap_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ struct drmtap_ctx {
* the per-frame "miss" honest in the log: nothing was ever cached to hit. */
int fast_no_cpu_map;

/* Set once drmtap_grab_mapped_fast has established that this process cannot
* export the scanout itself (drmModeGetFB2 returns handles[0]==0 without
* CAP_SYS_ADMIN). Unlike drmtap_grab_mapped, the fast path has no helper
* fallback -- caching a persistent CPU mapping per framebuffer is its whole
* purpose, and a helper hands over a fresh fd per grab, so there is nothing
* stable to cache. It therefore cannot work for an unprivileged caller on
* any GPU. Sticky so the diagnosis is stated once instead of once per frame:
* reported as pages of "CACHE MISS ... cold start" with the real reason only
* in drmtap_error(), it read as a broken cache (issue #36). */
int fast_no_privilege;

/* Deswizzle shadow buffer (for read-only mmap'd DMA-BUFs).
* Grow-once and reused across grabs; capped at DRMTAP_MAX_FB_BYTES;
* freed in drmtap_close(). */
Expand Down
Loading