1 """ 2 A wrapper around the Direct Rendering Manager (DRM) library, which itself is a 3 wrapper around the Direct Rendering Interface (DRI) between the kernel and 4 userland. 5 6 Since we are masochists, we use ctypes instead of cffi to load libdrm and 7 access several symbols within it. We use Python's file descriptor and mmap 8 wrappers. 9 10 At some point in the future, cffi could be used, for approximately the same 11 cost in lines of code. 12 """ 13 14 from ctypes import * 15 import mmap 16 import os 17 import subprocess 18 19 # drmModeConnection enum 20 DRM_MODE_CONNECTED = 1 21 DRM_MODE_DISCONNECTED = 2 22 DRM_MODE_UNKNOWNCONNECTION = 3 23 24 DRM_MODE_CONNECTOR_Unknown = 0 25 DRM_MODE_CONNECTOR_VGA = 1 26 DRM_MODE_CONNECTOR_DVII = 2 27 DRM_MODE_CONNECTOR_DVID = 3 28 DRM_MODE_CONNECTOR_DVIA = 4 29 DRM_MODE_CONNECTOR_Composite = 5 30 DRM_MODE_CONNECTOR_SVIDEO = 6 31 DRM_MODE_CONNECTOR_LVDS = 7 32 DRM_MODE_CONNECTOR_Component = 8 33 DRM_MODE_CONNECTOR_9PinDIN = 9 34 DRM_MODE_CONNECTOR_DisplayPort = 10 35 DRM_MODE_CONNECTOR_HDMIA = 11 36 DRM_MODE_CONNECTOR_HDMIB = 12 37 DRM_MODE_CONNECTOR_TV = 13 38 DRM_MODE_CONNECTOR_eDP = 14 39 DRM_MODE_CONNECTOR_VIRTUAL = 15 40 DRM_MODE_CONNECTOR_DSI = 16 41 42 # This constant is not defined in any one header; it is the pieced-together 43 # incantation for the ioctl that performs dumb mappings. I would love for this 44 # to not have to be here, but it can't be imported from any header easily. 45 DRM_IOCTL_MODE_MAP_DUMB = 0xc01064b3 46 47 # This define should be equal to O_CLOEXEC, which should be available in 48 # python's os module, but isn't until version 3.3. If we version up, we can 49 # set this to os.O_CLOEXEC. 50 DRM_CLOEXEC = 02000000 51 52 53 class DrmVersion(Structure): 54 """ 55 The version of a DRM node. 56 """ 57 58 _fields_ = [ 59 ("version_major", c_int), 60 ("version_minor", c_int), 61 ("version_patchlevel", c_int), 62 ("name_len", c_int), 63 ("name", c_char_p), 64 ("date_len", c_int), 65 ("date", c_char_p), 66 ("desc_len", c_int), 67 ("desc", c_char_p), 68 ] 69 70 _l = None 71 72 def __repr__(self): 73 return "%s %d.%d.%d (%s) (%s)" % (self.name, 74 self.version_major, 75 self.version_minor, 76 self.version_patchlevel, 77 self.desc, 78 self.date,) 79 80 def __del__(self): 81 if self._l: 82 self._l.drmFreeVersion(self) 83 84 85 class DrmModeResources(Structure): 86 """ 87 Resources associated with setting modes on a DRM node. 88 """ 89 90 _fields_ = [ 91 ("count_fbs", c_int), 92 ("fbs", POINTER(c_uint)), 93 ("count_crtcs", c_int), 94 ("crtcs", POINTER(c_uint)), 95 ("count_connectors", c_int), 96 ("connectors", POINTER(c_uint)), 97 ("count_encoders", c_int), 98 ("encoders", POINTER(c_uint)), 99 ("min_width", c_int), 100 ("max_width", c_int), 101 ("min_height", c_int), 102 ("max_height", c_int), 103 ] 104 105 _fd = None 106 _l = None 107 108 def __repr__(self): 109 return "<DRM mode resources>" 110 111 def __del__(self): 112 if self._l: 113 self._l.drmModeFreeResources(self) 114 115 def _wakeup_screen(self): 116 """ 117 Send a synchronous dbus message to power on screen. 118 """ 119 # Get and process reply to make this synchronous. 120 subprocess.check_output([ 121 "dbus-send", "--type=method_call", "--system", "--print-reply", 122 "--dest=org.chromium.PowerManager", "/org/chromium/PowerManager", 123 "org.chromium.PowerManager.HandleUserActivity", "int32:0" 124 ]) 125 126 def getValidCrtc(self): 127 for i in xrange(0, self.count_crtcs): 128 crtc_id = self.crtcs[i] 129 crtc = self._l.drmModeGetCrtc(self._fd, crtc_id).contents 130 if crtc.mode_valid: 131 return crtc 132 return None 133 134 def getCrtc(self, crtc_id): 135 """ 136 Obtain the CRTC at a given index. 137 138 @param crtc_id: The CRTC to get. 139 """ 140 if crtc_id: 141 return self._l.drmModeGetCrtc(self._fd, crtc_id).contents 142 return self.getValidCrtc() 143 144 def getCrtcRobust(self, crtc_id=None): 145 crtc = self.getCrtc(crtc_id) 146 if crtc is None: 147 self._wakeup_screen() 148 crtc = self.getCrtc(crtc_id) 149 if crtc is not None: 150 crtc._fd = self._fd 151 crtc._l = self._l 152 return crtc 153 154 155 class DrmModeModeInfo(Structure): 156 """ 157 A DRM modesetting mode info. 158 """ 159 160 _fields_ = [ 161 ("clock", c_uint), 162 ("hdisplay", c_ushort), 163 ("hsync_start", c_ushort), 164 ("hsync_end", c_ushort), 165 ("htotal", c_ushort), 166 ("hskew", c_ushort), 167 ("vdisplay", c_ushort), 168 ("vsync_start", c_ushort), 169 ("vsync_end", c_ushort), 170 ("vtotal", c_ushort), 171 ("vscan", c_ushort), 172 ("vrefresh", c_uint), 173 ("flags", c_uint), 174 ("type", c_uint), 175 ("name", c_char * 32), 176 ] 177 178 179 class DrmModeCrtc(Structure): 180 """ 181 A DRM modesetting CRTC. 182 """ 183 184 _fields_ = [ 185 ("crtc_id", c_uint), 186 ("buffer_id", c_uint), 187 ("x", c_uint), 188 ("y", c_uint), 189 ("width", c_uint), 190 ("height", c_uint), 191 ("mode_valid", c_int), 192 ("mode", DrmModeModeInfo), 193 ("gamma_size", c_int), 194 ] 195 196 _fd = None 197 _l = None 198 199 def __repr__(self): 200 return "<CRTC (%d)>" % self.crtc_id 201 202 def __del__(self): 203 if self._l: 204 self._l.drmModeFreeCrtc(self) 205 206 def hasFb(self): 207 """ 208 Whether this CRTC has an associated framebuffer. 209 """ 210 211 return self.buffer_id != 0 212 213 def fb(self): 214 """ 215 Obtain the framebuffer, if one is associated. 216 """ 217 218 if self.hasFb(): 219 fb = self._l.drmModeGetFB(self._fd, self.buffer_id).contents 220 fb._fd = self._fd 221 fb._l = self._l 222 return fb 223 else: 224 raise RuntimeError("CRTC %d doesn't have a framebuffer!" % 225 self.crtc_id) 226 227 228 class DrmModeEncoder(Structure): 229 """ 230 A DRM modesetting encoder. 231 """ 232 233 _fields_ = [ 234 ("encoder_id", c_uint), 235 ("encoder_type", c_uint), 236 ("crtc_id", c_uint), 237 ("possible_crtcs", c_uint), 238 ("possible_clones", c_uint), 239 ] 240 241 _fd = None 242 _l = None 243 244 def __repr__(self): 245 return "<Encoder (%d)>" % self.encoder_id 246 247 def __del__(self): 248 if self._l: 249 self._l.drmModeFreeEncoder(self) 250 251 252 class DrmModeConnector(Structure): 253 """ 254 A DRM modesetting connector. 255 """ 256 257 _fields_ = [ 258 ("connector_id", c_uint), 259 ("encoder_id", c_uint), 260 ("connector_type", c_uint), 261 ("connector_type_id", c_uint), 262 ("connection", c_uint), # drmModeConnection enum 263 ("mmWidth", c_uint), 264 ("mmHeight", c_uint), 265 ("subpixel", c_uint), # drmModeSubPixel enum 266 ("count_modes", c_int), 267 ("modes", POINTER(DrmModeModeInfo)), 268 ("count_propts", c_int), 269 ("props", POINTER(c_uint)), 270 ("prop_values", POINTER(c_ulonglong)), 271 ("count_encoders", c_int), 272 ("encoders", POINTER(c_uint)), 273 ] 274 275 _fd = None 276 _l = None 277 278 def __repr__(self): 279 return "<Connector (%d)>" % self.connector_id 280 281 def __del__(self): 282 if self._l: 283 self._l.drmModeFreeConnector(self) 284 285 def isInternal(self): 286 return (self.connector_type == DRM_MODE_CONNECTOR_LVDS or 287 self.connector_type == DRM_MODE_CONNECTOR_eDP or 288 self.connector_type == DRM_MODE_CONNECTOR_DSI) 289 290 def isConnected(self): 291 return self.connection == DRM_MODE_CONNECTED 292 293 294 class drm_mode_map_dumb(Structure): 295 """ 296 Request a mapping of a modesetting buffer. 297 298 The map will be "dumb;" it will be accessible via mmap() but very slow. 299 """ 300 301 _fields_ = [ 302 ("handle", c_uint), 303 ("pad", c_uint), 304 ("offset", c_ulonglong), 305 ] 306 307 308 class DrmModeFB(Structure): 309 """ 310 A DRM modesetting framebuffer. 311 """ 312 313 _fields_ = [ 314 ("fb_id", c_uint), 315 ("width", c_uint), 316 ("height", c_uint), 317 ("pitch", c_uint), 318 ("bpp", c_uint), 319 ("depth", c_uint), 320 ("handle", c_uint), 321 ] 322 323 _l = None 324 _map = None 325 326 def __repr__(self): 327 s = "<Framebuffer (%dx%d (pitch %d bytes), %d bits/pixel, depth %d)" 328 vitals = s % (self.width, 329 self.height, 330 self.pitch, 331 self.bpp, 332 self.depth,) 333 if self._map: 334 tail = " (mapped)>" 335 else: 336 tail = ">" 337 return vitals + tail 338 339 def __del__(self): 340 if self._l: 341 self._l.drmModeFreeFB(self) 342 343 def map(self, size): 344 """ 345 Map the framebuffer. 346 """ 347 348 if self._map: 349 return 350 351 mapDumb = drm_mode_map_dumb() 352 mapDumb.handle = self.handle 353 354 rv = self._l.drmIoctl(self._fd, DRM_IOCTL_MODE_MAP_DUMB, 355 pointer(mapDumb)) 356 if rv: 357 raise IOError(rv, os.strerror(rv)) 358 359 # mmap.mmap() has a totally different order of arguments in Python 360 # compared to C; check the documentation before altering this 361 # incantation. 362 self._map = mmap.mmap(self._fd, 363 size, 364 flags=mmap.MAP_SHARED, 365 prot=mmap.PROT_READ, 366 offset=mapDumb.offset) 367 368 def unmap(self): 369 """ 370 Unmap the framebuffer. 371 """ 372 373 if self._map: 374 self._map.close() 375 self._map = None 376 377 def getFD(self): 378 """ 379 Convert handle to a FD. 380 """ 381 prime_fd = c_int(0) 382 rv = self._l.drmPrimeHandleToFD(self._fd, self.handle, 383 DRM_CLOEXEC, byref(prime_fd)) 384 if rv: 385 raise RuntimeError("Failed to convert FB handle to FD. %d" % rv) 386 return prime_fd 387 388 def loadDRM(): 389 """ 390 Load a handle to libdrm. 391 392 In addition to loading, this function also configures the argument and 393 return types of functions. 394 """ 395 396 l = None 397 398 try: 399 l = cdll.LoadLibrary("libdrm.so") 400 except OSError: 401 l = cdll.LoadLibrary("libdrm.so.2") # ubuntu doesn't have libdrm.so 402 403 l.drmGetVersion.argtypes = [c_int] 404 l.drmGetVersion.restype = POINTER(DrmVersion) 405 406 l.drmFreeVersion.argtypes = [POINTER(DrmVersion)] 407 l.drmFreeVersion.restype = None 408 409 l.drmModeGetResources.argtypes = [c_int] 410 l.drmModeGetResources.restype = POINTER(DrmModeResources) 411 412 l.drmModeFreeResources.argtypes = [POINTER(DrmModeResources)] 413 l.drmModeFreeResources.restype = None 414 415 l.drmModeGetCrtc.argtypes = [c_int, c_uint] 416 l.drmModeGetCrtc.restype = POINTER(DrmModeCrtc) 417 418 l.drmModeFreeCrtc.argtypes = [POINTER(DrmModeCrtc)] 419 l.drmModeFreeCrtc.restype = None 420 421 l.drmModeGetEncoder.argtypes = [c_int, c_uint] 422 l.drmModeGetEncoder.restype = POINTER(DrmModeEncoder) 423 424 l.drmModeFreeEncoder.argtypes = [POINTER(DrmModeEncoder)] 425 l.drmModeFreeEncoder.restype = None 426 427 l.drmModeGetConnector.argtypes = [c_int, c_uint] 428 l.drmModeGetConnector.restype = POINTER(DrmModeConnector) 429 430 l.drmModeFreeConnector.argtypes = [POINTER(DrmModeConnector)] 431 l.drmModeFreeConnector.restype = None 432 433 l.drmModeGetFB.argtypes = [c_int, c_uint] 434 l.drmModeGetFB.restype = POINTER(DrmModeFB) 435 436 l.drmModeFreeFB.argtypes = [POINTER(DrmModeFB)] 437 l.drmModeFreeFB.restype = None 438 439 l.drmIoctl.argtypes = [c_int, c_ulong, c_voidp] 440 l.drmIoctl.restype = c_int 441 442 l.drmPrimeHandleToFD.argtypes = [c_int, c_uint, c_uint, POINTER(c_int)] 443 l.drmPrimeHandleToFD.restype = c_int 444 445 return l 446 447 448 class DRM(object): 449 """ 450 A DRM node. 451 """ 452 453 def __init__(self, library, fd): 454 self._l = library 455 self._fd = fd 456 457 def __repr__(self): 458 return "<DRM (FD %d)>" % self._fd 459 460 @classmethod 461 def fromHandle(cls, handle): 462 """ 463 Create a node from a file handle. 464 465 @param handle: A file-like object backed by a file descriptor. 466 """ 467 468 self = cls(loadDRM(), handle.fileno()) 469 # We must keep the handle alive, and we cannot trust the caller to 470 # keep it alive for us. 471 self._handle = handle 472 return self 473 474 def version(self): 475 """ 476 Obtain the version. 477 """ 478 479 v = self._l.drmGetVersion(self._fd).contents 480 v._l = self._l 481 return v 482 483 def resources(self): 484 """ 485 Obtain the modesetting resources. 486 """ 487 488 resources_ptr = self._l.drmModeGetResources(self._fd) 489 if resources_ptr: 490 r = resources_ptr.contents 491 r._fd = self._fd 492 r._l = self._l 493 return r 494 495 return None 496 497 def getCrtc(self, crtc_id): 498 c_ptr = self._l.drmModeGetCrtc(self._fd, crtc_id) 499 if c_ptr: 500 c = c_ptr.contents 501 c._fd = self._fd 502 c._l = self._l 503 return c 504 505 return None 506 507 def getEncoder(self, encoder_id): 508 e_ptr = self._l.drmModeGetEncoder(self._fd, encoder_id) 509 if e_ptr: 510 e = e_ptr.contents 511 e._fd = self._fd 512 e._l = self._l 513 return e 514 515 return None 516 517 def getConnector(self, connector_id): 518 c_ptr = self._l.drmModeGetConnector(self._fd, connector_id) 519 if c_ptr: 520 c = c_ptr.contents 521 c._fd = self._fd 522 c._l = self._l 523 return c 524 525 return None 526 527 528 529 def drmFromPath(path): 530 """ 531 Given a DRM node path, open the corresponding node. 532 533 @param path: The path of the minor node to open. 534 """ 535 # Always open the device as RW (r+) so that mmap works later. 536 handle = open(path, "r+") 537 return DRM.fromHandle(handle) 538 539 540 _drm = None 541 542 543 def getCrtc(crtc_id=None): 544 """ 545 @param crtc_id: None for first found CRTC with mode set 546 or "internal" for crtc connected to internal LCD 547 or "external" for crtc connected to external display 548 or "usb" "evdi" or "udl" for crtc with valid mode on evdi or 549 udl display 550 or DRM integer crtc_id 551 """ 552 global _drm 553 554 if not _drm: 555 paths = [ 556 "/dev/dri/" + n 557 for n in filter(lambda x: x.startswith("card"), 558 os.listdir("/dev/dri")) 559 ] 560 561 if crtc_id == "usb" or crtc_id == "evdi" or crtc_id == "udl": 562 for p in paths: 563 d = drmFromPath(p) 564 v = d.version() 565 566 if crtc_id == v.name: 567 _drm = d 568 break 569 570 if crtc_id == "usb" and (v.name == "evdi" or v.name == "udl"): 571 _drm = d 572 break 573 574 elif crtc_id == "internal" or crtc_id == "external": 575 internal = crtc_id == "internal" 576 for p in paths: 577 d = drmFromPath(p) 578 if d.resources() is None: 579 continue 580 if d.resources() and d.resources().count_connectors > 0: 581 for c in xrange(0, d.resources().count_connectors): 582 connector = d.getConnector(d.resources().connectors[c]) 583 if (internal == connector.isInternal() 584 and connector.isConnected() 585 and connector.encoder_id != 0): 586 e = d.getEncoder(connector.encoder_id) 587 crtc = d.getCrtc(e.crtc_id) 588 if crtc.mode_valid: 589 crtc_id = crtc.crtc_id 590 _drm = d 591 break 592 if _drm: 593 break 594 595 elif crtc_id is None or crtc_id == 0: 596 for p in paths: 597 d = drmFromPath(p) 598 if d.resources() is None: 599 continue 600 for c in xrange(0, d.resources().count_crtcs): 601 crtc = d.getCrtc(d.resources().crtcs[c]) 602 if crtc.mode_valid: 603 crtc_id = d.resources().crtcs[c] 604 _drm = d 605 break 606 if _drm: 607 break 608 609 else: 610 for p in paths: 611 d = drmFromPath(p) 612 if d.resources() is None: 613 continue 614 for c in xrange(0, d.resources().count_crtcs): 615 if crtc_id == d.resources().crtcs[c]: 616 _drm = d 617 break 618 if _drm: 619 break 620 621 if _drm: 622 return _drm.resources().getCrtcRobust(crtc_id) 623 624 return None 625