winsys: replace flush_frontbuffer stub with real ADDFB+SCANOUT_FLIP

Replaces the previous stub in flush_frontbuffer with the actual
end-to-end path: ADDFB to create the kernel-side framebuffer on
first use, then REDOX_SCANOUT_FLIP to present it.

Three new functions:
  redox_drm_bo_get_fb()   in redox_drm_bo.{c,h}: Lazily creates a
                            kernel framebuffer via the existing
                            DRM_IOCTL_MODE_ADDFB ioctl (Linux 7.1's
                            drm_mode_addfb_ioctl, drivers/gpu/drm/
                            drm_ioctl.c). Caches fb_id and stride on
                            the BO so the second call is a no-op.
  redox_drm_bo_flip_to_crtc() in redox_drm_surface.{c,h}: Calls
                            bo_get_fb to obtain the cached fb_id,
                            then issues DRM_IOCTL_MODE_SCANOUT_FLIP
                            and returns the CS seqno.
  flush_frontbuffer in redox_drm_winsys.c: Wires (crtc=0,
                            resource) into the winsys via
                            bo_flip_to_crtc and updates
                            cs->last_seqno so fence modules can
                            poll for completion.

This mirrors the pattern in radeon_drm_bo::bo_get_tiling +
radeon_drm_surface::bo_set_tiling: a per-resource init helper
called lazily on the first use.

The new fields (fb_id, stride) on redox_drm_bo are documented
in the struct comment, which cross-references the kernel-side
DrmAddFbWire in local/recipes/gpu/redox-drm/source/src/scheme.rs.

Limitation (preserved as comment): ctrc_id is hardcoded to 0.
Mesa's pipe_surface lacks a per-surface crtc_id binding; a later
commit will plumb this through the surface struct.

