modeset-atomic.c 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204
  1. /*
  2. * modeset-atomic - DRM Atomic-API Modesetting Example
  3. * Written 2019 by Ezequiel Garcia <ezequiel@collabora.com>
  4. *
  5. * Dedicated to the Public Domain.
  6. */
  7. /*
  8. * DRM Double-Buffered VSync'ed Atomic Modesetting Howto
  9. * This example extends modeset-vsync.c, introducing planes and the
  10. * atomic API.
  11. *
  12. * Planes can be used to blend or overlay images on top of a CRTC
  13. * framebuffer during the scanout process. Not all hardware provide
  14. * planes and the number of planes available is also limited. If there's
  15. * not enough planes available or the hardware does not provide them,
  16. * users should fallback to composition via GPU or CPU to blend or
  17. * overlay the planes. Notice that this render process will result
  18. * in delay, what justifies the usage of planes by modern hardware
  19. * that needs to be fast.
  20. *
  21. * There are three types of planes: primary, cursor and overlay. For
  22. * compatibility with legacy userspace, the default behavior is to expose
  23. * only overlay planes to userspace (we're going to see in the code that
  24. * we have to ask to receive all types of planes). A good example of plane
  25. * usage is this: imagine a static desktop screen and the user is moving
  26. * the cursor around. Only the cursor is moving. Instead of calculating the
  27. * complete scene for each time the user moves its cursor, we can update only
  28. * the cursor plane and it will be automatically overlayed by the hardware on
  29. * top of the primary plane. There's no need of software composition in this
  30. * case.
  31. *
  32. * But there was synchronisation problems related to multiple planes
  33. * usage. The KMS API was not atomic, so you'd have to update the primary
  34. * plane and then the overlay planes with distinct IOCTL's. This could lead
  35. * to tearing and also some trouble related to blocking, so the atomic
  36. * API was proposed to fix these problems.
  37. *
  38. * With the introduction of the KMS atomic API, all the planes could get
  39. * updated in a single IOCTL, using drmModeAtomicCommit(). This can be
  40. * either asynchronous or fully blocking.
  41. *
  42. * This example assumes that you are familiar with modeset-vsync. Only
  43. * the differences between both files are highlighted here.
  44. */
  45. #define _GNU_SOURCE
  46. #include <errno.h>
  47. #include <fcntl.h>
  48. #include <stdbool.h>
  49. #include <stdint.h>
  50. #include <stdio.h>
  51. #include <stdlib.h>
  52. #include <string.h>
  53. #include <sys/mman.h>
  54. #include <time.h>
  55. #include <unistd.h>
  56. #include <xf86drm.h>
  57. #include <xf86drmMode.h>
  58. #include <drm_fourcc.h>
  59. /*
  60. * A new struct is introduced: drm_object. It stores properties of certain
  61. * objects (connectors, CRTC and planes) that are used in atomic modeset setup
  62. * and also in atomic page-flips (all planes updated in a single IOCTL).
  63. */
  64. struct drm_object {
  65. drmModeObjectProperties *props;
  66. drmModePropertyRes **props_info;
  67. uint32_t id;
  68. };
  69. struct modeset_buf {
  70. uint32_t width;
  71. uint32_t height;
  72. uint32_t stride;
  73. uint32_t size;
  74. uint32_t handle;
  75. uint8_t *map;
  76. uint32_t fb;
  77. };
  78. struct modeset_output {
  79. struct modeset_output *next;
  80. unsigned int front_buf;
  81. struct modeset_buf bufs[2];
  82. struct drm_object connector;
  83. struct drm_object crtc;
  84. struct drm_object plane;
  85. drmModeModeInfo mode;
  86. uint32_t mode_blob_id;
  87. uint32_t crtc_index;
  88. bool pflip_pending;
  89. bool cleanup;
  90. uint8_t r, g, b;
  91. bool r_up, g_up, b_up;
  92. };
  93. static struct modeset_output *output_list = NULL;
  94. /*
  95. * modeset_open() changes just a little bit. We now have to set that we're going
  96. * to use the KMS atomic API and check if the device is capable of handling it.
  97. */
  98. static int modeset_open(int *out, const char *node)
  99. {
  100. int fd, ret;
  101. uint64_t cap;
  102. fd = open(node, O_RDWR | O_CLOEXEC);
  103. if (fd < 0) {
  104. ret = -errno;
  105. fprintf(stderr, "cannot open '%s': %m\n", node);
  106. return ret;
  107. }
  108. /* Set that we want to receive all the types of planes in the list. This
  109. * have to be done since, for legacy reasons, the default behavior is to
  110. * expose only the overlay planes to the users. The atomic API only
  111. * works if this is set.
  112. */
  113. ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
  114. if (ret) {
  115. fprintf(stderr, "failed to set universal planes cap, %d\n", ret);
  116. return ret;
  117. }
  118. /* Here we set that we're going to use the KMS atomic API. It's supposed
  119. * to set the DRM_CLIENT_CAP_UNIVERSAL_PLANES automatically, but it's a
  120. * safe behavior to set it explicitly as we did in the previous
  121. * commands. This is also good for learning purposes.
  122. */
  123. ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
  124. if (ret) {
  125. fprintf(stderr, "failed to set atomic cap, %d", ret);
  126. return ret;
  127. }
  128. if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap) < 0 || !cap) {
  129. fprintf(stderr, "drm device '%s' does not support dumb buffers\n",
  130. node);
  131. close(fd);
  132. return -EOPNOTSUPP;
  133. }
  134. if (drmGetCap(fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) < 0 || !cap) {
  135. fprintf(stderr, "drm device '%s' does not support atomic KMS\n",
  136. node);
  137. close(fd);
  138. return -EOPNOTSUPP;
  139. }
  140. *out = fd;
  141. return 0;
  142. }
  143. /*
  144. * get_property_value() is a new function. Given a device, the properties of
  145. * an object and a name, search for the value of property 'name'. If we can't
  146. * find it, return -1.
  147. */
  148. static int64_t get_property_value(int fd, drmModeObjectPropertiesPtr props,
  149. const char *name)
  150. {
  151. drmModePropertyPtr prop;
  152. uint64_t value;
  153. bool found;
  154. int j;
  155. found = false;
  156. for (j = 0; j < props->count_props && !found; j++) {
  157. prop = drmModeGetProperty(fd, props->props[j]);
  158. if (!strcmp(prop->name, name)) {
  159. value = props->prop_values[j];
  160. found = true;
  161. }
  162. drmModeFreeProperty(prop);
  163. }
  164. if (!found)
  165. return -1;
  166. return value;
  167. }
  168. /*
  169. * get_drm_object_properties() is a new helpfer function that retrieves
  170. * the properties of a certain CRTC, plane or connector object.
  171. */
  172. static void modeset_get_object_properties(int fd, struct drm_object *obj,
  173. uint32_t type)
  174. {
  175. const char *type_str;
  176. unsigned int i;
  177. obj->props = drmModeObjectGetProperties(fd, obj->id, type);
  178. if (!obj->props) {
  179. switch(type) {
  180. case DRM_MODE_OBJECT_CONNECTOR:
  181. type_str = "connector";
  182. break;
  183. case DRM_MODE_OBJECT_PLANE:
  184. type_str = "plane";
  185. break;
  186. case DRM_MODE_OBJECT_CRTC:
  187. type_str = "CRTC";
  188. break;
  189. default:
  190. type_str = "unknown type";
  191. break;
  192. }
  193. fprintf(stderr, "cannot get %s %d properties: %s\n",
  194. type_str, obj->id, strerror(errno));
  195. return;
  196. }
  197. obj->props_info = calloc(obj->props->count_props, sizeof(obj->props_info));
  198. for (i = 0; i < obj->props->count_props; i++)
  199. obj->props_info[i] = drmModeGetProperty(fd, obj->props->props[i]);
  200. }
  201. /*
  202. * set_drm_object_property() is a new function. It sets a property value to a
  203. * CRTC, plane or connector object.
  204. */
  205. static int set_drm_object_property(drmModeAtomicReq *req, struct drm_object *obj,
  206. const char *name, uint64_t value)
  207. {
  208. int i;
  209. uint32_t prop_id = 0;
  210. for (i = 0; i < obj->props->count_props; i++) {
  211. if (!strcmp(obj->props_info[i]->name, name)) {
  212. prop_id = obj->props_info[i]->prop_id;
  213. break;
  214. }
  215. }
  216. if (prop_id == 0) {
  217. fprintf(stderr, "no object property: %s\n", name);
  218. return -EINVAL;
  219. }
  220. return drmModeAtomicAddProperty(req, obj->id, prop_id, value);
  221. }
  222. /*
  223. * modeset_find_crtc() changes a little bit. Now we also have to save the CRTC
  224. * index, and not only its id.
  225. */
  226. static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
  227. struct modeset_output *out)
  228. {
  229. drmModeEncoder *enc;
  230. unsigned int i, j;
  231. uint32_t crtc;
  232. struct modeset_output *iter;
  233. /* first try the currently conected encoder+crtc */
  234. if (conn->encoder_id)
  235. enc = drmModeGetEncoder(fd, conn->encoder_id);
  236. else
  237. enc = NULL;
  238. if (enc) {
  239. if (enc->crtc_id) {
  240. crtc = enc->crtc_id;
  241. for (iter = output_list; iter; iter = iter->next) {
  242. if (iter->crtc.id == crtc) {
  243. crtc = 0;
  244. break;
  245. }
  246. }
  247. if (crtc > 0) {
  248. drmModeFreeEncoder(enc);
  249. out->crtc.id = crtc;
  250. return 0;
  251. }
  252. }
  253. drmModeFreeEncoder(enc);
  254. }
  255. /* If the connector is not currently bound to an encoder or if the
  256. * encoder+crtc is already used by another connector (actually unlikely
  257. * but lets be safe), iterate all other available encoders to find a
  258. * matching CRTC.
  259. */
  260. for (i = 0; i < conn->count_encoders; ++i) {
  261. enc = drmModeGetEncoder(fd, conn->encoders[i]);
  262. if (!enc) {
  263. fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n",
  264. i, conn->encoders[i], errno);
  265. continue;
  266. }
  267. /* iterate all global CRTCs */
  268. for (j = 0; j < res->count_crtcs; ++j) {
  269. /* check whether this CRTC works with the encoder */
  270. if (!(enc->possible_crtcs & (1 << j)))
  271. continue;
  272. /* check that no other output already uses this CRTC */
  273. crtc = res->crtcs[j];
  274. for (iter = output_list; iter; iter = iter->next) {
  275. if (iter->crtc.id == crtc) {
  276. crtc = 0;
  277. break;
  278. }
  279. }
  280. /* We have found a CRTC, so save it and return. Note
  281. * that we have to save its index as well. The CRTC
  282. * index (not its ID) will be used when searching for a
  283. * suitable plane.
  284. */
  285. if (crtc > 0) {
  286. fprintf(stdout, "crtc %u found for encoder %u, will need full modeset\n",
  287. crtc, conn->encoders[i]);;
  288. drmModeFreeEncoder(enc);
  289. out->crtc.id = crtc;
  290. out->crtc_index = j;
  291. return 0;
  292. }
  293. }
  294. drmModeFreeEncoder(enc);
  295. }
  296. fprintf(stderr, "cannot find suitable crtc for connector %u\n",
  297. conn->connector_id);
  298. return -ENOENT;
  299. }
  300. /*
  301. * modeset_find_plane() is a new function. Given a certain combination
  302. * of connector+CRTC, it looks for a primary plane for it.
  303. */
  304. static int modeset_find_plane(int fd, struct modeset_output *out)
  305. {
  306. drmModePlaneResPtr plane_res;
  307. bool found_primary = false;
  308. int i, ret = -EINVAL;
  309. plane_res = drmModeGetPlaneResources(fd);
  310. if (!plane_res) {
  311. fprintf(stderr, "drmModeGetPlaneResources failed: %s\n",
  312. strerror(errno));
  313. return -ENOENT;
  314. }
  315. /* iterates through all planes of a certain device */
  316. for (i = 0; (i < plane_res->count_planes) && !found_primary; i++) {
  317. int plane_id = plane_res->planes[i];
  318. drmModePlanePtr plane = drmModeGetPlane(fd, plane_id);
  319. if (!plane) {
  320. fprintf(stderr, "drmModeGetPlane(%u) failed: %s\n", plane_id,
  321. strerror(errno));
  322. continue;
  323. }
  324. /* check if the plane can be used by our CRTC */
  325. if (plane->possible_crtcs & (1 << out->crtc_index)) {
  326. drmModeObjectPropertiesPtr props =
  327. drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE);
  328. /* Get the "type" property to check if this is a primary
  329. * plane. Type property is special, as its enum value is
  330. * defined in UAPI headers. For the properties that are
  331. * not defined in the UAPI headers, we would have to
  332. * give kernel the property name and it would return the
  333. * corresponding enum value. We could also do this for
  334. * the "type" property, but it would make this simple
  335. * example more complex. The reason why defining enum
  336. * values for kernel properties in UAPI headers is
  337. * deprecated is that string names are easier to both
  338. * (userspace and kernel) make unique and keep
  339. * consistent between drivers and kernel versions. But
  340. * in order to not break userspace, some properties were
  341. * left in the UAPI headers as well.
  342. */
  343. if (get_property_value(fd, props, "type") == DRM_PLANE_TYPE_PRIMARY) {
  344. found_primary = true;
  345. out->plane.id = plane_id;
  346. ret = 0;
  347. }
  348. drmModeFreeObjectProperties(props);
  349. }
  350. drmModeFreePlane(plane);
  351. }
  352. drmModeFreePlaneResources(plane_res);
  353. if (found_primary)
  354. fprintf(stdout, "found primary plane, id: %d\n", out->plane.id);
  355. else
  356. fprintf(stdout, "couldn't find a primary plane\n");
  357. return ret;
  358. }
  359. /*
  360. * modeset_drm_object_fini() is a new helper function that destroys CRTCs,
  361. * connectors and planes
  362. */
  363. static void modeset_drm_object_fini(struct drm_object *obj)
  364. {
  365. for (int i = 0; i < obj->props->count_props; i++)
  366. drmModeFreeProperty(obj->props_info[i]);
  367. free(obj->props_info);
  368. drmModeFreeObjectProperties(obj->props);
  369. }
  370. /*
  371. * modeset_setup_objects() is a new function. It helps us to retrieve
  372. * connector, CRTC and plane objects properties from the device. These
  373. * properties will help us during the atomic modesetting commit, so we save
  374. * them in our struct modeset_output object.
  375. */
  376. static int modeset_setup_objects(int fd, struct modeset_output *out)
  377. {
  378. struct drm_object *connector = &out->connector;
  379. struct drm_object *crtc = &out->crtc;
  380. struct drm_object *plane = &out->plane;
  381. /* retrieve connector properties from the device */
  382. modeset_get_object_properties(fd, connector, DRM_MODE_OBJECT_CONNECTOR);
  383. if (!connector->props)
  384. goto out_conn;
  385. /* retrieve CRTC properties from the device */
  386. modeset_get_object_properties(fd, crtc, DRM_MODE_OBJECT_CRTC);
  387. if (!crtc->props)
  388. goto out_crtc;
  389. /* retrieve plane properties from the device */
  390. modeset_get_object_properties(fd, plane, DRM_MODE_OBJECT_PLANE);
  391. if (!plane->props)
  392. goto out_plane;
  393. return 0;
  394. out_plane:
  395. modeset_drm_object_fini(crtc);
  396. out_crtc:
  397. modeset_drm_object_fini(connector);
  398. out_conn:
  399. return -ENOMEM;
  400. }
  401. /*
  402. * modeset_destroy_objects() is a new function. It destroys what we allocate
  403. * in modeset_setup_objects().
  404. */
  405. static void modeset_destroy_objects(int fd, struct modeset_output *out)
  406. {
  407. modeset_drm_object_fini(&out->connector);
  408. modeset_drm_object_fini(&out->crtc);
  409. modeset_drm_object_fini(&out->plane);
  410. }
  411. /*
  412. * modeset_create_fb() stays the same.
  413. */
  414. static int modeset_create_fb(int fd, struct modeset_buf *buf)
  415. {
  416. struct drm_mode_create_dumb creq;
  417. struct drm_mode_destroy_dumb dreq;
  418. struct drm_mode_map_dumb mreq;
  419. int ret;
  420. uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
  421. /* create dumb buffer */
  422. memset(&creq, 0, sizeof(creq));
  423. creq.width = buf->width;
  424. creq.height = buf->height;
  425. creq.bpp = 32;
  426. ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
  427. if (ret < 0) {
  428. fprintf(stderr, "cannot create dumb buffer (%d): %m\n",
  429. errno);
  430. return -errno;
  431. }
  432. buf->stride = creq.pitch;
  433. buf->size = creq.size;
  434. buf->handle = creq.handle;
  435. /* create framebuffer object for the dumb-buffer */
  436. handles[0] = buf->handle;
  437. pitches[0] = buf->stride;
  438. ret = drmModeAddFB2(fd, buf->width, buf->height, DRM_FORMAT_RGB565,
  439. handles, pitches, offsets, &buf->fb, 0);
  440. if (ret) {
  441. fprintf(stderr, "cannot create framebuffer (%d): %m\n",
  442. errno);
  443. ret = -errno;
  444. goto err_destroy;
  445. }
  446. /* prepare buffer for memory mapping */
  447. memset(&mreq, 0, sizeof(mreq));
  448. mreq.handle = buf->handle;
  449. ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
  450. if (ret) {
  451. fprintf(stderr, "cannot map dumb buffer (%d): %m\n",
  452. errno);
  453. ret = -errno;
  454. goto err_fb;
  455. }
  456. /* perform actual memory mapping */
  457. buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED,
  458. fd, mreq.offset);
  459. if (buf->map == MAP_FAILED) {
  460. fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n",
  461. errno);
  462. ret = -errno;
  463. goto err_fb;
  464. }
  465. /* clear the framebuffer to 0 */
  466. memset(buf->map, 0, buf->size);
  467. return 0;
  468. err_fb:
  469. drmModeRmFB(fd, buf->fb);
  470. err_destroy:
  471. memset(&dreq, 0, sizeof(dreq));
  472. dreq.handle = buf->handle;
  473. drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
  474. return ret;
  475. }
  476. /*
  477. * modeset_destroy_fb() stays the same.
  478. */
  479. static void modeset_destroy_fb(int fd, struct modeset_buf *buf)
  480. {
  481. struct drm_mode_destroy_dumb dreq;
  482. /* unmap buffer */
  483. munmap(buf->map, buf->size);
  484. /* delete framebuffer */
  485. drmModeRmFB(fd, buf->fb);
  486. /* delete dumb buffer */
  487. memset(&dreq, 0, sizeof(dreq));
  488. dreq.handle = buf->handle;
  489. drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
  490. }
  491. /*
  492. * modeset_setup_framebuffers() creates framebuffers for the back and front
  493. * buffers of a certain output. Also, it copies the connector mode to these
  494. * buffers.
  495. */
  496. static int modeset_setup_framebuffers(int fd, drmModeConnector *conn,
  497. struct modeset_output *out)
  498. {
  499. int i, ret;
  500. /* setup the front and back framebuffers */
  501. for (i = 0; i < 2; i++) {
  502. /* copy mode info to buffer */
  503. out->bufs[i].width = conn->modes[0].hdisplay;
  504. out->bufs[i].height = conn->modes[0].vdisplay;
  505. /* create a framebuffer for the buffer */
  506. ret = modeset_create_fb(fd, &out->bufs[i]);
  507. if (ret) {
  508. /* the second framebuffer creation failed, so
  509. * we have to destroy the first before returning */
  510. if (i == 1)
  511. modeset_destroy_fb(fd, &out->bufs[0]);
  512. return ret;
  513. }
  514. }
  515. return 0;
  516. }
  517. /*
  518. * modeset_output_destroy() is new. It destroys the objects (connector, crtc and
  519. * plane), front and back buffers, the mode blob property and then destroys the
  520. * output itself.
  521. */
  522. static void modeset_output_destroy(int fd, struct modeset_output *out)
  523. {
  524. /* destroy connector, crtc and plane objects */
  525. modeset_destroy_objects(fd, out);
  526. /* destroy front/back framebuffers */
  527. modeset_destroy_fb(fd, &out->bufs[0]);
  528. modeset_destroy_fb(fd, &out->bufs[1]);
  529. /* destroy mode blob property */
  530. drmModeDestroyPropertyBlob(fd, out->mode_blob_id);
  531. free(out);
  532. }
  533. /*
  534. * With a certain combination of connector+CRTC, we look for a suitable primary
  535. * plane for it. After that, we retrieve connector, CRTC and plane objects
  536. * properties from the device. These objects are used during the atomic modeset
  537. * setup (see modeset_atomic_prepare_commit()) and also during the page-flips
  538. * (see modeset_draw_out() and modeset_atomic_commit()).
  539. *
  540. * Besides that, we have to create a blob property that receives the output
  541. * mode. When we perform an atomic commit, the driver expects a CRTC property
  542. * named "MODE_ID", which points to the id of a blob. This usually happens for
  543. * properties that are not simple types. In this particular case, out->mode is a
  544. * struct. But we could have another property that expects the id of a blob that
  545. * holds an array, for instance.
  546. */
  547. static struct modeset_output *modeset_output_create(int fd, drmModeRes *res,
  548. drmModeConnector *conn)
  549. {
  550. int ret;
  551. struct modeset_output *out;
  552. /* creates an output structure */
  553. out = malloc(sizeof(*out));
  554. memset(out, 0, sizeof(*out));
  555. out->connector.id = conn->connector_id;
  556. /* check if a monitor is connected */
  557. if (conn->connection != DRM_MODE_CONNECTED) {
  558. fprintf(stderr, "ignoring unused connector %u\n",
  559. conn->connector_id);
  560. goto out_error;
  561. }
  562. /* check if there is at least one valid mode */
  563. if (conn->count_modes == 0) {
  564. fprintf(stderr, "no valid mode for connector %u\n",
  565. conn->connector_id);
  566. goto out_error;
  567. }
  568. /* copy the mode information into our output structure */
  569. memcpy(&out->mode, &conn->modes[0], sizeof(out->mode));
  570. /* create the blob property using out->mode and save its id in the output*/
  571. if (drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode),
  572. &out->mode_blob_id) != 0) {
  573. fprintf(stderr, "couldn't create a blob property\n");
  574. goto out_error;
  575. }
  576. fprintf(stderr, "mode for connector %u is %ux%u\n",
  577. conn->connector_id, out->bufs[0].width, out->bufs[0].height);
  578. /* find a crtc for this connector */
  579. ret = modeset_find_crtc(fd, res, conn, out);
  580. if (ret) {
  581. fprintf(stderr, "no valid crtc for connector %u\n",
  582. conn->connector_id);
  583. goto out_blob;
  584. }
  585. /* with a connector and crtc, find a primary plane */
  586. ret = modeset_find_plane(fd, out);
  587. if (ret) {
  588. fprintf(stderr, "no valid plane for crtc %u\n", out->crtc.id);
  589. goto out_blob;
  590. }
  591. /* gather properties of our connector, CRTC and planes */
  592. ret = modeset_setup_objects(fd, out);
  593. if (ret) {
  594. fprintf(stderr, "cannot get plane properties\n");
  595. goto out_blob;
  596. }
  597. /* setup front/back framebuffers for this CRTC */
  598. ret = modeset_setup_framebuffers(fd, conn, out);
  599. if (ret) {
  600. fprintf(stderr, "cannot create framebuffers for connector %u\n",
  601. conn->connector_id);
  602. goto out_obj;
  603. }
  604. return out;
  605. out_obj:
  606. modeset_destroy_objects(fd, out);
  607. out_blob:
  608. drmModeDestroyPropertyBlob(fd, out->mode_blob_id);
  609. out_error:
  610. free(out);
  611. return NULL;
  612. }
  613. /*
  614. * modeset_prepare() changes a little bit. Now we use the new function
  615. * modeset_output_create() to allocate memory and setup the output.
  616. */
  617. static int modeset_prepare(int fd)
  618. {
  619. drmModeRes *res;
  620. drmModeConnector *conn;
  621. unsigned int i;
  622. struct modeset_output *out;
  623. /* retrieve resources */
  624. res = drmModeGetResources(fd);
  625. if (!res) {
  626. fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n",
  627. errno);
  628. return -errno;
  629. }
  630. /* iterate all connectors */
  631. for (i = 0; i < res->count_connectors; ++i) {
  632. /* get information for each connector */
  633. conn = drmModeGetConnector(fd, res->connectors[i]);
  634. if (!conn) {
  635. fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n",
  636. i, res->connectors[i], errno);
  637. continue;
  638. }
  639. /* create an output structure and free connector data */
  640. out = modeset_output_create(fd, res, conn);
  641. drmModeFreeConnector(conn);
  642. if (!out)
  643. continue;
  644. /* link output into global list */
  645. out->next = output_list;
  646. output_list = out;
  647. }
  648. if (!output_list) {
  649. fprintf(stderr, "couldn't create any outputs\n");
  650. return -1;
  651. }
  652. /* free resources again */
  653. drmModeFreeResources(res);
  654. return 0;
  655. }
  656. /*
  657. * modeset_atomic_prepare_commit() is new. Here we set the values of properties
  658. * (of our connector, CRTC and plane objects) that we want to change in the
  659. * atomic commit. These changes are temporarily stored in drmModeAtomicReq *req
  660. * until the commit actually happens.
  661. */
  662. static int modeset_atomic_prepare_commit(int fd, struct modeset_output *out,
  663. drmModeAtomicReq *req)
  664. {
  665. struct drm_object *plane = &out->plane;
  666. struct modeset_buf *buf = &out->bufs[out->front_buf ^ 1];
  667. /* set id of the CRTC id that the connector is using */
  668. if (set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id) < 0)
  669. return -1;
  670. /* set the mode id of the CRTC; this property receives the id of a blob
  671. * property that holds the struct that actually contains the mode info */
  672. if (set_drm_object_property(req, &out->crtc, "MODE_ID", out->mode_blob_id) < 0)
  673. return -1;
  674. /* set the CRTC object as active */
  675. if (set_drm_object_property(req, &out->crtc, "ACTIVE", 1) < 0)
  676. return -1;
  677. /* set properties of the plane related to the CRTC and the framebuffer */
  678. if (set_drm_object_property(req, plane, "FB_ID", buf->fb) < 0)
  679. return -1;
  680. if (set_drm_object_property(req, plane, "CRTC_ID", out->crtc.id) < 0)
  681. return -1;
  682. if (set_drm_object_property(req, plane, "SRC_X", 0) < 0)
  683. return -1;
  684. if (set_drm_object_property(req, plane, "SRC_Y", 0) < 0)
  685. return -1;
  686. if (set_drm_object_property(req, plane, "SRC_W", buf->width << 16) < 0)
  687. return -1;
  688. if (set_drm_object_property(req, plane, "SRC_H", buf->height << 16) < 0)
  689. return -1;
  690. if (set_drm_object_property(req, plane, "CRTC_X", 0) < 0)
  691. return -1;
  692. if (set_drm_object_property(req, plane, "CRTC_Y", 0) < 0)
  693. return -1;
  694. if (set_drm_object_property(req, plane, "CRTC_W", buf->width) < 0)
  695. return -1;
  696. if (set_drm_object_property(req, plane, "CRTC_H", buf->height) < 0)
  697. return -1;
  698. return 0;
  699. }
  700. /*
  701. * A short helper function to compute a changing color value. No need to
  702. * understand it.
  703. */
  704. static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod)
  705. {
  706. uint8_t next;
  707. next = cur + (*up ? 1 : -1) * (rand() % mod);
  708. if ((*up && next < cur) || (!*up && next > cur)) {
  709. *up = !*up;
  710. next = cur;
  711. }
  712. return next;
  713. }
  714. /*
  715. * Draw on back framebuffer before the page-flip is requested.
  716. */
  717. static void modeset_paint_framebuffer(struct modeset_output *out)
  718. {
  719. struct modeset_buf *buf;
  720. unsigned int j, k, off;
  721. /* draw on back framebuffer */
  722. out->r = next_color(&out->r_up, out->r, 5);
  723. out->g = next_color(&out->g_up, out->g, 5);
  724. out->b = next_color(&out->b_up, out->b, 5);
  725. buf = &out->bufs[out->front_buf ^ 1];
  726. for (j = 0; j < buf->height; ++j) {
  727. for (k = 0; k < buf->width; ++k) {
  728. off = buf->stride * j + k * 4;
  729. *(uint32_t*)&buf->map[off] =
  730. (out->r << 16) | (out->g << 8) | out->b;
  731. }
  732. }
  733. }
  734. /*
  735. * modeset_draw_out() prepares the framebuffer with the drawing and then it asks
  736. * for the driver to perform an atomic commit. This will lead to a page-flip and
  737. * the content of the framebuffer will be displayed. In this simple example
  738. * we're only using the primary plane, but we could also be updating other
  739. * planes in the same atomic commit.
  740. *
  741. * Just like in modeset_perform_modeset(), we first setup everything with
  742. * modeset_atomic_prepare_commit() and then actually perform the atomic commit.
  743. * But there are some important differences:
  744. *
  745. * 1. Here we just want to perform a commit that changes the state of a specific
  746. * output, and in modeset_perform_modeset() we did an atomic commit that was
  747. * supposed to setup all the outputs at once. So there's no need to prepare
  748. * every output before performing the atomic commit. But let's suppose you
  749. * prepare every output and then perform the commit. It should schedule a
  750. * page-flip for all of them, but modeset_draw_out() was called because the
  751. * page-flip for a specific output has finished. The others may not be
  752. * prepared for a page-flip yet (e.g. in the middle of a scanout), so these
  753. * page-flips will fail.
  754. *
  755. * 2. Here we have already painted the framebuffer and also we don't use the
  756. * flag DRM_MODE_ALLOW_MODESET anymore, since the modeset already happened.
  757. * We could continue to use this flag, as it makes no difference if
  758. * modeset_perform_modeset() is correct and there's no bug in the kernel.
  759. * The flag only allows (it doesn't force) the driver to perform a modeset,
  760. * but we have already performed it in modeset_perform_modeset() and now we
  761. * just want page-flips to occur. If we still need to perform modesets it
  762. * means that we have a bug somewhere, and it may be better to fail than to
  763. * glitch (a modeset can cause unecessary latency and also blank the screen).
  764. */
  765. static void modeset_draw_out(int fd, struct modeset_output *out)
  766. {
  767. drmModeAtomicReq *req;
  768. int ret, flags;
  769. /* draw on framebuffer of the output */
  770. modeset_paint_framebuffer(out);
  771. /* prepare output for atomic commit */
  772. req = drmModeAtomicAlloc();
  773. ret = modeset_atomic_prepare_commit(fd, out, req);
  774. if (ret < 0) {
  775. fprintf(stderr, "prepare atomic commit failed, %d\n", errno);
  776. return;
  777. }
  778. /* We've just draw on the framebuffer, prepared the commit and now it's
  779. * time to perform a page-flip to display its content.
  780. *
  781. * DRM_MODE_PAGE_FLIP_EVENT signalizes that we want to receive a
  782. * page-flip event in the DRM-fd when the page-flip happens. This flag
  783. * is also used in the non-atomic examples, so you're probably familiar
  784. * with it.
  785. *
  786. * DRM_MODE_ATOMIC_NONBLOCK makes the page-flip non-blocking. We don't
  787. * want to be blocked waiting for the commit to happen, since we can use
  788. * this time to prepare a new framebuffer, for instance. We can only do
  789. * this because there are mechanisms to know when the commit is complete
  790. * (like page flip event, explained above).
  791. */
  792. flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
  793. ret = drmModeAtomicCommit(fd, req, flags, NULL);
  794. drmModeAtomicFree(req);
  795. if (ret < 0) {
  796. fprintf(stderr, "atomic commit failed, %d\n", errno);
  797. return;
  798. }
  799. out->front_buf ^= 1;
  800. out->pflip_pending = true;
  801. }
  802. /*
  803. * modeset_page_flip_event() changes. Now that we are using page_flip_handler2,
  804. * we also receive the CRTC that is responsible for this event. When using the
  805. * atomic API we commit multiple CRTC's at once, so we need the information of
  806. * what output caused the event in order to schedule a new page-flip for it.
  807. */
  808. static void modeset_page_flip_event(int fd, unsigned int frame,
  809. unsigned int sec, unsigned int usec,
  810. unsigned int crtc_id, void *data)
  811. {
  812. struct modeset_output *out, *iter;
  813. /* find the output responsible for this event */
  814. out = NULL;
  815. for (iter = output_list; iter; iter = iter->next) {
  816. if (iter->crtc.id == crtc_id) {
  817. out = iter;
  818. break;
  819. }
  820. }
  821. if (out == NULL)
  822. return;
  823. out->pflip_pending = false;
  824. if (!out->cleanup)
  825. modeset_draw_out(fd, out);
  826. }
  827. /*
  828. * modeset_perform_modeset() is new. First we define what properties have to be
  829. * changed and the values that they will receive. To check if the modeset will
  830. * work as expected, we perform an atomic commit with the flag
  831. * DRM_MODE_ATOMIC_TEST_ONLY. With this flag the DRM driver tests if the atomic
  832. * commit would work, but it doesn't commit it to the hardware. After, the same
  833. * atomic commit is performed without the TEST_ONLY flag, but not only before we
  834. * draw on the framebuffers of the outputs. This is necessary to avoid
  835. * displaying unwanted content.
  836. *
  837. * NOTE: we can't perform an atomic commit without an attached frambeuffer
  838. * (even when we have DRM_MODE_ATOMIC_TEST_ONLY). It will simply fail.
  839. */
  840. static int modeset_perform_modeset(int fd)
  841. {
  842. int ret, flags;
  843. struct modeset_output *iter;
  844. drmModeAtomicReq *req;
  845. /* prepare modeset on all outputs */
  846. req = drmModeAtomicAlloc();
  847. for (iter = output_list; iter; iter = iter->next) {
  848. ret = modeset_atomic_prepare_commit(fd, iter, req);
  849. if (ret < 0)
  850. break;
  851. }
  852. if (ret < 0) {
  853. fprintf(stderr, "prepare atomic commit failed, %d\n", errno);
  854. return ret;
  855. }
  856. /* perform test-only atomic commit */
  857. flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET;
  858. ret = drmModeAtomicCommit(fd, req, flags, NULL);
  859. if (ret < 0) {
  860. fprintf(stderr, "test-only atomic commit failed, %d\n", errno);
  861. drmModeAtomicFree(req);
  862. return ret;
  863. }
  864. /* draw on back framebuffer of all outputs */
  865. for (iter = output_list; iter; iter = iter->next) {
  866. /* colors initialization, this is the first time we're drawing */
  867. iter->r = rand() % 0xff;
  868. iter->g = rand() % 0xff;
  869. iter->b = rand() % 0xff;
  870. iter->r_up = iter->g_up = iter->b_up = true;
  871. modeset_paint_framebuffer(iter);
  872. }
  873. /* initial modeset on all outputs */
  874. flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT;
  875. ret = drmModeAtomicCommit(fd, req, flags, NULL);
  876. if (ret < 0)
  877. fprintf(stderr, "modeset atomic commit failed, %d\n", errno);
  878. drmModeAtomicFree(req);
  879. return ret;
  880. }
  881. /*
  882. * modeset_draw() changes. If we got here, the modeset already occurred. When
  883. * the page-flip for a certain output is done, an event will be fired and we'll
  884. * be able to handle it.
  885. *
  886. * Here we define the function that should handle these events, which is
  887. * modeset_page_flip_event(). This function calls modeset_draw_out(), which is
  888. * responsible for preparing a new framebuffer and performing another atomic
  889. * commit for us.
  890. *
  891. * Then we have a 5 seconds loop that keeps waiting for the events that are
  892. * fired when the page-flip is complete. drmHandleEvent() is reponsible for
  893. * reading the events from the fd and to call modeset_page_flip_event() for
  894. * each one of them.
  895. */
  896. static void modeset_draw(int fd)
  897. {
  898. int ret;
  899. fd_set fds;
  900. time_t start, cur;
  901. struct timeval v;
  902. drmEventContext ev;
  903. /* init variables */
  904. srand(time(&start));
  905. FD_ZERO(&fds);
  906. memset(&v, 0, sizeof(v));
  907. memset(&ev, 0, sizeof(ev));
  908. /* 3 is the first version that allow us to use page_flip_handler2, which
  909. * is just like page_flip_handler but with the addition of passing the
  910. * crtc_id as argument to the function that will handle page-flip events
  911. * (in our case, modeset_page_flip_event()). This is good because we can
  912. * find out for what output the page-flip happened.
  913. *
  914. * The usage of page_flip_handler2 is the reason why we needed to verify
  915. * the support for DRM_CAP_CRTC_IN_VBLANK_EVENT.
  916. */
  917. ev.version = 3;
  918. ev.page_flip_handler2 = modeset_page_flip_event;
  919. /* perform modeset using atomic commit */
  920. modeset_perform_modeset(fd);
  921. /* wait 5s for VBLANK or input events */
  922. while (time(&cur) < start + 5) {
  923. FD_SET(0, &fds);
  924. FD_SET(fd, &fds);
  925. v.tv_sec = start + 5 - cur;
  926. ret = select(fd + 1, &fds, NULL, NULL, &v);
  927. if (ret < 0) {
  928. fprintf(stderr, "select() failed with %d: %m\n", errno);
  929. break;
  930. } else if (FD_ISSET(0, &fds)) {
  931. fprintf(stderr, "exit due to user-input\n");
  932. break;
  933. } else if (FD_ISSET(fd, &fds)) {
  934. /* read the fd looking for events and handle each event
  935. * by calling modeset_page_flip_event() */
  936. drmHandleEvent(fd, &ev);
  937. }
  938. }
  939. }
  940. /*
  941. * modeset_cleanup() stays the same.
  942. */
  943. static void modeset_cleanup(int fd)
  944. {
  945. struct modeset_output *iter;
  946. drmEventContext ev;
  947. int ret;
  948. /* init variables */
  949. memset(&ev, 0, sizeof(ev));
  950. ev.version = 3;
  951. ev.page_flip_handler2 = modeset_page_flip_event;
  952. while (output_list) {
  953. /* get first output from list */
  954. iter = output_list;
  955. /* if a page-flip is pending, wait for it to complete */
  956. iter->cleanup = true;
  957. fprintf(stderr, "wait for pending page-flip to complete...\n");
  958. while (iter->pflip_pending) {
  959. ret = drmHandleEvent(fd, &ev);
  960. if (ret)
  961. break;
  962. }
  963. /* move head of the list to the next output */
  964. output_list = iter->next;
  965. /* destroy current output */
  966. modeset_output_destroy(fd, iter);
  967. }
  968. }
  969. /*
  970. * main() also changes. Instead of performing the KMS setup calling
  971. * drmModeSetCrtc(), we instead setup it using the atomic API with the
  972. * function modeset_perform_modeset(), which is called by modeset_draw().
  973. */
  974. int main(int argc, char **argv)
  975. {
  976. int ret, fd;
  977. const char *card;
  978. /* check which DRM device to open */
  979. if (argc > 1)
  980. card = argv[1];
  981. else
  982. card = "/dev/dri/card0";
  983. fprintf(stderr, "using card '%s'\n", card);
  984. /* open the DRM device */
  985. ret = modeset_open(&fd, card);
  986. if (ret)
  987. goto out_return;
  988. /* prepare all connectors and CRTCs */
  989. ret = modeset_prepare(fd);
  990. if (ret)
  991. goto out_close;
  992. /* draw some colors for 5seconds */
  993. modeset_draw(fd);
  994. /* cleanup everything */
  995. modeset_cleanup(fd);
  996. ret = 0;
  997. out_close:
  998. close(fd);
  999. out_return:
  1000. if (ret) {
  1001. errno = -ret;
  1002. fprintf(stderr, "modeset failed with error %d: %m\n", errno);
  1003. } else {
  1004. fprintf(stderr, "exiting\n");
  1005. }
  1006. return ret;
  1007. }
  1008. /*
  1009. * This is a very simple example to show how to use the KMS atomic API and how
  1010. * different it is from legacy KMS. Most modern drivers are using the atomic
  1011. * API, so it is important to have this example.
  1012. *
  1013. * Just like vsync'ed double-buffering, the atomic API does not not solve all
  1014. * the problems that can happen and you have to figure out the best
  1015. * implementation for your use case.
  1016. *
  1017. * If you want to take a look at more complex examples that makes use of KMS
  1018. * atomic API, I can recommend you:
  1019. *
  1020. * - kms-quads: https://gitlab.freedesktop.org/daniels/kms-quads/
  1021. * A more complex (but also well-explained) example that can be used to
  1022. * learn how to build a compositor using the atomic API. Supports both
  1023. * GL and software rendering.
  1024. *
  1025. * - Weston: https://gitlab.freedesktop.org/wayland/weston
  1026. * Reference implementation of a Wayland compositor. It's a very
  1027. * sophisticated DRM renderer, hard to understand fully as it uses more
  1028. * complicated techniques like DRM planes.
  1029. *
  1030. * Any feedback is welcome. Feel free to use this code freely for your own
  1031. * documentation or projects.
  1032. *
  1033. * - Hosted on http://github.com/dvdhrm/docs
  1034. */