#include "display.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_NV12_FB 4 struct Display { char dev_path[64]; int fd; drmModeConnector *connector; drmModeEncoder *encoder; drmModeCrtc *saved_crtc; uint32_t crtc_id; uint32_t connector_id; int width; int height; /* The CRTC/mode size actually programmed on the connector. It can differ * from width/height (the scanout buffer size) after a monitor hot-plug: * the app allocates its output buffers once, so a rebuilt display may end * up driving a mode that does not match them. The plane then scales the * buffer into the mode instead of failing the whole scanout. */ int mode_w; int mode_h; int native_width; int native_height; /* Per-fd NV12 scanout FBs (the composer already produces NV12; we scan it * straight out on the plane and skip RGA colour conversion entirely -- * NV12->BGRA via RGA is broken on this driver, so it must not be used). */ struct { int key_fd; /* the fd passed to display_commit */ int owned_fd; /* dup'd fd keeping the dmabuf alive */ uint32_t handle; uint32_t fb_id; uint32_t stride; } fbs[MAX_NV12_FB]; int nfbs; uint32_t plane_id; uint32_t crtc_mode_id; uint32_t plane_fb_prop; uint32_t plane_crtc_prop; uint32_t plane_src_x_prop; uint32_t plane_src_y_prop; uint32_t plane_src_w_prop; uint32_t plane_src_h_prop; uint32_t plane_crtc_x_prop; uint32_t plane_crtc_y_prop; uint32_t plane_crtc_w_prop; uint32_t plane_crtc_h_prop; uint32_t conn_crtc_prop; uint32_t crtc_active_prop; uint32_t crtc_mode_id_prop; uint32_t conn_color_format_prop; uint32_t out_fence_prop; int flip_pending; int modeset_done; drmEventContext drm_ctx; /* Other planes sharing our CRTC (e.g. the desktop's RGB scans). They are * disabled on every commit so their colours can't blend under our NV12 * overlay (which makes the wall look purple after a hotplug). */ uint32_t ext_ids[8]; uint32_t ext_fb_prop[8]; uint32_t ext_crtc_prop[8]; int ext_n; }; /* Connector output format override for the first modeset commit. * 0 = auto (leave the driver's own negotiation alone), * 1 = force RGB, 2 = force YCbCr444, 3 = force YCbCr422, 4 = force YCbCr420. * Forcing RGB makes the VOP take the window through a real Y2R CSC path * instead of the YUV passthrough that comes out wrong after a hotplug. */ static int g_fmt_override = 1; int display_set_color_format(int fmt) { g_fmt_override = fmt; return 0; } static int dma_heap_alloc(const char *heap_name, size_t size, int *fd) { char path[64]; snprintf(path, sizeof(path), "/dev/dma_heap/%s", heap_name); int heap_fd = open(path, O_RDWR | O_CLOEXEC); if (heap_fd < 0) heap_fd = open("/dev/dma_heap/system-uncached", O_RDWR | O_CLOEXEC); if (heap_fd < 0) return -1; struct dma_heap_allocation_data data = { .len = size, .fd_flags = O_RDWR | O_CLOEXEC }; int ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data); close(heap_fd); if (ret < 0) return -1; *fd = data.fd; return 0; } static uint32_t find_prop_id(int fd, uint32_t obj_id, uint32_t obj_type, const char *name) { drmModeObjectProperties *props = drmModeObjectGetProperties(fd, obj_id, obj_type); if (!props) return 0; uint32_t id = 0; for (uint32_t i = 0; i < props->count_props; i++) { drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]); if (prop) { if (strcmp(prop->name, name) == 0) id = prop->prop_id; drmModeFreeProperty(prop); if (id) break; } } drmModeFreeObjectProperties(props); return id; } static int conn_allowed(int type) { const char *only = getenv("RTSP_CONN_ONLY"); if (only && strcmp(only, "ALL") == 0) return 1; /* legacy: accept any connected output */ /* Default (and RTSP_CONN_ONLY=HDMI): drive the real HDMI monitor only. * These boards report a phantom DSI connector as always-connected even * with no panel attached; handing the output to it would leave the real * HDMI monitor showing just the desktop. */ return type == DRM_MODE_CONNECTOR_HDMIA || type == DRM_MODE_CONNECTOR_HDMIB; } static int drm_get_connector(Display *disp) { drmModeRes *res = drmModeGetResources(disp->fd); if (!res) { fprintf(stderr, "drmModeGetResources failed\n"); return -1; } const char *only = getenv("RTSP_CONN_ONLY"); for (int i = 0; i < res->count_connectors; i++) { drmModeConnector *conn = drmModeGetConnector(disp->fd, res->connectors[i]); if (!conn) continue; if (conn_allowed(conn->connector_type) && conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) { disp->connector = conn; break; } drmModeFreeConnector(conn); } if (!disp->connector) { /* No connected output. Only fall back to another connector if it * actually has modes; a mode-less (unplugged) connector cannot drive * the CRTC and its modes[0] would be read out of bounds below. We fail * so the caller can retry until a monitor is plugged in. */ for (int i = 0; res && i < res->count_connectors && !disp->connector; i++) { drmModeConnector *c = drmModeGetConnector(disp->fd, res->connectors[i]); if (c && conn_allowed(c->connector_type) && c->count_modes > 0) disp->connector = c; else if (c) drmModeFreeConnector(c); } } if (!disp->connector || disp->connector->count_modes == 0) { if (disp->connector) { drmModeFreeConnector(disp->connector); disp->connector = NULL; } fprintf(stderr, "display: no monitor with an active mode (unplugged during boot%s%s); " "will retry until one is connected\n", only ? "; RTSP_CONN_ONLY=" : "", only ? only : ""); drmModeFreeResources(res); return -1; } disp->connector_id = disp->connector->connector_id; disp->native_width = disp->connector->modes[0].hdisplay; disp->native_height = disp->connector->modes[0].vdisplay; if (disp->connector->encoder_id) { disp->encoder = drmModeGetEncoder(disp->fd, disp->connector->encoder_id); if (disp->encoder) disp->crtc_id = disp->encoder->crtc_id; } if (!disp->encoder) { for (int i = 0; i < res->count_encoders; i++) { drmModeEncoder *enc = drmModeGetEncoder(disp->fd, res->encoders[i]); if (!enc) continue; if (enc->encoder_id == disp->connector->encoder_id || (enc->crtc_id > 0 && !disp->encoder)) { if (!disp->encoder) { disp->encoder = enc; disp->crtc_id = enc->crtc_id; } else drmModeFreeEncoder(enc); } else drmModeFreeEncoder(enc); } } if (disp->crtc_id == 0 && res->count_crtcs > 0) disp->crtc_id = res->crtcs[0]; drmModeFreeResources(res); return (disp->crtc_id != 0) ? 0 : -1; } static void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { (void)fd; (void)frame; (void)sec; (void)usec; Display *disp = data; disp->flip_pending = 0; } static int wait_prev_flip(Display *disp, int timeout_ms) { if (!disp->flip_pending) return 0; while (disp->flip_pending) { struct pollfd pfd = { .fd = disp->fd, .events = POLLIN }; int pret = poll(&pfd, 1, timeout_ms); if (pret > 0 && (pfd.revents & POLLIN)) drmHandleEvent(disp->fd, &disp->drm_ctx); else return -1; } return 0; } Display *display_create(const char *drm_dev) { Display *disp = calloc(1, sizeof(Display)); if (!disp) return NULL; strncpy(disp->dev_path, drm_dev ? drm_dev : "/dev/dri/card0", sizeof(disp->dev_path) - 1); disp->fd = open(disp->dev_path, O_RDWR | O_CLOEXEC); if (disp->fd < 0) { free(disp); return NULL; } for (int i = 0; i < MAX_NV12_FB; i++) { disp->fbs[i].key_fd = -1; disp->fbs[i].owned_fd = -1; } return disp; } void display_destroy(Display *disp) { if (!disp) return; if (disp->saved_crtc) { drmModeSetCrtc(disp->fd, disp->saved_crtc->crtc_id, disp->saved_crtc->buffer_id, disp->saved_crtc->x, disp->saved_crtc->y, &disp->connector_id, 1, &disp->saved_crtc->mode); drmModeFreeCrtc(disp->saved_crtc); } for (int i = 0; i < disp->nfbs; i++) { if (disp->fbs[i].fb_id) drmModeRmFB(disp->fd, disp->fbs[i].fb_id); if (disp->fbs[i].owned_fd >= 0) close(disp->fbs[i].owned_fd); } if (disp->encoder) drmModeFreeEncoder(disp->encoder); if (disp->connector) drmModeFreeConnector(disp->connector); if (disp->crtc_mode_id) drmModeDestroyPropertyBlob(disp->fd, disp->crtc_mode_id); if (disp->fd >= 0) { drmDropMaster(disp->fd); close(disp->fd); } free(disp); } static int setup_plane(Display *disp) { drmModePlaneRes *pres = drmModeGetPlaneResources(disp->fd); if (!pres) return -1; drmModeRes *res = drmModeGetResources(disp->fd); int crtc_index = -1; if (res) { for (int i = 0; i < res->count_crtcs; i++) { if (res->crtcs[i] == disp->crtc_id) { crtc_index = i; break; } } drmModeFreeResources(res); } if (crtc_index < 0) { fprintf(stderr, "display: CRTC %u not found\n", disp->crtc_id); drmModeFreePlaneResources(pres); return -1; } /* On this SoC the primary plane only scans RGB frames, while NV12 is only * supported by an overlay plane. We scan the composer's NV12 output * straight out on whichever plane advertises NV12 (preferred), falling back * to any primary plane otherwise. This avoids an NV12->BGRA RGA conversion * that is unsupported on this driver. */ drmModePlane *chosen = NULL; drmModePlane *fallback = NULL; for (uint32_t i = 0; i < pres->count_planes; i++) { drmModePlane *plane = drmModeGetPlane(disp->fd, pres->planes[i]); if (!plane) continue; if (!(plane->possible_crtcs & (1u << crtc_index))) { drmModeFreePlane(plane); continue; } uint32_t type_id = find_prop_id(disp->fd, plane->plane_id, DRM_MODE_OBJECT_PLANE, "type"); if (!type_id) { drmModeFreePlane(plane); continue; } drmModeObjectProperties *props = drmModeObjectGetProperties(disp->fd, plane->plane_id, DRM_MODE_OBJECT_PLANE); if (!props) { drmModeFreePlane(plane); continue; } uint64_t type_val = 0; for (uint32_t p = 0; p < props->count_props; p++) if (props->props[p] == type_id) type_val = props->prop_values[p]; drmModeFreeObjectProperties(props); int has_nv12 = 0; for (uint32_t f = 0; f < plane->count_formats; f++) if (plane->formats[f] == DRM_FORMAT_NV12) { has_nv12 = 1; break; } if (has_nv12) { chosen = plane; break; } if (type_val == DRM_PLANE_TYPE_PRIMARY && !fallback) fallback = plane; else drmModeFreePlane(plane); } if (!chosen) chosen = fallback; if (!chosen) { drmModeFreePlaneResources(pres); fprintf(stderr, "display: no usable plane found for crtc %u\n", disp->crtc_id); return -1; } disp->plane_id = chosen->plane_id; disp->plane_fb_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "FB_ID"); disp->plane_crtc_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_ID"); disp->plane_src_x_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "SRC_X"); disp->plane_src_y_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "SRC_Y"); disp->plane_src_w_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "SRC_W"); disp->plane_src_h_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "SRC_H"); disp->plane_crtc_x_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_X"); disp->plane_crtc_y_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_Y"); disp->plane_crtc_w_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_W"); disp->plane_crtc_h_prop = find_prop_id(disp->fd, chosen->plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_H"); fprintf(stderr, "display: using plane %u (NV12 scanout)\n", chosen->plane_id); drmModeFreePlane(chosen); if (fallback && fallback != chosen) drmModeFreePlane(fallback); drmModeFreePlaneResources(pres); return 0; } static int build_plane_commit(Display *disp, drmModeAtomicReq *req, uint32_t fb_id) { int props = 0; props += (disp->plane_fb_prop ? (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_fb_prop, fb_id) >= 0) : 0); props += (disp->plane_crtc_prop ? (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_prop, disp->crtc_id) >= 0) : 0); props += (disp->plane_src_x_prop ? (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_x_prop, (uint64_t)0) >= 0) : 0); props += (disp->plane_src_y_prop ? (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_y_prop, (uint64_t)0) >= 0) : 0); if (disp->plane_src_w_prop) props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_w_prop, (uint64_t)disp->width << 16) >= 0); if (disp->plane_src_h_prop) props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_h_prop, (uint64_t)disp->height << 16) >= 0); if (disp->plane_crtc_x_prop) props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_x_prop, (uint64_t)0) >= 0); if (disp->plane_crtc_y_prop) props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_y_prop, (uint64_t)0) >= 0); if (disp->plane_crtc_w_prop) props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_w_prop, (uint64_t)disp->mode_w) >= 0); if (disp->plane_crtc_h_prop) props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_h_prop, (uint64_t)disp->mode_h) >= 0); return props; } static void display_reset_output(Display *disp); /* Cache every OTHER plane that can target our CRTC (Xorg/desktop scans). * They must stay disabled while we render, or their RGB content blends under * our NV12 plane and tints the whole output. */ static void cache_ext_planes(Display *disp) { disp->ext_n = 0; drmModePlaneRes *pres = drmModeGetPlaneResources(disp->fd); if (!pres) return; drmModeRes *res = drmModeGetResources(disp->fd); int crtc_index = -1; if (res) { for (int i = 0; i < res->count_crtcs; i++) if (res->crtcs[i] == disp->crtc_id) { crtc_index = i; break; } drmModeFreeResources(res); } if (crtc_index < 0) { drmModeFreePlaneResources(pres); return; } for (uint32_t i = 0; i < pres->count_planes && disp->ext_n < 8; i++) { drmModePlane *pl = drmModeGetPlane(disp->fd, pres->planes[i]); if (!pl) continue; if (pl->plane_id != disp->plane_id && (pl->possible_crtcs & (1u << crtc_index))) { uint32_t idx = disp->ext_n; disp->ext_ids[idx] = pl->plane_id; disp->ext_fb_prop[idx] = find_prop_id(disp->fd, pl->plane_id, DRM_MODE_OBJECT_PLANE, "FB_ID"); disp->ext_crtc_prop[idx] = find_prop_id(disp->fd, pl->plane_id, DRM_MODE_OBJECT_PLANE, "CRTC_ID"); if (disp->ext_fb_prop[idx] || disp->ext_crtc_prop[idx]) disp->ext_n++; } drmModeFreePlane(pl); } drmModeFreePlaneResources(pres); } static void add_ext_planes_off(Display *disp, drmModeAtomicReq *req) { for (int i = 0; i < disp->ext_n; i++) { if (disp->ext_fb_prop[i]) drmModeAtomicAddProperty(req, disp->ext_ids[i], disp->ext_fb_prop[i], 0); if (disp->ext_crtc_prop[i]) drmModeAtomicAddProperty(req, disp->ext_ids[i], disp->ext_crtc_prop[i], 0); } } int display_init(Display *disp, int width, int height) { if (!disp || disp->fd < 0) return -1; if (drmSetMaster(disp->fd)) fprintf(stderr, "WARN: drmSetMaster: %s\n", strerror(errno)); drmSetClientCap(disp->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); drmSetClientCap(disp->fd, DRM_CLIENT_CAP_ATOMIC, 1); if (drm_get_connector(disp) < 0) return -1; /* Choose the CRTC mode. When the caller already owns scanout buffers of a * given size (the app allocates them once at start-up), prefer a connector * mode that matches them exactly so the plane never has to scale. Fall * back to the connector's preferred mode otherwise. */ drmModeModeInfo *mode = &disp->connector->modes[0]; if (width > 0 && height > 0) { for (int i = 0; i < disp->connector->count_modes; i++) { drmModeModeInfo *m = &disp->connector->modes[i]; if (m->hdisplay == width && m->vdisplay == height) { mode = m; break; } } } disp->mode_w = mode->hdisplay; disp->mode_h = mode->vdisplay; /* width/height stay the size of the frames that will be committed, so the * NV12 fb is always registered with the buffer's real geometry. */ disp->width = (width > 0) ? width : disp->mode_w; disp->height = (height > 0) ? height : disp->mode_h; disp->saved_crtc = drmModeGetCrtc(disp->fd, disp->crtc_id); if (setup_plane(disp) < 0) return -1; cache_ext_planes(disp); disp->conn_crtc_prop = find_prop_id(disp->fd, disp->connector_id, DRM_MODE_OBJECT_CONNECTOR, "CRTC_ID"); disp->conn_color_format_prop = find_prop_id(disp->fd, disp->connector_id, DRM_MODE_OBJECT_CONNECTOR, "color_format"); disp->crtc_active_prop = find_prop_id(disp->fd, disp->crtc_id, DRM_MODE_OBJECT_CRTC, "ACTIVE"); disp->crtc_mode_id_prop = find_prop_id(disp->fd, disp->crtc_id, DRM_MODE_OBJECT_CRTC, "MODE_ID"); disp->out_fence_prop = find_prop_id(disp->fd, disp->crtc_id, DRM_MODE_OBJECT_CRTC, "OUT_FENCE_PTR"); if (drmModeCreatePropertyBlob(disp->fd, mode, sizeof(*mode), &disp->crtc_mode_id)) { fprintf(stderr, "drmModeCreatePropertyBlob failed: %s\n", strerror(errno)); return -1; } disp->flip_pending = 0; memset(&disp->drm_ctx, 0, sizeof(disp->drm_ctx)); disp->drm_ctx.version = DRM_EVENT_CONTEXT_VERSION; disp->drm_ctx.page_flip_handler = page_flip_handler; /* The CRTC is activated on the first display_commit (which supplies the * NV12 buffer). No RGA / colour conversion is used anywhere in display. */ display_reset_output(disp); fprintf(stderr, "display: init OK buffer %dx%d mode %dx%d crtc=%u plane=%u%s\n", disp->width, disp->height, disp->mode_w, disp->mode_h, disp->crtc_id, disp->plane_id, (disp->width != disp->mode_w || disp->height != disp->mode_h) ? " (buffer != mode, plane will scale)" : ""); return 0; } /* Add (or find) an NV12 scanout FB for this fd. Returns fb_id or 0. */ static uint32_t get_nv12_fb(Display *disp, int fd, int stride) { for (int i = 0; i < disp->nfbs; i++) if (disp->fbs[i].key_fd == fd) return disp->fbs[i].fb_id; uint32_t handle = 0; if (drmPrimeFDToHandle(disp->fd, fd, &handle)) { fprintf(stderr, "display: drmPrimeFDToHandle(nv12) failed: %s\n", strerror(errno)); return 0; } uint32_t handles[4] = {handle, handle, 0, 0}; uint32_t pitches[4] = {(uint32_t)stride, (uint32_t)stride, 0, 0}; uint32_t offsets[4] = {0, (uint32_t)stride * disp->height, 0, 0}; uint32_t fb_id = 0; if (drmModeAddFB2(disp->fd, disp->width, disp->height, DRM_FORMAT_NV12, handles, pitches, offsets, &fb_id, 0)) { fprintf(stderr, "display: drmModeAddFB2(NV12) failed: %s\n", strerror(errno)); return 0; } int swap_idx = -1; if (disp->nfbs < MAX_NV12_FB) { swap_idx = disp->nfbs; disp->nfbs++; } else { /* table full: drop the last entry to make room (free its fb/handle) */ swap_idx = disp->nfbs - 1; drmModeRmFB(disp->fd, disp->fbs[swap_idx].fb_id); if (disp->fbs[swap_idx].owned_fd >= 0) close(disp->fbs[swap_idx].owned_fd); } int owned = fcntl(fd, F_DUPFD_CLOEXEC, 3); disp->fbs[swap_idx].key_fd = fd; disp->fbs[swap_idx].owned_fd = owned; disp->fbs[swap_idx].handle = handle; disp->fbs[swap_idx].fb_id = fb_id; disp->fbs[swap_idx].stride = (uint32_t)stride; return fb_id; } int display_signal_lost(Display *disp) { if (!disp || disp->fd < 0) return 1; drmModeConnector *conn = drmModeGetConnector(disp->fd, disp->connector_id); if (!conn) return 1; int lost = !(conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0); drmModeFreeConnector(conn); return lost; } static void display_reset_output(Display *disp) { if (!disp || disp->fd < 0) return; drmModeAtomicReq *req = drmModeAtomicAlloc(); if (!req) return; if (disp->plane_fb_prop) drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_fb_prop, 0); if (disp->plane_crtc_prop) drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_prop, 0); add_ext_planes_off(disp, req); if (disp->crtc_active_prop) drmModeAtomicAddProperty(req, disp->crtc_id, disp->crtc_active_prop, 0); if (disp->conn_crtc_prop) drmModeAtomicAddProperty(req, disp->connector_id, disp->conn_crtc_prop, 0); drmModeAtomicCommit(disp->fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_NONBLOCK, NULL); drmModeAtomicFree(req); } int display_commit(Display *disp, int nv12_fd, int nv12_stride) { if (!disp || disp->fd < 0 || nv12_fd < 0) return -1; if (wait_prev_flip(disp, 300) < 0) { fprintf(stderr, "display: previous flip not completed, dropping frame\n"); return -1; } uint32_t fb_id = get_nv12_fb(disp, nv12_fd, nv12_stride); if (!fb_id) return -1; drmModeAtomicReq *req = drmModeAtomicAlloc(); if (!req) return -1; int is_first = !disp->modeset_done; if (is_first) { /* First commit: enable connector + CRTC modeset together with the * plane so VOP shows the first NV12 frame immediately. */ if (disp->conn_crtc_prop) drmModeAtomicAddProperty(req, disp->connector_id, disp->conn_crtc_prop, disp->crtc_id); if (g_fmt_override > 0 && disp->conn_color_format_prop) drmModeAtomicAddProperty(req, disp->connector_id, disp->conn_color_format_prop, (uint64_t)(g_fmt_override - 1)); if (disp->crtc_active_prop) drmModeAtomicAddProperty(req, disp->crtc_id, disp->crtc_active_prop, 1); if (disp->crtc_mode_id_prop) drmModeAtomicAddProperty(req, disp->crtc_id, disp->crtc_mode_id_prop, disp->crtc_mode_id); build_plane_commit(disp, req, fb_id); add_ext_planes_off(disp, req); } else { /* Page flip: just swap the plane FB. */ if (disp->plane_fb_prop) drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_fb_prop, fb_id); add_ext_planes_off(disp, req); } int out_fence_fd = -1; if (disp->out_fence_prop && !is_first) { uint64_t fence_ptr = (uint64_t)(unsigned long)&out_fence_fd; drmModeAtomicAddProperty(req, disp->crtc_id, disp->out_fence_prop, fence_ptr); } int ret; uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | (is_first ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0); int retries = 5; do { ret = drmModeAtomicCommit(disp->fd, req, flags, disp); if (ret == 0) break; if (errno == EBUSY) { usleep(16000); continue; } /* A stale state after hot-plug can make a plain page flip EINVAL; * schedule the full reset+modeset on the next frame. */ if (!is_first) disp->modeset_done = 0; fprintf(stderr, "display: AtomicCommit failed: %s\n", strerror(errno)); if (out_fence_fd >= 0) close(out_fence_fd); drmModeAtomicFree(req); return -1; } while (--retries > 0); if (ret) { fprintf(stderr, "display: AtomicCommit failed after %d retries\n", retries); if (out_fence_fd >= 0) close(out_fence_fd); drmModeAtomicFree(req); return -1; } drmModeAtomicFree(req); disp->modeset_done = 1; disp->flip_pending = 1; int confirmed = 0; if (out_fence_fd >= 0) { struct pollfd pfd = { .fd = out_fence_fd, .events = POLLIN }; int pret = poll(&pfd, 1, 300); if (pret > 0 && (pfd.revents & POLLIN)) confirmed = 1; close(out_fence_fd); } if (!confirmed) confirmed = (wait_prev_flip(disp, 300) == 0); if (!confirmed) { fprintf(stderr, "display: flip not confirmed at vblank, frame status unknown\n"); return -1; } return 0; } int display_get_width(Display *disp) { return disp->width; } int display_get_height(Display *disp) { return disp->height; }