1038 lines
24 KiB
C
1038 lines
24 KiB
C
// SPDX-License-Identifier: LGPL-2.1-only
|
|
// Copyright (c) 2015 - 2024 DisplayLink (UK) Ltd.
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <libdrm/drm.h>
|
|
#ifndef __user
|
|
# define __user
|
|
#endif
|
|
#include "evdi_drm.h"
|
|
#include "evdi_lib.h"
|
|
#include <assert.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
|
|
// ********************* Private part **************************
|
|
|
|
#define MAX_DIRTS 16
|
|
#define EVDI_INVALID_DEVICE_INDEX -1
|
|
|
|
#define EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR 1
|
|
#define EVDI_MODULE_COMPATIBILITY_VERSION_MINOR 9
|
|
#define EVDI_MODULE_COMPATIBILITY_VERSION_PATCH 0
|
|
|
|
#define evdi_log(...) do { \
|
|
if (g_evdi_logging.function) { \
|
|
g_evdi_logging.function(g_evdi_logging.user_data, \
|
|
__VA_ARGS__); \
|
|
} else { \
|
|
printf("[libevdi] " __VA_ARGS__); \
|
|
printf("\n"); \
|
|
} \
|
|
} while (0)
|
|
|
|
struct evdi_logging g_evdi_logging = {
|
|
.function = NULL,
|
|
.user_data = NULL
|
|
};
|
|
|
|
struct evdi_frame_buffer_node {
|
|
struct evdi_buffer frame_buffer;
|
|
struct evdi_frame_buffer_node *next;
|
|
};
|
|
|
|
struct evdi_device_context {
|
|
int fd;
|
|
int bufferToUpdate;
|
|
struct evdi_frame_buffer_node *frameBuffersListHead;
|
|
int device_index;
|
|
};
|
|
|
|
#define EVDI_USAGE_LEN 64
|
|
static evdi_handle card_usage[EVDI_USAGE_LEN];
|
|
|
|
static int drm_ioctl(int fd, unsigned long request, void *arg)
|
|
{
|
|
int ret;
|
|
|
|
do {
|
|
ret = ioctl(fd, request, arg);
|
|
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int drm_auth_magic(int fd, drm_magic_t magic)
|
|
{
|
|
drm_auth_t auth;
|
|
|
|
memset(&auth, 0, sizeof(auth));
|
|
auth.magic = magic;
|
|
if (drm_ioctl(fd, DRM_IOCTL_AUTH_MAGIC, &auth))
|
|
return -errno;
|
|
return 0;
|
|
}
|
|
|
|
static int drm_is_master(int fd)
|
|
{
|
|
/* Detect master by attempting something that requires master.
|
|
*
|
|
* Authenticating magic tokens requires master and 0 is an
|
|
* internal kernel detail which we could use. Attempting this on
|
|
* a master fd would fail therefore fail with EINVAL because 0
|
|
* is invalid.
|
|
*
|
|
* A non-master fd will fail with EACCES, as the kernel checks
|
|
* for master before attempting to do anything else.
|
|
*
|
|
* Since we don't want to leak implementation details, use
|
|
* EACCES.
|
|
*/
|
|
return drm_auth_magic(fd, 0) != -EACCES;
|
|
}
|
|
|
|
static int do_ioctl(int fd, unsigned long request, void *data, const char *msg)
|
|
{
|
|
const int err = drm_ioctl(fd, request, data);
|
|
|
|
if (err < 0)
|
|
evdi_log("Ioctl %s error: %s", msg, strerror(errno));
|
|
return err;
|
|
}
|
|
|
|
static struct evdi_frame_buffer_node *findBuffer(evdi_handle context, int id)
|
|
{
|
|
struct evdi_frame_buffer_node *node = NULL;
|
|
|
|
assert(context);
|
|
|
|
for (node = context->frameBuffersListHead;
|
|
node != NULL;
|
|
node = (struct evdi_frame_buffer_node *)node->next) {
|
|
if (node->frame_buffer.id == id)
|
|
return node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void addFrameBuffer(evdi_handle context,
|
|
struct evdi_buffer const *frame_buffer)
|
|
{
|
|
for (struct evdi_frame_buffer_node **node = &context->frameBuffersListHead;
|
|
;
|
|
node = &(*node)->next) {
|
|
if (*node)
|
|
continue;
|
|
|
|
*node = calloc(1, sizeof(struct evdi_frame_buffer_node));
|
|
assert(node);
|
|
memcpy(*node, frame_buffer, sizeof(struct evdi_buffer));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @brief Removes all frame buffers matching the given id
|
|
* @param id of frame buffer to remove, NULL matches every buffer, thus all
|
|
* will be removed
|
|
*/
|
|
static void removeFrameBuffer(evdi_handle context, int const *id)
|
|
{
|
|
struct evdi_frame_buffer_node *current = NULL;
|
|
struct evdi_frame_buffer_node *next = NULL;
|
|
struct evdi_frame_buffer_node **prev = NULL;
|
|
|
|
current = context->frameBuffersListHead;
|
|
prev = &context->frameBuffersListHead;
|
|
while (current) {
|
|
next = current->next;
|
|
|
|
if (!id || current->frame_buffer.id == *id) {
|
|
free(current);
|
|
*prev = next;
|
|
} else {
|
|
prev = ¤t->next;
|
|
}
|
|
|
|
current = next;
|
|
}
|
|
}
|
|
|
|
static int is_evdi_compatible(int fd)
|
|
{
|
|
struct drm_version ver = { 0 };
|
|
|
|
evdi_log("LibEvdi version (%d.%d.%d)",
|
|
LIBEVDI_VERSION_MAJOR,
|
|
LIBEVDI_VERSION_MINOR,
|
|
LIBEVDI_VERSION_PATCH);
|
|
|
|
if (do_ioctl(fd, DRM_IOCTL_VERSION, &ver, "version") != 0)
|
|
return 0;
|
|
|
|
evdi_log("Evdi version (%d.%d.%d)",
|
|
ver.version_major,
|
|
ver.version_minor,
|
|
ver.version_patchlevel);
|
|
|
|
if (ver.version_major == EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR &&
|
|
ver.version_minor >= EVDI_MODULE_COMPATIBILITY_VERSION_MINOR)
|
|
return 1;
|
|
|
|
evdi_log("Doesn't match LibEvdi compatibility one (%d.%d.%d)",
|
|
EVDI_MODULE_COMPATIBILITY_VERSION_MAJOR,
|
|
EVDI_MODULE_COMPATIBILITY_VERSION_MINOR,
|
|
EVDI_MODULE_COMPATIBILITY_VERSION_PATCH);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int is_evdi(int fd)
|
|
{
|
|
char name[64] = { 0 };
|
|
char date[64] = { 0 };
|
|
char desc[64] = { 0 };
|
|
struct drm_version ver = {
|
|
.name_len = sizeof(name),
|
|
.name = name,
|
|
.date_len = sizeof(date),
|
|
.date = date,
|
|
.desc_len = sizeof(desc),
|
|
.desc = desc,
|
|
};
|
|
if (do_ioctl(fd, DRM_IOCTL_VERSION, &ver, "version") == 0
|
|
&& strcmp(name, "evdi") == 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int path_exists(const char *path)
|
|
{
|
|
struct stat buf;
|
|
|
|
return stat(path, &buf) == 0;
|
|
}
|
|
|
|
static int device_exists(int device)
|
|
{
|
|
char dev[PATH_MAX] = "";
|
|
|
|
snprintf(dev, PATH_MAX, "/dev/dri/card%d", device);
|
|
return path_exists(dev);
|
|
}
|
|
|
|
static int does_path_links_to(const char *link, const char *substr)
|
|
{
|
|
char real_path[PATH_MAX];
|
|
ssize_t r;
|
|
|
|
r = readlink(link, real_path, sizeof(real_path));
|
|
if (r < 0)
|
|
return 0;
|
|
real_path[r] = '\0';
|
|
|
|
return (strstr(real_path, substr) != NULL);
|
|
}
|
|
|
|
static int process_opened_device(const char *pid, const char *device_file_path)
|
|
{
|
|
char maps_path[PATH_MAX];
|
|
FILE *maps = NULL;
|
|
char line[BUFSIZ];
|
|
int result = 0;
|
|
|
|
snprintf(maps_path, PATH_MAX, "/proc/%s/maps", pid);
|
|
|
|
maps = fopen(maps_path, "r");
|
|
if (maps == NULL)
|
|
return 0;
|
|
|
|
while (fgets(line, BUFSIZ, maps)) {
|
|
if (strstr(line, device_file_path)) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(maps);
|
|
return result;
|
|
}
|
|
|
|
static int process_opened_files(const char *pid, const char *device_file_path)
|
|
{
|
|
char fd_path[PATH_MAX];
|
|
DIR *fd_dir;
|
|
struct dirent *fd_entry;
|
|
int result = 0;
|
|
|
|
snprintf(fd_path, PATH_MAX, "/proc/%s/fd", pid);
|
|
|
|
fd_dir = opendir(fd_path);
|
|
if (fd_dir == NULL)
|
|
return 0;
|
|
|
|
while ((fd_entry = readdir(fd_dir)) != NULL) {
|
|
char *d_name = fd_entry->d_name;
|
|
char path[PATH_MAX];
|
|
|
|
snprintf(path, PATH_MAX, "/proc/%s/fd/%s", pid, d_name);
|
|
|
|
if (does_path_links_to(path, device_file_path)) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(fd_dir);
|
|
return result;
|
|
}
|
|
|
|
static int device_has_master(const char *device_file_path)
|
|
{
|
|
pid_t myself = getpid();
|
|
DIR *proc_dir;
|
|
const struct dirent *proc_entry;
|
|
int result = 0;
|
|
|
|
proc_dir = opendir("/proc");
|
|
if (proc_dir == NULL)
|
|
return 0;
|
|
|
|
while ((proc_entry = readdir(proc_dir)) != NULL) {
|
|
const char *d_name = proc_entry->d_name;
|
|
|
|
if (d_name[0] < '0'
|
|
|| d_name[0] > '9'
|
|
|| myself == atoi(d_name)) {
|
|
continue;
|
|
}
|
|
|
|
if (process_opened_files(d_name, device_file_path)) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
|
|
if (process_opened_device(d_name, device_file_path)) {
|
|
result = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(proc_dir);
|
|
return result;
|
|
}
|
|
|
|
static void wait_for_master(const char *device_path)
|
|
{
|
|
const unsigned int TOTAL_WAIT_US = 5000000L;
|
|
const unsigned int SLEEP_INTERVAL_US = 100000L;
|
|
|
|
unsigned int cnt = TOTAL_WAIT_US / SLEEP_INTERVAL_US;
|
|
|
|
int has_master = 0;
|
|
|
|
while ((has_master = device_has_master(device_path)) == 0 && cnt--)
|
|
usleep(SLEEP_INTERVAL_US);
|
|
|
|
if (!has_master)
|
|
evdi_log("Wait for master timed out");
|
|
}
|
|
|
|
static int open_as_slave(const char *device_path)
|
|
{
|
|
int fd = 0;
|
|
int err = 0;
|
|
|
|
fd = open(device_path, O_RDWR);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
if (drm_is_master(fd)) {
|
|
evdi_log("Process has master on %s, err: %s",
|
|
device_path, strerror(errno));
|
|
err = drm_ioctl(fd, DRM_IOCTL_DROP_MASTER, NULL);
|
|
}
|
|
|
|
if (err < 0) {
|
|
evdi_log("Drop master on %s failed, err: %s",
|
|
device_path, strerror(errno));
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
if (drm_is_master(fd)) {
|
|
evdi_log("Drop master on %s failed, err: %s",
|
|
device_path, strerror(errno));
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
evdi_log("Opened %s as slave drm device", device_path);
|
|
return fd;
|
|
}
|
|
|
|
static int wait_for_device(const char *device_path)
|
|
{
|
|
const unsigned int TOTAL_WAIT_US = 5000000L;
|
|
const unsigned int SLEEP_INTERVAL_US = 100000L;
|
|
|
|
unsigned int cnt = TOTAL_WAIT_US / SLEEP_INTERVAL_US;
|
|
|
|
int fd = 0;
|
|
|
|
while ((fd = open_as_slave(device_path)) < 0 && cnt--)
|
|
usleep(SLEEP_INTERVAL_US);
|
|
|
|
if (fd < 0)
|
|
evdi_log("Failed to open a device: %s", strerror(errno));
|
|
return fd;
|
|
}
|
|
|
|
static int open_device(int device)
|
|
{
|
|
char dev[PATH_MAX] = "";
|
|
int fd = 0;
|
|
|
|
snprintf(dev, PATH_MAX, "/dev/dri/card%d", device);
|
|
|
|
if (Xorg_running())
|
|
wait_for_master(dev);
|
|
|
|
fd = wait_for_device(dev);
|
|
|
|
if (fd >= 0) {
|
|
const int err = drm_ioctl(fd, DRM_IOCTL_DROP_MASTER, NULL);
|
|
|
|
if (err == 0)
|
|
evdi_log("Dropped master on %s", dev);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
|
|
static int write_add_device(const char *buffer, size_t buffer_length)
|
|
{
|
|
FILE *add_devices = fopen("/sys/devices/evdi/add", "w");
|
|
int written = 0;
|
|
|
|
if (add_devices != NULL) {
|
|
written = fwrite(buffer,
|
|
1,
|
|
buffer_length,
|
|
add_devices);
|
|
fclose(add_devices);
|
|
}
|
|
|
|
return written;
|
|
}
|
|
|
|
static int get_drm_device_index(const char *evdi_sysfs_drm_dir)
|
|
{
|
|
struct dirent *fd_entry;
|
|
DIR *fd_dir;
|
|
int dev_index = EVDI_INVALID_DEVICE_INDEX;
|
|
|
|
fd_dir = opendir(evdi_sysfs_drm_dir);
|
|
if (fd_dir == NULL) {
|
|
evdi_log("Failed to open dir %s", evdi_sysfs_drm_dir);
|
|
return dev_index;
|
|
}
|
|
|
|
while ((fd_entry = readdir(fd_dir)) != NULL) {
|
|
if (strncmp(fd_entry->d_name, "card", 4) == 0)
|
|
dev_index = strtol(&fd_entry->d_name[4], NULL, 10);
|
|
}
|
|
closedir(fd_dir);
|
|
|
|
return dev_index;
|
|
}
|
|
|
|
static bool is_correct_parent_device(const char *dirname, size_t dirname_maxlen, const char *parent_device, size_t parent_length)
|
|
{
|
|
char link_path[PATH_MAX];
|
|
|
|
snprintf(link_path, MIN(PATH_MAX - 7, dirname_maxlen), "%s/device", dirname);
|
|
|
|
if (parent_device == NULL)
|
|
return access(link_path, F_OK) != 0;
|
|
|
|
char link_resolution[PATH_MAX];
|
|
const ssize_t link_resolution_len = readlink(link_path, link_resolution, PATH_MAX);
|
|
|
|
if (link_resolution_len == -1 || link_resolution_len == PATH_MAX)
|
|
return false;
|
|
|
|
link_resolution[link_resolution_len] = '\0';
|
|
char *parent_device_token = strrchr(link_resolution, '/');
|
|
|
|
if (parent_length < 2)
|
|
return false;
|
|
|
|
parent_device_token++;
|
|
size_t len = strnlen(parent_device_token, parent_length - (parent_device_token - parent_device));
|
|
bool is_same_device = parent_length == len && strncmp(parent_device_token, parent_device, len) == 0;
|
|
return is_same_device;
|
|
}
|
|
|
|
static int find_unused_card_for(const char *parent_device, size_t length)
|
|
{
|
|
char evdi_platform_root[] = "/sys/bus/platform/devices";
|
|
struct dirent *fd_entry;
|
|
DIR *fd_dir;
|
|
int device_index = EVDI_INVALID_DEVICE_INDEX;
|
|
|
|
fd_dir = opendir(evdi_platform_root);
|
|
if (fd_dir == NULL) {
|
|
evdi_log("Failed to open dir %s", evdi_platform_root);
|
|
return device_index;
|
|
}
|
|
|
|
while ((fd_entry = readdir(fd_dir)) != NULL) {
|
|
if (strncmp(fd_entry->d_name, "evdi", 4) != 0)
|
|
continue;
|
|
|
|
char evdi_path[PATH_MAX];
|
|
|
|
snprintf(evdi_path, PATH_MAX, "%s/%s", evdi_platform_root, fd_entry->d_name);
|
|
|
|
if (!is_correct_parent_device(evdi_path, PATH_MAX, parent_device, length))
|
|
continue;
|
|
|
|
char evdi_drm_path[PATH_MAX];
|
|
|
|
snprintf(evdi_drm_path, PATH_MAX - strnlen(evdi_path, PATH_MAX), "%s/drm", evdi_path);
|
|
int dev_index = get_drm_device_index(evdi_drm_path);
|
|
|
|
assert(dev_index < EVDI_USAGE_LEN && dev_index >= 0);
|
|
|
|
if (card_usage[dev_index] == EVDI_INVALID_HANDLE) {
|
|
device_index = dev_index;
|
|
break;
|
|
}
|
|
}
|
|
closedir(fd_dir);
|
|
|
|
return device_index;
|
|
}
|
|
|
|
static int get_generic_device(void)
|
|
{
|
|
int device_index = EVDI_INVALID_DEVICE_INDEX;
|
|
|
|
device_index = find_unused_card_for(NULL, 0);
|
|
if (device_index == EVDI_INVALID_DEVICE_INDEX) {
|
|
evdi_log("Creating card in /sys/devices/platform");
|
|
write_add_device("1", 1);
|
|
device_index = find_unused_card_for(NULL, 0);
|
|
}
|
|
|
|
return device_index;
|
|
}
|
|
|
|
static int get_device_attached_to_usb(const char *sysfs_parent_device, size_t length)
|
|
{
|
|
const size_t USB_OFFSET = 4;
|
|
int device_index = EVDI_INVALID_DEVICE_INDEX;
|
|
|
|
if (length < USB_OFFSET)
|
|
return EVDI_INVALID_DEVICE_INDEX;
|
|
const char *parent_device = &sysfs_parent_device[USB_OFFSET];
|
|
const size_t parent_length = length - USB_OFFSET;
|
|
|
|
device_index = find_unused_card_for(parent_device, parent_length);
|
|
if (device_index == EVDI_INVALID_DEVICE_INDEX) {
|
|
evdi_log("Creating card for usb device %s in /sys/bus/platform/devices", parent_device);
|
|
|
|
write_add_device(sysfs_parent_device, length);
|
|
device_index = find_unused_card_for(parent_device, parent_length);
|
|
}
|
|
|
|
return device_index;
|
|
}
|
|
|
|
|
|
// ********************* Public part **************************
|
|
|
|
evdi_handle evdi_open(int device)
|
|
{
|
|
int fd;
|
|
evdi_handle h = EVDI_INVALID_HANDLE;
|
|
|
|
fd = open_device(device);
|
|
if (fd > 0) {
|
|
if (is_evdi(fd) && is_evdi_compatible(fd)) {
|
|
h = calloc(1, sizeof(struct evdi_device_context));
|
|
if (h) {
|
|
h->fd = fd;
|
|
h->device_index = device;
|
|
card_usage[device] = h;
|
|
evdi_log("Using /dev/dri/card%d", device);
|
|
}
|
|
}
|
|
if (h == EVDI_INVALID_HANDLE)
|
|
close(fd);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static enum evdi_device_status evdi_device_to_platform(int device, char *path)
|
|
{
|
|
struct dirent *fd_entry;
|
|
DIR *fd_dir;
|
|
enum evdi_device_status status = UNRECOGNIZED;
|
|
char card_path[PATH_MAX];
|
|
|
|
if (!device_exists(device))
|
|
return NOT_PRESENT;
|
|
|
|
fd_dir = opendir("/sys/bus/platform/devices");
|
|
if (fd_dir == NULL) {
|
|
evdi_log("Failed to list platform devices");
|
|
return NOT_PRESENT;
|
|
}
|
|
|
|
while ((fd_entry = readdir(fd_dir)) != NULL) {
|
|
if (strncmp(fd_entry->d_name, "evdi", 4) != 0)
|
|
continue;
|
|
|
|
snprintf(path, PATH_MAX,
|
|
"/sys/bus/platform/devices/%s", fd_entry->d_name);
|
|
snprintf(card_path, PATH_MAX, "%s/drm/card%d", path, device);
|
|
if (path_exists(card_path)) {
|
|
status = AVAILABLE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(fd_dir);
|
|
return status;
|
|
}
|
|
|
|
enum evdi_device_status evdi_check_device(int device)
|
|
{
|
|
char path[PATH_MAX];
|
|
|
|
return evdi_device_to_platform(device, path);
|
|
}
|
|
|
|
int evdi_add_device(void)
|
|
{
|
|
return write_add_device("1", 1);
|
|
}
|
|
|
|
// deprecated, use evdi_open_attached_to_fixed
|
|
evdi_handle evdi_open_attached_to(const char *sysfs_parent_device)
|
|
{
|
|
return evdi_open_attached_to_fixed(sysfs_parent_device, strlen(sysfs_parent_device));
|
|
}
|
|
|
|
evdi_handle evdi_open_attached_to_fixed(const char *sysfs_parent_device, size_t length)
|
|
{
|
|
int device_index = EVDI_INVALID_DEVICE_INDEX;
|
|
|
|
if (sysfs_parent_device == NULL)
|
|
device_index = get_generic_device();
|
|
|
|
if (sysfs_parent_device != NULL && length > 4 && strncmp(sysfs_parent_device, "usb:", 4) == 0)
|
|
device_index = get_device_attached_to_usb(sysfs_parent_device, length);
|
|
|
|
if (device_index >= 0 && device_index < EVDI_USAGE_LEN) {
|
|
evdi_handle handle = evdi_open(device_index);
|
|
return handle;
|
|
}
|
|
|
|
return EVDI_INVALID_HANDLE;
|
|
}
|
|
|
|
|
|
void evdi_close(evdi_handle handle)
|
|
{
|
|
if (handle != EVDI_INVALID_HANDLE) {
|
|
close(handle->fd);
|
|
free(handle);
|
|
for (int device_index = 0; device_index < EVDI_USAGE_LEN; device_index++) {
|
|
if (card_usage[device_index] == handle) {
|
|
card_usage[device_index] = EVDI_INVALID_HANDLE;
|
|
evdi_log("Marking /dev/dri/card%d as unused", device_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void evdi_connect(evdi_handle handle,
|
|
const unsigned char *edid,
|
|
const unsigned int edid_length,
|
|
const uint32_t sku_area_limit)
|
|
{
|
|
evdi_connect2(handle, edid, edid_length, sku_area_limit, sku_area_limit*60);
|
|
}
|
|
|
|
void evdi_connect2(evdi_handle handle,
|
|
const unsigned char *edid,
|
|
const unsigned int edid_length,
|
|
const uint32_t pixel_area_limit,
|
|
const uint32_t pixel_per_second_limit)
|
|
{
|
|
struct drm_evdi_connect cmd = {
|
|
.connected = 1,
|
|
.dev_index = handle->device_index,
|
|
.edid = edid,
|
|
.edid_length = edid_length,
|
|
.pixel_area_limit = pixel_area_limit,
|
|
.pixel_per_second_limit = pixel_per_second_limit,
|
|
};
|
|
|
|
do_ioctl(handle->fd, DRM_IOCTL_EVDI_CONNECT, &cmd, "connect");
|
|
}
|
|
|
|
void evdi_disconnect(evdi_handle handle)
|
|
{
|
|
struct drm_evdi_connect cmd = { 0, 0, 0, 0, 0, 0 };
|
|
|
|
do_ioctl(handle->fd, DRM_IOCTL_EVDI_CONNECT, &cmd, "disconnect");
|
|
}
|
|
|
|
void evdi_enable_cursor_events(evdi_handle handle, bool enable)
|
|
{
|
|
struct drm_evdi_enable_cursor_events cmd = {
|
|
.enable = enable,
|
|
};
|
|
|
|
evdi_log("%s cursor events on /dev/dri/card%d",
|
|
(enable ? "Enabling" : "Disabling"),
|
|
handle->device_index);
|
|
|
|
do_ioctl(handle->fd, DRM_IOCTL_EVDI_ENABLE_CURSOR_EVENTS, &cmd, "enable cursor events");
|
|
}
|
|
|
|
void evdi_grab_pixels(evdi_handle handle,
|
|
struct evdi_rect *rects,
|
|
int *num_rects)
|
|
{
|
|
struct drm_clip_rect kernelDirts[MAX_DIRTS] = { { 0, 0, 0, 0 } };
|
|
struct evdi_frame_buffer_node *destinationNode = NULL;
|
|
struct evdi_buffer *destinationBuffer = NULL;
|
|
|
|
destinationNode = findBuffer(handle, handle->bufferToUpdate);
|
|
|
|
if (!destinationNode) {
|
|
evdi_log("Buffer %d not found. Not grabbing.",
|
|
handle->bufferToUpdate);
|
|
*num_rects = 0;
|
|
return;
|
|
}
|
|
|
|
destinationBuffer = &destinationNode->frame_buffer;
|
|
|
|
struct drm_evdi_grabpix grab = {
|
|
EVDI_GRABPIX_MODE_DIRTY,
|
|
destinationBuffer->width,
|
|
destinationBuffer->height,
|
|
destinationBuffer->stride,
|
|
destinationBuffer->buffer,
|
|
MAX_DIRTS,
|
|
kernelDirts
|
|
};
|
|
|
|
if (do_ioctl(
|
|
handle->fd, DRM_IOCTL_EVDI_GRABPIX, &grab, "grabpix") == 0) {
|
|
/*
|
|
* Buffer was filled by ioctl
|
|
* now we only have to fill the dirty rects
|
|
*/
|
|
int r = 0;
|
|
|
|
for (; r < grab.num_rects; ++r) {
|
|
rects[r].x1 = kernelDirts[r].x1;
|
|
rects[r].y1 = kernelDirts[r].y1;
|
|
rects[r].x2 = kernelDirts[r].x2;
|
|
rects[r].y2 = kernelDirts[r].y2;
|
|
}
|
|
|
|
*num_rects = grab.num_rects;
|
|
} else {
|
|
int id = destinationBuffer->id;
|
|
|
|
evdi_log("Grabbing pixels for buffer %d failed.", id);
|
|
evdi_log("Ignore if caused by change of mode.");
|
|
*num_rects = 0;
|
|
}
|
|
}
|
|
|
|
void evdi_register_buffer(evdi_handle handle, struct evdi_buffer buffer)
|
|
{
|
|
assert(handle);
|
|
assert(!findBuffer(handle, buffer.id));
|
|
|
|
addFrameBuffer(handle, &buffer);
|
|
}
|
|
|
|
void evdi_unregister_buffer(evdi_handle handle, int bufferId)
|
|
{
|
|
struct evdi_buffer *bufferToRemove = NULL;
|
|
|
|
assert(handle);
|
|
|
|
bufferToRemove = &findBuffer(handle, bufferId)->frame_buffer;
|
|
assert(bufferToRemove);
|
|
|
|
removeFrameBuffer(handle, &bufferId);
|
|
}
|
|
|
|
bool evdi_request_update(evdi_handle handle, int bufferId)
|
|
{
|
|
assert(handle);
|
|
handle->bufferToUpdate = bufferId;
|
|
{
|
|
struct drm_evdi_request_update cmd;
|
|
const int requestResult = do_ioctl(
|
|
handle->fd,
|
|
DRM_IOCTL_EVDI_REQUEST_UPDATE,
|
|
&cmd,
|
|
"request_update");
|
|
const bool grabImmediately = requestResult == 1;
|
|
|
|
return grabImmediately;
|
|
}
|
|
}
|
|
|
|
void evdi_ddcci_response(evdi_handle handle, const unsigned char *buffer,
|
|
const uint32_t buffer_length,
|
|
const bool result)
|
|
{
|
|
struct drm_evdi_ddcci_response cmd = {
|
|
.buffer = buffer,
|
|
.buffer_length = buffer_length,
|
|
.result = result,
|
|
};
|
|
|
|
do_ioctl(handle->fd, DRM_IOCTL_EVDI_DDCCI_RESPONSE, &cmd,
|
|
"ddcci_response");
|
|
}
|
|
|
|
static struct evdi_mode to_evdi_mode(struct drm_evdi_event_mode_changed *event)
|
|
{
|
|
struct evdi_mode mode;
|
|
|
|
mode.width = event->hdisplay;
|
|
mode.height = event->vdisplay;
|
|
mode.refresh_rate = event->vrefresh;
|
|
mode.bits_per_pixel = event->bits_per_pixel;
|
|
mode.pixel_format = event->pixel_format;
|
|
|
|
return mode;
|
|
}
|
|
|
|
static int evdi_get_dumb_offset(evdi_handle ehandle, uint32_t handle, uint64_t *offset)
|
|
{
|
|
struct drm_mode_map_dumb map_dumb = { 0 };
|
|
int ret = 0;
|
|
|
|
map_dumb.handle = handle;
|
|
ret = do_ioctl(ehandle->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb,
|
|
"DRM_MODE_MAP_DUMB");
|
|
*offset = map_dumb.offset;
|
|
return ret;
|
|
}
|
|
|
|
static struct evdi_cursor_set to_evdi_cursor_set(
|
|
evdi_handle handle, struct drm_evdi_event_cursor_set *event)
|
|
{
|
|
struct evdi_cursor_set cursor_set;
|
|
|
|
cursor_set.hot_x = event->hot_x;
|
|
cursor_set.hot_y = event->hot_y;
|
|
cursor_set.width = event->width;
|
|
cursor_set.height = event->height;
|
|
cursor_set.enabled = event->enabled;
|
|
cursor_set.buffer_length = 0;
|
|
cursor_set.buffer = NULL;
|
|
cursor_set.pixel_format = event->pixel_format;
|
|
cursor_set.stride = event->stride;
|
|
|
|
if (event->enabled) {
|
|
size_t size = event->buffer_length;
|
|
uint64_t offset = 0;
|
|
|
|
if (evdi_get_dumb_offset(handle, event->buffer_handle, &offset)) {
|
|
evdi_log("Error: DRM_IOCTL_MODE_MAP_DUMB failed with error: %s", strerror(errno));
|
|
return cursor_set;
|
|
}
|
|
|
|
void *ptr = mmap(0, size, PROT_READ,
|
|
MAP_SHARED, handle->fd, offset);
|
|
|
|
if (ptr != MAP_FAILED) {
|
|
cursor_set.buffer = malloc(size);
|
|
memcpy(cursor_set.buffer, ptr, size);
|
|
munmap(ptr, size);
|
|
cursor_set.buffer_length = size;
|
|
} else {
|
|
evdi_log("Error: mmap failed with error: %s", strerror(errno));
|
|
}
|
|
}
|
|
|
|
return cursor_set;
|
|
}
|
|
|
|
static struct evdi_cursor_move to_evdi_cursor_move(
|
|
struct drm_evdi_event_cursor_move *event)
|
|
{
|
|
struct evdi_cursor_move cursor_move;
|
|
|
|
cursor_move.x = event->x;
|
|
cursor_move.y = event->y;
|
|
|
|
return cursor_move;
|
|
}
|
|
|
|
static struct evdi_ddcci_data to_evdi_ddcci_data(
|
|
struct drm_evdi_event_ddcci_data *event)
|
|
{
|
|
struct evdi_ddcci_data ddcci_data;
|
|
|
|
ddcci_data.address = event->address;
|
|
ddcci_data.flags = event->flags;
|
|
ddcci_data.buffer = &event->buffer[0];
|
|
ddcci_data.buffer_length = event->buffer_length;
|
|
|
|
return ddcci_data;
|
|
}
|
|
|
|
static void evdi_handle_event(evdi_handle handle,
|
|
struct evdi_event_context *evtctx,
|
|
struct drm_event *e)
|
|
{
|
|
switch (e->type) {
|
|
case DRM_EVDI_EVENT_UPDATE_READY:
|
|
if (evtctx->update_ready_handler)
|
|
evtctx->update_ready_handler(handle->bufferToUpdate,
|
|
evtctx->user_data);
|
|
break;
|
|
|
|
case DRM_EVDI_EVENT_DPMS:
|
|
if (evtctx->dpms_handler) {
|
|
struct drm_evdi_event_dpms *event =
|
|
(struct drm_evdi_event_dpms *) e;
|
|
|
|
evtctx->dpms_handler(event->mode,
|
|
evtctx->user_data);
|
|
}
|
|
break;
|
|
|
|
case DRM_EVDI_EVENT_MODE_CHANGED:
|
|
if (evtctx->mode_changed_handler) {
|
|
struct drm_evdi_event_mode_changed *event =
|
|
(struct drm_evdi_event_mode_changed *) e;
|
|
|
|
evtctx->mode_changed_handler(to_evdi_mode(event),
|
|
evtctx->user_data);
|
|
}
|
|
break;
|
|
|
|
case DRM_EVDI_EVENT_CRTC_STATE:
|
|
if (evtctx->crtc_state_handler) {
|
|
struct drm_evdi_event_crtc_state *event =
|
|
(struct drm_evdi_event_crtc_state *) e;
|
|
|
|
evtctx->crtc_state_handler(event->state,
|
|
evtctx->user_data);
|
|
}
|
|
break;
|
|
|
|
case DRM_EVDI_EVENT_CURSOR_SET:
|
|
if (evtctx->cursor_set_handler) {
|
|
struct drm_evdi_event_cursor_set *event =
|
|
(struct drm_evdi_event_cursor_set *) e;
|
|
struct evdi_cursor_set cursor_set = to_evdi_cursor_set(handle, event);
|
|
|
|
if (cursor_set.enabled && cursor_set.buffer == NULL) {
|
|
evdi_log("Error: Cursor buffer is null!");
|
|
evdi_log("Disabling cursor events");
|
|
evdi_enable_cursor_events(handle, false);
|
|
cursor_set.enabled = false;
|
|
cursor_set.buffer_length = 0;
|
|
}
|
|
evtctx->cursor_set_handler(cursor_set, evtctx->user_data);
|
|
}
|
|
break;
|
|
|
|
case DRM_EVDI_EVENT_CURSOR_MOVE:
|
|
if (evtctx->cursor_move_handler) {
|
|
struct drm_evdi_event_cursor_move *event =
|
|
(struct drm_evdi_event_cursor_move *) e;
|
|
|
|
evtctx->cursor_move_handler(to_evdi_cursor_move(event),
|
|
evtctx->user_data);
|
|
}
|
|
break;
|
|
|
|
case DRM_EVDI_EVENT_DDCCI_DATA:
|
|
if (evtctx->ddcci_data_handler) {
|
|
struct drm_evdi_event_ddcci_data *event =
|
|
(struct drm_evdi_event_ddcci_data *) e;
|
|
|
|
evtctx->ddcci_data_handler(to_evdi_ddcci_data(event),
|
|
evtctx->user_data);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
evdi_log("Warning: Unhandled event");
|
|
}
|
|
}
|
|
|
|
void evdi_handle_events(evdi_handle handle, struct evdi_event_context *evtctx)
|
|
{
|
|
char buffer[1024];
|
|
int i = 0;
|
|
|
|
int bytesRead = read(handle->fd, buffer, sizeof(buffer));
|
|
|
|
if (!evtctx) {
|
|
evdi_log("Error: Event context is null!");
|
|
return;
|
|
}
|
|
|
|
while (i < bytesRead) {
|
|
struct drm_event *e = (struct drm_event *) &buffer[i];
|
|
|
|
evdi_handle_event(handle, evtctx, e);
|
|
|
|
i += e->length;
|
|
}
|
|
}
|
|
|
|
evdi_selectable evdi_get_event_ready(evdi_handle handle)
|
|
{
|
|
return handle->fd;
|
|
}
|
|
|
|
void evdi_get_lib_version(struct evdi_lib_version *version)
|
|
{
|
|
if (version != NULL) {
|
|
version->version_major = LIBEVDI_VERSION_MAJOR;
|
|
version->version_minor = LIBEVDI_VERSION_MINOR;
|
|
version->version_patchlevel = LIBEVDI_VERSION_PATCH;
|
|
}
|
|
}
|
|
|
|
void evdi_set_logging(struct evdi_logging evdi_logging)
|
|
{
|
|
g_evdi_logging = evdi_logging;
|
|
}
|