blob: 1938260a92f01ed978516966bb1f18468f7287ca [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A Python wrapper of Direct Rendering Manager (DRM) library.
This is a Python wrapper of libdrm, which itself is a wrapper around the Direct
Rendering Interface (DRI) between userland and kernel.
The Python ctypes representation of the following data structures are based on
libdrm-2.4.58: http://dri.freedesktop.org/libdrm/libdrm-2.4.58.tar.bz2
"""
import ctypes
import mmap
import os
from six.moves import xrange
import factory_common # pylint: disable=unused-import
from cros.factory.external import PIL
if PIL.MODULE_READY:
from cros.factory.external.PIL import Image
class DRMError(Exception):
"""Error raised when accessing DRM."""
class DRMModeBaseStruct(ctypes.Structure):
"""Base class for DRM mode struct classes.
Attributes:
fd: the file descriptor of the opened DRM handle.
need_free: set to True when a instance is created with a call to an allocate
function in the DRM library, in which case the dtor needs to free the
allocated memory by calling the corresponding free function.
"""
fd = None
need_free = False
class DRMModeResource(DRMModeBaseStruct):
"""C struct of DRM mode resource.
This is a Python representation of the following C struct in xf86drmMode.h:
typedef struct _drmModeRes {
int count_fbs;
uint32_t *fbs;
int count_crtcs;
uint32_t *crtcs;
int count_connectors;
uint32_t *connectors;
int count_encoders;
uint32_t *encoders;
uint32_t min_width, max_width;
uint32_t min_height, max_height;
} drmModeRes, *drmModeResPtr;
"""
_fields_ = [
('count_fbs', ctypes.c_int),
('fbs', ctypes.POINTER(ctypes.c_uint32)),
('count_crtcs', ctypes.c_int),
('_crtcs', ctypes.POINTER(ctypes.c_uint32)),
('count_connectors', ctypes.c_int),
('_connectors', ctypes.POINTER(ctypes.c_uint32)),
('count_encoders', ctypes.c_int),
('encoders', ctypes.POINTER(ctypes.c_uint32)),
('min_width', ctypes.c_uint32), ('max_width', ctypes.c_uint32),
('min_height', ctypes.c_uint32), ('max_height', ctypes.c_uint32),
]
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreeResources(ctypes.byref(self))
@property
def crtcs(self):
ret = []
for i in xrange(self.count_crtcs):
crtc = _GetDRMLibrary().drmModeGetCrtc(self.fd, self._crtcs[i]).contents
crtc.fd = self.fd
crtc.need_free = True
ret.append(crtc)
return ret
@property
def connectors(self):
ret = []
for i in xrange(self.count_connectors):
conn = _GetDRMLibrary().drmModeGetConnector(
self.fd, self._connectors[i]).contents
conn.fd = self.fd
conn.need_free = True
ret.append(conn)
return ret
class DRMModeModeInfo(DRMModeBaseStruct):
"""C struct of DRM mode modeinfo.
This is a Python representation of the following C struct in xf86drmMode.h:
#define DRM_DISPLAY_MODE_LEN 32
typedef struct _drmModeModeInfo {
uint32_t clock;
uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
uint32_t vrefresh;
uint32_t flags;
uint32_t type;
char name[DRM_DISPLAY_MODE_LEN];
} drmModeModeInfo, *drmModeModeInfoPtr;
"""
DRM_DISPLAY_MODE_LEN = 32
_fields_ = [
('clock', ctypes.c_uint32),
('hdisplay', ctypes.c_uint16), ('hsync_start', ctypes.c_uint16),
('hsync_end', ctypes.c_uint16), ('htotal', ctypes.c_uint16),
('hskew', ctypes.c_uint16),
('vdisplay', ctypes.c_uint16), ('vsync_start', ctypes.c_uint16),
('vsync_end', ctypes.c_uint16), ('vtotal', ctypes.c_uint16),
('vscan', ctypes.c_uint16),
('vrefresh', ctypes.c_uint32),
('flags', ctypes.c_uint32),
('type', ctypes.c_uint32),
('name', ctypes.c_char * DRM_DISPLAY_MODE_LEN),
]
def __repr__(self):
return '<Mode %dx%d, vrefresh=%d>' % (
self.hdisplay, self.vdisplay, self.vrefresh)
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreeModeInfo(ctypes.byref(self))
class DRMModeConnector(DRMModeBaseStruct):
"""C struct of DRM mode connector.
This is a Python representation of the following C struct in xf86drmMode.h:
typedef enum {
DRM_MODE_CONNECTED = 1,
DRM_MODE_DISCONNECTED = 2,
DRM_MODE_UNKNOWNCONNECTION = 3
} drmModeConnection;
typedef enum {
DRM_MODE_SUBPIXEL_UNKNOWN = 1,
DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2,
DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3,
DRM_MODE_SUBPIXEL_VERTICAL_RGB = 4,
DRM_MODE_SUBPIXEL_VERTICAL_BGR = 5,
DRM_MODE_SUBPIXEL_NONE = 6
} drmModeSubPixel;
typedef struct _drmModeConnector {
uint32_t connector_id;
uint32_t encoder_id; /**< Encoder currently connected to */
uint32_t connector_type;
uint32_t connector_type_id;
drmModeConnection connection;
uint32_t mmWidth, mmHeight; /**< HxW in millimeters */
drmModeSubPixel subpixel;
int count_modes;
drmModeModeInfoPtr modes;
int count_props;
uint32_t *props; /**< List of property ids */
uint64_t *prop_values; /**< List of property values */
int count_encoders;
uint32_t *encoders; /**< List of encoder ids */
} drmModeConnector, *drmModeConnectorPtr;
"""
# drmModeConnection
DRM_MODE_CONNECTED = 1
DRM_MODE_DISCONNECTED = 2
DRM_MODE_UNKNOWNCONNECTION = 3
# We use the same connector status names as in modetest.
CONNECTOR_STATUS_NAMES = {
DRM_MODE_CONNECTED: 'connected',
DRM_MODE_DISCONNECTED: 'disconnected',
DRM_MODE_UNKNOWNCONNECTION: 'unknown',
}
# drmModeSubPixel
DRM_MODE_SUBPIXEL_UNKNOWN = 1
DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2
DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3
DRM_MODE_SUBPIXEL_VERTICAL_RGB = 4
DRM_MODE_SUBPIXEL_VERTICAL_BGR = 5
DRM_MODE_SUBPIXEL_NONE = 6
DRM_MODE_CONNECTOR_Unknown = 0
DRM_MODE_CONNECTOR_VGA = 1
DRM_MODE_CONNECTOR_DVII = 2
DRM_MODE_CONNECTOR_DVID = 3
DRM_MODE_CONNECTOR_DVIA = 4
DRM_MODE_CONNECTOR_Composite = 5
DRM_MODE_CONNECTOR_SVIDEO = 6
DRM_MODE_CONNECTOR_LVDS = 7
DRM_MODE_CONNECTOR_Component = 8
DRM_MODE_CONNECTOR_9PinDIN = 9
DRM_MODE_CONNECTOR_DisplayPort = 10
DRM_MODE_CONNECTOR_HDMIA = 11
DRM_MODE_CONNECTOR_HDMIB = 12
DRM_MODE_CONNECTOR_TV = 13
DRM_MODE_CONNECTOR_eDP = 14
DRM_MODE_CONNECTOR_VIRTUAL = 15
DRM_MODE_CONNECTOR_DSI = 16
DRM_MODE_CONNECTOR_DPI = 17
# We use the same connector names as in modetest.
CONNECTOR_TYPE_NAMES = {
DRM_MODE_CONNECTOR_Unknown: 'unknown',
DRM_MODE_CONNECTOR_VGA: 'VGA',
DRM_MODE_CONNECTOR_DVII: 'DVI-I',
DRM_MODE_CONNECTOR_DVID: 'DVI-D',
DRM_MODE_CONNECTOR_DVIA: 'DVI-A',
DRM_MODE_CONNECTOR_Composite: 'composite',
DRM_MODE_CONNECTOR_SVIDEO: 's-video',
DRM_MODE_CONNECTOR_LVDS: 'LVDS',
DRM_MODE_CONNECTOR_Component: 'component',
DRM_MODE_CONNECTOR_9PinDIN: '9-pin DIN',
DRM_MODE_CONNECTOR_DisplayPort: 'DP',
DRM_MODE_CONNECTOR_HDMIA: 'HDMI-A',
DRM_MODE_CONNECTOR_HDMIB: 'HDMI-B',
DRM_MODE_CONNECTOR_TV: 'TV',
DRM_MODE_CONNECTOR_eDP: 'eDP',
DRM_MODE_CONNECTOR_VIRTUAL: 'Virtual',
DRM_MODE_CONNECTOR_DSI: 'DSI',
DRM_MODE_CONNECTOR_DPI: 'DPI',
}
_fields_ = [
('connector_id', ctypes.c_uint32),
('encoder_id', ctypes.c_uint32),
('connector_type', ctypes.c_uint32),
('connector_type_id', ctypes.c_uint32),
('connection', ctypes.c_uint),
('mmWidth', ctypes.c_uint32), ('mmHeight', ctypes.c_uint32),
('subpixel', ctypes.c_uint),
('count_modes', ctypes.c_int),
('modes', ctypes.POINTER(DRMModeModeInfo)),
('count_props', ctypes.c_int),
('props', ctypes.POINTER(ctypes.c_uint32)),
('prop_values', ctypes.POINTER(ctypes.c_uint64)),
('count_encoders', ctypes.c_int),
('encoders', ctypes.POINTER(ctypes.c_uint32)),
]
def __repr__(self):
return '<Connector %s, status=%s>' % (self.id, self.status)
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreeConnector(ctypes.byref(self))
@property
def id(self):
return '%s-%d' % (self.CONNECTOR_TYPE_NAMES[self.connector_type],
self.connector_type_id)
@property
def status(self):
return self.CONNECTOR_STATUS_NAMES[self.connection]
@property
def encoder(self):
encoder_ptr = _GetDRMLibrary().drmModeGetEncoder(self.fd, self.encoder_id)
if encoder_ptr:
encoder = encoder_ptr.contents
encoder.fd = self.fd
encoder.need_free = True
return encoder
else:
return None
@property
def edid(self):
blob_id = None
for i in xrange(self.count_props):
# 1 is the property id of "EDID" in Kernel Mode Setting (KMS):
# https://www.kernel.org/doc/htmldocs/drm/drm-kms-properties.html
if self.props[i] == 1:
blob_id = self.prop_values[i]
if not blob_id:
return None
blob = _GetDRMLibrary().drmModeGetPropertyBlob(self.fd, blob_id).contents
blob.fd = self.fd
blob.need_free = True
return ctypes.cast(blob.data, ctypes.POINTER(ctypes.c_uint8))[0:blob.length]
def GetAssociatedFramebuffer(self):
"""Gets the associate scanout buffer.
Returns:
A DRMModeFB instance.
"""
if self.encoder:
return self.encoder.crtc.framebuffer
return None
class DRMModeEncoder(DRMModeBaseStruct):
"""C struct of DRM mode encoder.
This is a Python representation of the following C struct in xf86drmMode.h:
typedef struct _drmModeEncoder {
uint32_t encoder_id;
uint32_t encoder_type;
uint32_t crtc_id;
uint32_t possible_crtcs;
uint32_t possible_clones;
} drmModeEncoder, *drmModeEncoderPtr;
"""
_fields_ = [
('encoder_id', ctypes.c_uint32),
('encoder_type', ctypes.c_uint32),
('crtc_id', ctypes.c_uint32),
('possible_crtcs', ctypes.c_uint32),
('possible_clones', ctypes.c_uint32),
]
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreeEncoder(ctypes.byref(self))
@property
def crtc(self):
crtc = _GetDRMLibrary().drmModeGetCrtc(self.fd, self.crtc_id).contents
crtc.fd = self.fd
crtc.need_free = True
return crtc
class DRMModeCrtc(DRMModeBaseStruct):
"""C struct of DRM mode CRTC.
This is a Python representation of the following C struct in xf86drmMode.h:
typedef struct _drmModeCrtc {
uint32_t crtc_id;
uint32_t buffer_id; /**< FB id to connect to 0 = disconnect */
uint32_t x, y; /**< Position on the framebuffer */
uint32_t width, height;
int mode_valid;
drmModeModeInfo mode;
int gamma_size; /**< Number of gamma stops */
} drmModeCrtc, *drmModeCrtcPtr;
"""
_fields_ = [
('crtc_id', ctypes.c_uint32),
('buffer_id', ctypes.c_uint32),
('x', ctypes.c_uint32), ('y', ctypes.c_uint32),
('width', ctypes.c_uint32), ('height', ctypes.c_uint32),
('mode_valid', ctypes.c_int),
('mode', DRMModeModeInfo),
('gamma_size', ctypes.c_int),
]
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreeCrtc(ctypes.byref(self))
@property
def framebuffer(self):
if not self.buffer_id:
return None
ret = _GetDRMLibrary().drmModeGetFB(self.fd, self.buffer_id).contents
ret.fd = self.fd
ret.need_free = True
return ret
class DRMModeFB(DRMModeBaseStruct):
"""C struct of DRM mode framebuffer.
This is a Python representation of the following C struct xf86drmMode.h:
typedef struct _drmModeFB {
uint32_t fb_id;
uint32_t width, height;
uint32_t pitch;
uint32_t bpp;
uint32_t depth;
/* driver specific handle */
uint32_t handle;
} drmModeFB, *drmModeFBPtr;
"""
_fields_ = [
('fb_id', ctypes.c_uint32),
('width', ctypes.c_uint32), ('height', ctypes.c_uint32),
('pitch', ctypes.c_uint32),
('bpp', ctypes.c_uint32),
('depth', ctypes.c_uint32),
('handle', ctypes.c_uint32),
]
_map = None
# The ioctl number here is pre-computed. It can't be imported from libdrm
# since the constant is a #define in the C source code.
DRM_IOCTL_MODE_MAP_DUMB = 0xc01064b3
class DRMModeMapDumb(ctypes.Structure):
"""/* set up for mmap of a dumb scanout buffer */ struct drm_mode_map_dumb {
/** Handle for the object being mapped. */
__u32 handle;
__u32 pad;
/**
* Fake offset to use for subsequent mmap call
*
* This is a fixed-size type for 32/64 compatibility.
*/
__u64 offset;
};
"""
_fields_ = [
('handle', ctypes.c_uint32),
('pad', ctypes.c_uint32),
('offset', ctypes.c_uint64),
]
def __repr__(self):
return '<Framebuffer %dx%d, pitch=%d, bpp=%d, depth=%d>' % (
self.width, self.height, self.pitch, self.bpp, self.depth)
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreeFB(ctypes.byref(self))
@property
def contents(self):
try:
self.map()
size = self.pitch * self.height
return self._map.read(size)
finally:
self.unmap()
def AsRGBImage(self):
"""Converts the contents of the framebuffer to a RGB Image() instance.
Returns:
A Image() instance with mode='RGB' of the converted framebuffer.
"""
if self.depth != 24:
raise DRMError('Unable to convert depth %s' % self.depth)
return Image.fromstring(
'RGB', (self.width, self.height), self.contents, 'raw', 'BGRX')
def map(self):
if self._map:
return
map_dumb = self.DRMModeMapDumb()
# pylint: disable=attribute-defined-outside-init
map_dumb.handle = self.handle
ret = _GetDRMLibrary().drmIoctl(self.fd, self.DRM_IOCTL_MODE_MAP_DUMB,
ctypes.byref(map_dumb))
if ret:
raise DRMError(ret, os.strerror(ret))
size = self.pitch * self.height
self._map = mmap.mmap(self.fd, size, flags=mmap.MAP_SHARED,
prot=mmap.PROT_READ, offset=map_dumb.offset)
def unmap(self):
if self._map:
self._map.close()
self._map = None
class DRMModePropertyBlob(DRMModeBaseStruct):
"""C struct of DRM mode property blob.
This is a Python representation of the following C struct xf86drmMode.h:
typedef struct _drmModePropertyBlob {
uint32_t id;
uint32_t length;
void *data;
} drmModePropertyBlobRes, *drmModePropertyBlobPtr;
"""
_fields_ = [
('id', ctypes.c_uint32),
('length', ctypes.c_uint32),
('data', ctypes.c_void_p),
]
def __del__(self):
if self.need_free:
_GetDRMLibrary().drmModeFreePropertyBlob(ctypes.byref(self))
class DRM(object):
"""An abstraction of the DRM device."""
def __init__(self, handle):
self.handle = handle
self.fd = handle.fileno()
@property
def resources(self):
resources_ptr = _GetDRMLibrary().drmModeGetResources(self.fd)
if resources_ptr and resources_ptr.contents.count_connectors:
ret = resources_ptr.contents
ret.fd = self.fd
ret.need_free = True
return ret
return None
def DRMFromPath(path):
"""Opens the DRM device from path.
Args:
path: the path of minor node.
Returns:
A DRM instance.
"""
return DRM(open(path))
def _LoadDRMLibrary():
"""Loads the userland DRM library."""
lib = ctypes.cdll.LoadLibrary('libdrm.so')
lib.drmModeGetResources.argtypes = [ctypes.c_int]
lib.drmModeGetResources.restype = ctypes.POINTER(DRMModeResource)
lib.drmModeFreeResources.argtypes = [ctypes.POINTER(DRMModeResource)]
lib.drmModeFreeResources.restype = None
lib.drmModeGetConnector.argtypes = [ctypes.c_int, ctypes.c_uint32]
lib.drmModeGetConnector.restype = ctypes.POINTER(DRMModeConnector)
lib.drmModeFreeConnector.argtypes = [ctypes.POINTER(DRMModeConnector)]
lib.drmModeFreeConnector.restype = None
lib.drmModeGetEncoder.argtypes = [ctypes.c_int, ctypes.c_uint32]
lib.drmModeGetEncoder.restype = ctypes.POINTER(DRMModeEncoder)
lib.drmModeFreeEncoder.argtypes = [ctypes.POINTER(DRMModeEncoder)]
lib.drmModeFreeEncoder.restype = None
lib.drmModeGetCrtc.argtypes = [ctypes.c_int, ctypes.c_uint32]
lib.drmModeGetCrtc.restype = ctypes.POINTER(DRMModeCrtc)
lib.drmModeFreeCrtc.argtypes = [ctypes.POINTER(DRMModeCrtc)]
lib.drmModeFreeCrtc.restype = None
lib.drmModeGetFB.argtypes = [ctypes.c_int, ctypes.c_uint32]
lib.drmModeGetFB.restype = ctypes.POINTER(DRMModeFB)
lib.drmModeFreeFB.argtypes = [ctypes.POINTER(DRMModeFB)]
lib.drmModeFreeFB.restype = None
lib.drmModeGetPropertyBlob.argtypes = [ctypes.c_int, ctypes.c_uint32]
lib.drmModeGetPropertyBlob.restype = ctypes.POINTER(DRMModePropertyBlob)
lib.drmModeFreePropertyBlob.argtypes = [ctypes.POINTER(DRMModePropertyBlob)]
lib.drmModeFreePropertyBlob.restype = None
lib.drmIoctl.argtypes = [ctypes.c_int, ctypes.c_ulong, ctypes.c_voidp]
lib.drmIoctl.restype = ctypes.c_int
lib.drmSetMaster.argtypes = [ctypes.c_int]
lib.drmSetMaster.restypes = ctypes.c_int
lib.drmDropMaster.argtypes = [ctypes.c_int]
lib.drmDropMaster.restypes = ctypes.c_int
lib.drmModeConnectorSetProperty.argtypes = [ctypes.c_int, ctypes.c_uint32,
ctypes.c_uint32, ctypes.c_uint64]
lib.drmModeConnectorSetProperty.restype = ctypes.c_int
return lib
def _GetDRMLibrary():
if _lib:
return _lib
else:
raise DRMError('DRM library is not available')
try:
_lib = _LoadDRMLibrary()
except OSError:
_lib = None