vdi-cloud/terminal/native-cursor/kms.py

125 lines
6.4 KiB
Python

"""Fail-closed guard for the current virtio GPU's cursor-free KMS scanout.
Only primary planes visible with Sunshine's exact DRM client capabilities are
accepted. Virtio cursor hotspot planes are hidden from atomic clients that do
not request DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT. This is not a cursor filter.
"""
import ctypes as C
import os
import re
class PlaneResources(C.Structure):
_fields_ = [('count', C.c_uint32), ('planes', C.POINTER(C.c_uint32))]
class Plane(C.Structure):
_fields_ = [('count_formats', C.c_uint32), ('formats', C.POINTER(C.c_uint32)),
('plane_id', C.c_uint32), ('crtc_id', C.c_uint32),
('fb_id', C.c_uint32), ('crtc_x', C.c_uint32),
('crtc_y', C.c_uint32), ('x', C.c_uint32), ('y', C.c_uint32),
('possible_crtcs', C.c_uint32), ('gamma_size', C.c_uint32)]
class Properties(C.Structure):
_fields_ = [('count', C.c_uint32), ('props', C.POINTER(C.c_uint32)),
('values', C.POINTER(C.c_uint64))]
class PropertyPrefix(C.Structure):
_fields_ = [('id', C.c_uint32), ('flags', C.c_uint32), ('name', C.c_char * 32)]
class Framebuffer(C.Structure):
_fields_ = [('id', C.c_uint32), ('width', C.c_uint32), ('height', C.c_uint32),
('format', C.c_uint32), ('modifier', C.c_uint64),
('flags', C.c_uint32), ('handles', C.c_uint32 * 4),
('pitches', C.c_uint32 * 4), ('offsets', C.c_uint32 * 4)]
class VersionPrefix(C.Structure):
_fields_ = [('major', C.c_int), ('minor', C.c_int), ('patch', C.c_int),
('name_len', C.c_int), ('name', C.c_char_p)]
class VirtioScanout:
def __init__(self, config):
if set(config) != {'backend', 'card', 'size'} or config['backend'] != 'kms-virtio-no-cursor-plane':
raise ValueError('unsupported KMS cursor configuration')
self.card = config['card']
if not isinstance(self.card, str) or not re.fullmatch('/dev/dri/card[0-9]+', self.card):
raise ValueError('invalid DRM card')
self.size = config['size']
if self.size != [2560, 1440]:
raise ValueError('unverified KMS geometry')
self.drm = C.CDLL('libdrm.so.2')
self.drm.drmSetClientCap.argtypes = [C.c_int, C.c_uint64, C.c_uint64]
for name, args, result in [
('drmModeGetPlaneResources', [C.c_int], C.POINTER(PlaneResources)),
('drmModeGetPlane', [C.c_int,C.c_uint32], C.POINTER(Plane)),
('drmModeObjectGetProperties', [C.c_int,C.c_uint32,C.c_uint32], C.POINTER(Properties)),
('drmModeGetProperty', [C.c_int,C.c_uint32], C.POINTER(PropertyPrefix)),
('drmModeGetFB2', [C.c_int,C.c_uint32], C.POINTER(Framebuffer)),
('drmGetVersion', [C.c_int], C.POINTER(VersionPrefix)),
]:
fn = getattr(self.drm, name); fn.argtypes = args; fn.restype = result
for name in ['drmModeFreePlaneResources','drmModeFreePlane',
'drmModeFreeObjectProperties','drmModeFreeProperty','drmModeFreeFB2',
'drmFreeVersion']:
fn = getattr(self.drm, name); fn.argtypes = [C.c_void_p]; fn.restype = None
def check(self):
fd = os.open(self.card, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW)
try:
version = self.drm.drmGetVersion(fd)
if not version: raise RuntimeError('DRM driver unavailable')
try:
if version.contents.name != b'virtio_gpu':
raise RuntimeError('KMS driver changed')
finally: self.drm.drmFreeVersion(version)
if self.drm.drmSetClientCap(fd, 2, 1):
raise RuntimeError('cannot enumerate universal planes')
# Match the pinned Sunshine KMS backend. Do not request capability
# 6 (cursor-plane hotspots), which would expose the virtio cursor.
if self.drm.drmSetClientCap(fd, 3, 1):
raise RuntimeError('atomic KMS is required')
resources = self.drm.drmModeGetPlaneResources(fd)
if not resources: raise RuntimeError('DRM plane list unavailable')
try:
# This tested virtio device exposes exactly one primary plane.
if resources.contents.count != 1:
raise RuntimeError('unverified DRM plane topology')
plane = self.drm.drmModeGetPlane(fd, resources.contents.planes[0])
if not plane: raise RuntimeError('DRM plane unavailable')
try:
p = plane.contents
props = self.drm.drmModeObjectGetProperties(fd, p.plane_id, 0xeeeeeeee)
if not props: raise RuntimeError('DRM plane properties unavailable')
kind = None
crtc_w = None
crtc_h = None
try:
if props.contents.count > 128: raise RuntimeError('invalid property count')
for i in range(props.contents.count):
prop = self.drm.drmModeGetProperty(fd, props.contents.props[i])
if not prop: raise RuntimeError('DRM property unavailable')
try:
name = prop.contents.name
value = props.contents.values[i]
if name == b'type': kind = value
elif name == b'CRTC_W': crtc_w = value
elif name == b'CRTC_H': crtc_h = value
finally: self.drm.drmModeFreeProperty(prop)
finally: self.drm.drmModeFreeObjectProperties(props)
# Atomic virtio clients expose CRTC geometry while the
# legacy plane fields remain zero. Sunshine uses the same
# atomic client capability pair and validates the actual
# framebuffer when it captures it.
if kind != 1 or [crtc_w, crtc_h] != self.size:
raise RuntimeError('scanout is not an active primary-only plane')
return {'driver': 'virtio_gpu', 'plane': p.plane_id, 'type': kind,
'crtc': int(p.crtc_id), 'size': self.size, 'cursor_planes': 0}
finally: self.drm.drmModeFreePlane(plane)
finally: self.drm.drmModeFreePlaneResources(resources)
finally: os.close(fd)