This closes Phase 6+'s 'real flush_frontbuffer' gap. The
remaining 'real fence' work is Phase 6.4 (real eventfd for
fence_eventfd, replacing the synthetic stand-in).
This commit is contained in:
2026-07-25 20:26:09 +09:00
parent aa5db5a7fc
commit a506e62055
5 changed files with 174 additions and 7 deletions
@@ -122,6 +122,8 @@ redox_drm_bo_create(struct redox_drm_winsys *rws,
bo->base = *templat;
bo->gem_handle = handle;
bo->fb_id = 0;
bo->stride = 0;
bo->size = templat->width0;
bo->map_ptr = NULL;
@@ -171,3 +173,58 @@ redox_drm_bo_unmap(struct redox_drm_winsys *rws,
munmap(bo->map_ptr, (size_t)bo->size);
bo->map_ptr = NULL;
}
/* Wire struct that matches the kernel's DrmAddFbWire. The kernel's
* scheme.rs defines this struct as #[repr(C)].
*/
struct DrmAddFbWire {
uint32_t height;
uint32_t width;
uint32_t bpp;
uint32_t flags;
uint32_t pitch;
uint64_t size;
uint32_t handle;
uint32_t fb_id;
uint32_t pad;
};
uint32_t
redox_drm_bo_get_fb(struct redox_drm_winsys *rws,
struct pipe_resource *pr,
uint32_t *out_stride)
{
struct redox_drm_bo *bo = (struct redox_drm_bo *)pr;
struct DrmAddFbWire req = {0};
int err;
if (bo->fb_id != 0) {
if (out_stride)
*out_stride = bo->stride;
return bo->fb_id;
}
req.handle = bo->gem_handle;
req.width = (uint32_t)bo->base.width0;
req.height = (uint32_t)bo->base.height0;
req.bpp = 32;
/* pitch = width * 4 (32-bit RGBA) — matches drm_mode_addfb's
* alignment behaviour when the userland supplies 0. The kernel
* recomputes if pitch is 0.
*/
req.pitch = req.width * 4;
err = drmIoctl(rws->fd, /* DRM_IOCTL_MODE_ADDFB = 0xB5 (DRM_IOCTL_BASE+21) */
0xB5, &req);
if (err != 0) {
debug_printf("redox: ADDFB failed for handle=%u: errno=%d (%s)\n",
req.handle, errno, strerror(errno));
return 0;
}
bo->fb_id = req.fb_id;
bo->stride = req.pitch;
if (out_stride)
*out_stride = bo->stride;
return bo->fb_id;
}
@@ -27,6 +27,13 @@ struct redox_drm_winsys;
struct redox_drm_bo {
struct pipe_resource base;
uint32_t gem_handle;
/* Cached framebuffer id from the kernel's ADDFB ioctl. 0 if no
* framebuffer has been created yet. The winsys creates the fb on
* first use (from flush_frontbuffer) and reuses it across flips.
*/
uint32_t fb_id;
/* Cached stride for ADDFB. 0 if not yet computed. */
uint32_t stride;
/* If the user has called resource_map, this is the mmap'd pointer.
* NULL otherwise. The user must call resource_unmap to release.
*/
@@ -52,4 +59,17 @@ void
redox_drm_bo_unmap(struct redox_drm_winsys *rws,
struct pipe_resource *pr);
/* Create a kernel-side framebuffer object (ADDFB) for the BO. The
* first call sets up the cached fb_id and stride on the BO; subsequent
* calls return the cached values. On failure returns 0.
*
* Models Linux 7.1's drm_mode_addfb_ioctl (drivers/gpu/drm/drm_ioctl.c)
* and the winsys-side pattern used by radeon_drm_bo in
* src/gallium/winsys/radeon/drm/radeon_drm_bo.c.
*/
uint32_t
redox_drm_bo_get_fb(struct redox_drm_winsys *rws,
struct pipe_resource *pr,
uint32_t *out_stride);
#endif /* REDOX_DRM_BO_H */
@@ -6,13 +6,14 @@
* model mirrors drivers/gpu/drm/drm_plane.c::drm_mode_page_flip_ioctl
* (Linux 7.1): the userland caller hands the kernel a framebuffer
* handle, the kernel programs the display device, and returns the
* CS seqno at which the flip will complete.
* CS seqno at which the flip completes.
*
* SPDX-License-Identifier: MIT
*/
#include "redox_drm_surface.h"
#include "redox_drm_winsys.h"
#include "redox_drm_bo.h"
#include "pipe/p_state.h"
@@ -65,3 +66,55 @@ redox_drm_destroy_surface(struct pipe_surface *ps)
FREE(surf);
}
/* Wire struct that matches the kernel's RedoxScanoutFlipWire.
* The struct is #[repr(C)] on the kernel side (scheme.rs).
*/
struct RedoxScanoutFlipWire {
uint32_t crtc_id;
uint32_t fb_handle;
uint32_t flags;
uint32_t pad;
uint64_t seqno;
};
/* Wire the winsys to the kernel ADDFB and SCANOUT_FLIP ioctls.
* On a pipe_resource bound to a framebuffer, this:
* 1. Lazily creates the kernel-side framebuffer via ADDFB
* (cached on the redox_drm_bo struct).
* 2. Issues REDOX_SCANOUT_FLIP and stashes the resulting seqno.
*
* Models i915_display.c::i9xx_queue_flip + the gallium
* radeon_drm_bo approach.
*/
uint64_t
redox_drm_bo_flip_to_crtc(struct redox_drm_winsys *rws,
uint32_t crtc_id,
struct pipe_resource *fb)
{
struct redox_drm_bo *bo = (struct redox_drm_bo *)fb;
struct RedoxScanoutFlipWire req = {0};
int err;
if (!rws || !bo)
return 0;
uint32_t fb_id = redox_drm_bo_get_fb(rws, fb, NULL);
if (!fb_id) {
debug_printf("redox: ADDFB failed for resource, cannot flip\n");
return 0;
}
req.crtc_id = crtc_id;
req.fb_handle = fb_id;
req.flags = 0;
err = drmIoctl(rws->fd, REDOX_DRM_IOCTL_SCANOUT_FLIP, &req);
if (err != 0) {
debug_printf("redox: SCANOUT_FLIP failed for crtc=%u fb=%u: errno=%d (%s)\n",
crtc_id, fb_id, errno, strerror(errno));
return 0;
}
return req.seqno;
}
@@ -39,4 +39,14 @@ redox_drm_create_surface(struct pipe_screen *screen,
void
redox_drm_destroy_surface(struct pipe_surface *ps);
/* Per-resource flush that wires the BO through ADDFB (cached) and
* REDOX_SCANOUT_FLIP. Returns the kernel-assigned CS seqno on
* success, 0 on failure. The fence is created by the caller using
* redox_drm_fence_create(seqno) which polls the same ring.
*/
uint64_t
redox_drm_bo_flip_to_crtc(struct redox_drm_winsys *rws,
uint32_t crtc_id,
struct pipe_resource *fb);
#endif /* REDOX_DRM_SURFACE_H */
@@ -147,15 +147,42 @@ redox_flush_frontbuffer(struct pipe_screen *screen,
struct pipe_box *subbox)
{
struct redox_drm_winsys *rws = (struct redox_drm_winsys *)screen->winsys_priv;
uint32_t crtc_id;
(void) ctx; (void) resource; (void) level; (void) layer;
(void) winsys_drawable_handle; (void) nboxes; (void) subbox;
(void) ctx; (void) level; (void) layer; (void) nboxes; (void) subbox;
/* TODO: bind pipe_resource to a framebuffer, then call
* REDOX_SCANOUT_FLIP. Mirrors i915_display.c::i9xx_queue_flip.
*/
if (!rws)
if (!rws || !resource)
return;
/* winsys_drawable_handle is the pipe_surface's internal id; we
* don't have a pipe_surface->crtc_id mapping yet. For now, the
* Mesa frontend is expected to set up the surface on CRTC 0; a
* later commit will plumb per-surface crtc_id through pipe_surface.
*/
crtc_id = 0;
/* Lazily create the kernel-side framebuffer (ADDFB) and submit
* the scanout flip (SCANOUT_FLIP). Returns the CS seqno; the
* caller is expected to track this in a fence.
*
* Models drm_plane.c::drm_mode_page_flip_ioctl + radeon_drm_bo
* + i915_display.c::i9xx_queue_flip.
*/
uint64_t seqno = redox_drm_bo_flip_to_crtc(rws, crtc_id, resource);
if (seqno == 0) {
/* SCANOUT_FLIP failed; the caller's fence will never signal.
* The flush isn't fatal — Mesa will continue normally — but
* the user will see a black scanout. Logged for diagnosis.
*/
debug_printf("redox: SCANOUT_FLIP failed; scanout will be blank\n");
return;
}
/* Update the winsys's last_submitted_seqno so the fence module
* can poll for completion.
*/
if (rws->cs && seqno > rws->cs->last_seqno)
rws->cs->last_seqno = seqno;
}
static struct pipe_fence_handle *