├── .gitignore └── drm-howto ├── Makefile ├── modeset-atomic.c ├── modeset-double-buffered.c ├── modeset-vsync.c └── modeset.c /.gitignore: -------------------------------------------------------------------------------- 1 | drm-howto/modeset 2 | drm-howto/modeset-double-buffered 3 | drm-howto/modeset-vsync 4 | -------------------------------------------------------------------------------- /drm-howto/Makefile: -------------------------------------------------------------------------------- 1 | FLAGS=`pkg-config --cflags --libs libdrm` 2 | FLAGS+=-Wall -O0 -g 3 | FLAGS+=-D_FILE_OFFSET_BITS=64 4 | 5 | all: 6 | gcc -o modeset modeset.c $(FLAGS) 7 | gcc -o modeset-double-buffered modeset-double-buffered.c $(FLAGS) 8 | gcc -o modeset-vsync modeset-vsync.c $(FLAGS) 9 | gcc -o modeset-atomic modeset-atomic.c $(FLAGS) 10 | -------------------------------------------------------------------------------- /drm-howto/modeset-atomic.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset-atomic - DRM Atomic-API Modesetting Example 3 | * Written 2019 by Ezequiel Garcia 4 | * 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Double-Buffered VSync'ed Atomic Modesetting Howto 10 | * This example extends modeset-vsync.c, introducing planes and the 11 | * atomic API. 12 | * 13 | * Planes can be used to blend or overlay images on top of a CRTC 14 | * framebuffer during the scanout process. Not all hardware provide 15 | * planes and the number of planes available is also limited. If there's 16 | * not enough planes available or the hardware does not provide them, 17 | * users should fallback to composition via GPU or CPU to blend or 18 | * overlay the planes. Notice that this render process will result 19 | * in delay, what justifies the usage of planes by modern hardware 20 | * that needs to be fast. 21 | * 22 | * There are three types of planes: primary, cursor and overlay. For 23 | * compatibility with legacy userspace, the default behavior is to expose 24 | * only overlay planes to userspace (we're going to see in the code that 25 | * we have to ask to receive all types of planes). A good example of plane 26 | * usage is this: imagine a static desktop screen and the user is moving 27 | * the cursor around. Only the cursor is moving. Instead of calculating the 28 | * complete scene for each time the user moves its cursor, we can update only 29 | * the cursor plane and it will be automatically overlayed by the hardware on 30 | * top of the primary plane. There's no need of software composition in this 31 | * case. 32 | * 33 | * But there was synchronisation problems related to multiple planes 34 | * usage. The KMS API was not atomic, so you'd have to update the primary 35 | * plane and then the overlay planes with distinct IOCTL's. This could lead 36 | * to tearing and also some trouble related to blocking, so the atomic 37 | * API was proposed to fix these problems. 38 | * 39 | * With the introduction of the KMS atomic API, all the planes could get 40 | * updated in a single IOCTL, using drmModeAtomicCommit(). This can be 41 | * either asynchronous or fully blocking. 42 | * 43 | * This example assumes that you are familiar with modeset-vsync. Only 44 | * the differences between both files are highlighted here. 45 | */ 46 | 47 | #define _GNU_SOURCE 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | /* 63 | * A new struct is introduced: drm_object. It stores properties of certain 64 | * objects (connectors, CRTC and planes) that are used in atomic modeset setup 65 | * and also in atomic page-flips (all planes updated in a single IOCTL). 66 | */ 67 | 68 | struct drm_object { 69 | drmModeObjectProperties *props; 70 | drmModePropertyRes **props_info; 71 | uint32_t id; 72 | }; 73 | 74 | struct modeset_buf { 75 | uint32_t width; 76 | uint32_t height; 77 | uint32_t stride; 78 | uint32_t size; 79 | uint32_t handle; 80 | uint8_t *map; 81 | uint32_t fb; 82 | }; 83 | 84 | struct modeset_output { 85 | struct modeset_output *next; 86 | 87 | unsigned int front_buf; 88 | struct modeset_buf bufs[2]; 89 | 90 | struct drm_object connector; 91 | struct drm_object crtc; 92 | struct drm_object plane; 93 | 94 | drmModeModeInfo mode; 95 | uint32_t mode_blob_id; 96 | uint32_t crtc_index; 97 | 98 | bool pflip_pending; 99 | bool cleanup; 100 | 101 | uint8_t r, g, b; 102 | bool r_up, g_up, b_up; 103 | }; 104 | static struct modeset_output *output_list = NULL; 105 | 106 | /* 107 | * modeset_open() changes just a little bit. We now have to set that we're going 108 | * to use the KMS atomic API and check if the device is capable of handling it. 109 | */ 110 | 111 | static int modeset_open(int *out, const char *node) 112 | { 113 | int fd, ret; 114 | uint64_t cap; 115 | 116 | fd = open(node, O_RDWR | O_CLOEXEC); 117 | if (fd < 0) { 118 | ret = -errno; 119 | fprintf(stderr, "cannot open '%s': %m\n", node); 120 | return ret; 121 | } 122 | 123 | /* Set that we want to receive all the types of planes in the list. This 124 | * have to be done since, for legacy reasons, the default behavior is to 125 | * expose only the overlay planes to the users. The atomic API only 126 | * works if this is set. 127 | */ 128 | ret = drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); 129 | if (ret) { 130 | fprintf(stderr, "failed to set universal planes cap, %d\n", ret); 131 | return ret; 132 | } 133 | 134 | /* Here we set that we're going to use the KMS atomic API. It's supposed 135 | * to set the DRM_CLIENT_CAP_UNIVERSAL_PLANES automatically, but it's a 136 | * safe behavior to set it explicitly as we did in the previous 137 | * commands. This is also good for learning purposes. 138 | */ 139 | ret = drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); 140 | if (ret) { 141 | fprintf(stderr, "failed to set atomic cap, %d", ret); 142 | return ret; 143 | } 144 | 145 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &cap) < 0 || !cap) { 146 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 147 | node); 148 | close(fd); 149 | return -EOPNOTSUPP; 150 | } 151 | 152 | if (drmGetCap(fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) < 0 || !cap) { 153 | fprintf(stderr, "drm device '%s' does not support atomic KMS\n", 154 | node); 155 | close(fd); 156 | return -EOPNOTSUPP; 157 | } 158 | 159 | *out = fd; 160 | return 0; 161 | } 162 | 163 | /* 164 | * get_property_value() is a new function. Given a device, the properties of 165 | * an object and a name, search for the value of property 'name'. If we can't 166 | * find it, return -1. 167 | */ 168 | 169 | static int64_t get_property_value(int fd, drmModeObjectPropertiesPtr props, 170 | const char *name) 171 | { 172 | drmModePropertyPtr prop; 173 | uint64_t value; 174 | bool found; 175 | int j; 176 | 177 | found = false; 178 | for (j = 0; j < props->count_props && !found; j++) { 179 | prop = drmModeGetProperty(fd, props->props[j]); 180 | if (!strcmp(prop->name, name)) { 181 | value = props->prop_values[j]; 182 | found = true; 183 | } 184 | drmModeFreeProperty(prop); 185 | } 186 | 187 | if (!found) 188 | return -1; 189 | return value; 190 | } 191 | 192 | /* 193 | * get_drm_object_properties() is a new helpfer function that retrieves 194 | * the properties of a certain CRTC, plane or connector object. 195 | */ 196 | 197 | static void modeset_get_object_properties(int fd, struct drm_object *obj, 198 | uint32_t type) 199 | { 200 | const char *type_str; 201 | unsigned int i; 202 | 203 | obj->props = drmModeObjectGetProperties(fd, obj->id, type); 204 | if (!obj->props) { 205 | switch(type) { 206 | case DRM_MODE_OBJECT_CONNECTOR: 207 | type_str = "connector"; 208 | break; 209 | case DRM_MODE_OBJECT_PLANE: 210 | type_str = "plane"; 211 | break; 212 | case DRM_MODE_OBJECT_CRTC: 213 | type_str = "CRTC"; 214 | break; 215 | default: 216 | type_str = "unknown type"; 217 | break; 218 | } 219 | fprintf(stderr, "cannot get %s %d properties: %s\n", 220 | type_str, obj->id, strerror(errno)); 221 | return; 222 | } 223 | 224 | obj->props_info = calloc(obj->props->count_props, sizeof(obj->props_info)); 225 | for (i = 0; i < obj->props->count_props; i++) 226 | obj->props_info[i] = drmModeGetProperty(fd, obj->props->props[i]); 227 | } 228 | 229 | /* 230 | * set_drm_object_property() is a new function. It sets a property value to a 231 | * CRTC, plane or connector object. 232 | */ 233 | 234 | static int set_drm_object_property(drmModeAtomicReq *req, struct drm_object *obj, 235 | const char *name, uint64_t value) 236 | { 237 | int i; 238 | uint32_t prop_id = 0; 239 | 240 | for (i = 0; i < obj->props->count_props; i++) { 241 | if (!strcmp(obj->props_info[i]->name, name)) { 242 | prop_id = obj->props_info[i]->prop_id; 243 | break; 244 | } 245 | } 246 | 247 | if (prop_id == 0) { 248 | fprintf(stderr, "no object property: %s\n", name); 249 | return -EINVAL; 250 | } 251 | 252 | return drmModeAtomicAddProperty(req, obj->id, prop_id, value); 253 | } 254 | 255 | /* 256 | * modeset_find_crtc() changes a little bit. Now we also have to save the CRTC 257 | * index, and not only its id. 258 | */ 259 | 260 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 261 | struct modeset_output *out) 262 | { 263 | drmModeEncoder *enc; 264 | unsigned int i, j; 265 | uint32_t crtc; 266 | struct modeset_output *iter; 267 | 268 | /* first try the currently conected encoder+crtc */ 269 | if (conn->encoder_id) 270 | enc = drmModeGetEncoder(fd, conn->encoder_id); 271 | else 272 | enc = NULL; 273 | 274 | if (enc) { 275 | if (enc->crtc_id) { 276 | crtc = enc->crtc_id; 277 | for (iter = output_list; iter; iter = iter->next) { 278 | if (iter->crtc.id == crtc) { 279 | crtc = 0; 280 | break; 281 | } 282 | } 283 | 284 | if (crtc > 0) { 285 | drmModeFreeEncoder(enc); 286 | out->crtc.id = crtc; 287 | /* find the CRTC's index */ 288 | for (i = 0; i < res->count_crtcs; ++i) { 289 | if (res->crtcs[i] == crtc) { 290 | out->crtc_index = i; 291 | break; 292 | } 293 | } 294 | return 0; 295 | } 296 | } 297 | 298 | drmModeFreeEncoder(enc); 299 | } 300 | 301 | /* If the connector is not currently bound to an encoder or if the 302 | * encoder+crtc is already used by another connector (actually unlikely 303 | * but lets be safe), iterate all other available encoders to find a 304 | * matching CRTC. 305 | */ 306 | for (i = 0; i < conn->count_encoders; ++i) { 307 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 308 | if (!enc) { 309 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 310 | i, conn->encoders[i], errno); 311 | continue; 312 | } 313 | 314 | /* iterate all global CRTCs */ 315 | for (j = 0; j < res->count_crtcs; ++j) { 316 | /* check whether this CRTC works with the encoder */ 317 | if (!(enc->possible_crtcs & (1 << j))) 318 | continue; 319 | 320 | /* check that no other output already uses this CRTC */ 321 | crtc = res->crtcs[j]; 322 | for (iter = output_list; iter; iter = iter->next) { 323 | if (iter->crtc.id == crtc) { 324 | crtc = 0; 325 | break; 326 | } 327 | } 328 | 329 | /* We have found a CRTC, so save it and return. Note 330 | * that we have to save its index as well. The CRTC 331 | * index (not its ID) will be used when searching for a 332 | * suitable plane. 333 | */ 334 | if (crtc > 0) { 335 | fprintf(stdout, "crtc %u found for encoder %u, will need full modeset\n", 336 | crtc, conn->encoders[i]);; 337 | drmModeFreeEncoder(enc); 338 | out->crtc.id = crtc; 339 | out->crtc_index = j; 340 | return 0; 341 | } 342 | } 343 | 344 | drmModeFreeEncoder(enc); 345 | } 346 | 347 | fprintf(stderr, "cannot find suitable crtc for connector %u\n", 348 | conn->connector_id); 349 | return -ENOENT; 350 | } 351 | 352 | /* 353 | * modeset_find_plane() is a new function. Given a certain combination 354 | * of connector+CRTC, it looks for a primary plane for it. 355 | */ 356 | 357 | static int modeset_find_plane(int fd, struct modeset_output *out) 358 | { 359 | drmModePlaneResPtr plane_res; 360 | bool found_primary = false; 361 | int i, ret = -EINVAL; 362 | 363 | plane_res = drmModeGetPlaneResources(fd); 364 | if (!plane_res) { 365 | fprintf(stderr, "drmModeGetPlaneResources failed: %s\n", 366 | strerror(errno)); 367 | return -ENOENT; 368 | } 369 | 370 | /* iterates through all planes of a certain device */ 371 | for (i = 0; (i < plane_res->count_planes) && !found_primary; i++) { 372 | int plane_id = plane_res->planes[i]; 373 | 374 | drmModePlanePtr plane = drmModeGetPlane(fd, plane_id); 375 | if (!plane) { 376 | fprintf(stderr, "drmModeGetPlane(%u) failed: %s\n", plane_id, 377 | strerror(errno)); 378 | continue; 379 | } 380 | 381 | /* check if the plane can be used by our CRTC */ 382 | if (plane->possible_crtcs & (1 << out->crtc_index)) { 383 | drmModeObjectPropertiesPtr props = 384 | drmModeObjectGetProperties(fd, plane_id, DRM_MODE_OBJECT_PLANE); 385 | 386 | /* Get the "type" property to check if this is a primary 387 | * plane. Type property is special, as its enum value is 388 | * defined in UAPI headers. For the properties that are 389 | * not defined in the UAPI headers, we would have to 390 | * give kernel the property name and it would return the 391 | * corresponding enum value. We could also do this for 392 | * the "type" property, but it would make this simple 393 | * example more complex. The reason why defining enum 394 | * values for kernel properties in UAPI headers is 395 | * deprecated is that string names are easier to both 396 | * (userspace and kernel) make unique and keep 397 | * consistent between drivers and kernel versions. But 398 | * in order to not break userspace, some properties were 399 | * left in the UAPI headers as well. 400 | */ 401 | if (get_property_value(fd, props, "type") == DRM_PLANE_TYPE_PRIMARY) { 402 | found_primary = true; 403 | out->plane.id = plane_id; 404 | ret = 0; 405 | } 406 | 407 | drmModeFreeObjectProperties(props); 408 | } 409 | 410 | drmModeFreePlane(plane); 411 | } 412 | 413 | drmModeFreePlaneResources(plane_res); 414 | 415 | if (found_primary) 416 | fprintf(stdout, "found primary plane, id: %d\n", out->plane.id); 417 | else 418 | fprintf(stdout, "couldn't find a primary plane\n"); 419 | return ret; 420 | } 421 | 422 | /* 423 | * modeset_drm_object_fini() is a new helper function that destroys CRTCs, 424 | * connectors and planes 425 | */ 426 | 427 | static void modeset_drm_object_fini(struct drm_object *obj) 428 | { 429 | for (int i = 0; i < obj->props->count_props; i++) 430 | drmModeFreeProperty(obj->props_info[i]); 431 | free(obj->props_info); 432 | drmModeFreeObjectProperties(obj->props); 433 | } 434 | 435 | /* 436 | * modeset_setup_objects() is a new function. It helps us to retrieve 437 | * connector, CRTC and plane objects properties from the device. These 438 | * properties will help us during the atomic modesetting commit, so we save 439 | * them in our struct modeset_output object. 440 | */ 441 | 442 | static int modeset_setup_objects(int fd, struct modeset_output *out) 443 | { 444 | struct drm_object *connector = &out->connector; 445 | struct drm_object *crtc = &out->crtc; 446 | struct drm_object *plane = &out->plane; 447 | 448 | /* retrieve connector properties from the device */ 449 | modeset_get_object_properties(fd, connector, DRM_MODE_OBJECT_CONNECTOR); 450 | if (!connector->props) 451 | goto out_conn; 452 | 453 | /* retrieve CRTC properties from the device */ 454 | modeset_get_object_properties(fd, crtc, DRM_MODE_OBJECT_CRTC); 455 | if (!crtc->props) 456 | goto out_crtc; 457 | 458 | /* retrieve plane properties from the device */ 459 | modeset_get_object_properties(fd, plane, DRM_MODE_OBJECT_PLANE); 460 | if (!plane->props) 461 | goto out_plane; 462 | 463 | return 0; 464 | 465 | out_plane: 466 | modeset_drm_object_fini(crtc); 467 | out_crtc: 468 | modeset_drm_object_fini(connector); 469 | out_conn: 470 | return -ENOMEM; 471 | } 472 | 473 | /* 474 | * modeset_destroy_objects() is a new function. It destroys what we allocate 475 | * in modeset_setup_objects(). 476 | */ 477 | 478 | static void modeset_destroy_objects(int fd, struct modeset_output *out) 479 | { 480 | modeset_drm_object_fini(&out->connector); 481 | modeset_drm_object_fini(&out->crtc); 482 | modeset_drm_object_fini(&out->plane); 483 | } 484 | 485 | /* 486 | * modeset_create_fb() stays the same. 487 | */ 488 | 489 | static int modeset_create_fb(int fd, struct modeset_buf *buf) 490 | { 491 | struct drm_mode_create_dumb creq; 492 | struct drm_mode_destroy_dumb dreq; 493 | struct drm_mode_map_dumb mreq; 494 | int ret; 495 | uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0}; 496 | 497 | /* create dumb buffer */ 498 | memset(&creq, 0, sizeof(creq)); 499 | creq.width = buf->width; 500 | creq.height = buf->height; 501 | creq.bpp = 32; 502 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 503 | if (ret < 0) { 504 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 505 | errno); 506 | return -errno; 507 | } 508 | buf->stride = creq.pitch; 509 | buf->size = creq.size; 510 | buf->handle = creq.handle; 511 | 512 | /* create framebuffer object for the dumb-buffer */ 513 | handles[0] = buf->handle; 514 | pitches[0] = buf->stride; 515 | ret = drmModeAddFB2(fd, buf->width, buf->height, DRM_FORMAT_XRGB8888, 516 | handles, pitches, offsets, &buf->fb, 0); 517 | if (ret) { 518 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 519 | errno); 520 | ret = -errno; 521 | goto err_destroy; 522 | } 523 | 524 | /* prepare buffer for memory mapping */ 525 | memset(&mreq, 0, sizeof(mreq)); 526 | mreq.handle = buf->handle; 527 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 528 | if (ret) { 529 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 530 | errno); 531 | ret = -errno; 532 | goto err_fb; 533 | } 534 | 535 | /* perform actual memory mapping */ 536 | buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, 537 | fd, mreq.offset); 538 | if (buf->map == MAP_FAILED) { 539 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 540 | errno); 541 | ret = -errno; 542 | goto err_fb; 543 | } 544 | 545 | /* clear the framebuffer to 0 */ 546 | memset(buf->map, 0, buf->size); 547 | 548 | return 0; 549 | 550 | err_fb: 551 | drmModeRmFB(fd, buf->fb); 552 | err_destroy: 553 | memset(&dreq, 0, sizeof(dreq)); 554 | dreq.handle = buf->handle; 555 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 556 | return ret; 557 | } 558 | 559 | /* 560 | * modeset_destroy_fb() stays the same. 561 | */ 562 | 563 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf) 564 | { 565 | struct drm_mode_destroy_dumb dreq; 566 | 567 | /* unmap buffer */ 568 | munmap(buf->map, buf->size); 569 | 570 | /* delete framebuffer */ 571 | drmModeRmFB(fd, buf->fb); 572 | 573 | /* delete dumb buffer */ 574 | memset(&dreq, 0, sizeof(dreq)); 575 | dreq.handle = buf->handle; 576 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 577 | } 578 | 579 | /* 580 | * modeset_setup_framebuffers() creates framebuffers for the back and front 581 | * buffers of a certain output. Also, it copies the connector mode to these 582 | * buffers. 583 | */ 584 | 585 | static int modeset_setup_framebuffers(int fd, drmModeConnector *conn, 586 | struct modeset_output *out) 587 | { 588 | int i, ret; 589 | 590 | /* setup the front and back framebuffers */ 591 | for (i = 0; i < 2; i++) { 592 | 593 | /* copy mode info to buffer */ 594 | out->bufs[i].width = conn->modes[0].hdisplay; 595 | out->bufs[i].height = conn->modes[0].vdisplay; 596 | 597 | /* create a framebuffer for the buffer */ 598 | ret = modeset_create_fb(fd, &out->bufs[i]); 599 | if (ret) { 600 | /* the second framebuffer creation failed, so 601 | * we have to destroy the first before returning */ 602 | if (i == 1) 603 | modeset_destroy_fb(fd, &out->bufs[0]); 604 | return ret; 605 | } 606 | } 607 | 608 | return 0; 609 | } 610 | 611 | /* 612 | * modeset_output_destroy() is new. It destroys the objects (connector, crtc and 613 | * plane), front and back buffers, the mode blob property and then destroys the 614 | * output itself. 615 | */ 616 | 617 | static void modeset_output_destroy(int fd, struct modeset_output *out) 618 | { 619 | /* destroy connector, crtc and plane objects */ 620 | modeset_destroy_objects(fd, out); 621 | 622 | /* destroy front/back framebuffers */ 623 | modeset_destroy_fb(fd, &out->bufs[0]); 624 | modeset_destroy_fb(fd, &out->bufs[1]); 625 | 626 | /* destroy mode blob property */ 627 | drmModeDestroyPropertyBlob(fd, out->mode_blob_id); 628 | 629 | free(out); 630 | } 631 | 632 | /* 633 | * With a certain combination of connector+CRTC, we look for a suitable primary 634 | * plane for it. After that, we retrieve connector, CRTC and plane objects 635 | * properties from the device. These objects are used during the atomic modeset 636 | * setup (see modeset_atomic_prepare_commit()) and also during the page-flips 637 | * (see modeset_draw_out() and modeset_atomic_commit()). 638 | * 639 | * Besides that, we have to create a blob property that receives the output 640 | * mode. When we perform an atomic commit, the driver expects a CRTC property 641 | * named "MODE_ID", which points to the id of a blob. This usually happens for 642 | * properties that are not simple types. In this particular case, out->mode is a 643 | * struct. But we could have another property that expects the id of a blob that 644 | * holds an array, for instance. 645 | */ 646 | 647 | static struct modeset_output *modeset_output_create(int fd, drmModeRes *res, 648 | drmModeConnector *conn) 649 | { 650 | int ret; 651 | struct modeset_output *out; 652 | 653 | /* creates an output structure */ 654 | out = malloc(sizeof(*out)); 655 | memset(out, 0, sizeof(*out)); 656 | out->connector.id = conn->connector_id; 657 | 658 | /* check if a monitor is connected */ 659 | if (conn->connection != DRM_MODE_CONNECTED) { 660 | fprintf(stderr, "ignoring unused connector %u\n", 661 | conn->connector_id); 662 | goto out_error; 663 | } 664 | 665 | /* check if there is at least one valid mode */ 666 | if (conn->count_modes == 0) { 667 | fprintf(stderr, "no valid mode for connector %u\n", 668 | conn->connector_id); 669 | goto out_error; 670 | } 671 | 672 | /* copy the mode information into our output structure */ 673 | memcpy(&out->mode, &conn->modes[0], sizeof(out->mode)); 674 | /* create the blob property using out->mode and save its id in the output*/ 675 | if (drmModeCreatePropertyBlob(fd, &out->mode, sizeof(out->mode), 676 | &out->mode_blob_id) != 0) { 677 | fprintf(stderr, "couldn't create a blob property\n"); 678 | goto out_error; 679 | } 680 | fprintf(stderr, "mode for connector %u is %ux%u\n", 681 | conn->connector_id, out->bufs[0].width, out->bufs[0].height); 682 | 683 | /* find a crtc for this connector */ 684 | ret = modeset_find_crtc(fd, res, conn, out); 685 | if (ret) { 686 | fprintf(stderr, "no valid crtc for connector %u\n", 687 | conn->connector_id); 688 | goto out_blob; 689 | } 690 | 691 | /* with a connector and crtc, find a primary plane */ 692 | ret = modeset_find_plane(fd, out); 693 | if (ret) { 694 | fprintf(stderr, "no valid plane for crtc %u\n", out->crtc.id); 695 | goto out_blob; 696 | } 697 | 698 | /* gather properties of our connector, CRTC and planes */ 699 | ret = modeset_setup_objects(fd, out); 700 | if (ret) { 701 | fprintf(stderr, "cannot get plane properties\n"); 702 | goto out_blob; 703 | } 704 | 705 | /* setup front/back framebuffers for this CRTC */ 706 | ret = modeset_setup_framebuffers(fd, conn, out); 707 | if (ret) { 708 | fprintf(stderr, "cannot create framebuffers for connector %u\n", 709 | conn->connector_id); 710 | goto out_obj; 711 | } 712 | 713 | return out; 714 | 715 | out_obj: 716 | modeset_destroy_objects(fd, out); 717 | out_blob: 718 | drmModeDestroyPropertyBlob(fd, out->mode_blob_id); 719 | out_error: 720 | free(out); 721 | return NULL; 722 | } 723 | 724 | /* 725 | * modeset_prepare() changes a little bit. Now we use the new function 726 | * modeset_output_create() to allocate memory and setup the output. 727 | */ 728 | 729 | static int modeset_prepare(int fd) 730 | { 731 | drmModeRes *res; 732 | drmModeConnector *conn; 733 | unsigned int i; 734 | struct modeset_output *out; 735 | 736 | /* retrieve resources */ 737 | res = drmModeGetResources(fd); 738 | if (!res) { 739 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 740 | errno); 741 | return -errno; 742 | } 743 | 744 | /* iterate all connectors */ 745 | for (i = 0; i < res->count_connectors; ++i) { 746 | /* get information for each connector */ 747 | conn = drmModeGetConnector(fd, res->connectors[i]); 748 | if (!conn) { 749 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 750 | i, res->connectors[i], errno); 751 | continue; 752 | } 753 | 754 | /* create an output structure and free connector data */ 755 | out = modeset_output_create(fd, res, conn); 756 | drmModeFreeConnector(conn); 757 | if (!out) 758 | continue; 759 | 760 | /* link output into global list */ 761 | out->next = output_list; 762 | output_list = out; 763 | } 764 | if (!output_list) { 765 | fprintf(stderr, "couldn't create any outputs\n"); 766 | return -1; 767 | } 768 | 769 | /* free resources again */ 770 | drmModeFreeResources(res); 771 | return 0; 772 | } 773 | 774 | /* 775 | * modeset_atomic_prepare_commit() is new. Here we set the values of properties 776 | * (of our connector, CRTC and plane objects) that we want to change in the 777 | * atomic commit. These changes are temporarily stored in drmModeAtomicReq *req 778 | * until the commit actually happens. 779 | */ 780 | 781 | static int modeset_atomic_prepare_commit(int fd, struct modeset_output *out, 782 | drmModeAtomicReq *req) 783 | { 784 | struct drm_object *plane = &out->plane; 785 | struct modeset_buf *buf = &out->bufs[out->front_buf ^ 1]; 786 | 787 | /* set id of the CRTC id that the connector is using */ 788 | if (set_drm_object_property(req, &out->connector, "CRTC_ID", out->crtc.id) < 0) 789 | return -1; 790 | 791 | /* set the mode id of the CRTC; this property receives the id of a blob 792 | * property that holds the struct that actually contains the mode info */ 793 | if (set_drm_object_property(req, &out->crtc, "MODE_ID", out->mode_blob_id) < 0) 794 | return -1; 795 | 796 | /* set the CRTC object as active */ 797 | if (set_drm_object_property(req, &out->crtc, "ACTIVE", 1) < 0) 798 | return -1; 799 | 800 | /* set properties of the plane related to the CRTC and the framebuffer */ 801 | if (set_drm_object_property(req, plane, "FB_ID", buf->fb) < 0) 802 | return -1; 803 | if (set_drm_object_property(req, plane, "CRTC_ID", out->crtc.id) < 0) 804 | return -1; 805 | if (set_drm_object_property(req, plane, "SRC_X", 0) < 0) 806 | return -1; 807 | if (set_drm_object_property(req, plane, "SRC_Y", 0) < 0) 808 | return -1; 809 | if (set_drm_object_property(req, plane, "SRC_W", buf->width << 16) < 0) 810 | return -1; 811 | if (set_drm_object_property(req, plane, "SRC_H", buf->height << 16) < 0) 812 | return -1; 813 | if (set_drm_object_property(req, plane, "CRTC_X", 0) < 0) 814 | return -1; 815 | if (set_drm_object_property(req, plane, "CRTC_Y", 0) < 0) 816 | return -1; 817 | if (set_drm_object_property(req, plane, "CRTC_W", buf->width) < 0) 818 | return -1; 819 | if (set_drm_object_property(req, plane, "CRTC_H", buf->height) < 0) 820 | return -1; 821 | 822 | return 0; 823 | } 824 | 825 | /* 826 | * A short helper function to compute a changing color value. No need to 827 | * understand it. 828 | */ 829 | 830 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 831 | { 832 | uint8_t next; 833 | 834 | next = cur + (*up ? 1 : -1) * (rand() % mod); 835 | if ((*up && next < cur) || (!*up && next > cur)) { 836 | *up = !*up; 837 | next = cur; 838 | } 839 | 840 | return next; 841 | } 842 | 843 | /* 844 | * Draw on back framebuffer before the page-flip is requested. 845 | */ 846 | 847 | static void modeset_paint_framebuffer(struct modeset_output *out) 848 | { 849 | struct modeset_buf *buf; 850 | unsigned int j, k, off; 851 | 852 | /* draw on back framebuffer */ 853 | out->r = next_color(&out->r_up, out->r, 5); 854 | out->g = next_color(&out->g_up, out->g, 5); 855 | out->b = next_color(&out->b_up, out->b, 5); 856 | buf = &out->bufs[out->front_buf ^ 1]; 857 | for (j = 0; j < buf->height; ++j) { 858 | for (k = 0; k < buf->width; ++k) { 859 | off = buf->stride * j + k * 4; 860 | *(uint32_t*)&buf->map[off] = 861 | (out->r << 16) | (out->g << 8) | out->b; 862 | } 863 | } 864 | 865 | } 866 | 867 | /* 868 | * modeset_draw_out() prepares the framebuffer with the drawing and then it asks 869 | * for the driver to perform an atomic commit. This will lead to a page-flip and 870 | * the content of the framebuffer will be displayed. In this simple example 871 | * we're only using the primary plane, but we could also be updating other 872 | * planes in the same atomic commit. 873 | * 874 | * Just like in modeset_perform_modeset(), we first setup everything with 875 | * modeset_atomic_prepare_commit() and then actually perform the atomic commit. 876 | * But there are some important differences: 877 | * 878 | * 1. Here we just want to perform a commit that changes the state of a specific 879 | * output, and in modeset_perform_modeset() we did an atomic commit that was 880 | * supposed to setup all the outputs at once. So there's no need to prepare 881 | * every output before performing the atomic commit. But let's suppose you 882 | * prepare every output and then perform the commit. It should schedule a 883 | * page-flip for all of them, but modeset_draw_out() was called because the 884 | * page-flip for a specific output has finished. The others may not be 885 | * prepared for a page-flip yet (e.g. in the middle of a scanout), so these 886 | * page-flips will fail. 887 | * 888 | * 2. Here we have already painted the framebuffer and also we don't use the 889 | * flag DRM_MODE_ALLOW_MODESET anymore, since the modeset already happened. 890 | * We could continue to use this flag, as it makes no difference if 891 | * modeset_perform_modeset() is correct and there's no bug in the kernel. 892 | * The flag only allows (it doesn't force) the driver to perform a modeset, 893 | * but we have already performed it in modeset_perform_modeset() and now we 894 | * just want page-flips to occur. If we still need to perform modesets it 895 | * means that we have a bug somewhere, and it may be better to fail than to 896 | * glitch (a modeset can cause unecessary latency and also blank the screen). 897 | */ 898 | 899 | static void modeset_draw_out(int fd, struct modeset_output *out) 900 | { 901 | drmModeAtomicReq *req; 902 | int ret, flags; 903 | 904 | /* draw on framebuffer of the output */ 905 | modeset_paint_framebuffer(out); 906 | 907 | /* prepare output for atomic commit */ 908 | req = drmModeAtomicAlloc(); 909 | ret = modeset_atomic_prepare_commit(fd, out, req); 910 | if (ret < 0) { 911 | fprintf(stderr, "prepare atomic commit failed, %d\n", errno); 912 | return; 913 | } 914 | 915 | /* We've just draw on the framebuffer, prepared the commit and now it's 916 | * time to perform a page-flip to display its content. 917 | * 918 | * DRM_MODE_PAGE_FLIP_EVENT signalizes that we want to receive a 919 | * page-flip event in the DRM-fd when the page-flip happens. This flag 920 | * is also used in the non-atomic examples, so you're probably familiar 921 | * with it. 922 | * 923 | * DRM_MODE_ATOMIC_NONBLOCK makes the page-flip non-blocking. We don't 924 | * want to be blocked waiting for the commit to happen, since we can use 925 | * this time to prepare a new framebuffer, for instance. We can only do 926 | * this because there are mechanisms to know when the commit is complete 927 | * (like page flip event, explained above). 928 | */ 929 | flags = DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK; 930 | ret = drmModeAtomicCommit(fd, req, flags, NULL); 931 | drmModeAtomicFree(req); 932 | 933 | if (ret < 0) { 934 | fprintf(stderr, "atomic commit failed, %d\n", errno); 935 | return; 936 | } 937 | out->front_buf ^= 1; 938 | out->pflip_pending = true; 939 | } 940 | 941 | /* 942 | * modeset_page_flip_event() changes. Now that we are using page_flip_handler2, 943 | * we also receive the CRTC that is responsible for this event. When using the 944 | * atomic API we commit multiple CRTC's at once, so we need the information of 945 | * what output caused the event in order to schedule a new page-flip for it. 946 | */ 947 | 948 | static void modeset_page_flip_event(int fd, unsigned int frame, 949 | unsigned int sec, unsigned int usec, 950 | unsigned int crtc_id, void *data) 951 | { 952 | struct modeset_output *out, *iter; 953 | 954 | /* find the output responsible for this event */ 955 | out = NULL; 956 | for (iter = output_list; iter; iter = iter->next) { 957 | if (iter->crtc.id == crtc_id) { 958 | out = iter; 959 | break; 960 | } 961 | } 962 | if (out == NULL) 963 | return; 964 | 965 | out->pflip_pending = false; 966 | if (!out->cleanup) 967 | modeset_draw_out(fd, out); 968 | } 969 | 970 | /* 971 | * modeset_perform_modeset() is new. First we define what properties have to be 972 | * changed and the values that they will receive. To check if the modeset will 973 | * work as expected, we perform an atomic commit with the flag 974 | * DRM_MODE_ATOMIC_TEST_ONLY. With this flag the DRM driver tests if the atomic 975 | * commit would work, but it doesn't commit it to the hardware. After, the same 976 | * atomic commit is performed without the TEST_ONLY flag, but not only before we 977 | * draw on the framebuffers of the outputs. This is necessary to avoid 978 | * displaying unwanted content. 979 | * 980 | * NOTE: we can't perform an atomic commit without an attached frambeuffer 981 | * (even when we have DRM_MODE_ATOMIC_TEST_ONLY). It will simply fail. 982 | */ 983 | 984 | static int modeset_perform_modeset(int fd) 985 | { 986 | int ret, flags; 987 | struct modeset_output *iter; 988 | drmModeAtomicReq *req; 989 | 990 | /* prepare modeset on all outputs */ 991 | req = drmModeAtomicAlloc(); 992 | for (iter = output_list; iter; iter = iter->next) { 993 | ret = modeset_atomic_prepare_commit(fd, iter, req); 994 | if (ret < 0) 995 | break; 996 | } 997 | if (ret < 0) { 998 | fprintf(stderr, "prepare atomic commit failed, %d\n", errno); 999 | return ret; 1000 | } 1001 | 1002 | /* perform test-only atomic commit */ 1003 | flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_ALLOW_MODESET; 1004 | ret = drmModeAtomicCommit(fd, req, flags, NULL); 1005 | if (ret < 0) { 1006 | fprintf(stderr, "test-only atomic commit failed, %d\n", errno); 1007 | drmModeAtomicFree(req); 1008 | return ret; 1009 | } 1010 | 1011 | /* draw on back framebuffer of all outputs */ 1012 | for (iter = output_list; iter; iter = iter->next) { 1013 | 1014 | /* colors initialization, this is the first time we're drawing */ 1015 | iter->r = rand() % 0xff; 1016 | iter->g = rand() % 0xff; 1017 | iter->b = rand() % 0xff; 1018 | iter->r_up = iter->g_up = iter->b_up = true; 1019 | 1020 | modeset_paint_framebuffer(iter); 1021 | } 1022 | 1023 | /* initial modeset on all outputs */ 1024 | flags = DRM_MODE_ATOMIC_ALLOW_MODESET | DRM_MODE_PAGE_FLIP_EVENT; 1025 | ret = drmModeAtomicCommit(fd, req, flags, NULL); 1026 | if (ret < 0) 1027 | fprintf(stderr, "modeset atomic commit failed, %d\n", errno); 1028 | 1029 | drmModeAtomicFree(req); 1030 | 1031 | return ret; 1032 | } 1033 | 1034 | /* 1035 | * modeset_draw() changes. If we got here, the modeset already occurred. When 1036 | * the page-flip for a certain output is done, an event will be fired and we'll 1037 | * be able to handle it. 1038 | * 1039 | * Here we define the function that should handle these events, which is 1040 | * modeset_page_flip_event(). This function calls modeset_draw_out(), which is 1041 | * responsible for preparing a new framebuffer and performing another atomic 1042 | * commit for us. 1043 | * 1044 | * Then we have a 5 seconds loop that keeps waiting for the events that are 1045 | * fired when the page-flip is complete. drmHandleEvent() is reponsible for 1046 | * reading the events from the fd and to call modeset_page_flip_event() for 1047 | * each one of them. 1048 | */ 1049 | 1050 | static void modeset_draw(int fd) 1051 | { 1052 | int ret; 1053 | fd_set fds; 1054 | time_t start, cur; 1055 | struct timeval v; 1056 | drmEventContext ev; 1057 | 1058 | /* init variables */ 1059 | srand(time(&start)); 1060 | FD_ZERO(&fds); 1061 | memset(&v, 0, sizeof(v)); 1062 | memset(&ev, 0, sizeof(ev)); 1063 | 1064 | /* 3 is the first version that allow us to use page_flip_handler2, which 1065 | * is just like page_flip_handler but with the addition of passing the 1066 | * crtc_id as argument to the function that will handle page-flip events 1067 | * (in our case, modeset_page_flip_event()). This is good because we can 1068 | * find out for what output the page-flip happened. 1069 | * 1070 | * The usage of page_flip_handler2 is the reason why we needed to verify 1071 | * the support for DRM_CAP_CRTC_IN_VBLANK_EVENT. 1072 | */ 1073 | ev.version = 3; 1074 | ev.page_flip_handler2 = modeset_page_flip_event; 1075 | 1076 | /* perform modeset using atomic commit */ 1077 | modeset_perform_modeset(fd); 1078 | 1079 | /* wait 5s for VBLANK or input events */ 1080 | while (time(&cur) < start + 5) { 1081 | FD_SET(0, &fds); 1082 | FD_SET(fd, &fds); 1083 | v.tv_sec = start + 5 - cur; 1084 | 1085 | ret = select(fd + 1, &fds, NULL, NULL, &v); 1086 | if (ret < 0) { 1087 | fprintf(stderr, "select() failed with %d: %m\n", errno); 1088 | break; 1089 | } else if (FD_ISSET(0, &fds)) { 1090 | fprintf(stderr, "exit due to user-input\n"); 1091 | break; 1092 | } else if (FD_ISSET(fd, &fds)) { 1093 | /* read the fd looking for events and handle each event 1094 | * by calling modeset_page_flip_event() */ 1095 | drmHandleEvent(fd, &ev); 1096 | } 1097 | } 1098 | } 1099 | 1100 | /* 1101 | * modeset_cleanup() stays the same. 1102 | */ 1103 | 1104 | static void modeset_cleanup(int fd) 1105 | { 1106 | struct modeset_output *iter; 1107 | drmEventContext ev; 1108 | int ret; 1109 | 1110 | /* init variables */ 1111 | memset(&ev, 0, sizeof(ev)); 1112 | ev.version = 3; 1113 | ev.page_flip_handler2 = modeset_page_flip_event; 1114 | 1115 | while (output_list) { 1116 | /* get first output from list */ 1117 | iter = output_list; 1118 | 1119 | /* if a page-flip is pending, wait for it to complete */ 1120 | iter->cleanup = true; 1121 | fprintf(stderr, "wait for pending page-flip to complete...\n"); 1122 | while (iter->pflip_pending) { 1123 | ret = drmHandleEvent(fd, &ev); 1124 | if (ret) 1125 | break; 1126 | } 1127 | 1128 | /* move head of the list to the next output */ 1129 | output_list = iter->next; 1130 | 1131 | /* destroy current output */ 1132 | modeset_output_destroy(fd, iter); 1133 | } 1134 | } 1135 | 1136 | /* 1137 | * main() also changes. Instead of performing the KMS setup calling 1138 | * drmModeSetCrtc(), we instead setup it using the atomic API with the 1139 | * function modeset_perform_modeset(), which is called by modeset_draw(). 1140 | */ 1141 | 1142 | int main(int argc, char **argv) 1143 | { 1144 | int ret, fd; 1145 | const char *card; 1146 | 1147 | /* check which DRM device to open */ 1148 | if (argc > 1) 1149 | card = argv[1]; 1150 | else 1151 | card = "/dev/dri/card0"; 1152 | 1153 | fprintf(stderr, "using card '%s'\n", card); 1154 | 1155 | /* open the DRM device */ 1156 | ret = modeset_open(&fd, card); 1157 | if (ret) 1158 | goto out_return; 1159 | 1160 | /* prepare all connectors and CRTCs */ 1161 | ret = modeset_prepare(fd); 1162 | if (ret) 1163 | goto out_close; 1164 | 1165 | /* draw some colors for 5seconds */ 1166 | modeset_draw(fd); 1167 | 1168 | /* cleanup everything */ 1169 | modeset_cleanup(fd); 1170 | 1171 | ret = 0; 1172 | 1173 | out_close: 1174 | close(fd); 1175 | out_return: 1176 | if (ret) { 1177 | errno = -ret; 1178 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 1179 | } else { 1180 | fprintf(stderr, "exiting\n"); 1181 | } 1182 | return ret; 1183 | } 1184 | 1185 | /* 1186 | * This is a very simple example to show how to use the KMS atomic API and how 1187 | * different it is from legacy KMS. Most modern drivers are using the atomic 1188 | * API, so it is important to have this example. 1189 | * 1190 | * Just like vsync'ed double-buffering, the atomic API does not not solve all 1191 | * the problems that can happen and you have to figure out the best 1192 | * implementation for your use case. 1193 | * 1194 | * If you want to take a look at more complex examples that makes use of KMS 1195 | * atomic API, I can recommend you: 1196 | * 1197 | * - kms-quads: https://gitlab.freedesktop.org/daniels/kms-quads/ 1198 | * A more complex (but also well-explained) example that can be used to 1199 | * learn how to build a compositor using the atomic API. Supports both 1200 | * GL and software rendering. 1201 | * 1202 | * - Weston: https://gitlab.freedesktop.org/wayland/weston 1203 | * Reference implementation of a Wayland compositor. It's a very 1204 | * sophisticated DRM renderer, hard to understand fully as it uses more 1205 | * complicated techniques like DRM planes. 1206 | * 1207 | * Any feedback is welcome. Feel free to use this code freely for your own 1208 | * documentation or projects. 1209 | * 1210 | * - Hosted on http://github.com/dvdhrm/docs 1211 | */ 1212 | -------------------------------------------------------------------------------- /drm-howto/modeset-double-buffered.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset - DRM Double-Buffered Modesetting Example 3 | * 4 | * Written 2012 by David Rheinsberg 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Double-Buffered Modesetting Howto 10 | * This example extends the modeset.c howto and introduces double-buffering. 11 | * When drawing a new frame into a framebuffer, we should always draw into an 12 | * unused buffer and not into the front buffer. If we draw into the front 13 | * buffer, we might have drawn half the frame when the display-controller starts 14 | * scanning out the next frame. Hence, we see flickering on the screen. 15 | * The technique to avoid this is called double-buffering. We have two 16 | * framebuffers, the front buffer which is currently used for scanout and a 17 | * back-buffer that is used for drawing operations. When a frame is done, we 18 | * simply swap both buffers. 19 | * Swapping does not mean copying data, instead, only the pointers to the 20 | * buffers are swapped. 21 | * 22 | * Please read modeset.c before reading this file as most of the functions stay 23 | * the same. Only the differences are highlighted here. 24 | * Also note that triple-buffering or any other number of buffers can be easily 25 | * implemented by following the scheme here. However, in this example we limit 26 | * the number of buffers to 2 so it is easier to follow. 27 | */ 28 | 29 | #define _GNU_SOURCE 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | struct modeset_buf; 44 | struct modeset_dev; 45 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 46 | struct modeset_dev *dev); 47 | static int modeset_create_fb(int fd, struct modeset_buf *buf); 48 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf); 49 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 50 | struct modeset_dev *dev); 51 | static int modeset_open(int *out, const char *node); 52 | static int modeset_prepare(int fd); 53 | static void modeset_draw(int fd); 54 | static void modeset_cleanup(int fd); 55 | 56 | /* 57 | * modeset_open() stays the same as before. 58 | */ 59 | 60 | static int modeset_open(int *out, const char *node) 61 | { 62 | int fd, ret; 63 | uint64_t has_dumb; 64 | 65 | fd = open(node, O_RDWR | O_CLOEXEC); 66 | if (fd < 0) { 67 | ret = -errno; 68 | fprintf(stderr, "cannot open '%s': %m\n", node); 69 | return ret; 70 | } 71 | 72 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || 73 | !has_dumb) { 74 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 75 | node); 76 | close(fd); 77 | return -EOPNOTSUPP; 78 | } 79 | 80 | *out = fd; 81 | return 0; 82 | } 83 | 84 | /* 85 | * Previously, we used the modeset_dev objects to hold buffer informations, too. 86 | * Technically, we could have split them but avoided this to make the 87 | * example simpler. 88 | * However, in this example we need 2 buffers. One back buffer and one front 89 | * buffer. So we introduce a new structure modeset_buf which contains everything 90 | * related to a single buffer. Each device now gets an array of two of these 91 | * buffers. 92 | * Each buffer consists of width, height, stride, size, handle, map and fb-id. 93 | * They have the same meaning as before. 94 | * 95 | * Each device also gets a new integer field: front_buf. This field contains the 96 | * index of the buffer that is currently used as front buffer / scanout buffer. 97 | * In our example it can be 0 or 1. We flip it by using XOR: 98 | * dev->front_buf ^= dev->front_buf 99 | * 100 | * Everything else stays the same. 101 | */ 102 | 103 | struct modeset_buf { 104 | uint32_t width; 105 | uint32_t height; 106 | uint32_t stride; 107 | uint32_t size; 108 | uint32_t handle; 109 | uint8_t *map; 110 | uint32_t fb; 111 | }; 112 | 113 | struct modeset_dev { 114 | struct modeset_dev *next; 115 | 116 | unsigned int front_buf; 117 | struct modeset_buf bufs[2]; 118 | 119 | drmModeModeInfo mode; 120 | uint32_t conn; 121 | uint32_t crtc; 122 | drmModeCrtc *saved_crtc; 123 | }; 124 | 125 | static struct modeset_dev *modeset_list = NULL; 126 | 127 | /* 128 | * modeset_prepare() stays the same. 129 | */ 130 | 131 | static int modeset_prepare(int fd) 132 | { 133 | drmModeRes *res; 134 | drmModeConnector *conn; 135 | unsigned int i; 136 | struct modeset_dev *dev; 137 | int ret; 138 | 139 | /* retrieve resources */ 140 | res = drmModeGetResources(fd); 141 | if (!res) { 142 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 143 | errno); 144 | return -errno; 145 | } 146 | 147 | /* iterate all connectors */ 148 | for (i = 0; i < res->count_connectors; ++i) { 149 | /* get information for each connector */ 150 | conn = drmModeGetConnector(fd, res->connectors[i]); 151 | if (!conn) { 152 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 153 | i, res->connectors[i], errno); 154 | continue; 155 | } 156 | 157 | /* create a device structure */ 158 | dev = malloc(sizeof(*dev)); 159 | memset(dev, 0, sizeof(*dev)); 160 | dev->conn = conn->connector_id; 161 | 162 | /* call helper function to prepare this connector */ 163 | ret = modeset_setup_dev(fd, res, conn, dev); 164 | if (ret) { 165 | if (ret != -ENOENT) { 166 | errno = -ret; 167 | fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", 168 | i, res->connectors[i], errno); 169 | } 170 | free(dev); 171 | drmModeFreeConnector(conn); 172 | continue; 173 | } 174 | 175 | /* free connector data and link device into global list */ 176 | drmModeFreeConnector(conn); 177 | dev->next = modeset_list; 178 | modeset_list = dev; 179 | } 180 | 181 | /* free resources again */ 182 | drmModeFreeResources(res); 183 | return 0; 184 | } 185 | 186 | /* 187 | * modeset_setup_dev() sets up all resources for a single device. It mostly 188 | * stays the same, but one thing changes: We allocate two framebuffers instead 189 | * of one. That is, we call modeset_create_fb() twice. 190 | * We also copy the width/height information into both framebuffers so 191 | * modeset_create_fb() can use them without requiring a pointer to modeset_dev. 192 | */ 193 | 194 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 195 | struct modeset_dev *dev) 196 | { 197 | int ret; 198 | 199 | /* check if a monitor is connected */ 200 | if (conn->connection != DRM_MODE_CONNECTED) { 201 | fprintf(stderr, "ignoring unused connector %u\n", 202 | conn->connector_id); 203 | return -ENOENT; 204 | } 205 | 206 | /* check if there is at least one valid mode */ 207 | if (conn->count_modes == 0) { 208 | fprintf(stderr, "no valid mode for connector %u\n", 209 | conn->connector_id); 210 | return -EFAULT; 211 | } 212 | 213 | /* copy the mode information into our device structure and into both 214 | * buffers */ 215 | memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); 216 | dev->bufs[0].width = conn->modes[0].hdisplay; 217 | dev->bufs[0].height = conn->modes[0].vdisplay; 218 | dev->bufs[1].width = conn->modes[0].hdisplay; 219 | dev->bufs[1].height = conn->modes[0].vdisplay; 220 | fprintf(stderr, "mode for connector %u is %ux%u\n", 221 | conn->connector_id, dev->bufs[0].width, dev->bufs[0].height); 222 | 223 | /* find a crtc for this connector */ 224 | ret = modeset_find_crtc(fd, res, conn, dev); 225 | if (ret) { 226 | fprintf(stderr, "no valid crtc for connector %u\n", 227 | conn->connector_id); 228 | return ret; 229 | } 230 | 231 | /* create framebuffer #1 for this CRTC */ 232 | ret = modeset_create_fb(fd, &dev->bufs[0]); 233 | if (ret) { 234 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 235 | conn->connector_id); 236 | return ret; 237 | } 238 | 239 | /* create framebuffer #2 for this CRTC */ 240 | ret = modeset_create_fb(fd, &dev->bufs[1]); 241 | if (ret) { 242 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 243 | conn->connector_id); 244 | modeset_destroy_fb(fd, &dev->bufs[0]); 245 | return ret; 246 | } 247 | 248 | return 0; 249 | } 250 | 251 | /* 252 | * modeset_find_crtc() stays the same. 253 | */ 254 | 255 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 256 | struct modeset_dev *dev) 257 | { 258 | drmModeEncoder *enc; 259 | unsigned int i, j; 260 | int32_t crtc; 261 | struct modeset_dev *iter; 262 | 263 | /* first try the currently conected encoder+crtc */ 264 | if (conn->encoder_id) 265 | enc = drmModeGetEncoder(fd, conn->encoder_id); 266 | else 267 | enc = NULL; 268 | 269 | if (enc) { 270 | if (enc->crtc_id) { 271 | crtc = enc->crtc_id; 272 | for (iter = modeset_list; iter; iter = iter->next) { 273 | if (iter->crtc == crtc) { 274 | crtc = -1; 275 | break; 276 | } 277 | } 278 | 279 | if (crtc >= 0) { 280 | drmModeFreeEncoder(enc); 281 | dev->crtc = crtc; 282 | return 0; 283 | } 284 | } 285 | 286 | drmModeFreeEncoder(enc); 287 | } 288 | 289 | /* If the connector is not currently bound to an encoder or if the 290 | * encoder+crtc is already used by another connector (actually unlikely 291 | * but lets be safe), iterate all other available encoders to find a 292 | * matching CRTC. */ 293 | for (i = 0; i < conn->count_encoders; ++i) { 294 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 295 | if (!enc) { 296 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 297 | i, conn->encoders[i], errno); 298 | continue; 299 | } 300 | 301 | /* iterate all global CRTCs */ 302 | for (j = 0; j < res->count_crtcs; ++j) { 303 | /* check whether this CRTC works with the encoder */ 304 | if (!(enc->possible_crtcs & (1 << j))) 305 | continue; 306 | 307 | /* check that no other device already uses this CRTC */ 308 | crtc = res->crtcs[j]; 309 | for (iter = modeset_list; iter; iter = iter->next) { 310 | if (iter->crtc == crtc) { 311 | crtc = -1; 312 | break; 313 | } 314 | } 315 | 316 | /* we have found a CRTC, so save it and return */ 317 | if (crtc >= 0) { 318 | drmModeFreeEncoder(enc); 319 | dev->crtc = crtc; 320 | return 0; 321 | } 322 | } 323 | 324 | drmModeFreeEncoder(enc); 325 | } 326 | 327 | fprintf(stderr, "cannot find suitable CRTC for connector %u\n", 328 | conn->connector_id); 329 | return -ENOENT; 330 | } 331 | 332 | /* 333 | * modeset_create_fb() is mostly the same as before. Buf instead of writing the 334 | * fields of a modeset_dev, we now require a buffer pointer passed as @buf. 335 | * Please note that buf->width and buf->height are initialized by 336 | * modeset_setup_dev() so we can use them here. 337 | */ 338 | 339 | static int modeset_create_fb(int fd, struct modeset_buf *buf) 340 | { 341 | struct drm_mode_create_dumb creq; 342 | struct drm_mode_destroy_dumb dreq; 343 | struct drm_mode_map_dumb mreq; 344 | int ret; 345 | 346 | /* create dumb buffer */ 347 | memset(&creq, 0, sizeof(creq)); 348 | creq.width = buf->width; 349 | creq.height = buf->height; 350 | creq.bpp = 32; 351 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 352 | if (ret < 0) { 353 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 354 | errno); 355 | return -errno; 356 | } 357 | buf->stride = creq.pitch; 358 | buf->size = creq.size; 359 | buf->handle = creq.handle; 360 | 361 | /* create framebuffer object for the dumb-buffer */ 362 | ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride, 363 | buf->handle, &buf->fb); 364 | if (ret) { 365 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 366 | errno); 367 | ret = -errno; 368 | goto err_destroy; 369 | } 370 | 371 | /* prepare buffer for memory mapping */ 372 | memset(&mreq, 0, sizeof(mreq)); 373 | mreq.handle = buf->handle; 374 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 375 | if (ret) { 376 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 377 | errno); 378 | ret = -errno; 379 | goto err_fb; 380 | } 381 | 382 | /* perform actual memory mapping */ 383 | buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, 384 | fd, mreq.offset); 385 | if (buf->map == MAP_FAILED) { 386 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 387 | errno); 388 | ret = -errno; 389 | goto err_fb; 390 | } 391 | 392 | /* clear the framebuffer to 0 */ 393 | memset(buf->map, 0, buf->size); 394 | 395 | return 0; 396 | 397 | err_fb: 398 | drmModeRmFB(fd, buf->fb); 399 | err_destroy: 400 | memset(&dreq, 0, sizeof(dreq)); 401 | dreq.handle = buf->handle; 402 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 403 | return ret; 404 | } 405 | 406 | /* 407 | * modeset_destroy_fb() is a new function. It does exactly the reverse of 408 | * modeset_create_fb() and destroys a single framebuffer. The modeset.c example 409 | * used to do this directly in modeset_cleanup(). 410 | * We simply unmap the buffer, remove the drm-FB and destroy the memory buffer. 411 | */ 412 | 413 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf) 414 | { 415 | struct drm_mode_destroy_dumb dreq; 416 | 417 | /* unmap buffer */ 418 | munmap(buf->map, buf->size); 419 | 420 | /* delete framebuffer */ 421 | drmModeRmFB(fd, buf->fb); 422 | 423 | /* delete dumb buffer */ 424 | memset(&dreq, 0, sizeof(dreq)); 425 | dreq.handle = buf->handle; 426 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 427 | } 428 | 429 | /* 430 | * main() also stays almost exactly the same as before. We only need to change 431 | * the way that we initially set the CRTCs. Instead of using the buffer 432 | * information from modeset_dev, we now use dev->bufs[iter->front_buf] to get 433 | * the current front-buffer and use this framebuffer for drmModeSetCrtc(). 434 | */ 435 | 436 | int main(int argc, char **argv) 437 | { 438 | int ret, fd; 439 | const char *card; 440 | struct modeset_dev *iter; 441 | struct modeset_buf *buf; 442 | 443 | /* check which DRM device to open */ 444 | if (argc > 1) 445 | card = argv[1]; 446 | else 447 | card = "/dev/dri/card0"; 448 | 449 | fprintf(stderr, "using card '%s'\n", card); 450 | 451 | /* open the DRM device */ 452 | ret = modeset_open(&fd, card); 453 | if (ret) 454 | goto out_return; 455 | 456 | /* prepare all connectors and CRTCs */ 457 | ret = modeset_prepare(fd); 458 | if (ret) 459 | goto out_close; 460 | 461 | /* perform actual modesetting on each found connector+CRTC */ 462 | for (iter = modeset_list; iter; iter = iter->next) { 463 | iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); 464 | buf = &iter->bufs[iter->front_buf]; 465 | ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, 466 | &iter->conn, 1, &iter->mode); 467 | if (ret) 468 | fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", 469 | iter->conn, errno); 470 | } 471 | 472 | /* draw some colors for 5seconds */ 473 | modeset_draw(fd); 474 | 475 | /* cleanup everything */ 476 | modeset_cleanup(fd); 477 | 478 | ret = 0; 479 | 480 | out_close: 481 | close(fd); 482 | out_return: 483 | if (ret) { 484 | errno = -ret; 485 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 486 | } else { 487 | fprintf(stderr, "exiting\n"); 488 | } 489 | return ret; 490 | } 491 | 492 | /* 493 | * A short helper function to compute a changing color value. No need to 494 | * understand it. 495 | */ 496 | 497 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 498 | { 499 | uint8_t next; 500 | 501 | next = cur + (*up ? 1 : -1) * (rand() % mod); 502 | if ((*up && next < cur) || (!*up && next > cur)) { 503 | *up = !*up; 504 | next = cur; 505 | } 506 | 507 | return next; 508 | } 509 | 510 | /* 511 | * modeset_draw() is the place where things change. The render-logic is the same 512 | * and we still draw a solid-color on the whole screen. However, we now have two 513 | * buffers and need to flip between them. 514 | * 515 | * So before drawing into a framebuffer, we need to find the back-buffer. 516 | * Remember, dev->font_buf is the index of the front buffer, so 517 | * dev->front_buf ^ 1 is the index of the back buffer. We simply use 518 | * dev->bufs[dev->front_buf ^ 1] to get the back-buffer and draw into it. 519 | * 520 | * After we finished drawing, we need to flip the buffers. We do this with the 521 | * same call as we initially set the CRTC: drmModeSetCrtc(). However, we now 522 | * pass the back-buffer as new framebuffer as we want to flip them. 523 | * The only thing left to do is to change the dev->front_buf index to point to 524 | * the new back-buffer (which was previously the front buffer). 525 | * We then sleep for a short time period and start drawing again. 526 | * 527 | * If you run this example, you will notice that there is almost no flickering, 528 | * anymore. The buffers are now swapped as a whole so each new frame shows 529 | * always the whole new image. If you look carefully, you will notice that the 530 | * modeset.c example showed many screen corruptions during redraw-cycles. 531 | * 532 | * However, this example is still not perfect. Imagine the display-controller is 533 | * currently scanning out a new image and we call drmModeSetCrtc() 534 | * simultaneously. It will then have the same effect as if we used a single 535 | * buffer and we get some tearing. But, the chance that this happens is a lot 536 | * less likely as with a single-buffer. This is because there is a long period 537 | * between each frame called vertical-blank where the display-controller does 538 | * not perform a scanout. If we swap the buffers in this period, we have the 539 | * guarantee that there will be no tearing. See the modeset-vsync.c example if 540 | * you want to know how you can guarantee that the swap takes place at a 541 | * vertical-sync. 542 | */ 543 | 544 | static void modeset_draw(int fd) 545 | { 546 | uint8_t r, g, b; 547 | bool r_up, g_up, b_up; 548 | unsigned int i, j, k, off; 549 | struct modeset_dev *iter; 550 | struct modeset_buf *buf; 551 | int ret; 552 | 553 | srand(time(NULL)); 554 | r = rand() % 0xff; 555 | g = rand() % 0xff; 556 | b = rand() % 0xff; 557 | r_up = g_up = b_up = true; 558 | 559 | for (i = 0; i < 50; ++i) { 560 | r = next_color(&r_up, r, 20); 561 | g = next_color(&g_up, g, 10); 562 | b = next_color(&b_up, b, 5); 563 | 564 | for (iter = modeset_list; iter; iter = iter->next) { 565 | buf = &iter->bufs[iter->front_buf ^ 1]; 566 | for (j = 0; j < buf->height; ++j) { 567 | for (k = 0; k < buf->width; ++k) { 568 | off = buf->stride * j + k * 4; 569 | *(uint32_t*)&buf->map[off] = 570 | (r << 16) | (g << 8) | b; 571 | } 572 | } 573 | 574 | ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, 575 | &iter->conn, 1, &iter->mode); 576 | if (ret) 577 | fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n", 578 | iter->conn, errno); 579 | else 580 | iter->front_buf ^= 1; 581 | } 582 | 583 | usleep(100000); 584 | } 585 | } 586 | 587 | /* 588 | * modeset_cleanup() stays the same as before. But it now calls 589 | * modeset_destroy_fb() instead of accessing the framebuffers directly. 590 | */ 591 | 592 | static void modeset_cleanup(int fd) 593 | { 594 | struct modeset_dev *iter; 595 | 596 | while (modeset_list) { 597 | /* remove from global list */ 598 | iter = modeset_list; 599 | modeset_list = iter->next; 600 | 601 | /* restore saved CRTC configuration */ 602 | drmModeSetCrtc(fd, 603 | iter->saved_crtc->crtc_id, 604 | iter->saved_crtc->buffer_id, 605 | iter->saved_crtc->x, 606 | iter->saved_crtc->y, 607 | &iter->conn, 608 | 1, 609 | &iter->saved_crtc->mode); 610 | drmModeFreeCrtc(iter->saved_crtc); 611 | 612 | /* destroy framebuffers */ 613 | modeset_destroy_fb(fd, &iter->bufs[1]); 614 | modeset_destroy_fb(fd, &iter->bufs[0]); 615 | 616 | /* free allocated memory */ 617 | free(iter); 618 | } 619 | } 620 | 621 | /* 622 | * This was a very short extension to the basic modesetting example that shows 623 | * how double-buffering is implemented. Double-buffering is the de-facto 624 | * standard in any graphics application so any other example will be based on 625 | * this. It is important to understand the ideas behind it as the code is pretty 626 | * easy and short compared to modeset.c. 627 | * 628 | * Double-buffering doesn't solve all problems. Vsync'ed page-flips solve most 629 | * of the problems that still occur, but has problems on it's own (see 630 | * modeset-vsync.c for a discussion). 631 | * 632 | * If you want more code, I can recommend reading the source-code of: 633 | * - plymouth (which uses dumb-buffers like this example; very easy to understand) 634 | * - kmscon (which uses libuterm to do this) 635 | * - wayland (very sophisticated DRM renderer; hard to understand fully as it 636 | * uses more complicated techniques like DRM planes) 637 | * - xserver (very hard to understand as it is split across many files/projects) 638 | * 639 | * Any feedback is welcome. Feel free to use this code freely for your own 640 | * documentation or projects. 641 | * 642 | * - Hosted on http://github.com/dvdhrm/docs 643 | * - Written by David Rheinsberg 644 | */ 645 | -------------------------------------------------------------------------------- /drm-howto/modeset-vsync.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset - DRM Double-Buffered VSync'ed Modesetting Example 3 | * 4 | * Written 2012 by David Rheinsberg 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Double-Buffered VSync'ed Modesetting Howto 10 | * This example extends modeset-double-buffered.c and introduces page-flips 11 | * synced with vertical-blanks (vsync'ed). A vertical-blank is the time-period 12 | * when a display-controller pauses from scanning out the framebuffer. After the 13 | * vertical-blank is over, the framebuffer is again scanned out line by line and 14 | * followed again by a vertical-blank. 15 | * 16 | * Vertical-blanks are important when changing a framebuffer. We already 17 | * introduced double-buffering, so this example shows how we can flip the 18 | * buffers during a vertical blank and _not_ during the scanout period. 19 | * 20 | * This example assumes that you are familiar with modeset-double-buffered. Only 21 | * the differences between both files are highlighted here. 22 | */ 23 | 24 | #define _GNU_SOURCE 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | struct modeset_buf; 39 | struct modeset_dev; 40 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 41 | struct modeset_dev *dev); 42 | static int modeset_create_fb(int fd, struct modeset_buf *buf); 43 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf); 44 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 45 | struct modeset_dev *dev); 46 | static int modeset_open(int *out, const char *node); 47 | static int modeset_prepare(int fd); 48 | static void modeset_draw(int fd); 49 | static void modeset_draw_dev(int fd, struct modeset_dev *dev); 50 | static void modeset_cleanup(int fd); 51 | 52 | /* 53 | * modeset_open() stays the same. 54 | */ 55 | 56 | static int modeset_open(int *out, const char *node) 57 | { 58 | int fd, ret; 59 | uint64_t has_dumb; 60 | 61 | fd = open(node, O_RDWR | O_CLOEXEC); 62 | if (fd < 0) { 63 | ret = -errno; 64 | fprintf(stderr, "cannot open '%s': %m\n", node); 65 | return ret; 66 | } 67 | 68 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || 69 | !has_dumb) { 70 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 71 | node); 72 | close(fd); 73 | return -EOPNOTSUPP; 74 | } 75 | 76 | *out = fd; 77 | return 0; 78 | } 79 | 80 | /* 81 | * modeset_buf and modeset_dev stay mostly the same. But 6 new fields are added 82 | * to modeset_dev: r, g, b, r_up, g_up, b_up. They are used to compute the 83 | * current color that is drawn on this output device. You can ignore them as 84 | * they aren't important for this example. 85 | * The modeset-double-buffered.c example used exactly the same fields but as 86 | * local variables in modeset_draw(). 87 | * 88 | * The \pflip_pending variable is true when a page-flip is currently pending, 89 | * that is, the kernel will flip buffers on the next vertical blank. The 90 | * \cleanup variable is true if the device is currently cleaned up and no more 91 | * pageflips should be scheduled. They are used to synchronize the cleanup 92 | * routines. 93 | */ 94 | 95 | struct modeset_buf { 96 | uint32_t width; 97 | uint32_t height; 98 | uint32_t stride; 99 | uint32_t size; 100 | uint32_t handle; 101 | uint8_t *map; 102 | uint32_t fb; 103 | }; 104 | 105 | struct modeset_dev { 106 | struct modeset_dev *next; 107 | 108 | unsigned int front_buf; 109 | struct modeset_buf bufs[2]; 110 | 111 | drmModeModeInfo mode; 112 | uint32_t conn; 113 | uint32_t crtc; 114 | drmModeCrtc *saved_crtc; 115 | 116 | bool pflip_pending; 117 | bool cleanup; 118 | 119 | uint8_t r, g, b; 120 | bool r_up, g_up, b_up; 121 | }; 122 | 123 | static struct modeset_dev *modeset_list = NULL; 124 | 125 | /* 126 | * modeset_prepare() stays the same. 127 | */ 128 | 129 | static int modeset_prepare(int fd) 130 | { 131 | drmModeRes *res; 132 | drmModeConnector *conn; 133 | unsigned int i; 134 | struct modeset_dev *dev; 135 | int ret; 136 | 137 | /* retrieve resources */ 138 | res = drmModeGetResources(fd); 139 | if (!res) { 140 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 141 | errno); 142 | return -errno; 143 | } 144 | 145 | /* iterate all connectors */ 146 | for (i = 0; i < res->count_connectors; ++i) { 147 | /* get information for each connector */ 148 | conn = drmModeGetConnector(fd, res->connectors[i]); 149 | if (!conn) { 150 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 151 | i, res->connectors[i], errno); 152 | continue; 153 | } 154 | 155 | /* create a device structure */ 156 | dev = malloc(sizeof(*dev)); 157 | memset(dev, 0, sizeof(*dev)); 158 | dev->conn = conn->connector_id; 159 | 160 | /* call helper function to prepare this connector */ 161 | ret = modeset_setup_dev(fd, res, conn, dev); 162 | if (ret) { 163 | if (ret != -ENOENT) { 164 | errno = -ret; 165 | fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", 166 | i, res->connectors[i], errno); 167 | } 168 | free(dev); 169 | drmModeFreeConnector(conn); 170 | continue; 171 | } 172 | 173 | /* free connector data and link device into global list */ 174 | drmModeFreeConnector(conn); 175 | dev->next = modeset_list; 176 | modeset_list = dev; 177 | } 178 | 179 | /* free resources again */ 180 | drmModeFreeResources(res); 181 | return 0; 182 | } 183 | 184 | /* 185 | * modeset_setup_dev() stays the same. 186 | */ 187 | 188 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 189 | struct modeset_dev *dev) 190 | { 191 | int ret; 192 | 193 | /* check if a monitor is connected */ 194 | if (conn->connection != DRM_MODE_CONNECTED) { 195 | fprintf(stderr, "ignoring unused connector %u\n", 196 | conn->connector_id); 197 | return -ENOENT; 198 | } 199 | 200 | /* check if there is at least one valid mode */ 201 | if (conn->count_modes == 0) { 202 | fprintf(stderr, "no valid mode for connector %u\n", 203 | conn->connector_id); 204 | return -EFAULT; 205 | } 206 | 207 | /* copy the mode information into our device structure and into both 208 | * buffers */ 209 | memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); 210 | dev->bufs[0].width = conn->modes[0].hdisplay; 211 | dev->bufs[0].height = conn->modes[0].vdisplay; 212 | dev->bufs[1].width = conn->modes[0].hdisplay; 213 | dev->bufs[1].height = conn->modes[0].vdisplay; 214 | fprintf(stderr, "mode for connector %u is %ux%u\n", 215 | conn->connector_id, dev->bufs[0].width, dev->bufs[0].height); 216 | 217 | /* find a crtc for this connector */ 218 | ret = modeset_find_crtc(fd, res, conn, dev); 219 | if (ret) { 220 | fprintf(stderr, "no valid crtc for connector %u\n", 221 | conn->connector_id); 222 | return ret; 223 | } 224 | 225 | /* create framebuffer #1 for this CRTC */ 226 | ret = modeset_create_fb(fd, &dev->bufs[0]); 227 | if (ret) { 228 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 229 | conn->connector_id); 230 | return ret; 231 | } 232 | 233 | /* create framebuffer #2 for this CRTC */ 234 | ret = modeset_create_fb(fd, &dev->bufs[1]); 235 | if (ret) { 236 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 237 | conn->connector_id); 238 | modeset_destroy_fb(fd, &dev->bufs[0]); 239 | return ret; 240 | } 241 | 242 | return 0; 243 | } 244 | 245 | /* 246 | * modeset_find_crtc() stays the same. 247 | */ 248 | 249 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 250 | struct modeset_dev *dev) 251 | { 252 | drmModeEncoder *enc; 253 | unsigned int i, j; 254 | int32_t crtc; 255 | struct modeset_dev *iter; 256 | 257 | /* first try the currently conected encoder+crtc */ 258 | if (conn->encoder_id) 259 | enc = drmModeGetEncoder(fd, conn->encoder_id); 260 | else 261 | enc = NULL; 262 | 263 | if (enc) { 264 | if (enc->crtc_id) { 265 | crtc = enc->crtc_id; 266 | for (iter = modeset_list; iter; iter = iter->next) { 267 | if (iter->crtc == crtc) { 268 | crtc = -1; 269 | break; 270 | } 271 | } 272 | 273 | if (crtc >= 0) { 274 | drmModeFreeEncoder(enc); 275 | dev->crtc = crtc; 276 | return 0; 277 | } 278 | } 279 | 280 | drmModeFreeEncoder(enc); 281 | } 282 | 283 | /* If the connector is not currently bound to an encoder or if the 284 | * encoder+crtc is already used by another connector (actually unlikely 285 | * but lets be safe), iterate all other available encoders to find a 286 | * matching CRTC. */ 287 | for (i = 0; i < conn->count_encoders; ++i) { 288 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 289 | if (!enc) { 290 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 291 | i, conn->encoders[i], errno); 292 | continue; 293 | } 294 | 295 | /* iterate all global CRTCs */ 296 | for (j = 0; j < res->count_crtcs; ++j) { 297 | /* check whether this CRTC works with the encoder */ 298 | if (!(enc->possible_crtcs & (1 << j))) 299 | continue; 300 | 301 | /* check that no other device already uses this CRTC */ 302 | crtc = res->crtcs[j]; 303 | for (iter = modeset_list; iter; iter = iter->next) { 304 | if (iter->crtc == crtc) { 305 | crtc = -1; 306 | break; 307 | } 308 | } 309 | 310 | /* we have found a CRTC, so save it and return */ 311 | if (crtc >= 0) { 312 | drmModeFreeEncoder(enc); 313 | dev->crtc = crtc; 314 | return 0; 315 | } 316 | } 317 | 318 | drmModeFreeEncoder(enc); 319 | } 320 | 321 | fprintf(stderr, "cannot find suitable CRTC for connector %u\n", 322 | conn->connector_id); 323 | return -ENOENT; 324 | } 325 | 326 | /* 327 | * modeset_create_fb() stays the same. 328 | */ 329 | 330 | static int modeset_create_fb(int fd, struct modeset_buf *buf) 331 | { 332 | struct drm_mode_create_dumb creq; 333 | struct drm_mode_destroy_dumb dreq; 334 | struct drm_mode_map_dumb mreq; 335 | int ret; 336 | 337 | /* create dumb buffer */ 338 | memset(&creq, 0, sizeof(creq)); 339 | creq.width = buf->width; 340 | creq.height = buf->height; 341 | creq.bpp = 32; 342 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 343 | if (ret < 0) { 344 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 345 | errno); 346 | return -errno; 347 | } 348 | buf->stride = creq.pitch; 349 | buf->size = creq.size; 350 | buf->handle = creq.handle; 351 | 352 | /* create framebuffer object for the dumb-buffer */ 353 | ret = drmModeAddFB(fd, buf->width, buf->height, 24, 32, buf->stride, 354 | buf->handle, &buf->fb); 355 | if (ret) { 356 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 357 | errno); 358 | ret = -errno; 359 | goto err_destroy; 360 | } 361 | 362 | /* prepare buffer for memory mapping */ 363 | memset(&mreq, 0, sizeof(mreq)); 364 | mreq.handle = buf->handle; 365 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 366 | if (ret) { 367 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 368 | errno); 369 | ret = -errno; 370 | goto err_fb; 371 | } 372 | 373 | /* perform actual memory mapping */ 374 | buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, 375 | fd, mreq.offset); 376 | if (buf->map == MAP_FAILED) { 377 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 378 | errno); 379 | ret = -errno; 380 | goto err_fb; 381 | } 382 | 383 | /* clear the framebuffer to 0 */ 384 | memset(buf->map, 0, buf->size); 385 | 386 | return 0; 387 | 388 | err_fb: 389 | drmModeRmFB(fd, buf->fb); 390 | err_destroy: 391 | memset(&dreq, 0, sizeof(dreq)); 392 | dreq.handle = buf->handle; 393 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 394 | return ret; 395 | } 396 | 397 | /* 398 | * modeset_destroy_fb() stays the same. 399 | */ 400 | 401 | static void modeset_destroy_fb(int fd, struct modeset_buf *buf) 402 | { 403 | struct drm_mode_destroy_dumb dreq; 404 | 405 | /* unmap buffer */ 406 | munmap(buf->map, buf->size); 407 | 408 | /* delete framebuffer */ 409 | drmModeRmFB(fd, buf->fb); 410 | 411 | /* delete dumb buffer */ 412 | memset(&dreq, 0, sizeof(dreq)); 413 | dreq.handle = buf->handle; 414 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 415 | } 416 | 417 | /* 418 | * main() also stays the same. 419 | */ 420 | 421 | int main(int argc, char **argv) 422 | { 423 | int ret, fd; 424 | const char *card; 425 | struct modeset_dev *iter; 426 | struct modeset_buf *buf; 427 | 428 | /* check which DRM device to open */ 429 | if (argc > 1) 430 | card = argv[1]; 431 | else 432 | card = "/dev/dri/card0"; 433 | 434 | fprintf(stderr, "using card '%s'\n", card); 435 | 436 | /* open the DRM device */ 437 | ret = modeset_open(&fd, card); 438 | if (ret) 439 | goto out_return; 440 | 441 | /* prepare all connectors and CRTCs */ 442 | ret = modeset_prepare(fd); 443 | if (ret) 444 | goto out_close; 445 | 446 | /* perform actual modesetting on each found connector+CRTC */ 447 | for (iter = modeset_list; iter; iter = iter->next) { 448 | iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); 449 | buf = &iter->bufs[iter->front_buf]; 450 | ret = drmModeSetCrtc(fd, iter->crtc, buf->fb, 0, 0, 451 | &iter->conn, 1, &iter->mode); 452 | if (ret) 453 | fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", 454 | iter->conn, errno); 455 | } 456 | 457 | /* draw some colors for 5seconds */ 458 | modeset_draw(fd); 459 | 460 | /* cleanup everything */ 461 | modeset_cleanup(fd); 462 | 463 | ret = 0; 464 | 465 | out_close: 466 | close(fd); 467 | out_return: 468 | if (ret) { 469 | errno = -ret; 470 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 471 | } else { 472 | fprintf(stderr, "exiting\n"); 473 | } 474 | return ret; 475 | } 476 | 477 | /* 478 | * modeset_page_flip_event() is a callback-helper for modeset_draw() below. 479 | * Please see modeset_draw() for more information. 480 | * 481 | * Note that this does nothing if the device is currently cleaned up. This 482 | * allows to wait for outstanding page-flips during cleanup. 483 | */ 484 | 485 | static void modeset_page_flip_event(int fd, unsigned int frame, 486 | unsigned int sec, unsigned int usec, 487 | void *data) 488 | { 489 | struct modeset_dev *dev = data; 490 | 491 | dev->pflip_pending = false; 492 | if (!dev->cleanup) 493 | modeset_draw_dev(fd, dev); 494 | } 495 | 496 | /* 497 | * modeset_draw() changes heavily from all previous examples. The rendering has 498 | * moved into another helper modeset_draw_dev() below, but modeset_draw() is now 499 | * responsible of controlling when we have to redraw the outputs. 500 | * 501 | * So what we do: first redraw all outputs. We initialize the r/g/b/_up 502 | * variables of each output first, although, you can safely ignore these. 503 | * They're solely used to compute the next color. Then we call 504 | * modeset_draw_dev() for each output. This function _always_ redraws the output 505 | * and schedules a buffer-swap/flip for the next vertical-blank. 506 | * We now have to wait for each vertical-blank to happen so we can draw the next 507 | * frame. If a vblank happens, we simply call modeset_draw_dev() again and wait 508 | * for the next vblank. 509 | * 510 | * Note: Different monitors can have different refresh-rates. That means, a 511 | * vblank event is always assigned to a CRTC. Hence, we get different vblank 512 | * events for each CRTC/modeset_dev that we use. This also means, that our 513 | * framerate-controlled color-morphing is different on each monitor. If you want 514 | * exactly the same frame on all monitors, we would have to share the 515 | * color-values between all devices. However, for simplicity reasons, we don't 516 | * do this here. 517 | * 518 | * So the last piece missing is how we get vblank events. libdrm provides 519 | * drmWaitVBlank(), however, we aren't interested in _all_ vblanks, but only in 520 | * the vblanks for our page-flips. We could use drmWaitVBlank() but there is a 521 | * more convenient way: drmModePageFlip() 522 | * drmModePageFlip() schedules a buffer-flip for the next vblank and then 523 | * notifies us about it. It takes a CRTC-id, fb-id and an arbitrary 524 | * data-pointer and then schedules the page-flip. This is fully asynchronous and 525 | * returns immediately. 526 | * When the page-flip happens, the DRM-fd will become readable and we can call 527 | * drmHandleEvent(). This will read all vblank/page-flip events and call our 528 | * modeset_page_flip_event() callback with the data-pointer that we passed to 529 | * drmModePageFlip(). We simply call modeset_draw_dev() then so the next frame 530 | * is rendered.. 531 | * 532 | * 533 | * So modeset_draw() is reponsible of waiting for the page-flip/vblank events 534 | * for _all_ currently used output devices and schedule a redraw for them. We 535 | * could easily do this in a while (1) { drmHandleEvent() } loop, however, this 536 | * example shows how you can use the DRM-fd to integrate this into your own 537 | * main-loop. If you aren't familiar with select(), poll() or epoll, please read 538 | * it up somewhere else. There is plenty of documentation elsewhere on the 539 | * internet. 540 | * 541 | * So what we do is adding the DRM-fd and the keyboard-input-fd (more precisely: 542 | * the stdin FD) to a select-set and then we wait on this set. If the DRM-fd is 543 | * readable, we call drmHandleEvents() to handle the page-flip events. If the 544 | * input-fd is readable, we exit. So on any keyboard input we exit this loop 545 | * (you need to press RETURN after each keyboard input to make this work). 546 | */ 547 | 548 | static void modeset_draw(int fd) 549 | { 550 | int ret; 551 | fd_set fds; 552 | time_t start, cur; 553 | struct timeval v; 554 | drmEventContext ev; 555 | struct modeset_dev *iter; 556 | 557 | /* init variables */ 558 | srand(time(&start)); 559 | FD_ZERO(&fds); 560 | memset(&v, 0, sizeof(v)); 561 | memset(&ev, 0, sizeof(ev)); 562 | /* Set this to only the latest version you support. Version 2 563 | * introduced the page_flip_handler, so we use that. */ 564 | ev.version = 2; 565 | ev.page_flip_handler = modeset_page_flip_event; 566 | 567 | /* redraw all outputs */ 568 | for (iter = modeset_list; iter; iter = iter->next) { 569 | iter->r = rand() % 0xff; 570 | iter->g = rand() % 0xff; 571 | iter->b = rand() % 0xff; 572 | iter->r_up = iter->g_up = iter->b_up = true; 573 | 574 | modeset_draw_dev(fd, iter); 575 | } 576 | 577 | /* wait 5s for VBLANK or input events */ 578 | while (time(&cur) < start + 5) { 579 | FD_SET(0, &fds); 580 | FD_SET(fd, &fds); 581 | v.tv_sec = start + 5 - cur; 582 | 583 | ret = select(fd + 1, &fds, NULL, NULL, &v); 584 | if (ret < 0) { 585 | fprintf(stderr, "select() failed with %d: %m\n", errno); 586 | break; 587 | } else if (FD_ISSET(0, &fds)) { 588 | fprintf(stderr, "exit due to user-input\n"); 589 | break; 590 | } else if (FD_ISSET(fd, &fds)) { 591 | drmHandleEvent(fd, &ev); 592 | } 593 | } 594 | } 595 | 596 | /* 597 | * A short helper function to compute a changing color value. No need to 598 | * understand it. 599 | */ 600 | 601 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 602 | { 603 | uint8_t next; 604 | 605 | next = cur + (*up ? 1 : -1) * (rand() % mod); 606 | if ((*up && next < cur) || (!*up && next > cur)) { 607 | *up = !*up; 608 | next = cur; 609 | } 610 | 611 | return next; 612 | } 613 | 614 | /* 615 | * modeset_draw_dev() is a new function that redraws the screen of a single 616 | * output. It takes the DRM-fd and the output devices as arguments, redraws a 617 | * new frame and schedules the page-flip for the next vsync. 618 | * 619 | * This function does the same as modeset_draw() did in the previous examples 620 | * but only for a single output device now. 621 | * After we are done rendering a frame, we have to swap the buffers. Instead of 622 | * calling drmModeSetCrtc() as we did previously, we now want to schedule this 623 | * page-flip for the next vertical-blank (vblank). We use drmModePageFlip() for 624 | * this. It takes the CRTC-id and FB-id and will asynchronously swap the buffers 625 | * when the next vblank occurs. Note that this is done by the kernel, so neither 626 | * a thread is started nor any other magic is done in libdrm. 627 | * The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a 628 | * page-flip event on the DRM-fd when the page-flip happened. The last argument 629 | * is a data-pointer that is returned with this event. 630 | * If we wouldn't pass this flag, we would not get notified when the page-flip 631 | * happened. 632 | * 633 | * Note: If you called drmModePageFlip() and directly call it again, it will 634 | * return EBUSY if the page-flip hasn't happened in between. So you almost 635 | * always want to pass DRM_MODE_PAGE_FLIP_EVENT to get notified when the 636 | * page-flip happens so you know when to render the next frame. 637 | * If you scheduled a page-flip but call drmModeSetCrtc() before the next 638 | * vblank, then the scheduled page-flip will become a no-op. However, you will 639 | * still get notified when it happens and you still cannot call 640 | * drmModePageFlip() again until it finished. So to sum it up: there is no way 641 | * to effectively cancel a page-flip. 642 | * 643 | * If you wonder why drmModePageFlip() takes fewer arguments than 644 | * drmModeSetCrtc(), then you should take into account, that drmModePageFlip() 645 | * reuses the arguments from drmModeSetCrtc(). So things like connector-ids, 646 | * x/y-offsets and so on have to be set via drmModeSetCrtc() first before you 647 | * can use drmModePageFlip()! We do this in main() as all the previous examples 648 | * did, too. 649 | */ 650 | 651 | static void modeset_draw_dev(int fd, struct modeset_dev *dev) 652 | { 653 | struct modeset_buf *buf; 654 | unsigned int j, k, off; 655 | int ret; 656 | 657 | dev->r = next_color(&dev->r_up, dev->r, 20); 658 | dev->g = next_color(&dev->g_up, dev->g, 10); 659 | dev->b = next_color(&dev->b_up, dev->b, 5); 660 | 661 | buf = &dev->bufs[dev->front_buf ^ 1]; 662 | for (j = 0; j < buf->height; ++j) { 663 | for (k = 0; k < buf->width; ++k) { 664 | off = buf->stride * j + k * 4; 665 | *(uint32_t*)&buf->map[off] = 666 | (dev->r << 16) | (dev->g << 8) | dev->b; 667 | } 668 | } 669 | 670 | ret = drmModePageFlip(fd, dev->crtc, buf->fb, 671 | DRM_MODE_PAGE_FLIP_EVENT, dev); 672 | if (ret) { 673 | fprintf(stderr, "cannot flip CRTC for connector %u (%d): %m\n", 674 | dev->conn, errno); 675 | } else { 676 | dev->front_buf ^= 1; 677 | dev->pflip_pending = true; 678 | } 679 | } 680 | 681 | /* 682 | * modeset_cleanup() stays mostly the same. However, before resetting a CRTC to 683 | * its previous state, we wait for any outstanding page-flip to complete. This 684 | * isn't strictly neccessary, however, some DRM drivers are known to be buggy if 685 | * we call drmModeSetCrtc() if there is a pending page-flip. 686 | * Furthermore, we don't want any pending page-flips when our application exist. 687 | * Because another application might pick up the DRM device and try to schedule 688 | * their own page-flips which might then fail as long as our page-flip is 689 | * pending. 690 | * So lets be safe here and simply wait for any page-flips to complete. This is 691 | * a blocking operation, but it's mostly just <16ms so we can ignore that. 692 | */ 693 | 694 | static void modeset_cleanup(int fd) 695 | { 696 | struct modeset_dev *iter; 697 | drmEventContext ev; 698 | int ret; 699 | 700 | /* init variables */ 701 | memset(&ev, 0, sizeof(ev)); 702 | ev.version = DRM_EVENT_CONTEXT_VERSION; 703 | ev.page_flip_handler = modeset_page_flip_event; 704 | 705 | while (modeset_list) { 706 | /* remove from global list */ 707 | iter = modeset_list; 708 | modeset_list = iter->next; 709 | 710 | /* if a pageflip is pending, wait for it to complete */ 711 | iter->cleanup = true; 712 | fprintf(stderr, "wait for pending page-flip to complete...\n"); 713 | while (iter->pflip_pending) { 714 | ret = drmHandleEvent(fd, &ev); 715 | if (ret) 716 | break; 717 | } 718 | 719 | /* restore saved CRTC configuration */ 720 | if (!iter->pflip_pending) 721 | drmModeSetCrtc(fd, 722 | iter->saved_crtc->crtc_id, 723 | iter->saved_crtc->buffer_id, 724 | iter->saved_crtc->x, 725 | iter->saved_crtc->y, 726 | &iter->conn, 727 | 1, 728 | &iter->saved_crtc->mode); 729 | drmModeFreeCrtc(iter->saved_crtc); 730 | 731 | /* destroy framebuffers */ 732 | modeset_destroy_fb(fd, &iter->bufs[1]); 733 | modeset_destroy_fb(fd, &iter->bufs[0]); 734 | 735 | /* free allocated memory */ 736 | free(iter); 737 | } 738 | } 739 | 740 | /* 741 | * This example shows how to make the kernel handle page-flips and how to wait 742 | * for them in user-space. The select() example here should show you how you can 743 | * integrate these loops into your own applications without the need for a 744 | * separate modesetting thread. 745 | * 746 | * However, please note that vsync'ed double-buffering doesn't solve all 747 | * problems. Imagine that you cannot render a frame fast enough to satisfy all 748 | * vertical-blanks. In this situation, you don't want to wait after scheduling a 749 | * page-flip until the vblank happens to draw the next frame. A solution for 750 | * this is triple-buffering. It should be farily easy to extend this example to 751 | * use triple-buffering, but feel free to contact me if you have any questions 752 | * about it. 753 | * Also note that the DRM kernel API is quite limited if you want to reschedule 754 | * page-flips that haven't happened, yet. You cannot call drmModePageFlip() 755 | * twice in a single scanout-period. The behavior of drmModeSetCrtc() while a 756 | * page-flip is pending might also be unexpected. 757 | * Unfortunately, there is no ultimate solution to all modesetting problems. 758 | * This example shows the tools to do vsync'ed page-flips, however, it depends 759 | * on your use-case how you have to implement it. 760 | * 761 | * If you want more code, I can recommend reading the source-code of: 762 | * - plymouth (which uses dumb-buffers like this example; very easy to understand) 763 | * - kmscon (which uses libuterm to do this) 764 | * - wayland (very sophisticated DRM renderer; hard to understand fully as it 765 | * uses more complicated techniques like DRM planes) 766 | * - xserver (very hard to understand as it is split across many files/projects) 767 | * 768 | * Any feedback is welcome. Feel free to use this code freely for your own 769 | * documentation or projects. 770 | * 771 | * - Hosted on http://github.com/dvdhrm/docs 772 | * - Written by David Rheinsberg 773 | */ 774 | -------------------------------------------------------------------------------- /drm-howto/modeset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * modeset - DRM Modesetting Example 3 | * 4 | * Written 2012 by David Rheinsberg 5 | * Dedicated to the Public Domain. 6 | */ 7 | 8 | /* 9 | * DRM Modesetting Howto 10 | * This document describes the DRM modesetting API. Before we can use the DRM 11 | * API, we have to include xf86drm.h and xf86drmMode.h. Both are provided by 12 | * libdrm which every major distribution ships by default. It has no other 13 | * dependencies and is pretty small. 14 | * 15 | * Please ignore all forward-declarations of functions which are used later. I 16 | * reordered the functions so you can read this document from top to bottom. If 17 | * you reimplement it, you would probably reorder the functions to avoid all the 18 | * nasty forward declarations. 19 | * 20 | * For easier reading, we ignore all memory-allocation errors of malloc() and 21 | * friends here. However, we try to correctly handle all other kinds of errors 22 | * that may occur. 23 | * 24 | * All functions and global variables are prefixed with "modeset_*" in this 25 | * file. So it should be clear whether a function is a local helper or if it is 26 | * provided by some external library. 27 | */ 28 | 29 | #define _GNU_SOURCE 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | struct modeset_dev; 44 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 45 | struct modeset_dev *dev); 46 | static int modeset_create_fb(int fd, struct modeset_dev *dev); 47 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 48 | struct modeset_dev *dev); 49 | static int modeset_open(int *out, const char *node); 50 | static int modeset_prepare(int fd); 51 | static void modeset_draw(void); 52 | static void modeset_cleanup(int fd); 53 | 54 | /* 55 | * When the linux kernel detects a graphics-card on your machine, it loads the 56 | * correct device driver (located in kernel-tree at ./drivers/gpu/drm/) and 57 | * provides two character-devices to control it. Udev (or whatever hotplugging 58 | * application you use) will create them as: 59 | * /dev/dri/card0 60 | * /dev/dri/controlID64 61 | * We only need the first one. You can hard-code this path into your application 62 | * like we do here, but it is recommended to use libudev with real hotplugging 63 | * and multi-seat support. However, this is beyond the scope of this document. 64 | * Also note that if you have multiple graphics-cards, there may also be 65 | * /dev/dri/card1, /dev/dri/card2, ... 66 | * 67 | * We simply use /dev/dri/card0 here but the user can specify another path on 68 | * the command line. 69 | * 70 | * modeset_open(out, node): This small helper function opens the DRM device 71 | * which is given as @node. The new fd is stored in @out on success. On failure, 72 | * a negative error code is returned. 73 | * After opening the file, we also check for the DRM_CAP_DUMB_BUFFER capability. 74 | * If the driver supports this capability, we can create simple memory-mapped 75 | * buffers without any driver-dependent code. As we want to avoid any radeon, 76 | * nvidia, intel, etc. specific code, we depend on DUMB_BUFFERs here. 77 | */ 78 | 79 | static int modeset_open(int *out, const char *node) 80 | { 81 | int fd, ret; 82 | uint64_t has_dumb; 83 | 84 | fd = open(node, O_RDWR | O_CLOEXEC); 85 | if (fd < 0) { 86 | ret = -errno; 87 | fprintf(stderr, "cannot open '%s': %m\n", node); 88 | return ret; 89 | } 90 | 91 | if (drmGetCap(fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || 92 | !has_dumb) { 93 | fprintf(stderr, "drm device '%s' does not support dumb buffers\n", 94 | node); 95 | close(fd); 96 | return -EOPNOTSUPP; 97 | } 98 | 99 | *out = fd; 100 | return 0; 101 | } 102 | 103 | /* 104 | * As a next step we need to find our available display devices. libdrm provides 105 | * a drmModeRes structure that contains all the needed information. We can 106 | * retrieve it via drmModeGetResources(fd) and free it via 107 | * drmModeFreeResources(res) again. 108 | * 109 | * A physical connector on your graphics card is called a "connector". You can 110 | * plug a monitor into it and control what is displayed. We are definitely 111 | * interested in what connectors are currently used, so we simply iterate 112 | * through the list of connectors and try to display a test-picture on each 113 | * available monitor. 114 | * However, this isn't as easy as it sounds. First, we need to check whether the 115 | * connector is actually used (a monitor is plugged in and turned on). Then we 116 | * need to find a CRTC that can control this connector. CRTCs are described 117 | * later on. After that we create a framebuffer object. If we have all this, we 118 | * can mmap() the framebuffer and draw a test-picture into it. Then we can tell 119 | * the DRM device to show the framebuffer on the given CRTC with the selected 120 | * connector. 121 | * 122 | * As we want to draw moving pictures on the framebuffer, we actually have to 123 | * remember all these settings. Therefore, we create one "struct modeset_dev" 124 | * object for each connector+crtc+framebuffer pair that we successfully 125 | * initialized and push it into the global device-list. 126 | * 127 | * Each field of this structure is described when it is first used. But as a 128 | * summary: 129 | * "struct modeset_dev" contains: { 130 | * - @next: points to the next device in the single-linked list 131 | * 132 | * - @width: width of our buffer object 133 | * - @height: height of our buffer object 134 | * - @stride: stride value of our buffer object 135 | * - @size: size of the memory mapped buffer 136 | * - @handle: a DRM handle to the buffer object that we can draw into 137 | * - @map: pointer to the memory mapped buffer 138 | * 139 | * - @mode: the display mode that we want to use 140 | * - @fb: a framebuffer handle with our buffer object as scanout buffer 141 | * - @conn: the connector ID that we want to use with this buffer 142 | * - @crtc: the crtc ID that we want to use with this connector 143 | * - @saved_crtc: the configuration of the crtc before we changed it. We use it 144 | * so we can restore the same mode when we exit. 145 | * } 146 | */ 147 | 148 | struct modeset_dev { 149 | struct modeset_dev *next; 150 | 151 | uint32_t width; 152 | uint32_t height; 153 | uint32_t stride; 154 | uint32_t size; 155 | uint32_t handle; 156 | uint8_t *map; 157 | 158 | drmModeModeInfo mode; 159 | uint32_t fb; 160 | uint32_t conn; 161 | uint32_t crtc; 162 | drmModeCrtc *saved_crtc; 163 | }; 164 | 165 | static struct modeset_dev *modeset_list = NULL; 166 | 167 | /* 168 | * So as next step we need to actually prepare all connectors that we find. We 169 | * do this in this little helper function: 170 | * 171 | * modeset_prepare(fd): This helper function takes the DRM fd as argument and 172 | * then simply retrieves the resource-info from the device. It then iterates 173 | * through all connectors and calls other helper functions to initialize this 174 | * connector (described later on). 175 | * If the initialization was successful, we simply add this object as new device 176 | * into the global modeset device list. 177 | * 178 | * The resource-structure contains a list of all connector-IDs. We use the 179 | * helper function drmModeGetConnector() to retrieve more information on each 180 | * connector. After we are done with it, we free it again with 181 | * drmModeFreeConnector(). 182 | * Our helper modeset_setup_dev() returns -ENOENT if the connector is currently 183 | * unused and no monitor is plugged in. So we can ignore this connector. 184 | */ 185 | 186 | static int modeset_prepare(int fd) 187 | { 188 | drmModeRes *res; 189 | drmModeConnector *conn; 190 | unsigned int i; 191 | struct modeset_dev *dev; 192 | int ret; 193 | 194 | /* retrieve resources */ 195 | res = drmModeGetResources(fd); 196 | if (!res) { 197 | fprintf(stderr, "cannot retrieve DRM resources (%d): %m\n", 198 | errno); 199 | return -errno; 200 | } 201 | 202 | /* iterate all connectors */ 203 | for (i = 0; i < res->count_connectors; ++i) { 204 | /* get information for each connector */ 205 | conn = drmModeGetConnector(fd, res->connectors[i]); 206 | if (!conn) { 207 | fprintf(stderr, "cannot retrieve DRM connector %u:%u (%d): %m\n", 208 | i, res->connectors[i], errno); 209 | continue; 210 | } 211 | 212 | /* create a device structure */ 213 | dev = malloc(sizeof(*dev)); 214 | memset(dev, 0, sizeof(*dev)); 215 | dev->conn = conn->connector_id; 216 | 217 | /* call helper function to prepare this connector */ 218 | ret = modeset_setup_dev(fd, res, conn, dev); 219 | if (ret) { 220 | if (ret != -ENOENT) { 221 | errno = -ret; 222 | fprintf(stderr, "cannot setup device for connector %u:%u (%d): %m\n", 223 | i, res->connectors[i], errno); 224 | } 225 | free(dev); 226 | drmModeFreeConnector(conn); 227 | continue; 228 | } 229 | 230 | /* free connector data and link device into global list */ 231 | drmModeFreeConnector(conn); 232 | dev->next = modeset_list; 233 | modeset_list = dev; 234 | } 235 | 236 | /* free resources again */ 237 | drmModeFreeResources(res); 238 | return 0; 239 | } 240 | 241 | /* 242 | * Now we dig deeper into setting up a single connector. As described earlier, 243 | * we need to check several things first: 244 | * * If the connector is currently unused, that is, no monitor is plugged in, 245 | * then we can ignore it. 246 | * * We have to find a suitable resolution and refresh-rate. All this is 247 | * available in drmModeModeInfo structures saved for each crtc. We simply 248 | * use the first mode that is available. This is always the mode with the 249 | * highest resolution. 250 | * A more sophisticated mode-selection should be done in real applications, 251 | * though. 252 | * * Then we need to find an CRTC that can drive this connector. A CRTC is an 253 | * internal resource of each graphics-card. The number of CRTCs controls how 254 | * many connectors can be controlled indepedently. That is, a graphics-cards 255 | * may have more connectors than CRTCs, which means, not all monitors can be 256 | * controlled independently. 257 | * There is actually the possibility to control multiple connectors via a 258 | * single CRTC if the monitors should display the same content. However, we 259 | * do not make use of this here. 260 | * So think of connectors as pipelines to the connected monitors and the 261 | * CRTCs are the controllers that manage which data goes to which pipeline. 262 | * If there are more pipelines than CRTCs, then we cannot control all of 263 | * them at the same time. 264 | * * We need to create a framebuffer for this connector. A framebuffer is a 265 | * memory buffer that we can write XRGB32 data into. So we use this to 266 | * render our graphics and then the CRTC can scan-out this data from the 267 | * framebuffer onto the monitor. 268 | */ 269 | 270 | static int modeset_setup_dev(int fd, drmModeRes *res, drmModeConnector *conn, 271 | struct modeset_dev *dev) 272 | { 273 | int ret; 274 | 275 | /* check if a monitor is connected */ 276 | if (conn->connection != DRM_MODE_CONNECTED) { 277 | fprintf(stderr, "ignoring unused connector %u\n", 278 | conn->connector_id); 279 | return -ENOENT; 280 | } 281 | 282 | /* check if there is at least one valid mode */ 283 | if (conn->count_modes == 0) { 284 | fprintf(stderr, "no valid mode for connector %u\n", 285 | conn->connector_id); 286 | return -EFAULT; 287 | } 288 | 289 | /* copy the mode information into our device structure */ 290 | memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode)); 291 | dev->width = conn->modes[0].hdisplay; 292 | dev->height = conn->modes[0].vdisplay; 293 | fprintf(stderr, "mode for connector %u is %ux%u\n", 294 | conn->connector_id, dev->width, dev->height); 295 | 296 | /* find a crtc for this connector */ 297 | ret = modeset_find_crtc(fd, res, conn, dev); 298 | if (ret) { 299 | fprintf(stderr, "no valid crtc for connector %u\n", 300 | conn->connector_id); 301 | return ret; 302 | } 303 | 304 | /* create a framebuffer for this CRTC */ 305 | ret = modeset_create_fb(fd, dev); 306 | if (ret) { 307 | fprintf(stderr, "cannot create framebuffer for connector %u\n", 308 | conn->connector_id); 309 | return ret; 310 | } 311 | 312 | return 0; 313 | } 314 | 315 | /* 316 | * modeset_find_crtc(fd, res, conn, dev): This small helper tries to find a 317 | * suitable CRTC for the given connector. We have actually have to introduce one 318 | * more DRM object to make this more clear: Encoders. 319 | * Encoders help the CRTC to convert data from a framebuffer into the right 320 | * format that can be used for the chosen connector. We do not have to 321 | * understand any more of these conversions to make use of it. However, you must 322 | * know that each connector has a limited list of encoders that it can use. And 323 | * each encoder can only work with a limited list of CRTCs. So what we do is 324 | * trying each encoder that is available and looking for a CRTC that this 325 | * encoder can work with. If we find the first working combination, we are happy 326 | * and write it into the @dev structure. 327 | * But before iterating all available encoders, we first try the currently 328 | * active encoder+crtc on a connector to avoid a full modeset. 329 | * 330 | * However, before we can use a CRTC we must make sure that no other device, 331 | * that we setup previously, is already using this CRTC. Remember, we can only 332 | * drive one connector per CRTC! So we simply iterate through the "modeset_list" 333 | * of previously setup devices and check that this CRTC wasn't used before. 334 | * Otherwise, we continue with the next CRTC/Encoder combination. 335 | */ 336 | 337 | static int modeset_find_crtc(int fd, drmModeRes *res, drmModeConnector *conn, 338 | struct modeset_dev *dev) 339 | { 340 | drmModeEncoder *enc; 341 | unsigned int i, j; 342 | int32_t crtc; 343 | struct modeset_dev *iter; 344 | 345 | /* first try the currently conected encoder+crtc */ 346 | if (conn->encoder_id) 347 | enc = drmModeGetEncoder(fd, conn->encoder_id); 348 | else 349 | enc = NULL; 350 | 351 | if (enc) { 352 | if (enc->crtc_id) { 353 | crtc = enc->crtc_id; 354 | for (iter = modeset_list; iter; iter = iter->next) { 355 | if (iter->crtc == crtc) { 356 | crtc = -1; 357 | break; 358 | } 359 | } 360 | 361 | if (crtc >= 0) { 362 | drmModeFreeEncoder(enc); 363 | dev->crtc = crtc; 364 | return 0; 365 | } 366 | } 367 | 368 | drmModeFreeEncoder(enc); 369 | } 370 | 371 | /* If the connector is not currently bound to an encoder or if the 372 | * encoder+crtc is already used by another connector (actually unlikely 373 | * but lets be safe), iterate all other available encoders to find a 374 | * matching CRTC. */ 375 | for (i = 0; i < conn->count_encoders; ++i) { 376 | enc = drmModeGetEncoder(fd, conn->encoders[i]); 377 | if (!enc) { 378 | fprintf(stderr, "cannot retrieve encoder %u:%u (%d): %m\n", 379 | i, conn->encoders[i], errno); 380 | continue; 381 | } 382 | 383 | /* iterate all global CRTCs */ 384 | for (j = 0; j < res->count_crtcs; ++j) { 385 | /* check whether this CRTC works with the encoder */ 386 | if (!(enc->possible_crtcs & (1 << j))) 387 | continue; 388 | 389 | /* check that no other device already uses this CRTC */ 390 | crtc = res->crtcs[j]; 391 | for (iter = modeset_list; iter; iter = iter->next) { 392 | if (iter->crtc == crtc) { 393 | crtc = -1; 394 | break; 395 | } 396 | } 397 | 398 | /* we have found a CRTC, so save it and return */ 399 | if (crtc >= 0) { 400 | drmModeFreeEncoder(enc); 401 | dev->crtc = crtc; 402 | return 0; 403 | } 404 | } 405 | 406 | drmModeFreeEncoder(enc); 407 | } 408 | 409 | fprintf(stderr, "cannot find suitable CRTC for connector %u\n", 410 | conn->connector_id); 411 | return -ENOENT; 412 | } 413 | 414 | /* 415 | * modeset_create_fb(fd, dev): After we have found a crtc+connector+mode 416 | * combination, we need to actually create a suitable framebuffer that we can 417 | * use with it. There are actually two ways to do that: 418 | * * We can create a so called "dumb buffer". This is a buffer that we can 419 | * memory-map via mmap() and every driver supports this. We can use it for 420 | * unaccelerated software rendering on the CPU. 421 | * * We can use libgbm to create buffers available for hardware-acceleration. 422 | * libgbm is an abstraction layer that creates these buffers for each 423 | * available DRM driver. As there is no generic API for this, each driver 424 | * provides its own way to create these buffers. 425 | * We can then use such buffers to create OpenGL contexts with the mesa3D 426 | * library. 427 | * We use the first solution here as it is much simpler and doesn't require any 428 | * external libraries. However, if you want to use hardware-acceleration via 429 | * OpenGL, it is actually pretty easy to create such buffers with libgbm and 430 | * libEGL. But this is beyond the scope of this document. 431 | * 432 | * So what we do is requesting a new dumb-buffer from the driver. We specify the 433 | * same size as the current mode that we selected for the connector. 434 | * Then we request the driver to prepare this buffer for memory mapping. After 435 | * that we perform the actual mmap() call. So we can now access the framebuffer 436 | * memory directly via the dev->map memory map. 437 | */ 438 | 439 | static int modeset_create_fb(int fd, struct modeset_dev *dev) 440 | { 441 | struct drm_mode_create_dumb creq; 442 | struct drm_mode_destroy_dumb dreq; 443 | struct drm_mode_map_dumb mreq; 444 | int ret; 445 | 446 | /* create dumb buffer */ 447 | memset(&creq, 0, sizeof(creq)); 448 | creq.width = dev->width; 449 | creq.height = dev->height; 450 | creq.bpp = 32; 451 | ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq); 452 | if (ret < 0) { 453 | fprintf(stderr, "cannot create dumb buffer (%d): %m\n", 454 | errno); 455 | return -errno; 456 | } 457 | dev->stride = creq.pitch; 458 | dev->size = creq.size; 459 | dev->handle = creq.handle; 460 | 461 | /* create framebuffer object for the dumb-buffer */ 462 | ret = drmModeAddFB(fd, dev->width, dev->height, 24, 32, dev->stride, 463 | dev->handle, &dev->fb); 464 | if (ret) { 465 | fprintf(stderr, "cannot create framebuffer (%d): %m\n", 466 | errno); 467 | ret = -errno; 468 | goto err_destroy; 469 | } 470 | 471 | /* prepare buffer for memory mapping */ 472 | memset(&mreq, 0, sizeof(mreq)); 473 | mreq.handle = dev->handle; 474 | ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq); 475 | if (ret) { 476 | fprintf(stderr, "cannot map dumb buffer (%d): %m\n", 477 | errno); 478 | ret = -errno; 479 | goto err_fb; 480 | } 481 | 482 | /* perform actual memory mapping */ 483 | dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, 484 | fd, mreq.offset); 485 | if (dev->map == MAP_FAILED) { 486 | fprintf(stderr, "cannot mmap dumb buffer (%d): %m\n", 487 | errno); 488 | ret = -errno; 489 | goto err_fb; 490 | } 491 | 492 | /* clear the framebuffer to 0 */ 493 | memset(dev->map, 0, dev->size); 494 | 495 | return 0; 496 | 497 | err_fb: 498 | drmModeRmFB(fd, dev->fb); 499 | err_destroy: 500 | memset(&dreq, 0, sizeof(dreq)); 501 | dreq.handle = dev->handle; 502 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 503 | return ret; 504 | } 505 | 506 | /* 507 | * Finally! We have a connector with a suitable CRTC. We know which mode we want 508 | * to use and we have a framebuffer of the correct size that we can write to. 509 | * There is nothing special left to do. We only have to program the CRTC to 510 | * connect each new framebuffer to each selected connector for each combination 511 | * that we saved in the global modeset_list. 512 | * This is done with a call to drmModeSetCrtc(). 513 | * 514 | * So we are ready for our main() function. First we check whether the user 515 | * specified a DRM device on the command line, otherwise we use the default 516 | * /dev/dri/card0. Then we open the device via modeset_open(). modeset_prepare() 517 | * prepares all connectors and we can loop over "modeset_list" and call 518 | * drmModeSetCrtc() on every CRTC/connector combination. 519 | * 520 | * But printing empty black pages is boring so we have another helper function 521 | * modeset_draw() that draws some colors into the framebuffer for 5 seconds and 522 | * then returns. And then we have all the cleanup functions which correctly free 523 | * all devices again after we used them. All these functions are described below 524 | * the main() function. 525 | * 526 | * As a side note: drmModeSetCrtc() actually takes a list of connectors that we 527 | * want to control with this CRTC. We pass only one connector, though. As 528 | * explained earlier, if we used multiple connectors, then all connectors would 529 | * have the same controlling framebuffer so the output would be cloned. This is 530 | * most often not what you want so we avoid explaining this feature here. 531 | * Furthermore, all connectors will have to run with the same mode, which is 532 | * also often not guaranteed. So instead, we only use one connector per CRTC. 533 | * 534 | * Before calling drmModeSetCrtc() we also save the current CRTC configuration. 535 | * This is used in modeset_cleanup() to restore the CRTC to the same mode as was 536 | * before we changed it. 537 | * If we don't do this, the screen will stay blank after we exit until another 538 | * application performs modesetting itself. 539 | */ 540 | 541 | int main(int argc, char **argv) 542 | { 543 | int ret, fd; 544 | const char *card; 545 | struct modeset_dev *iter; 546 | 547 | /* check which DRM device to open */ 548 | if (argc > 1) 549 | card = argv[1]; 550 | else 551 | card = "/dev/dri/card0"; 552 | 553 | fprintf(stderr, "using card '%s'\n", card); 554 | 555 | /* open the DRM device */ 556 | ret = modeset_open(&fd, card); 557 | if (ret) 558 | goto out_return; 559 | 560 | /* prepare all connectors and CRTCs */ 561 | ret = modeset_prepare(fd); 562 | if (ret) 563 | goto out_close; 564 | 565 | /* perform actual modesetting on each found connector+CRTC */ 566 | for (iter = modeset_list; iter; iter = iter->next) { 567 | iter->saved_crtc = drmModeGetCrtc(fd, iter->crtc); 568 | ret = drmModeSetCrtc(fd, iter->crtc, iter->fb, 0, 0, 569 | &iter->conn, 1, &iter->mode); 570 | if (ret) 571 | fprintf(stderr, "cannot set CRTC for connector %u (%d): %m\n", 572 | iter->conn, errno); 573 | } 574 | 575 | /* draw some colors for 5seconds */ 576 | modeset_draw(); 577 | 578 | /* cleanup everything */ 579 | modeset_cleanup(fd); 580 | 581 | ret = 0; 582 | 583 | out_close: 584 | close(fd); 585 | out_return: 586 | if (ret) { 587 | errno = -ret; 588 | fprintf(stderr, "modeset failed with error %d: %m\n", errno); 589 | } else { 590 | fprintf(stderr, "exiting\n"); 591 | } 592 | return ret; 593 | } 594 | 595 | /* 596 | * A short helper function to compute a changing color value. No need to 597 | * understand it. 598 | */ 599 | 600 | static uint8_t next_color(bool *up, uint8_t cur, unsigned int mod) 601 | { 602 | uint8_t next; 603 | 604 | next = cur + (*up ? 1 : -1) * (rand() % mod); 605 | if ((*up && next < cur) || (!*up && next > cur)) { 606 | *up = !*up; 607 | next = cur; 608 | } 609 | 610 | return next; 611 | } 612 | 613 | /* 614 | * modeset_draw(): This draws a solid color into all configured framebuffers. 615 | * Every 100ms the color changes to a slightly different color so we get some 616 | * kind of smoothly changing color-gradient. 617 | * 618 | * The color calculation can be ignored as it is pretty boring. So the 619 | * interesting stuff is iterating over "modeset_list" and then through all lines 620 | * and width. We then set each pixel individually to the current color. 621 | * 622 | * We do this 50 times as we sleep 100ms after each redraw round. This makes 623 | * 50*100ms = 5000ms = 5s so it takes about 5seconds to finish this loop. 624 | * 625 | * Please note that we draw directly into the framebuffer. This means that you 626 | * will see flickering as the monitor might refresh while we redraw the screen. 627 | * To avoid this you would need to use two framebuffers and a call to 628 | * drmModeSetCrtc() to switch between both buffers. 629 | * You can also use drmModePageFlip() to do a vsync'ed pageflip. But this is 630 | * beyond the scope of this document. 631 | */ 632 | 633 | static void modeset_draw(void) 634 | { 635 | uint8_t r, g, b; 636 | bool r_up, g_up, b_up; 637 | unsigned int i, j, k, off; 638 | struct modeset_dev *iter; 639 | 640 | srand(time(NULL)); 641 | r = rand() % 0xff; 642 | g = rand() % 0xff; 643 | b = rand() % 0xff; 644 | r_up = g_up = b_up = true; 645 | 646 | for (i = 0; i < 50; ++i) { 647 | r = next_color(&r_up, r, 20); 648 | g = next_color(&g_up, g, 10); 649 | b = next_color(&b_up, b, 5); 650 | 651 | for (iter = modeset_list; iter; iter = iter->next) { 652 | for (j = 0; j < iter->height; ++j) { 653 | for (k = 0; k < iter->width; ++k) { 654 | off = iter->stride * j + k * 4; 655 | *(uint32_t*)&iter->map[off] = 656 | (r << 16) | (g << 8) | b; 657 | } 658 | } 659 | } 660 | 661 | usleep(100000); 662 | } 663 | } 664 | 665 | /* 666 | * modeset_cleanup(fd): This cleans up all the devices we created during 667 | * modeset_prepare(). It resets the CRTCs to their saved states and deallocates 668 | * all memory. 669 | * It should be pretty obvious how all of this works. 670 | */ 671 | 672 | static void modeset_cleanup(int fd) 673 | { 674 | struct modeset_dev *iter; 675 | struct drm_mode_destroy_dumb dreq; 676 | 677 | while (modeset_list) { 678 | /* remove from global list */ 679 | iter = modeset_list; 680 | modeset_list = iter->next; 681 | 682 | /* restore saved CRTC configuration */ 683 | drmModeSetCrtc(fd, 684 | iter->saved_crtc->crtc_id, 685 | iter->saved_crtc->buffer_id, 686 | iter->saved_crtc->x, 687 | iter->saved_crtc->y, 688 | &iter->conn, 689 | 1, 690 | &iter->saved_crtc->mode); 691 | drmModeFreeCrtc(iter->saved_crtc); 692 | 693 | /* unmap buffer */ 694 | munmap(iter->map, iter->size); 695 | 696 | /* delete framebuffer */ 697 | drmModeRmFB(fd, iter->fb); 698 | 699 | /* delete dumb buffer */ 700 | memset(&dreq, 0, sizeof(dreq)); 701 | dreq.handle = iter->handle; 702 | drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); 703 | 704 | /* free allocated memory */ 705 | free(iter); 706 | } 707 | } 708 | 709 | /* 710 | * I hope this was a short but easy overview of the DRM modesetting API. The DRM 711 | * API offers much more capabilities including: 712 | * - double-buffering or tripple-buffering (or whatever you want) 713 | * - vsync'ed page-flips 714 | * - hardware-accelerated rendering (for example via OpenGL) 715 | * - output cloning 716 | * - graphics-clients plus authentication 717 | * - DRM planes/overlays/sprites 718 | * - ... 719 | * If you are interested in these topics, I can currently only redirect you to 720 | * existing implementations, including: 721 | * - plymouth (which uses dumb-buffers like this example; very easy to understand) 722 | * - kmscon (which uses libuterm to do this) 723 | * - wayland (very sophisticated DRM renderer; hard to understand fully as it 724 | * uses more complicated techniques like DRM planes) 725 | * - xserver (very hard to understand as it is split across many files/projects) 726 | * 727 | * But understanding how modesetting (as described in this document) works, is 728 | * essential to understand all further DRM topics. 729 | * 730 | * Any feedback is welcome. Feel free to use this code freely for your own 731 | * documentation or projects. 732 | * 733 | * - Hosted on http://github.com/dvdhrm/docs 734 | * - Written by David Rheinsberg 735 | */ 736 | --------------------------------------------------------------------------------