display.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. #include "display.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <fcntl.h>
  7. #include <errno.h>
  8. #include <sys/mman.h>
  9. #include <sys/ioctl.h>
  10. #include <poll.h>
  11. #include <xf86drm.h>
  12. #include <xf86drmMode.h>
  13. #include <drm_fourcc.h>
  14. #include <linux/dma-heap.h>
  15. #include <linux/dma-buf.h>
  16. #define MAX_NV12_FB 4
  17. struct Display {
  18. char dev_path[64];
  19. int fd;
  20. drmModeConnector *connector;
  21. drmModeEncoder *encoder;
  22. drmModeCrtc *saved_crtc;
  23. uint32_t crtc_id;
  24. uint32_t connector_id;
  25. int width;
  26. int height;
  27. /* The CRTC/mode size actually programmed on the connector. It can differ
  28. * from width/height (the scanout buffer size) after a monitor hot-plug:
  29. * the app allocates its output buffers once, so a rebuilt display may end
  30. * up driving a mode that does not match them. The plane then scales the
  31. * buffer into the mode instead of failing the whole scanout. */
  32. int mode_w;
  33. int mode_h;
  34. int native_width;
  35. int native_height;
  36. /* Per-fd NV12 scanout FBs (the composer already produces NV12; we scan it
  37. * straight out on the plane and skip RGA colour conversion entirely --
  38. * NV12->BGRA via RGA is broken on this driver, so it must not be used). */
  39. struct {
  40. int key_fd; /* the fd passed to display_commit */
  41. int owned_fd; /* dup'd fd keeping the dmabuf alive */
  42. uint32_t handle;
  43. uint32_t fb_id;
  44. uint32_t stride;
  45. } fbs[MAX_NV12_FB];
  46. int nfbs;
  47. uint32_t plane_id;
  48. uint32_t crtc_mode_id;
  49. uint32_t plane_fb_prop;
  50. uint32_t plane_crtc_prop;
  51. uint32_t plane_src_x_prop;
  52. uint32_t plane_src_y_prop;
  53. uint32_t plane_src_w_prop;
  54. uint32_t plane_src_h_prop;
  55. uint32_t plane_crtc_x_prop;
  56. uint32_t plane_crtc_y_prop;
  57. uint32_t plane_crtc_w_prop;
  58. uint32_t plane_crtc_h_prop;
  59. uint32_t conn_crtc_prop;
  60. uint32_t crtc_active_prop;
  61. uint32_t crtc_mode_id_prop;
  62. uint32_t conn_color_format_prop;
  63. uint32_t out_fence_prop;
  64. int flip_pending;
  65. int modeset_done;
  66. drmEventContext drm_ctx;
  67. /* Other planes sharing our CRTC (e.g. the desktop's RGB scans). They are
  68. * disabled on every commit so their colours can't blend under our NV12
  69. * overlay (which makes the wall look purple after a hotplug). */
  70. uint32_t ext_ids[8];
  71. uint32_t ext_fb_prop[8];
  72. uint32_t ext_crtc_prop[8];
  73. int ext_n;
  74. };
  75. /* Connector output format override for the first modeset commit.
  76. * 0 = auto (leave the driver's own negotiation alone),
  77. * 1 = force RGB, 2 = force YCbCr444, 3 = force YCbCr422, 4 = force YCbCr420.
  78. * Forcing RGB makes the VOP take the window through a real Y2R CSC path
  79. * instead of the YUV passthrough that comes out wrong after a hotplug. */
  80. static int g_fmt_override = 1;
  81. int display_set_color_format(int fmt) {
  82. g_fmt_override = fmt;
  83. return 0;
  84. }
  85. static int dma_heap_alloc(const char *heap_name, size_t size, int *fd) {
  86. char path[64];
  87. snprintf(path, sizeof(path), "/dev/dma_heap/%s", heap_name);
  88. int heap_fd = open(path, O_RDWR | O_CLOEXEC);
  89. if (heap_fd < 0) heap_fd = open("/dev/dma_heap/system-uncached", O_RDWR | O_CLOEXEC);
  90. if (heap_fd < 0) return -1;
  91. struct dma_heap_allocation_data data = { .len = size, .fd_flags = O_RDWR | O_CLOEXEC };
  92. int ret = ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data);
  93. close(heap_fd);
  94. if (ret < 0) return -1;
  95. *fd = data.fd;
  96. return 0;
  97. }
  98. static uint32_t find_prop_id(int fd, uint32_t obj_id, uint32_t obj_type, const char *name) {
  99. drmModeObjectProperties *props = drmModeObjectGetProperties(fd, obj_id, obj_type);
  100. if (!props) return 0;
  101. uint32_t id = 0;
  102. for (uint32_t i = 0; i < props->count_props; i++) {
  103. drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]);
  104. if (prop) {
  105. if (strcmp(prop->name, name) == 0) id = prop->prop_id;
  106. drmModeFreeProperty(prop);
  107. if (id) break;
  108. }
  109. }
  110. drmModeFreeObjectProperties(props);
  111. return id;
  112. }
  113. static int conn_allowed(int type) {
  114. const char *only = getenv("RTSP_CONN_ONLY");
  115. if (only && strcmp(only, "ALL") == 0) return 1; /* legacy: accept any connected output */
  116. /* Default (and RTSP_CONN_ONLY=HDMI): drive the real HDMI monitor only.
  117. * These boards report a phantom DSI connector as always-connected even
  118. * with no panel attached; handing the output to it would leave the real
  119. * HDMI monitor showing just the desktop. */
  120. return type == DRM_MODE_CONNECTOR_HDMIA || type == DRM_MODE_CONNECTOR_HDMIB;
  121. }
  122. static int drm_get_connector(Display *disp) {
  123. drmModeRes *res = drmModeGetResources(disp->fd);
  124. if (!res) { fprintf(stderr, "drmModeGetResources failed\n"); return -1; }
  125. const char *only = getenv("RTSP_CONN_ONLY");
  126. for (int i = 0; i < res->count_connectors; i++) {
  127. drmModeConnector *conn = drmModeGetConnector(disp->fd, res->connectors[i]);
  128. if (!conn) continue;
  129. if (conn_allowed(conn->connector_type) &&
  130. conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) {
  131. disp->connector = conn;
  132. break;
  133. }
  134. drmModeFreeConnector(conn);
  135. }
  136. if (!disp->connector) {
  137. /* No connected output. Only fall back to another connector if it
  138. * actually has modes; a mode-less (unplugged) connector cannot drive
  139. * the CRTC and its modes[0] would be read out of bounds below. We fail
  140. * so the caller can retry until a monitor is plugged in. */
  141. for (int i = 0; res && i < res->count_connectors && !disp->connector; i++) {
  142. drmModeConnector *c = drmModeGetConnector(disp->fd, res->connectors[i]);
  143. if (c && conn_allowed(c->connector_type) && c->count_modes > 0)
  144. disp->connector = c;
  145. else if (c) drmModeFreeConnector(c);
  146. }
  147. }
  148. if (!disp->connector || disp->connector->count_modes == 0) {
  149. if (disp->connector) {
  150. drmModeFreeConnector(disp->connector);
  151. disp->connector = NULL;
  152. }
  153. fprintf(stderr, "display: no monitor with an active mode (unplugged during boot%s%s); "
  154. "will retry until one is connected\n",
  155. only ? "; RTSP_CONN_ONLY=" : "", only ? only : "");
  156. drmModeFreeResources(res);
  157. return -1;
  158. }
  159. disp->connector_id = disp->connector->connector_id;
  160. disp->native_width = disp->connector->modes[0].hdisplay;
  161. disp->native_height = disp->connector->modes[0].vdisplay;
  162. if (disp->connector->encoder_id) {
  163. disp->encoder = drmModeGetEncoder(disp->fd, disp->connector->encoder_id);
  164. if (disp->encoder) disp->crtc_id = disp->encoder->crtc_id;
  165. }
  166. if (!disp->encoder) {
  167. for (int i = 0; i < res->count_encoders; i++) {
  168. drmModeEncoder *enc = drmModeGetEncoder(disp->fd, res->encoders[i]);
  169. if (!enc) continue;
  170. if (enc->encoder_id == disp->connector->encoder_id ||
  171. (enc->crtc_id > 0 && !disp->encoder)) {
  172. if (!disp->encoder) { disp->encoder = enc; disp->crtc_id = enc->crtc_id; }
  173. else drmModeFreeEncoder(enc);
  174. } else drmModeFreeEncoder(enc);
  175. }
  176. }
  177. if (disp->crtc_id == 0 && res->count_crtcs > 0)
  178. disp->crtc_id = res->crtcs[0];
  179. drmModeFreeResources(res);
  180. return (disp->crtc_id != 0) ? 0 : -1;
  181. }
  182. static void page_flip_handler(int fd, unsigned int frame, unsigned int sec,
  183. unsigned int usec, void *data) {
  184. (void)fd; (void)frame; (void)sec; (void)usec;
  185. Display *disp = data;
  186. disp->flip_pending = 0;
  187. }
  188. static int wait_prev_flip(Display *disp, int timeout_ms) {
  189. if (!disp->flip_pending) return 0;
  190. while (disp->flip_pending) {
  191. struct pollfd pfd = { .fd = disp->fd, .events = POLLIN };
  192. int pret = poll(&pfd, 1, timeout_ms);
  193. if (pret > 0 && (pfd.revents & POLLIN))
  194. drmHandleEvent(disp->fd, &disp->drm_ctx);
  195. else
  196. return -1;
  197. }
  198. return 0;
  199. }
  200. Display *display_create(const char *drm_dev) {
  201. Display *disp = calloc(1, sizeof(Display));
  202. if (!disp) return NULL;
  203. strncpy(disp->dev_path, drm_dev ? drm_dev : "/dev/dri/card0", sizeof(disp->dev_path) - 1);
  204. disp->fd = open(disp->dev_path, O_RDWR | O_CLOEXEC);
  205. if (disp->fd < 0) { free(disp); return NULL; }
  206. for (int i = 0; i < MAX_NV12_FB; i++) { disp->fbs[i].key_fd = -1; disp->fbs[i].owned_fd = -1; }
  207. return disp;
  208. }
  209. void display_destroy(Display *disp) {
  210. if (!disp) return;
  211. if (disp->saved_crtc) {
  212. drmModeSetCrtc(disp->fd, disp->saved_crtc->crtc_id,
  213. disp->saved_crtc->buffer_id,
  214. disp->saved_crtc->x, disp->saved_crtc->y,
  215. &disp->connector_id, 1, &disp->saved_crtc->mode);
  216. drmModeFreeCrtc(disp->saved_crtc);
  217. }
  218. for (int i = 0; i < disp->nfbs; i++) {
  219. if (disp->fbs[i].fb_id) drmModeRmFB(disp->fd, disp->fbs[i].fb_id);
  220. if (disp->fbs[i].owned_fd >= 0) close(disp->fbs[i].owned_fd);
  221. }
  222. if (disp->encoder) drmModeFreeEncoder(disp->encoder);
  223. if (disp->connector) drmModeFreeConnector(disp->connector);
  224. if (disp->crtc_mode_id) drmModeDestroyPropertyBlob(disp->fd, disp->crtc_mode_id);
  225. if (disp->fd >= 0) {
  226. drmDropMaster(disp->fd);
  227. close(disp->fd);
  228. }
  229. free(disp);
  230. }
  231. static int setup_plane(Display *disp) {
  232. drmModePlaneRes *pres = drmModeGetPlaneResources(disp->fd);
  233. if (!pres) return -1;
  234. drmModeRes *res = drmModeGetResources(disp->fd);
  235. int crtc_index = -1;
  236. if (res) {
  237. for (int i = 0; i < res->count_crtcs; i++) {
  238. if (res->crtcs[i] == disp->crtc_id) { crtc_index = i; break; }
  239. }
  240. drmModeFreeResources(res);
  241. }
  242. if (crtc_index < 0) {
  243. fprintf(stderr, "display: CRTC %u not found\n", disp->crtc_id);
  244. drmModeFreePlaneResources(pres);
  245. return -1;
  246. }
  247. /* On this SoC the primary plane only scans RGB frames, while NV12 is only
  248. * supported by an overlay plane. We scan the composer's NV12 output
  249. * straight out on whichever plane advertises NV12 (preferred), falling back
  250. * to any primary plane otherwise. This avoids an NV12->BGRA RGA conversion
  251. * that is unsupported on this driver. */
  252. drmModePlane *chosen = NULL;
  253. drmModePlane *fallback = NULL;
  254. for (uint32_t i = 0; i < pres->count_planes; i++) {
  255. drmModePlane *plane = drmModeGetPlane(disp->fd, pres->planes[i]);
  256. if (!plane) continue;
  257. if (!(plane->possible_crtcs & (1u << crtc_index))) { drmModeFreePlane(plane); continue; }
  258. uint32_t type_id = find_prop_id(disp->fd, plane->plane_id,
  259. DRM_MODE_OBJECT_PLANE, "type");
  260. if (!type_id) { drmModeFreePlane(plane); continue; }
  261. drmModeObjectProperties *props = drmModeObjectGetProperties(disp->fd,
  262. plane->plane_id, DRM_MODE_OBJECT_PLANE);
  263. if (!props) { drmModeFreePlane(plane); continue; }
  264. uint64_t type_val = 0;
  265. for (uint32_t p = 0; p < props->count_props; p++)
  266. if (props->props[p] == type_id) type_val = props->prop_values[p];
  267. drmModeFreeObjectProperties(props);
  268. int has_nv12 = 0;
  269. for (uint32_t f = 0; f < plane->count_formats; f++)
  270. if (plane->formats[f] == DRM_FORMAT_NV12) { has_nv12 = 1; break; }
  271. if (has_nv12) { chosen = plane; break; }
  272. if (type_val == DRM_PLANE_TYPE_PRIMARY && !fallback) fallback = plane;
  273. else drmModeFreePlane(plane);
  274. }
  275. if (!chosen) chosen = fallback;
  276. if (!chosen) {
  277. drmModeFreePlaneResources(pres);
  278. fprintf(stderr, "display: no usable plane found for crtc %u\n", disp->crtc_id);
  279. return -1;
  280. }
  281. disp->plane_id = chosen->plane_id;
  282. disp->plane_fb_prop = find_prop_id(disp->fd, chosen->plane_id,
  283. DRM_MODE_OBJECT_PLANE, "FB_ID");
  284. disp->plane_crtc_prop = find_prop_id(disp->fd, chosen->plane_id,
  285. DRM_MODE_OBJECT_PLANE, "CRTC_ID");
  286. disp->plane_src_x_prop = find_prop_id(disp->fd, chosen->plane_id,
  287. DRM_MODE_OBJECT_PLANE, "SRC_X");
  288. disp->plane_src_y_prop = find_prop_id(disp->fd, chosen->plane_id,
  289. DRM_MODE_OBJECT_PLANE, "SRC_Y");
  290. disp->plane_src_w_prop = find_prop_id(disp->fd, chosen->plane_id,
  291. DRM_MODE_OBJECT_PLANE, "SRC_W");
  292. disp->plane_src_h_prop = find_prop_id(disp->fd, chosen->plane_id,
  293. DRM_MODE_OBJECT_PLANE, "SRC_H");
  294. disp->plane_crtc_x_prop = find_prop_id(disp->fd, chosen->plane_id,
  295. DRM_MODE_OBJECT_PLANE, "CRTC_X");
  296. disp->plane_crtc_y_prop = find_prop_id(disp->fd, chosen->plane_id,
  297. DRM_MODE_OBJECT_PLANE, "CRTC_Y");
  298. disp->plane_crtc_w_prop = find_prop_id(disp->fd, chosen->plane_id,
  299. DRM_MODE_OBJECT_PLANE, "CRTC_W");
  300. disp->plane_crtc_h_prop = find_prop_id(disp->fd, chosen->plane_id,
  301. DRM_MODE_OBJECT_PLANE, "CRTC_H");
  302. fprintf(stderr, "display: using plane %u (NV12 scanout)\n", chosen->plane_id);
  303. drmModeFreePlane(chosen);
  304. if (fallback && fallback != chosen) drmModeFreePlane(fallback);
  305. drmModeFreePlaneResources(pres);
  306. return 0;
  307. }
  308. static int build_plane_commit(Display *disp, drmModeAtomicReq *req, uint32_t fb_id) {
  309. int props = 0;
  310. props += (disp->plane_fb_prop ?
  311. (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_fb_prop, fb_id) >= 0) : 0);
  312. props += (disp->plane_crtc_prop ?
  313. (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_prop, disp->crtc_id) >= 0) : 0);
  314. props += (disp->plane_src_x_prop ?
  315. (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_x_prop, (uint64_t)0) >= 0) : 0);
  316. props += (disp->plane_src_y_prop ?
  317. (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_y_prop, (uint64_t)0) >= 0) : 0);
  318. if (disp->plane_src_w_prop)
  319. props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_w_prop,
  320. (uint64_t)disp->width << 16) >= 0);
  321. if (disp->plane_src_h_prop)
  322. props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_src_h_prop,
  323. (uint64_t)disp->height << 16) >= 0);
  324. if (disp->plane_crtc_x_prop)
  325. props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_x_prop, (uint64_t)0) >= 0);
  326. if (disp->plane_crtc_y_prop)
  327. props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_y_prop, (uint64_t)0) >= 0);
  328. if (disp->plane_crtc_w_prop)
  329. props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_w_prop, (uint64_t)disp->mode_w) >= 0);
  330. if (disp->plane_crtc_h_prop)
  331. props += (drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_h_prop, (uint64_t)disp->mode_h) >= 0);
  332. return props;
  333. }
  334. static void display_reset_output(Display *disp);
  335. /* Cache every OTHER plane that can target our CRTC (Xorg/desktop scans).
  336. * They must stay disabled while we render, or their RGB content blends under
  337. * our NV12 plane and tints the whole output. */
  338. static void cache_ext_planes(Display *disp) {
  339. disp->ext_n = 0;
  340. drmModePlaneRes *pres = drmModeGetPlaneResources(disp->fd);
  341. if (!pres) return;
  342. drmModeRes *res = drmModeGetResources(disp->fd);
  343. int crtc_index = -1;
  344. if (res) {
  345. for (int i = 0; i < res->count_crtcs; i++)
  346. if (res->crtcs[i] == disp->crtc_id) { crtc_index = i; break; }
  347. drmModeFreeResources(res);
  348. }
  349. if (crtc_index < 0) { drmModeFreePlaneResources(pres); return; }
  350. for (uint32_t i = 0; i < pres->count_planes && disp->ext_n < 8; i++) {
  351. drmModePlane *pl = drmModeGetPlane(disp->fd, pres->planes[i]);
  352. if (!pl) continue;
  353. if (pl->plane_id != disp->plane_id &&
  354. (pl->possible_crtcs & (1u << crtc_index))) {
  355. uint32_t idx = disp->ext_n;
  356. disp->ext_ids[idx] = pl->plane_id;
  357. disp->ext_fb_prop[idx] = find_prop_id(disp->fd, pl->plane_id,
  358. DRM_MODE_OBJECT_PLANE, "FB_ID");
  359. disp->ext_crtc_prop[idx] = find_prop_id(disp->fd, pl->plane_id,
  360. DRM_MODE_OBJECT_PLANE, "CRTC_ID");
  361. if (disp->ext_fb_prop[idx] || disp->ext_crtc_prop[idx])
  362. disp->ext_n++;
  363. }
  364. drmModeFreePlane(pl);
  365. }
  366. drmModeFreePlaneResources(pres);
  367. }
  368. static void add_ext_planes_off(Display *disp, drmModeAtomicReq *req) {
  369. for (int i = 0; i < disp->ext_n; i++) {
  370. if (disp->ext_fb_prop[i])
  371. drmModeAtomicAddProperty(req, disp->ext_ids[i], disp->ext_fb_prop[i], 0);
  372. if (disp->ext_crtc_prop[i])
  373. drmModeAtomicAddProperty(req, disp->ext_ids[i], disp->ext_crtc_prop[i], 0);
  374. }
  375. }
  376. int display_init(Display *disp, int width, int height) {
  377. if (!disp || disp->fd < 0) return -1;
  378. if (drmSetMaster(disp->fd))
  379. fprintf(stderr, "WARN: drmSetMaster: %s\n", strerror(errno));
  380. drmSetClientCap(disp->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
  381. drmSetClientCap(disp->fd, DRM_CLIENT_CAP_ATOMIC, 1);
  382. if (drm_get_connector(disp) < 0) return -1;
  383. /* Choose the CRTC mode. When the caller already owns scanout buffers of a
  384. * given size (the app allocates them once at start-up), prefer a connector
  385. * mode that matches them exactly so the plane never has to scale. Fall
  386. * back to the connector's preferred mode otherwise. */
  387. drmModeModeInfo *mode = &disp->connector->modes[0];
  388. if (width > 0 && height > 0) {
  389. for (int i = 0; i < disp->connector->count_modes; i++) {
  390. drmModeModeInfo *m = &disp->connector->modes[i];
  391. if (m->hdisplay == width && m->vdisplay == height) { mode = m; break; }
  392. }
  393. }
  394. disp->mode_w = mode->hdisplay;
  395. disp->mode_h = mode->vdisplay;
  396. /* width/height stay the size of the frames that will be committed, so the
  397. * NV12 fb is always registered with the buffer's real geometry. */
  398. disp->width = (width > 0) ? width : disp->mode_w;
  399. disp->height = (height > 0) ? height : disp->mode_h;
  400. disp->saved_crtc = drmModeGetCrtc(disp->fd, disp->crtc_id);
  401. if (setup_plane(disp) < 0) return -1;
  402. cache_ext_planes(disp);
  403. disp->conn_crtc_prop = find_prop_id(disp->fd, disp->connector_id,
  404. DRM_MODE_OBJECT_CONNECTOR, "CRTC_ID");
  405. disp->conn_color_format_prop = find_prop_id(disp->fd, disp->connector_id,
  406. DRM_MODE_OBJECT_CONNECTOR, "color_format");
  407. disp->crtc_active_prop = find_prop_id(disp->fd, disp->crtc_id,
  408. DRM_MODE_OBJECT_CRTC, "ACTIVE");
  409. disp->crtc_mode_id_prop = find_prop_id(disp->fd, disp->crtc_id,
  410. DRM_MODE_OBJECT_CRTC, "MODE_ID");
  411. disp->out_fence_prop = find_prop_id(disp->fd, disp->crtc_id,
  412. DRM_MODE_OBJECT_CRTC, "OUT_FENCE_PTR");
  413. if (drmModeCreatePropertyBlob(disp->fd, mode, sizeof(*mode), &disp->crtc_mode_id)) {
  414. fprintf(stderr, "drmModeCreatePropertyBlob failed: %s\n", strerror(errno));
  415. return -1;
  416. }
  417. disp->flip_pending = 0;
  418. memset(&disp->drm_ctx, 0, sizeof(disp->drm_ctx));
  419. disp->drm_ctx.version = DRM_EVENT_CONTEXT_VERSION;
  420. disp->drm_ctx.page_flip_handler = page_flip_handler;
  421. /* The CRTC is activated on the first display_commit (which supplies the
  422. * NV12 buffer). No RGA / colour conversion is used anywhere in display. */
  423. display_reset_output(disp);
  424. fprintf(stderr, "display: init OK buffer %dx%d mode %dx%d crtc=%u plane=%u%s\n",
  425. disp->width, disp->height, disp->mode_w, disp->mode_h,
  426. disp->crtc_id, disp->plane_id,
  427. (disp->width != disp->mode_w || disp->height != disp->mode_h)
  428. ? " (buffer != mode, plane will scale)" : "");
  429. return 0;
  430. }
  431. /* Add (or find) an NV12 scanout FB for this fd. Returns fb_id or 0. */
  432. static uint32_t get_nv12_fb(Display *disp, int fd, int stride) {
  433. for (int i = 0; i < disp->nfbs; i++)
  434. if (disp->fbs[i].key_fd == fd) return disp->fbs[i].fb_id;
  435. uint32_t handle = 0;
  436. if (drmPrimeFDToHandle(disp->fd, fd, &handle)) {
  437. fprintf(stderr, "display: drmPrimeFDToHandle(nv12) failed: %s\n", strerror(errno));
  438. return 0;
  439. }
  440. uint32_t handles[4] = {handle, handle, 0, 0};
  441. uint32_t pitches[4] = {(uint32_t)stride, (uint32_t)stride, 0, 0};
  442. uint32_t offsets[4] = {0, (uint32_t)stride * disp->height, 0, 0};
  443. uint32_t fb_id = 0;
  444. if (drmModeAddFB2(disp->fd, disp->width, disp->height, DRM_FORMAT_NV12,
  445. handles, pitches, offsets, &fb_id, 0)) {
  446. fprintf(stderr, "display: drmModeAddFB2(NV12) failed: %s\n", strerror(errno));
  447. return 0;
  448. }
  449. int swap_idx = -1;
  450. if (disp->nfbs < MAX_NV12_FB) {
  451. swap_idx = disp->nfbs;
  452. disp->nfbs++;
  453. } else {
  454. /* table full: drop the last entry to make room (free its fb/handle) */
  455. swap_idx = disp->nfbs - 1;
  456. drmModeRmFB(disp->fd, disp->fbs[swap_idx].fb_id);
  457. if (disp->fbs[swap_idx].owned_fd >= 0) close(disp->fbs[swap_idx].owned_fd);
  458. }
  459. int owned = fcntl(fd, F_DUPFD_CLOEXEC, 3);
  460. disp->fbs[swap_idx].key_fd = fd;
  461. disp->fbs[swap_idx].owned_fd = owned;
  462. disp->fbs[swap_idx].handle = handle;
  463. disp->fbs[swap_idx].fb_id = fb_id;
  464. disp->fbs[swap_idx].stride = (uint32_t)stride;
  465. return fb_id;
  466. }
  467. int display_signal_lost(Display *disp) {
  468. if (!disp || disp->fd < 0) return 1;
  469. drmModeConnector *conn = drmModeGetConnector(disp->fd, disp->connector_id);
  470. if (!conn) return 1;
  471. int lost = !(conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0);
  472. drmModeFreeConnector(conn);
  473. return lost;
  474. }
  475. static void display_reset_output(Display *disp) {
  476. if (!disp || disp->fd < 0) return;
  477. drmModeAtomicReq *req = drmModeAtomicAlloc();
  478. if (!req) return;
  479. if (disp->plane_fb_prop)
  480. drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_fb_prop, 0);
  481. if (disp->plane_crtc_prop)
  482. drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_crtc_prop, 0);
  483. add_ext_planes_off(disp, req);
  484. if (disp->crtc_active_prop)
  485. drmModeAtomicAddProperty(req, disp->crtc_id, disp->crtc_active_prop, 0);
  486. if (disp->conn_crtc_prop)
  487. drmModeAtomicAddProperty(req, disp->connector_id, disp->conn_crtc_prop, 0);
  488. drmModeAtomicCommit(disp->fd, req,
  489. DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_ATOMIC_NONBLOCK,
  490. NULL);
  491. drmModeAtomicFree(req);
  492. }
  493. int display_commit(Display *disp, int nv12_fd, int nv12_stride) {
  494. if (!disp || disp->fd < 0 || nv12_fd < 0) return -1;
  495. if (wait_prev_flip(disp, 300) < 0) {
  496. fprintf(stderr, "display: previous flip not completed, dropping frame\n");
  497. return -1;
  498. }
  499. uint32_t fb_id = get_nv12_fb(disp, nv12_fd, nv12_stride);
  500. if (!fb_id) return -1;
  501. drmModeAtomicReq *req = drmModeAtomicAlloc();
  502. if (!req) return -1;
  503. int is_first = !disp->modeset_done;
  504. if (is_first) {
  505. /* First commit: enable connector + CRTC modeset together with the
  506. * plane so VOP shows the first NV12 frame immediately. */
  507. if (disp->conn_crtc_prop)
  508. drmModeAtomicAddProperty(req, disp->connector_id, disp->conn_crtc_prop, disp->crtc_id);
  509. if (g_fmt_override > 0 && disp->conn_color_format_prop)
  510. drmModeAtomicAddProperty(req, disp->connector_id,
  511. disp->conn_color_format_prop,
  512. (uint64_t)(g_fmt_override - 1));
  513. if (disp->crtc_active_prop)
  514. drmModeAtomicAddProperty(req, disp->crtc_id, disp->crtc_active_prop, 1);
  515. if (disp->crtc_mode_id_prop)
  516. drmModeAtomicAddProperty(req, disp->crtc_id, disp->crtc_mode_id_prop, disp->crtc_mode_id);
  517. build_plane_commit(disp, req, fb_id);
  518. add_ext_planes_off(disp, req);
  519. } else {
  520. /* Page flip: just swap the plane FB. */
  521. if (disp->plane_fb_prop)
  522. drmModeAtomicAddProperty(req, disp->plane_id, disp->plane_fb_prop, fb_id);
  523. add_ext_planes_off(disp, req);
  524. }
  525. int out_fence_fd = -1;
  526. if (disp->out_fence_prop && !is_first) {
  527. uint64_t fence_ptr = (uint64_t)(unsigned long)&out_fence_fd;
  528. drmModeAtomicAddProperty(req, disp->crtc_id, disp->out_fence_prop, fence_ptr);
  529. }
  530. int ret;
  531. uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT | (is_first ? DRM_MODE_ATOMIC_ALLOW_MODESET : 0);
  532. int retries = 5;
  533. do {
  534. ret = drmModeAtomicCommit(disp->fd, req, flags, disp);
  535. if (ret == 0) break;
  536. if (errno == EBUSY) { usleep(16000); continue; }
  537. /* A stale state after hot-plug can make a plain page flip EINVAL;
  538. * schedule the full reset+modeset on the next frame. */
  539. if (!is_first) disp->modeset_done = 0;
  540. fprintf(stderr, "display: AtomicCommit failed: %s\n", strerror(errno));
  541. if (out_fence_fd >= 0) close(out_fence_fd);
  542. drmModeAtomicFree(req);
  543. return -1;
  544. } while (--retries > 0);
  545. if (ret) {
  546. fprintf(stderr, "display: AtomicCommit failed after %d retries\n", retries);
  547. if (out_fence_fd >= 0) close(out_fence_fd);
  548. drmModeAtomicFree(req);
  549. return -1;
  550. }
  551. drmModeAtomicFree(req);
  552. disp->modeset_done = 1;
  553. disp->flip_pending = 1;
  554. int confirmed = 0;
  555. if (out_fence_fd >= 0) {
  556. struct pollfd pfd = { .fd = out_fence_fd, .events = POLLIN };
  557. int pret = poll(&pfd, 1, 300);
  558. if (pret > 0 && (pfd.revents & POLLIN)) confirmed = 1;
  559. close(out_fence_fd);
  560. }
  561. if (!confirmed)
  562. confirmed = (wait_prev_flip(disp, 300) == 0);
  563. if (!confirmed) {
  564. fprintf(stderr, "display: flip not confirmed at vblank, frame status unknown\n");
  565. return -1;
  566. }
  567. return 0;
  568. }
  569. int display_get_width(Display *disp) { return disp->width; }
  570. int display_get_height(Display *disp) { return disp->height; }