You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
497 lines
15 KiB
497 lines
15 KiB
/*****************************************************************************
|
|
* kms.c : kernel mode setting plugin for vlc
|
|
*****************************************************************************
|
|
* Copyright © 2018 Intel Corporation
|
|
* Copyright © 2021 Videolabs
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice (including the next
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
* Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Preamble
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
|
|
#include <xf86drm.h>
|
|
#include <xf86drmMode.h>
|
|
#include <drm_fourcc.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_fs.h>
|
|
#include <vlc_window.h>
|
|
|
|
#include <assert.h>
|
|
|
|
/*****************************************************************************
|
|
* Local prototypes
|
|
*****************************************************************************/
|
|
#define KMS_DEVICE_VAR "kms-device"
|
|
|
|
#define DEVICE_TEXT "Framebuffer device"
|
|
#define DEVICE_LONGTEXT \
|
|
"Framebuffer device to use for rendering (usually /dev/dri/card0 or /dev/drm0)."
|
|
|
|
#define KMS_CONNECTOR_TEXT "DRM Connector"
|
|
#define KMS_CONNECTOR_LONGTEXT \
|
|
"The DRM connector to use, using the naming conn-i where <conn> is the " \
|
|
"connector type name and <i> is the connector type id"
|
|
|
|
typedef enum { drvSuccess, drvTryNext, drvFail } deviceRval;
|
|
|
|
static const char * const 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] = "SVIDEO",
|
|
[DRM_MODE_CONNECTOR_LVDS] = "LVDS",
|
|
[DRM_MODE_CONNECTOR_Component] = "Component",
|
|
[DRM_MODE_CONNECTOR_9PinDIN] = "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",
|
|
[DRM_MODE_CONNECTOR_WRITEBACK] = "Writeback",
|
|
#ifdef DRM_MODE_CONNECTOR_SPI
|
|
[DRM_MODE_CONNECTOR_SPI] = "SPI",
|
|
#endif
|
|
#ifdef DRM_MODE_CONNECTOR_USB
|
|
[DRM_MODE_CONNECTOR_USB] = "USB",
|
|
#endif
|
|
};
|
|
|
|
|
|
struct vout_window_sys_t {
|
|
/* modeset information */
|
|
uint32_t crtc;
|
|
|
|
/* other generic stuff */
|
|
int drm_fd;
|
|
|
|
/*
|
|
* buffer information
|
|
*/
|
|
uint32_t width;
|
|
uint32_t height;
|
|
|
|
drmModeRes *modeRes;
|
|
drmModeModeInfo *mode;
|
|
drmModeConnector *conn;
|
|
uint32_t connector;
|
|
drmModeCrtc *saved_crtc;
|
|
|
|
uint32_t framebuffer;
|
|
uint32_t main_buffer;
|
|
};
|
|
|
|
static deviceRval FindCRTC(vlc_window_t *wnd, drmModeRes const *res,
|
|
drmModeConnector const *conn)
|
|
{
|
|
struct vout_window_sys_t *sys = wnd->sys;
|
|
drmModeEncoder *enc;
|
|
int i, j;
|
|
|
|
/*
|
|
* Try current encoder and CRTC combo
|
|
*/
|
|
if (conn->encoder_id) {
|
|
enc = drmModeGetEncoder(sys->drm_fd, conn->encoder_id);
|
|
if (enc) {
|
|
if (enc->crtc_id) {
|
|
msg_Dbg(wnd, "Got CRTC %d from current encoder", enc->crtc_id);
|
|
|
|
sys->crtc = enc->crtc_id;
|
|
drmModeFreeEncoder(enc);
|
|
return drvSuccess;
|
|
}
|
|
drmModeFreeEncoder(enc);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Iterate all available encoders to find CRTC
|
|
*/
|
|
for (i = 0; i < conn->count_encoders; i++) {
|
|
enc = drmModeGetEncoder(sys->drm_fd, conn->encoders[i]);
|
|
|
|
for (j = 0; enc && j < res->count_crtcs; ++j) {
|
|
if ((enc->possible_crtcs & (1 << j)) != 0 && res->crtcs[j]) {
|
|
sys->crtc = res->crtcs[j];
|
|
drmModeFreeEncoder(enc);
|
|
return drvSuccess;
|
|
}
|
|
}
|
|
drmModeFreeEncoder(enc);
|
|
}
|
|
|
|
msg_Err(wnd , "Cannot find CRTC for connector %d", conn->connector_id);
|
|
return drvTryNext;
|
|
}
|
|
|
|
|
|
static deviceRval SetupDevice(vlc_window_t *wnd, drmModeRes const *res,
|
|
drmModeConnector const *conn)
|
|
{
|
|
struct vout_window_sys_t *sys = wnd->sys;
|
|
deviceRval ret;
|
|
|
|
if (conn->connection != DRM_MODE_CONNECTED || conn->count_modes == 0)
|
|
return drvTryNext;
|
|
|
|
sys->width = conn->modes[0].hdisplay;
|
|
sys->height = conn->modes[0].vdisplay;
|
|
sys->mode = &conn->modes[0];
|
|
msg_Dbg(wnd, "Mode resolution for connector %u is %ux%u",
|
|
conn->connector_id, sys->width, sys->height);
|
|
|
|
ret = FindCRTC(wnd, res, conn);
|
|
if (ret != drvSuccess) {
|
|
msg_Dbg(wnd, "No valid CRTC for connector %d", conn->connector_id);
|
|
return ret;
|
|
}
|
|
return drvSuccess;
|
|
}
|
|
|
|
static void UpdateOutputs(vlc_window_t *wnd)
|
|
{
|
|
struct vout_window_sys_t *sys = wnd->sys;
|
|
drmModeRes *modeRes = sys->modeRes;
|
|
|
|
msg_Info(wnd, "Updating list of connectors:");
|
|
for (int c = 0; c < modeRes->count_connectors && sys->crtc == 0; c++)
|
|
{
|
|
drmModeConnector *conn =
|
|
drmModeGetConnector(sys->drm_fd, modeRes->connectors[c]);
|
|
if (conn == NULL)
|
|
continue;
|
|
|
|
if (conn->connector_type >= ARRAY_SIZE(connector_type_names) ||
|
|
conn->connection != DRM_MODE_CONNECTED)
|
|
{
|
|
drmModeFreeConnector(conn);
|
|
continue;
|
|
}
|
|
|
|
char name[32];
|
|
snprintf(name, sizeof name, "%s-%d",
|
|
connector_type_names[conn->connector_type],
|
|
conn->connector_type_id);
|
|
|
|
/* Iterate all available encoders to find CRTC */
|
|
for (int i = 0; i < conn->count_encoders; i++)
|
|
{
|
|
drmModeEncoder *enc =
|
|
drmModeGetEncoder(sys->drm_fd, conn->encoders[i]);
|
|
|
|
size_t crtc_index = sys->crtc;
|
|
if ((enc->possible_crtcs & (1 << crtc_index)) != 0) {
|
|
|
|
drmModeFreeEncoder(enc);
|
|
vlc_window_ReportOutputDevice(wnd, name, name);
|
|
break;
|
|
}
|
|
drmModeFreeEncoder(enc);
|
|
}
|
|
drmModeFreeConnector(conn);
|
|
}
|
|
}
|
|
|
|
static int WindowEnable(vlc_window_t *wnd, const vlc_window_cfg_t *cfg)
|
|
{
|
|
struct vout_window_sys_t *sys = wnd->sys;
|
|
(void)cfg;
|
|
drmModeRes *modeRes = sys->modeRes;
|
|
|
|
bool found_connector = false;
|
|
|
|
// TODO: use kms-connector as a list of connector for clones
|
|
char *connector_request = var_InheritString(wnd, "kms-connector");
|
|
msg_Info(wnd, "Looping over %d resources", modeRes->count_connectors);
|
|
for (int c = 0; c < modeRes->count_connectors && sys->crtc == 0; c++) {
|
|
|
|
drmModeConnector *conn =
|
|
drmModeGetConnector(sys->drm_fd, modeRes->connectors[c]);
|
|
if (conn == NULL)
|
|
continue;
|
|
|
|
if (conn->connector_type >= ARRAY_SIZE(connector_type_names))
|
|
{
|
|
drmModeFreeConnector(conn);
|
|
continue;
|
|
}
|
|
|
|
char name[32];
|
|
snprintf(name, sizeof name, "%s-%d",
|
|
connector_type_names[conn->connector_type],
|
|
conn->connector_type_id);
|
|
|
|
if (connector_request != NULL && strcmp(name, connector_request) != 0)
|
|
{
|
|
msg_Info(wnd, "connector %s skipped", name);
|
|
drmModeFreeConnector(conn);
|
|
continue;
|
|
}
|
|
|
|
msg_Info(wnd, "connector %d: %s", c, name);
|
|
|
|
found_connector = true;
|
|
|
|
int ret = SetupDevice(wnd, modeRes, conn);
|
|
if (ret != drvSuccess) {
|
|
if (ret != drvTryNext) {
|
|
msg_Err(wnd, "Cannot do setup for connector %s %u:%u",
|
|
name, c, modeRes->connectors[c]);
|
|
|
|
drmModeFreeConnector(conn);
|
|
drmModeFreeResources(modeRes);
|
|
free(connector_request);
|
|
return VLC_EGENERIC;
|
|
}
|
|
drmModeFreeConnector(conn);
|
|
found_connector = false;
|
|
msg_Dbg(wnd, " - Connector %d: could not setup device", c);
|
|
continue;
|
|
}
|
|
sys->connector = conn->connector_id;
|
|
sys->conn = conn;
|
|
break;
|
|
}
|
|
free(connector_request);
|
|
|
|
if (!found_connector)
|
|
{
|
|
msg_Warn(wnd, "Could not find a valid connector");
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
wnd->handle.crtc = sys->crtc;
|
|
/* Store current KMS state before modifying */
|
|
sys->saved_crtc = drmModeGetCrtc(sys->drm_fd, sys->crtc);
|
|
|
|
/*
|
|
* Create a new framebuffer to avoid compositing the planes into the saved
|
|
* framebuffer for the current CRTC.
|
|
*/
|
|
|
|
struct drm_mode_create_dumb request = {
|
|
.width = sys->width, .height = sys->height, .bpp = 32
|
|
};
|
|
|
|
int ret = drmIoctl(sys->drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &request);
|
|
if (ret != drvSuccess)
|
|
goto error_create_dumb;
|
|
|
|
uint32_t new_fb;
|
|
ret = drmModeAddFB(sys->drm_fd, sys->width, sys->height, 24, 32, request.pitch,
|
|
request.handle, &new_fb);
|
|
if (ret != drvSuccess)
|
|
goto error_add_fb;
|
|
|
|
ret = drmModeSetCrtc(sys->drm_fd, sys->crtc, new_fb, 0, 0, &sys->connector, 1, sys->mode);
|
|
if (ret != drvSuccess)
|
|
goto error_set_crtc;
|
|
|
|
sys->framebuffer = new_fb;
|
|
sys->main_buffer = request.handle;
|
|
|
|
vlc_window_ReportSize(wnd, sys->width, sys->height);
|
|
|
|
return VLC_SUCCESS;
|
|
|
|
error_set_crtc:
|
|
drmModeRmFB(sys->drm_fd, new_fb);
|
|
|
|
error_add_fb:
|
|
{
|
|
struct drm_mode_destroy_dumb destroy_request = {
|
|
.handle = request.handle
|
|
};
|
|
ret = drmIoctl(sys->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_request);
|
|
/* This must be a programmation error if we cannot destroy the resources
|
|
* we created. */
|
|
assert(ret == drvSuccess);
|
|
}
|
|
|
|
error_create_dumb:
|
|
if (sys->saved_crtc != NULL)
|
|
drmModeFreeCrtc(sys->saved_crtc);
|
|
sys->saved_crtc = NULL;
|
|
drmModeFreeConnector(sys->conn);
|
|
sys->conn = NULL;
|
|
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
static void WindowDisable(vlc_window_t *wnd)
|
|
{
|
|
struct vout_window_sys_t *sys = wnd->sys;
|
|
sys->crtc = 0;
|
|
|
|
/* Restore previous CRTC state */
|
|
if (sys->saved_crtc)
|
|
{
|
|
int ret = drmModeSetCrtc(sys->drm_fd,
|
|
sys->saved_crtc->crtc_id,
|
|
sys->saved_crtc->buffer_id,
|
|
sys->saved_crtc->x, sys->saved_crtc->y,
|
|
&sys->connector, 1,
|
|
&sys->saved_crtc->mode);
|
|
assert(ret == drvSuccess);
|
|
drmModeFreeCrtc(sys->saved_crtc);
|
|
}
|
|
sys->saved_crtc = NULL;
|
|
|
|
drmModeFreeConnector(sys->conn);
|
|
|
|
drmModeRmFB(sys->drm_fd, sys->framebuffer);
|
|
|
|
struct drm_mode_destroy_dumb destroy_request = {
|
|
.handle = sys->main_buffer,
|
|
};
|
|
int ret = drmIoctl(sys->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_request);
|
|
/* This must be a programmation error if we cannot destroy the resources
|
|
* we created. */
|
|
assert(ret == drvSuccess);
|
|
(void)ret;
|
|
}
|
|
|
|
static void WindowClose(vlc_window_t *wnd)
|
|
{
|
|
struct vout_window_sys_t *sys = wnd->sys;
|
|
drmModeFreeResources(sys->modeRes);
|
|
drmSetClientCap(sys->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0);
|
|
drmDropMaster(sys->drm_fd);
|
|
vlc_close(sys->drm_fd);
|
|
free(sys);
|
|
}
|
|
|
|
static const struct vlc_window_operations window_ops =
|
|
{
|
|
.destroy = WindowClose,
|
|
.enable = WindowEnable,
|
|
.disable = WindowDisable,
|
|
};
|
|
|
|
static int OpenWindow(vlc_window_t *wnd)
|
|
{
|
|
char *psz_device;
|
|
|
|
|
|
struct vout_window_sys_t *sys
|
|
= wnd->sys
|
|
= malloc(sizeof *sys);
|
|
if (sys == NULL)
|
|
return VLC_ENOMEM;
|
|
|
|
sys->crtc = 0;
|
|
|
|
/*
|
|
* Open framebuffer device
|
|
*/
|
|
psz_device = var_InheritString(wnd, KMS_DEVICE_VAR);
|
|
if (psz_device == NULL) {
|
|
msg_Err(wnd, "Don't know which DRM device to open");
|
|
goto error_end;
|
|
}
|
|
|
|
sys->drm_fd = vlc_open(psz_device, O_RDWR);
|
|
if (sys->drm_fd == -1) {
|
|
msg_Err(wnd, "cannot open %s", psz_device);
|
|
free(psz_device);
|
|
goto error_end;
|
|
}
|
|
|
|
drmVersionPtr version;
|
|
if ((version = drmGetVersion(sys->drm_fd)) != NULL)
|
|
{
|
|
const char *date = version->date ? version->date : "unknown";
|
|
const char *desc = version->desc ? version->desc : "unknown";
|
|
|
|
msg_Dbg(wnd, "Using DRM driver %s version %d.%d.%d (build %s): %s",
|
|
version->name, version->version_major, version->version_minor,
|
|
version->version_patchlevel, date, desc);
|
|
|
|
drmFreeVersion(version);
|
|
}
|
|
else
|
|
{
|
|
msg_Err(wnd, "device %s doesn't support DRM", psz_device);
|
|
free(psz_device);
|
|
goto error_drm;
|
|
}
|
|
free(psz_device);
|
|
|
|
drmSetClientCap(sys->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
|
|
|
|
sys->modeRes = drmModeGetResources(sys->drm_fd);
|
|
if (sys->modeRes == NULL) {
|
|
msg_Err(wnd, "Didn't get DRM resources");
|
|
drmSetClientCap(sys->drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 0);
|
|
goto error_drm;
|
|
}
|
|
|
|
wnd->ops = &window_ops;
|
|
wnd->type = VLC_WINDOW_TYPE_KMS;
|
|
wnd->display.drm_fd = sys->drm_fd;
|
|
/* Note: wnd->handle.crtc will be initialized later */
|
|
|
|
UpdateOutputs(wnd);
|
|
|
|
return VLC_SUCCESS;
|
|
error_drm:
|
|
drmDropMaster(sys->drm_fd);
|
|
vlc_close(sys->drm_fd);
|
|
|
|
error_end:
|
|
free(sys);
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* Module descriptor
|
|
*****************************************************************************/
|
|
vlc_module_begin ()
|
|
set_shortname("kms")
|
|
set_subcategory(SUBCAT_VIDEO_VOUT)
|
|
|
|
add_obsolete_string("kms") /* Since 4.0.0 */
|
|
add_loadfile(KMS_DEVICE_VAR, DRM_DIR_NAME "/" DRM_PRIMARY_MINOR_NAME "0",
|
|
DEVICE_TEXT, DEVICE_LONGTEXT)
|
|
add_string("kms-connector", "", KMS_CONNECTOR_TEXT, KMS_CONNECTOR_LONGTEXT)
|
|
|
|
set_description("Linux kernel mode setting window provider")
|
|
set_callback(OpenWindow)
|
|
set_capability("vout window", 9)
|
|
|
|
vlc_module_end ()
|
|
|