Home | History | Annotate | Download | only in graphics
      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