Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
#
# Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
#
# This file is subject to the terms and conditions of the GNU General Public
# License v2. See the file COPYING in the main directory of this archive for
# more details.
#
config DRM_EVDI
tristate "Extensible Virtual Display Interface"
depends on DRM
select DRM_KMS_HELPER
help
This is a KMS interface driver allowing user-space programs to
register a virtual display (that imitates physical monitor) and
retrieve contents (as a frame buffer) that system renders on it.
Say M/Y to add support for these devices via DRM/KMS interfaces.

View File

@@ -0,0 +1,13 @@
#
# Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
#
# This file is subject to the terms and conditions of the GNU General Public
# License v2. See the file COPYING in the main directory of this archive for
# more details.
#
ccflags-y := -Iinclude/drm
evdi-y := evdi_drv.o evdi_modeset.o evdi_connector.o evdi_encoder.o evdi_main.o evdi_fb.o evdi_gem.o evdi_stats.o evdi_painter.o evdi_debug.o evdi_cursor.o
obj-$(CONFIG_DRM_EVDI) := evdi.o

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2017 DisplayLink (UK) Ltd.
*
* Based on parts on udlfb.c:
* Copyright (C) 2009 its respective authors
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_edid.h>
#include <drm/drm_crtc_helper.h>
#include "evdi_drv.h"
/*
* dummy connector to just get EDID,
* all EVDI appear to have a DVI-D
*/
static int evdi_get_modes(struct drm_connector *connector)
{
struct evdi_device *evdi = connector->dev->dev_private;
struct edid *edid = NULL;
int ret = 0;
edid = (struct edid *)evdi_painter_get_edid_copy(evdi);
if (!edid) {
drm_mode_connector_update_edid_property(connector, NULL);
return 0;
}
ret = drm_mode_connector_update_edid_property(connector, edid);
if (!ret)
ret = drm_add_edid_modes(connector, edid);
else
EVDI_ERROR("Failed to set edid modes! error: %d", ret);
kfree(edid);
return ret;
}
static int evdi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct evdi_device *evdi = connector->dev->dev_private;
uint32_t mode_area = mode->hdisplay * mode->vdisplay;
if (evdi->sku_area_limit == 0)
return MODE_OK;
if (mode_area > evdi->sku_area_limit) {
EVDI_WARN("Mode %dx%d@%d rejected\n",
mode->hdisplay,
mode->vdisplay,
drm_mode_vrefresh(mode));
return MODE_BAD;
}
return MODE_OK;
}
static enum drm_connector_status
evdi_detect(struct drm_connector *connector, __always_unused bool force)
{
struct evdi_device *evdi = connector->dev->dev_private;
EVDI_CHECKPT();
if (evdi_painter_is_connected(evdi)) {
EVDI_DEBUG("(dev=%d) poll connector state: connected\n",
evdi->dev_index);
return connector_status_connected;
}
EVDI_DEBUG("(dev=%d) poll connector state: disconnected\n",
evdi->dev_index);
return connector_status_disconnected;
}
static struct drm_encoder *evdi_best_single_encoder(struct drm_connector
*connector)
{
int enc_id = connector->encoder_ids[0];
struct drm_mode_object *obj;
struct drm_encoder *encoder;
obj =
drm_mode_object_find(connector->dev, enc_id,
DRM_MODE_OBJECT_ENCODER);
if (!obj)
return NULL;
encoder = obj_to_encoder(obj);
return encoder;
}
static int evdi_connector_set_property(
__always_unused struct drm_connector *connector,
__always_unused struct drm_property *property,
__always_unused uint64_t val)
{
return 0;
}
static void evdi_connector_destroy(struct drm_connector *connector)
{
drm_sysfs_connector_remove(connector);
drm_connector_cleanup(connector);
kfree(connector);
}
static struct drm_connector_helper_funcs evdi_connector_helper_funcs = {
.get_modes = evdi_get_modes,
.mode_valid = evdi_mode_valid,
.best_encoder = evdi_best_single_encoder,
};
static const struct drm_connector_funcs evdi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = evdi_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = evdi_connector_destroy,
.set_property = evdi_connector_set_property,
};
int evdi_connector_init(struct drm_device *dev, struct drm_encoder *encoder)
{
struct drm_connector *connector;
connector = kzalloc(sizeof(struct drm_connector), GFP_KERNEL);
if (!connector)
return -ENOMEM;
/* TODO: Initialize connector with actual connector type */
drm_connector_init(dev, connector, &evdi_connector_funcs,
DRM_MODE_CONNECTOR_DVII);
drm_connector_helper_add(connector, &evdi_connector_helper_funcs);
connector->polled = DRM_CONNECTOR_POLL_HPD;
drm_sysfs_connector_add(connector);
drm_mode_connector_attach_encoder(connector, encoder);
drm_object_attach_property(&connector->base,
dev->mode_config.dirty_info_property, 1);
return 0;
}

View File

@@ -0,0 +1,281 @@
/*
* evdi_cursor.c
*
* Copyright (c) 2016 The Chromium OS Authors
* Copyright (c) 2016 - 2017 DisplayLink (UK) Ltd.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <linux/compiler.h>
#include <linux/mutex.h>
#include "evdi_cursor.h"
#include "evdi_drv.h"
/*
* EVDI drm cursor private structure.
*/
struct evdi_cursor {
bool enabled;
int32_t x;
int32_t y;
uint32_t width;
uint32_t height;
int32_t hot_x;
int32_t hot_y;
uint32_t pixel_format;
uint32_t stride;
struct evdi_gem_object *obj;
struct mutex lock;
};
static void evdi_cursor_set_gem(struct evdi_cursor *cursor,
struct evdi_gem_object *obj)
{
if (obj)
drm_gem_object_reference(&obj->base);
if (cursor->obj)
drm_gem_object_unreference_unlocked(&cursor->obj->base);
cursor->obj = obj;
}
struct evdi_gem_object *evdi_cursor_gem(struct evdi_cursor *cursor)
{
return cursor->obj;
}
int evdi_cursor_init(struct evdi_cursor **cursor)
{
if (WARN_ON(*cursor))
return -EINVAL;
*cursor = kzalloc(sizeof(struct evdi_cursor), GFP_KERNEL);
if (*cursor) {
mutex_init(&(*cursor)->lock);
return 0;
} else {
return -ENOMEM;
}
}
void evdi_cursor_lock(struct evdi_cursor *cursor)
{
mutex_lock(&cursor->lock);
}
void evdi_cursor_unlock(struct evdi_cursor *cursor)
{
mutex_unlock(&cursor->lock);
}
void evdi_cursor_free(struct evdi_cursor *cursor)
{
if (WARN_ON(!cursor))
return;
evdi_cursor_set_gem(cursor, NULL);
kfree(cursor);
}
bool evdi_cursor_enabled(struct evdi_cursor *cursor)
{
return cursor->enabled;
}
void evdi_cursor_enable(struct evdi_cursor *cursor, bool enable)
{
evdi_cursor_lock(cursor);
cursor->enabled = enable;
if (!enable)
evdi_cursor_set_gem(cursor, NULL);
evdi_cursor_unlock(cursor);
}
void evdi_cursor_set(struct evdi_cursor *cursor,
struct evdi_gem_object *obj,
uint32_t width, uint32_t height,
int32_t hot_x, int32_t hot_y,
uint32_t pixel_format, uint32_t stride)
{
int err = 0;
evdi_cursor_lock(cursor);
if (obj && !obj->vmapping)
err = evdi_gem_vmap(obj);
if (err != 0) {
EVDI_ERROR("Failed to map cursor.\n");
obj = NULL;
}
cursor->enabled = obj != NULL;
cursor->width = width;
cursor->height = height;
cursor->hot_x = hot_x;
cursor->hot_y = hot_y;
cursor->pixel_format = pixel_format;
cursor->stride = stride;
evdi_cursor_set_gem(cursor, obj);
evdi_cursor_unlock(cursor);
}
void evdi_cursor_move(struct evdi_cursor *cursor, int32_t x, int32_t y)
{
evdi_cursor_lock(cursor);
cursor->x = x;
cursor->y = y;
evdi_cursor_unlock(cursor);
}
static inline uint32_t blend_component(uint32_t pixel,
uint32_t blend,
uint32_t alpha)
{
uint32_t pre_blend = (pixel * (255 - alpha) + blend * alpha);
return (pre_blend + ((pre_blend + 1) << 8)) >> 16;
}
static inline uint32_t blend_alpha(const uint32_t pixel_val32,
uint32_t blend_val32)
{
uint32_t alpha = (blend_val32 >> 24);
return blend_component(pixel_val32 & 0xff,
blend_val32 & 0xff, alpha) |
blend_component((pixel_val32 & 0xff00) >> 8,
(blend_val32 & 0xff00) >> 8, alpha) << 8 |
blend_component((pixel_val32 & 0xff0000) >> 16,
(blend_val32 & 0xff0000) >> 16, alpha) << 16;
}
static int evdi_cursor_compose_pixel(char __user *buffer,
int const cursor_value,
int const fb_value,
int cmd_offset)
{
int const composed_value = blend_alpha(fb_value, cursor_value);
return copy_to_user(buffer + cmd_offset, &composed_value, 4);
}
int evdi_cursor_compose_and_copy(struct evdi_cursor *cursor,
struct evdi_framebuffer *ufb,
char __user *buffer,
int buf_byte_stride)
{
int x, y;
struct drm_framebuffer *fb = &ufb->base;
const int h_cursor_w = cursor->width >> 1;
const int h_cursor_h = cursor->height >> 1;
uint32_t *cursor_buffer = NULL;
uint32_t bytespp = 0;
if (!cursor->enabled)
return 0;
if (!cursor->obj)
return -EINVAL;
if (!cursor->obj->vmapping)
return -EINVAL;
bytespp = evdi_fb_get_bpp(cursor->pixel_format);
bytespp = DIV_ROUND_UP(bytespp, 8);
if (bytespp != 4) {
EVDI_ERROR("Unsupported cursor format bpp=%u\n", bytespp);
return -EINVAL;
}
if (cursor->width * cursor->height * bytespp >
cursor->obj->base.size){
EVDI_ERROR("Wrong cursor size\n");
return -EINVAL;
}
cursor_buffer = (uint32_t *)cursor->obj->vmapping;
for (y = -h_cursor_h; y < h_cursor_h; ++y) {
for (x = -h_cursor_w; x < h_cursor_w; ++x) {
uint32_t curs_val;
int *fbsrc;
int fb_value;
int cmd_offset;
int cursor_pix;
int const mouse_pix_x = cursor->x + x + h_cursor_w;
int const mouse_pix_y = cursor->y + y + h_cursor_h;
bool const is_pix_sane =
mouse_pix_x >= 0 &&
mouse_pix_y >= 0 &&
mouse_pix_x < fb->width &&
mouse_pix_y < fb->height;
if (!is_pix_sane)
continue;
cursor_pix = h_cursor_w+x +
(h_cursor_h+y)*cursor->width;
curs_val = le32_to_cpu(cursor_buffer[cursor_pix]);
fbsrc = (int *)ufb->obj->vmapping;
fb_value = *(fbsrc + ((fb->pitches[0]>>2) *
mouse_pix_y + mouse_pix_x));
cmd_offset = (buf_byte_stride * mouse_pix_y) +
(mouse_pix_x * bytespp);
if (evdi_cursor_compose_pixel(buffer,
curs_val,
fb_value,
cmd_offset)) {
EVDI_ERROR("Failed to compose cursor pixel\n");
return -EFAULT;
}
}
}
return 0;
}
void evdi_cursor_position(struct evdi_cursor *cursor, int32_t *x, int32_t *y)
{
*x = cursor->x;
*y = cursor->y;
}
void evdi_cursor_hotpoint(struct evdi_cursor *cursor,
int32_t *hot_x, int32_t *hot_y)
{
*hot_x = cursor->hot_x;
*hot_y = cursor->hot_y;
}
void evdi_cursor_size(struct evdi_cursor *cursor,
uint32_t *width, uint32_t *height)
{
*width = cursor->width;
*height = cursor->height;
}
void evdi_cursor_format(struct evdi_cursor *cursor, uint32_t *format)
{
*format = cursor->pixel_format;
}
void evdi_cursor_stride(struct evdi_cursor *cursor, uint32_t *stride)
{
*stride = cursor->stride;
}

View File

@@ -0,0 +1,58 @@
/*
* evdi_cursor.h
*
* Copyright (c) 2016 The Chromium OS Authors
* Copyright (c) 2016 - 2017 DisplayLink (UK) Ltd.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _EVDI_CURSOR_H_
#define _EVDI_CURSOR_H_
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
struct evdi_cursor;
struct evdi_framebuffer;
struct evdi_gem_object;
int evdi_cursor_init(struct evdi_cursor **cursor);
void evdi_cursor_free(struct evdi_cursor *cursor);
void evdi_cursor_lock(struct evdi_cursor *cursor);
void evdi_cursor_unlock(struct evdi_cursor *cursor);
bool evdi_cursor_enabled(struct evdi_cursor *cursor);
void evdi_cursor_enable(struct evdi_cursor *cursor, bool enabled);
void evdi_cursor_set(struct evdi_cursor *cursor,
struct evdi_gem_object *obj,
uint32_t width, uint32_t height,
int32_t hot_x, int32_t hot_y,
uint32_t pixel_format, uint32_t stride);
void evdi_cursor_move(struct evdi_cursor *cursor, int32_t x, int32_t y);
void evdi_cursor_position(struct evdi_cursor *cursor, int32_t *x, int32_t *y);
void evdi_cursor_hotpoint(struct evdi_cursor *cursor,
int32_t *hot_x, int32_t *hot_y);
void evdi_cursor_size(struct evdi_cursor *cursor,
uint32_t *width, uint32_t *height);
void evdi_cursor_format(struct evdi_cursor *cursor, uint32_t *format);
void evdi_cursor_stride(struct evdi_cursor *cursor, uint32_t *stride);
struct evdi_gem_object *evdi_cursor_gem(struct evdi_cursor *cursor);
int evdi_cursor_compose_and_copy(struct evdi_cursor *cursor,
struct evdi_framebuffer *ufb,
char __user *buffer,
int buf_byte_stride);
#endif

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include "evdi_debug.h"
unsigned int evdi_loglevel = EVDI_LOGLEVEL_DEBUG;
module_param_named(initial_loglevel, evdi_loglevel, int, 0400);
MODULE_PARM_DESC(initial_loglevel, "Initial log level");
void evdi_log_process(void)
{
int task_pid = (int)task_pid_nr(current);
char task_comm[TASK_COMM_LEN] = { 0 };
get_task_comm(task_comm, current);
if (current->group_leader) {
char process_comm[TASK_COMM_LEN] = { 0 };
get_task_comm(process_comm, current->group_leader);
EVDI_INFO("Task %d (%s) of process %d (%s)\n",
task_pid,
task_comm,
(int)task_pid_nr(current->group_leader),
process_comm);
} else {
EVDI_INFO("Task %d (%s)\n",
task_pid,
task_comm);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#ifndef EVDI_DEBUG_H
#define EVDI_DEBUG_H
#define EVDI_LOGLEVEL_ALWAYS 0
#define EVDI_LOGLEVEL_FATAL 1
#define EVDI_LOGLEVEL_ERROR 2
#define EVDI_LOGLEVEL_WARN 3
#define EVDI_LOGLEVEL_INFO 4
#define EVDI_LOGLEVEL_DEBUG 5
#define EVDI_LOGLEVEL_VERBOSE 6
extern unsigned int evdi_loglevel;
#define EVDI_PRINTK(kLEVEL, lEVEL, FORMAT_STR, ...) do { \
if (lEVEL <= evdi_loglevel) {\
printk(kLEVEL "evdi: " FORMAT_STR, ##__VA_ARGS__); \
} \
} while (0)
#define EVDI_FATAL(FORMAT_STR, ...) \
EVDI_PRINTK(KERN_CRIT, EVDI_LOGLEVEL_FATAL,\
"[F] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__)
#define EVDI_ERROR(FORMAT_STR, ...) \
EVDI_PRINTK(KERN_ERR, EVDI_LOGLEVEL_ERROR,\
"[E] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__)
#define EVDI_WARN(FORMAT_STR, ...) \
EVDI_PRINTK(KERN_WARNING, EVDI_LOGLEVEL_WARN,\
"[W] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__)
#define EVDI_INFO(FORMAT_STR, ...) \
EVDI_PRINTK(KERN_DEFAULT, EVDI_LOGLEVEL_INFO,\
"[I] " FORMAT_STR, ##__VA_ARGS__)
#define EVDI_DEBUG(FORMAT_STR, ...) \
EVDI_PRINTK(KERN_DEFAULT, EVDI_LOGLEVEL_DEBUG,\
"[D] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__)
#define EVDI_VERBOSE(FORMAT_STR, ...) \
EVDI_PRINTK(KERN_DEFAULT, EVDI_LOGLEVEL_VERBOSE,\
"[V] %s:%d " FORMAT_STR, __func__, __LINE__, ##__VA_ARGS__)
#define EVDI_CHECKPT() EVDI_VERBOSE("\n")
#define EVDI_ENTER() EVDI_VERBOSE("enter\n")
#define EVDI_EXIT() EVDI_VERBOSE("exit\n")
void evdi_log_process(void);
#endif /* EVDI_DEBUG_H */

View File

@@ -0,0 +1,288 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "evdi_drv.h"
#include <uapi/drm/evdi_drm.h>
#include "evdi_debug.h"
MODULE_AUTHOR("DisplayLink (UK) Ltd.");
MODULE_DESCRIPTION("Extensible Virtual Display Interface");
MODULE_LICENSE("GPL");
#define EVDI_DEVICE_COUNT_MAX 16
static struct evdi_context {
struct device *root_dev;
unsigned int dev_count;
struct platform_device *devices[EVDI_DEVICE_COUNT_MAX];
} evdi_context;
static struct drm_driver driver;
struct drm_ioctl_desc evdi_painter_ioctls[] = {
DRM_IOCTL_DEF_DRV(EVDI_CONNECT, evdi_painter_connect_ioctl,
DRM_UNLOCKED),
DRM_IOCTL_DEF_DRV(EVDI_REQUEST_UPDATE,
evdi_painter_request_update_ioctl, DRM_UNLOCKED),
DRM_IOCTL_DEF_DRV(EVDI_GRABPIX, evdi_painter_grabpix_ioctl,
DRM_UNLOCKED),
};
static const struct vm_operations_struct evdi_gem_vm_ops = {
.fault = evdi_gem_fault,
.open = drm_gem_vm_open,
.close = drm_gem_vm_close,
};
static const struct file_operations evdi_driver_fops = {
.owner = THIS_MODULE,
.open = drm_open,
.mmap = evdi_drm_gem_mmap,
.poll = drm_poll,
.read = drm_read,
.unlocked_ioctl = drm_ioctl,
.release = drm_release,
#ifdef CONFIG_COMPAT
.compat_ioctl = drm_compat_ioctl,
#endif
.llseek = noop_llseek,
};
static struct drm_driver driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME,
.load = evdi_driver_load,
.unload = evdi_driver_unload,
.preclose = evdi_driver_preclose,
.postclose = evdi_driver_postclose,
/* gem hooks */
.gem_free_object = evdi_gem_free_object,
.gem_vm_ops = &evdi_gem_vm_ops,
.dumb_create = evdi_dumb_create,
.dumb_map_offset = evdi_gem_mmap,
.dumb_destroy = drm_gem_dumb_destroy,
.ioctls = evdi_painter_ioctls,
.num_ioctls = ARRAY_SIZE(evdi_painter_ioctls),
.fops = &evdi_driver_fops,
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
.gem_prime_import = evdi_gem_prime_import,
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
.gem_prime_export = evdi_gem_prime_export,
.name = DRIVER_NAME,
.desc = DRIVER_DESC,
.date = DRIVER_DATE,
.major = DRIVER_MAJOR,
.minor = DRIVER_MINOR,
.patchlevel = DRIVER_PATCHLEVEL,
};
static void evdi_add_device(void)
{
struct platform_device_info pdevinfo = {
.parent = NULL,
.name = "evdi",
.id = evdi_context.dev_count,
.res = NULL,
.num_res = 0,
.data = NULL,
.size_data = 0,
.dma_mask = DMA_BIT_MASK(32),
};
evdi_context.devices[evdi_context.dev_count] =
platform_device_register_full(&pdevinfo);
if (dma_set_mask(&evdi_context.devices[evdi_context.dev_count]->dev,
DMA_BIT_MASK(64))) {
EVDI_DEBUG("Unable to change dma mask to 64 bit. ");
EVDI_DEBUG("Sticking with 32 bit\n");
}
evdi_context.dev_count++;
}
static int evdi_platform_probe(struct platform_device *pdev)
{
EVDI_CHECKPT();
return drm_platform_init(&driver, pdev);
}
static int evdi_platform_remove(struct platform_device *pdev)
{
struct drm_device *drm_dev =
(struct drm_device *)platform_get_drvdata(pdev);
EVDI_CHECKPT();
drm_unplug_dev(drm_dev);
return 0;
}
static void evdi_remove_all(void)
{
int i;
EVDI_DEBUG("removing all evdi devices\n");
for (i = 0; i < evdi_context.dev_count; ++i) {
if (evdi_context.devices[i]) {
EVDI_DEBUG("removing evdi %d\n", i);
platform_device_unregister(evdi_context.devices[i]);
evdi_context.devices[i] = NULL;
}
}
evdi_context.dev_count = 0;
}
static struct platform_driver evdi_platform_driver = {
.probe = evdi_platform_probe,
.remove = evdi_platform_remove,
.driver = {
.name = "evdi",
.mod_name = KBUILD_MODNAME,
.owner = THIS_MODULE,
}
};
static ssize_t version_show(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u.%u.%u\n", DRIVER_MAJOR,
DRIVER_MINOR, DRIVER_PATCHLEVEL);
}
static ssize_t count_show(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", evdi_context.dev_count);
}
static ssize_t add_store(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int val;
if (kstrtouint(buf, 10, &val)) {
EVDI_ERROR("Invalid device count \"%s\"\n", buf);
return -EINVAL;
}
if (val == 0) {
EVDI_WARN("Adding 0 devices has no effect\n");
return count;
}
if (val > EVDI_DEVICE_COUNT_MAX - evdi_context.dev_count) {
EVDI_ERROR("Evdi device add failed. Too many devices.\n");
return -EINVAL;
}
EVDI_DEBUG("Increasing device count to %u\n",
evdi_context.dev_count + val);
while (val--)
evdi_add_device();
return count;
}
static ssize_t remove_all_store(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
__always_unused const char *buf,
size_t count)
{
evdi_remove_all();
return count;
}
static ssize_t loglevel_show(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
return snprintf(buf, PAGE_SIZE, "%u\n", evdi_loglevel);
}
static ssize_t loglevel_store(__always_unused struct device *dev,
__always_unused struct device_attribute *attr,
const char *buf,
size_t count)
{
unsigned int val;
if (kstrtouint(buf, 10, &val)) {
EVDI_ERROR("Unable to parse %u\n", val);
return -EINVAL;
}
if (val > EVDI_LOGLEVEL_VERBOSE) {
EVDI_ERROR("Invalid loglevel %u\n", val);
return -EINVAL;
}
EVDI_INFO("Setting loglevel to %u\n", val);
evdi_loglevel = val;
return count;
}
static struct device_attribute evdi_device_attributes[] = {
__ATTR_RO(count),
__ATTR_RO(version),
__ATTR_RW(loglevel),
__ATTR_WO(add),
__ATTR_WO(remove_all)
};
static int __init evdi_init(void)
{
int i;
EVDI_INFO("Initialising logging on level %u\n", evdi_loglevel);
EVDI_INFO("Atomic driver:no");
evdi_context.root_dev = root_device_register("evdi");
if (!PTR_RET(evdi_context.root_dev))
for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++) {
device_create_file(evdi_context.root_dev,
&evdi_device_attributes[i]);
}
return platform_driver_register(&evdi_platform_driver);
}
static void __exit evdi_exit(void)
{
int i;
EVDI_CHECKPT();
evdi_remove_all();
platform_driver_unregister(&evdi_platform_driver);
if (!PTR_RET(evdi_context.root_dev)) {
for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++) {
device_remove_file(evdi_context.root_dev,
&evdi_device_attributes[i]);
}
root_device_unregister(evdi_context.root_dev);
}
}
module_init(evdi_init);
module_exit(evdi_exit);
bool evdi_enable_cursor_blending __read_mostly = true;
module_param_named(enable_cursor_blending,
evdi_enable_cursor_blending, bool, 0644);
MODULE_PARM_DESC(enable_cursor_blending, "Enables cursor compositing on user supplied framebuffer via EVDI_GRABPIX ioctl. (default: true)");

View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2017 DisplayLink (UK) Ltd.
*
* Based on parts on udlfb.c:
* Copyright (C) 2009 its respective authors
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#ifndef EVDI_DRV_H
#define EVDI_DRV_H
#include <linux/module.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_rect.h>
#include "evdi_debug.h"
#define DRIVER_NAME "evdi"
#define DRIVER_DESC "Extensible Virtual Display Interface"
#define DRIVER_DATE "20190103"
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 5
#define DRIVER_PATCHLEVEL 1
struct evdi_fbdev;
struct evdi_painter;
struct evdi_flip_queue;
extern bool evdi_enable_cursor_blending __read_mostly;
struct evdi_device {
struct device *dev;
struct drm_device *ddev;
struct evdi_cursor *cursor;
uint32_t sku_area_limit;
struct evdi_fbdev *fbdev;
struct evdi_painter *painter;
atomic_t frame_count;
int dev_index;
struct evdi_flip_queue *flip_queue;
};
struct evdi_gem_object {
struct drm_gem_object base;
struct page **pages;
void *vmapping;
struct sg_table *sg;
};
#define to_evdi_bo(x) container_of(x, struct evdi_gem_object, base)
struct evdi_framebuffer {
struct drm_framebuffer base;
struct evdi_gem_object *obj;
bool active;
};
#define to_evdi_fb(x) container_of(x, struct evdi_framebuffer, base)
/* modeset */
int evdi_modeset_init(struct drm_device *dev);
void evdi_modeset_cleanup(struct drm_device *dev);
int evdi_connector_init(struct drm_device *dev, struct drm_encoder *encoder);
struct drm_encoder *evdi_encoder_init(struct drm_device *dev);
int evdi_driver_load(struct drm_device *dev, unsigned long flags);
int evdi_driver_unload(struct drm_device *dev);
void evdi_driver_preclose(struct drm_device *dev, struct drm_file *file_priv);
void evdi_driver_postclose(struct drm_device *dev, struct drm_file *file_priv);
#ifdef CONFIG_FB
int evdi_fbdev_init(struct drm_device *dev);
void evdi_fbdev_cleanup(struct drm_device *dev);
void evdi_fbdev_unplug(struct drm_device *dev);
#endif /* CONFIG_FB */
struct drm_framebuffer *evdi_fb_user_fb_create(
struct drm_device *dev,
struct drm_file *file,
struct drm_mode_fb_cmd2 *mode_cmd);
int evdi_dumb_create(struct drm_file *file_priv,
struct drm_device *dev, struct drm_mode_create_dumb *args);
int evdi_gem_mmap(struct drm_file *file_priv,
struct drm_device *dev, uint32_t handle, uint64_t *offset);
void evdi_gem_free_object(struct drm_gem_object *gem_obj);
struct evdi_gem_object *evdi_gem_alloc_object(struct drm_device *dev,
size_t size);
uint32_t evdi_gem_object_handle_lookup(struct drm_file *filp,
struct drm_gem_object *obj);
struct drm_gem_object *evdi_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf);
struct dma_buf *evdi_gem_prime_export(struct drm_device *dev,
struct drm_gem_object *obj, int flags);
int evdi_gem_vmap(struct evdi_gem_object *obj);
void evdi_gem_vunmap(struct evdi_gem_object *obj);
int evdi_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma);
int evdi_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf);
void evdi_stats_init(struct evdi_device *evdi);
void evdi_stats_cleanup(struct evdi_device *evdi);
bool evdi_painter_is_connected(struct evdi_device *evdi);
void evdi_painter_close(struct evdi_device *evdi, struct drm_file *file);
u8 *evdi_painter_get_edid_copy(struct evdi_device *evdi);
void evdi_painter_mark_dirty(struct evdi_device *evdi,
const struct drm_clip_rect *rect);
void evdi_painter_dpms_notify(struct evdi_device *evdi, int mode);
void evdi_painter_mode_changed_notify(struct evdi_device *evdi,
struct drm_framebuffer *fb,
struct drm_display_mode *mode);
void evdi_painter_crtc_state_notify(struct evdi_device *evdi, int state);
unsigned int evdi_painter_poll(struct file *filp,
struct poll_table_struct *wait);
int evdi_painter_status_ioctl(struct drm_device *drm_dev, void *data,
struct drm_file *file);
int evdi_painter_connect_ioctl(struct drm_device *drm_dev, void *data,
struct drm_file *file);
int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data,
struct drm_file *file);
int evdi_painter_request_update_ioctl(struct drm_device *drm_dev, void *data,
struct drm_file *file);
int evdi_painter_init(struct evdi_device *evdi);
void evdi_painter_cleanup(struct evdi_device *evdi);
void evdi_painter_set_new_scanout_buffer(struct evdi_device *evdi,
struct evdi_framebuffer *buffer);
void evdi_painter_commit_scanout_buffer(struct evdi_device *evdi);
struct drm_clip_rect evdi_framebuffer_sanitize_rect(
const struct evdi_framebuffer *fb,
const struct drm_clip_rect *rect);
void evdi_painter_send_cursor_set(struct evdi_painter *painter,
struct evdi_cursor *cursor);
void evdi_painter_send_cursor_move(struct evdi_painter *painter,
struct evdi_cursor *cursor);
int evdi_fb_get_bpp(uint32_t format);
#endif

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* Based on parts on udlfb.c:
* Copyright (C) 2009 its respective authors
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "evdi_drv.h"
/* dummy encoder */
static void evdi_enc_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
kfree(encoder);
}
static void evdi_encoder_disable(__always_unused struct drm_encoder *encoder)
{
}
static bool evdi_mode_fixup(
__always_unused struct drm_encoder *encoder,
__always_unused const struct drm_display_mode *mode,
__always_unused struct drm_display_mode *adjusted_mode)
{
return true;
}
static void evdi_encoder_prepare(__always_unused struct drm_encoder *encoder)
{
}
static void evdi_encoder_commit(__always_unused struct drm_encoder *encoder)
{
}
static void evdi_encoder_mode_set(
__always_unused struct drm_encoder *encoder,
__always_unused struct drm_display_mode *mode,
__always_unused struct drm_display_mode *adjusted_mode)
{
}
static void evdi_encoder_dpms(
__always_unused struct drm_encoder *encoder,
__always_unused int mode)
{
}
static const struct drm_encoder_helper_funcs evdi_helper_funcs = {
.dpms = evdi_encoder_dpms,
.mode_fixup = evdi_mode_fixup,
.prepare = evdi_encoder_prepare,
.mode_set = evdi_encoder_mode_set,
.commit = evdi_encoder_commit,
.disable = evdi_encoder_disable,
};
static const struct drm_encoder_funcs evdi_enc_funcs = {
.destroy = evdi_enc_destroy,
};
struct drm_encoder *evdi_encoder_init(struct drm_device *dev)
{
struct drm_encoder *encoder;
int ret = 0;
encoder = kzalloc(sizeof(struct drm_encoder), GFP_KERNEL);
if (!encoder)
goto err;
ret = drm_encoder_init(dev, encoder, &evdi_enc_funcs,
DRM_MODE_ENCODER_TMDS);
if (ret) {
EVDI_ERROR("Failed to initialize encoder: %d\n", ret);
goto err_encoder;
}
drm_encoder_helper_add(encoder, &evdi_helper_funcs);
encoder->possible_crtcs = 1;
return encoder;
err_encoder:
kfree(encoder);
err:
return NULL;
}

View File

@@ -0,0 +1,523 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* Based on parts on udlfb.c:
* Copyright (C) 2009 its respective authors
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/slab.h>
#ifdef CONFIG_FB
#include <linux/fb.h>
#endif /* CONFIG_FB */
#include <linux/dma-buf.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
#include "evdi_drv.h"
struct evdi_fbdev {
struct drm_fb_helper helper;
struct evdi_framebuffer ufb;
struct list_head fbdev_list;
int fb_count;
};
struct drm_clip_rect evdi_framebuffer_sanitize_rect(
const struct evdi_framebuffer *fb,
const struct drm_clip_rect *dirty_rect)
{
struct drm_clip_rect rect = *dirty_rect;
if (rect.x1 > rect.x2) {
unsigned short tmp = rect.x2;
EVDI_WARN("Wrong clip rect: x1 > x2\n");
rect.x2 = rect.x1;
rect.x1 = tmp;
}
if (rect.y1 > rect.y2) {
unsigned short tmp = rect.y2;
EVDI_WARN("Wrong clip rect: y1 > y2\n");
rect.y2 = rect.y1;
rect.y1 = tmp;
}
if (rect.x1 > fb->base.width) {
EVDI_WARN("Wrong clip rect: x1 > fb.width\n");
rect.x1 = fb->base.width;
}
if (rect.y1 > fb->base.height) {
EVDI_WARN("Wrong clip rect: y1 > fb.height\n");
rect.y1 = fb->base.height;
}
if (rect.x2 > fb->base.width) {
EVDI_WARN("Wrong clip rect: x2 > fb.width\n");
rect.x2 = fb->base.width;
}
if (rect.y2 > fb->base.height) {
EVDI_WARN("Wrong clip rect: y2 > fb.height\n");
rect.y2 = fb->base.height;
}
return rect;
}
static int evdi_handle_damage(struct evdi_framebuffer *fb,
int x, int y, int width, int height)
{
const struct drm_clip_rect dirty_rect = { x, y, x + width, y + height };
const struct drm_clip_rect rect =
evdi_framebuffer_sanitize_rect(fb, &dirty_rect);
struct drm_device *dev = fb->base.dev;
struct evdi_device *evdi = dev->dev_private;
EVDI_CHECKPT();
if (!fb->active)
return 0;
evdi_painter_set_new_scanout_buffer(evdi, fb);
evdi_painter_commit_scanout_buffer(evdi);
evdi_painter_mark_dirty(evdi, &rect);
return 0;
}
#ifdef CONFIG_FB
static int evdi_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
unsigned long start = vma->vm_start;
unsigned long size = vma->vm_end - vma->vm_start;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long page, pos;
if (offset > info->fix.smem_len ||
size > info->fix.smem_len - offset)
return -EINVAL;
pos = (unsigned long)info->fix.smem_start + offset;
pr_notice("mmap() framebuffer addr:%lu size:%lu\n", pos, size);
while (size > 0) {
page = vmalloc_to_pfn((void *)pos);
if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED))
return -EAGAIN;
start += PAGE_SIZE;
pos += PAGE_SIZE;
if (size > PAGE_SIZE)
size -= PAGE_SIZE;
else
size = 0;
}
return 0;
}
static void evdi_fb_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct evdi_fbdev *ufbdev = info->par;
EVDI_CHECKPT();
sys_fillrect(info, rect);
evdi_handle_damage(&ufbdev->ufb, rect->dx, rect->dy, rect->width,
rect->height);
}
static void evdi_fb_copyarea(struct fb_info *info,
const struct fb_copyarea *region)
{
struct evdi_fbdev *ufbdev = info->par;
EVDI_CHECKPT();
sys_copyarea(info, region);
evdi_handle_damage(&ufbdev->ufb, region->dx, region->dy, region->width,
region->height);
}
static void evdi_fb_imageblit(struct fb_info *info,
const struct fb_image *image)
{
struct evdi_fbdev *ufbdev = info->par;
EVDI_CHECKPT();
sys_imageblit(info, image);
evdi_handle_damage(&ufbdev->ufb, image->dx, image->dy, image->width,
image->height);
}
/*
* It's common for several clients to have framebuffer open simultaneously.
* e.g. both fbcon and X. Makes things interesting.
* Assumes caller is holding info->lock (for open and release at least)
*/
static int evdi_fb_open(struct fb_info *info, int user)
{
struct evdi_fbdev *ufbdev = info->par;
ufbdev->fb_count++;
pr_notice("open /dev/fb%d user=%d fb_info=%p count=%d\n",
info->node, user, info, ufbdev->fb_count);
return 0;
}
/*
* Assumes caller is holding info->lock mutex (for open and release at least)
*/
static int evdi_fb_release(struct fb_info *info, int user)
{
struct evdi_fbdev *ufbdev = info->par;
ufbdev->fb_count--;
pr_warn("released /dev/fb%d user=%d count=%d\n",
info->node, user, ufbdev->fb_count);
return 0;
}
static struct fb_ops evdifb_ops = {
.owner = THIS_MODULE,
.fb_check_var = drm_fb_helper_check_var,
.fb_set_par = drm_fb_helper_set_par,
.fb_fillrect = evdi_fb_fillrect,
.fb_copyarea = evdi_fb_copyarea,
.fb_imageblit = evdi_fb_imageblit,
.fb_pan_display = drm_fb_helper_pan_display,
.fb_blank = drm_fb_helper_blank,
.fb_setcmap = drm_fb_helper_setcmap,
.fb_debug_enter = drm_fb_helper_debug_enter,
.fb_debug_leave = drm_fb_helper_debug_leave,
.fb_mmap = evdi_fb_mmap,
.fb_open = evdi_fb_open,
.fb_release = evdi_fb_release,
};
#endif /* CONFIG_FB */
static int evdi_user_framebuffer_dirty(struct drm_framebuffer *fb,
__always_unused struct drm_file *file,
__always_unused unsigned int flags,
__always_unused unsigned int color,
struct drm_clip_rect *clips,
unsigned int num_clips)
{
struct evdi_framebuffer *ufb = to_evdi_fb(fb);
struct drm_device *dev = ufb->base.dev;
struct evdi_device *evdi = dev->dev_private;
int i;
int ret = 0;
EVDI_CHECKPT();
drm_modeset_lock_all(fb->dev);
if (!ufb->active)
goto unlock;
if (ufb->obj->base.import_attach) {
ret =
dma_buf_begin_cpu_access(
ufb->obj->base.import_attach->dmabuf,
DMA_FROM_DEVICE);
if (ret)
goto unlock;
}
for (i = 0; i < num_clips; i++) {
ret = evdi_handle_damage(ufb, clips[i].x1, clips[i].y1,
clips[i].x2 - clips[i].x1,
clips[i].y2 - clips[i].y1);
if (ret)
goto unlock;
}
if (ufb->obj->base.import_attach)
dma_buf_end_cpu_access(ufb->obj->base.import_attach->dmabuf,
DMA_FROM_DEVICE);
atomic_add(1, &evdi->frame_count);
unlock:
drm_modeset_unlock_all(fb->dev);
return ret;
}
static int evdi_user_framebuffer_create_handle(struct drm_framebuffer *fb,
struct drm_file *file_priv,
unsigned int *handle)
{
struct evdi_framebuffer *efb = to_evdi_fb(fb);
return drm_gem_handle_create(file_priv, &efb->obj->base, handle);
}
static void evdi_user_framebuffer_destroy(struct drm_framebuffer *fb)
{
struct evdi_framebuffer *ufb = to_evdi_fb(fb);
EVDI_CHECKPT();
if (ufb->obj)
drm_gem_object_unreference_unlocked(&ufb->obj->base);
drm_framebuffer_cleanup(fb);
kfree(ufb);
}
static const struct drm_framebuffer_funcs evdifb_funcs = {
.create_handle = evdi_user_framebuffer_create_handle,
.destroy = evdi_user_framebuffer_destroy,
.dirty = evdi_user_framebuffer_dirty,
};
static int
evdi_framebuffer_init(struct drm_device *dev,
struct evdi_framebuffer *ufb,
struct drm_mode_fb_cmd2 *mode_cmd,
struct evdi_gem_object *obj)
{
ufb->obj = obj;
drm_helper_mode_fill_fb_struct(&ufb->base, mode_cmd);
return drm_framebuffer_init(dev, &ufb->base, &evdifb_funcs);
}
#ifdef CONFIG_FB
static int evdifb_create(struct drm_fb_helper *helper,
struct drm_fb_helper_surface_size *sizes)
{
struct evdi_fbdev *ufbdev = (struct evdi_fbdev *)helper;
struct drm_device *dev = ufbdev->helper.dev;
struct fb_info *info;
struct device *device = dev->dev;
struct drm_framebuffer *fb;
struct drm_mode_fb_cmd2 mode_cmd;
struct evdi_gem_object *obj;
uint32_t size;
int ret = 0;
if (sizes->surface_bpp == 24) {
sizes->surface_bpp = 32;
} else if (sizes->surface_bpp != 32) {
EVDI_ERROR("Not supported pixel format (bpp=%d)\n",
sizes->surface_bpp);
return -EINVAL;
}
mode_cmd.width = sizes->surface_width;
mode_cmd.height = sizes->surface_height;
mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7) / 8);
mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
sizes->surface_depth);
size = mode_cmd.pitches[0] * mode_cmd.height;
size = ALIGN(size, PAGE_SIZE);
obj = evdi_gem_alloc_object(dev, size);
if (!obj)
goto out;
ret = evdi_gem_vmap(obj);
if (ret) {
DRM_ERROR("failed to vmap fb\n");
goto out_gfree;
}
info = framebuffer_alloc(0, device);
if (!info) {
ret = -ENOMEM;
goto out_gfree;
}
info->par = ufbdev;
ret = evdi_framebuffer_init(dev, &ufbdev->ufb, &mode_cmd, obj);
if (ret)
goto out_gfree;
fb = &ufbdev->ufb.base;
ufbdev->helper.fb = fb;
ufbdev->helper.fbdev = info;
strcpy(info->fix.id, "evdidrmfb");
info->screen_base = ufbdev->ufb.obj->vmapping;
info->fix.smem_len = size;
info->fix.smem_start = (unsigned long)ufbdev->ufb.obj->vmapping;
info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
info->fbops = &evdifb_ops;
drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
drm_fb_helper_fill_var(info, &ufbdev->helper, sizes->fb_width,
sizes->fb_height);
ret = fb_alloc_cmap(&info->cmap, 256, 0);
if (ret) {
ret = -ENOMEM;
goto out_gfree;
}
DRM_DEBUG_KMS("allocated %dx%d vmal %p\n",
fb->width, fb->height, ufbdev->ufb.obj->vmapping);
return ret;
out_gfree:
drm_gem_object_unreference_unlocked(&ufbdev->ufb.obj->base);
out:
return ret;
}
static struct drm_fb_helper_funcs evdi_fb_helper_funcs = {
.fb_probe = evdifb_create,
};
static void evdi_fbdev_destroy(__always_unused struct drm_device *dev,
struct evdi_fbdev *ufbdev)
{
struct fb_info *info;
if (ufbdev->helper.fbdev) {
info = ufbdev->helper.fbdev;
unregister_framebuffer(info);
if (info->cmap.len)
fb_dealloc_cmap(&info->cmap);
framebuffer_release(info);
}
drm_fb_helper_fini(&ufbdev->helper);
drm_framebuffer_unregister_private(&ufbdev->ufb.base);
drm_framebuffer_cleanup(&ufbdev->ufb.base);
drm_gem_object_unreference_unlocked(&ufbdev->ufb.obj->base);
}
int evdi_fbdev_init(struct drm_device *dev)
{
struct evdi_device *evdi;
struct evdi_fbdev *ufbdev;
int ret;
evdi = dev->dev_private;
ufbdev = kzalloc(sizeof(struct evdi_fbdev), GFP_KERNEL);
if (!ufbdev)
return -ENOMEM;
evdi->fbdev = ufbdev;
ufbdev->helper.funcs = &evdi_fb_helper_funcs;
ufbdev->helper.dev = dev;
ret = drm_fb_helper_init(dev, &ufbdev->helper, 1, 1);
if (ret) {
kfree(ufbdev);
return ret;
}
drm_fb_helper_single_add_all_connectors(&ufbdev->helper);
/* disable all the possible outputs/crtcs before entering KMS mode */
drm_helper_disable_unused_functions(dev);
ret = drm_fb_helper_initial_config(&ufbdev->helper, 32);
if (ret) {
drm_fb_helper_fini(&ufbdev->helper);
kfree(ufbdev);
}
return ret;
}
void evdi_fbdev_cleanup(struct drm_device *dev)
{
struct evdi_device *evdi = dev->dev_private;
if (!evdi->fbdev)
return;
evdi_fbdev_destroy(dev, evdi->fbdev);
kfree(evdi->fbdev);
evdi->fbdev = NULL;
}
void evdi_fbdev_unplug(struct drm_device *dev)
{
struct evdi_device *evdi = dev->dev_private;
struct evdi_fbdev *ufbdev;
if (!evdi->fbdev)
return;
ufbdev = evdi->fbdev;
if (ufbdev->helper.fbdev) {
struct fb_info *info;
info = ufbdev->helper.fbdev;
unlink_framebuffer(info);
}
}
#endif /* CONFIG_FB */
int evdi_fb_get_bpp(uint32_t format)
{
unsigned int depth;
int bpp;
drm_fb_get_bpp_depth(format, &depth, &bpp);
return bpp;
}
struct drm_framebuffer *evdi_fb_user_fb_create(
struct drm_device *dev,
struct drm_file *file,
struct drm_mode_fb_cmd2 *mode_cmd)
{
struct drm_gem_object *obj;
struct evdi_framebuffer *ufb;
int ret;
uint32_t size;
int bpp = evdi_fb_get_bpp(mode_cmd->pixel_format);
if (bpp != 32) {
EVDI_ERROR("Unsupported bpp (%d)\n", bpp);
return ERR_PTR(-EINVAL);
}
obj = drm_gem_object_lookup(dev, file, mode_cmd->handles[0]);
if (obj == NULL)
return ERR_PTR(-ENOENT);
size = mode_cmd->pitches[0] * mode_cmd->height;
size = ALIGN(size, PAGE_SIZE);
if (size > obj->size) {
DRM_ERROR("object size not sufficient for fb %d %zu %d %d\n",
size, obj->size, mode_cmd->pitches[0],
mode_cmd->height);
goto err_no_mem;
}
ufb = kzalloc(sizeof(*ufb), GFP_KERNEL);
if (ufb == NULL)
goto err_no_mem;
ret = evdi_framebuffer_init(dev, ufb, mode_cmd, to_evdi_bo(obj));
if (ret)
goto err_inval;
return &ufb->base;
err_no_mem:
drm_gem_object_unreference(obj);
return ERR_PTR(-ENOMEM);
err_inval:
kfree(ufb);
drm_gem_object_unreference(obj);
return ERR_PTR(-EINVAL);
}

View File

@@ -0,0 +1,506 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <drm/drmP.h>
#include "evdi_drv.h"
#include <linux/shmem_fs.h>
#include <linux/dma-buf.h>
uint32_t evdi_gem_object_handle_lookup(struct drm_file *filp,
struct drm_gem_object *obj)
{
uint32_t it_handle = 0;
struct drm_gem_object *it_obj = NULL;
spin_lock(&filp->table_lock);
idr_for_each_entry(&filp->object_idr, it_obj, it_handle) {
if (it_obj == obj)
break;
}
spin_unlock(&filp->table_lock);
if (!it_obj)
it_handle = 0;
return it_handle;
}
struct evdi_gem_object *evdi_gem_alloc_object(struct drm_device *dev,
size_t size)
{
struct evdi_gem_object *obj;
obj = kzalloc(sizeof(*obj), GFP_KERNEL);
if (obj == NULL)
return NULL;
if (drm_gem_object_init(dev, &obj->base, size) != 0) {
kfree(obj);
return NULL;
}
return obj;
}
static int
evdi_gem_create(struct drm_file *file,
struct drm_device *dev, uint64_t size, uint32_t *handle_p)
{
struct evdi_gem_object *obj;
int ret;
u32 handle;
size = roundup(size, PAGE_SIZE);
obj = evdi_gem_alloc_object(dev, size);
if (obj == NULL)
return -ENOMEM;
ret = drm_gem_handle_create(file, &obj->base, &handle);
if (ret) {
drm_gem_object_release(&obj->base);
kfree(obj);
return ret;
}
drm_gem_object_unreference_unlocked(&obj->base);
*handle_p = handle;
return 0;
}
int evdi_dumb_create(struct drm_file *file,
struct drm_device *dev, struct drm_mode_create_dumb *args)
{
args->pitch = args->width * DIV_ROUND_UP(args->bpp, 8);
args->size = args->pitch * args->height;
return evdi_gem_create(file, dev, args->size, &args->handle);
}
int evdi_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
ret = drm_gem_mmap(filp, vma);
if (ret)
return ret;
vma->vm_flags &= ~VM_PFNMAP;
vma->vm_flags |= VM_MIXEDMAP;
return ret;
}
int evdi_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct evdi_gem_object *obj = to_evdi_bo(vma->vm_private_data);
struct page *page;
unsigned int page_offset;
int ret = 0;
page_offset = ((unsigned long)vmf->virtual_address - vma->vm_start) >>
PAGE_SHIFT;
if (!obj->pages)
return VM_FAULT_SIGBUS;
page = obj->pages[page_offset];
ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address, page);
switch (ret) {
case -EAGAIN:
case 0:
case -ERESTARTSYS:
return VM_FAULT_NOPAGE;
case -ENOMEM:
return VM_FAULT_OOM;
default:
return VM_FAULT_SIGBUS;
}
return VM_FAULT_SIGBUS;
}
static int evdi_gem_get_pages(struct evdi_gem_object *obj,
__always_unused gfp_t gfpmask)
{
struct page **pages;
if (obj->pages)
return 0;
pages = drm_gem_get_pages(&obj->base, gfpmask);
if (IS_ERR(pages))
return PTR_ERR(pages);
obj->pages = pages;
#if defined(CONFIG_X86)
drm_clflush_pages(obj->pages, obj->base.size / PAGE_SIZE);
#endif
return 0;
}
static void evdi_gem_put_pages(struct evdi_gem_object *obj)
{
if (obj->base.import_attach) {
drm_free_large(obj->pages);
obj->pages = NULL;
return;
}
drm_gem_put_pages(&obj->base, obj->pages, false, false);
obj->pages = NULL;
}
int evdi_gem_vmap(struct evdi_gem_object *obj)
{
int page_count = obj->base.size / PAGE_SIZE;
int ret;
if (obj->base.import_attach) {
obj->vmapping = dma_buf_vmap(obj->base.import_attach->dmabuf);
if (!obj->vmapping)
return -ENOMEM;
return 0;
}
ret = evdi_gem_get_pages(obj, GFP_KERNEL);
if (ret)
return ret;
obj->vmapping = vmap(obj->pages, page_count, 0, PAGE_KERNEL);
if (!obj->vmapping)
return -ENOMEM;
return 0;
}
void evdi_gem_vunmap(struct evdi_gem_object *obj)
{
if (obj->base.import_attach) {
dma_buf_vunmap(obj->base.import_attach->dmabuf, obj->vmapping);
obj->vmapping = NULL;
return;
}
if (obj->vmapping) {
vunmap(obj->vmapping);
obj->vmapping = NULL;
}
evdi_gem_put_pages(obj);
}
void evdi_gem_free_object(struct drm_gem_object *gem_obj)
{
struct evdi_gem_object *obj = to_evdi_bo(gem_obj);
struct drm_device *dev = gem_obj->dev;
if (obj->vmapping)
evdi_gem_vunmap(obj);
if (gem_obj->import_attach) {
drm_prime_gem_destroy(gem_obj, obj->sg);
put_device(gem_obj->dev->dev);
}
if (obj->pages)
evdi_gem_put_pages(obj);
if (dev) {
struct drm_gem_mm *mm = dev->mm_private;
if (mm)
drm_gem_free_mmap_offset(gem_obj);
}
}
/*
* the dumb interface doesn't work with the GEM straight MMAP
* interface, it expects to do MMAP on the drm fd, like normal
*/
int evdi_gem_mmap(struct drm_file *file,
struct drm_device *dev, uint32_t handle, uint64_t *offset)
{
struct evdi_gem_object *gobj;
struct drm_gem_object *obj;
int ret = 0;
mutex_lock(&dev->struct_mutex);
obj = drm_gem_object_lookup(dev, file, handle);
if (obj == NULL) {
ret = -ENOENT;
goto unlock;
}
gobj = to_evdi_bo(obj);
ret = evdi_gem_get_pages(gobj, GFP_KERNEL);
if (ret)
goto out;
ret = drm_gem_create_mmap_offset(obj);
if (ret)
goto out;
*offset = drm_vma_node_offset_addr(&gobj->base.vma_node);
out:
drm_gem_object_unreference(&gobj->base);
unlock:
mutex_unlock(&dev->struct_mutex);
return ret;
}
static int evdi_prime_create(struct drm_device *dev,
size_t size,
struct sg_table *sg,
struct evdi_gem_object **obj_p)
{
struct evdi_gem_object *obj;
int npages;
npages = size / PAGE_SIZE;
*obj_p = NULL;
obj = evdi_gem_alloc_object(dev, npages * PAGE_SIZE);
if (!obj)
return -ENOMEM;
obj->sg = sg;
obj->pages = drm_malloc_ab(npages, sizeof(struct page *));
if (obj->pages == NULL) {
DRM_ERROR("obj pages is NULL %d\n", npages);
return -ENOMEM;
}
drm_prime_sg_to_page_addr_arrays(sg, obj->pages, NULL, npages);
*obj_p = obj;
return 0;
}
struct evdi_drm_dmabuf_attachment {
struct sg_table sgt;
enum dma_data_direction dir;
bool is_mapped;
};
static int evdi_attach_dma_buf(__always_unused struct dma_buf *dmabuf,
__always_unused struct device *dev,
struct dma_buf_attachment *attach)
{
struct evdi_drm_dmabuf_attachment *evdi_attach;
evdi_attach = kzalloc(sizeof(*evdi_attach), GFP_KERNEL);
if (!evdi_attach)
return -ENOMEM;
evdi_attach->dir = DMA_NONE;
attach->priv = evdi_attach;
return 0;
}
static void evdi_detach_dma_buf(__always_unused struct dma_buf *dmabuf,
struct dma_buf_attachment *attach)
{
struct evdi_drm_dmabuf_attachment *evdi_attach = attach->priv;
struct sg_table *sgt;
if (!evdi_attach)
return;
sgt = &evdi_attach->sgt;
if (evdi_attach->dir != DMA_NONE)
dma_unmap_sg(attach->dev, sgt->sgl, sgt->nents,
evdi_attach->dir);
sg_free_table(sgt);
kfree(evdi_attach);
attach->priv = NULL;
}
static struct sg_table *evdi_map_dma_buf(struct dma_buf_attachment *attach,
enum dma_data_direction dir)
{
struct evdi_drm_dmabuf_attachment *evdi_attach = attach->priv;
struct evdi_gem_object *obj = to_evdi_bo(attach->dmabuf->priv);
struct drm_device *dev = obj->base.dev;
struct scatterlist *rd, *wr;
struct sg_table *sgt = NULL;
unsigned int i;
int page_count;
int nents, ret;
DRM_DEBUG_PRIME("[DEV:%s] size:%zd dir=%d\n", dev_name(attach->dev),
attach->dmabuf->size, dir);
/* just return current sgt if already requested. */
if (evdi_attach->dir == dir && evdi_attach->is_mapped)
return &evdi_attach->sgt;
if (!obj->pages) {
ret = evdi_gem_get_pages(obj, GFP_KERNEL);
if (ret) {
DRM_ERROR("failed to map pages.\n");
return ERR_PTR(ret);
}
}
page_count = obj->base.size / PAGE_SIZE;
obj->sg = drm_prime_pages_to_sg(obj->pages, page_count);
if (IS_ERR(obj->sg)) {
DRM_ERROR("failed to allocate sgt.\n");
return ERR_CAST(obj->sg);
}
sgt = &evdi_attach->sgt;
ret = sg_alloc_table(sgt, obj->sg->orig_nents, GFP_KERNEL);
if (ret) {
DRM_ERROR("failed to alloc sgt.\n");
return ERR_PTR(-ENOMEM);
}
mutex_lock(&dev->struct_mutex);
rd = obj->sg->sgl;
wr = sgt->sgl;
for (i = 0; i < sgt->orig_nents; ++i) {
sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
rd = sg_next(rd);
wr = sg_next(wr);
}
if (dir != DMA_NONE) {
nents = dma_map_sg(attach->dev, sgt->sgl, sgt->orig_nents, dir);
if (!nents) {
DRM_ERROR("failed to map sgl with iommu.\n");
sg_free_table(sgt);
sgt = ERR_PTR(-EIO);
goto err_unlock;
}
}
evdi_attach->is_mapped = true;
evdi_attach->dir = dir;
attach->priv = evdi_attach;
err_unlock:
mutex_unlock(&dev->struct_mutex);
return sgt;
}
static void evdi_unmap_dma_buf(
__always_unused struct dma_buf_attachment *attach,
__always_unused struct sg_table *sgt,
__always_unused enum dma_data_direction dir)
{
}
static void *evdi_dmabuf_kmap(__always_unused struct dma_buf *dma_buf,
__always_unused unsigned long page_num)
{
return NULL;
}
static void *evdi_dmabuf_kmap_atomic(__always_unused struct dma_buf *dma_buf,
__always_unused unsigned long page_num)
{
return NULL;
}
static void evdi_dmabuf_kunmap(
__always_unused struct dma_buf *dma_buf,
__always_unused unsigned long page_num,
__always_unused void *addr)
{
}
static void evdi_dmabuf_kunmap_atomic(
__always_unused struct dma_buf *dma_buf,
__always_unused unsigned long page_num,
__always_unused void *addr)
{
}
static int evdi_dmabuf_mmap(__always_unused struct dma_buf *dma_buf,
__always_unused struct vm_area_struct *vma)
{
return -EINVAL;
}
static struct dma_buf_ops evdi_dmabuf_ops = {
.attach = evdi_attach_dma_buf,
.detach = evdi_detach_dma_buf,
.map_dma_buf = evdi_map_dma_buf,
.unmap_dma_buf = evdi_unmap_dma_buf,
.kmap = evdi_dmabuf_kmap,
.kmap_atomic = evdi_dmabuf_kmap_atomic,
.kunmap = evdi_dmabuf_kunmap,
.kunmap_atomic = evdi_dmabuf_kunmap_atomic,
.mmap = evdi_dmabuf_mmap,
.release = drm_gem_dmabuf_release,
};
struct drm_gem_object *evdi_gem_prime_import(struct drm_device *dev,
struct dma_buf *dma_buf)
{
struct dma_buf_attachment *attach;
struct sg_table *sg;
struct evdi_gem_object *uobj;
int ret;
/* check if our object */
if (dma_buf->ops == &evdi_dmabuf_ops) {
uobj = to_evdi_bo(dma_buf->priv);
if (uobj->base.dev == dev) {
drm_gem_object_reference(&uobj->base);
return &uobj->base;
}
}
/* need to attach */
get_device(dev->dev);
attach = dma_buf_attach(dma_buf, dev->dev);
if (IS_ERR(attach)) {
put_device(dev->dev);
return ERR_CAST(attach);
}
get_dma_buf(dma_buf);
sg = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
if (IS_ERR(sg)) {
ret = PTR_ERR(sg);
goto fail_detach;
}
ret = evdi_prime_create(dev, dma_buf->size, sg, &uobj);
if (ret)
goto fail_unmap;
uobj->base.import_attach = attach;
return &uobj->base;
fail_unmap:
dma_buf_unmap_attachment(attach, sg, DMA_BIDIRECTIONAL);
fail_detach:
dma_buf_detach(dma_buf, attach);
dma_buf_put(dma_buf);
put_device(dev->dev);
return ERR_PTR(ret);
}
struct dma_buf *evdi_gem_prime_export(__always_unused struct drm_device *dev,
struct drm_gem_object *obj, int flags)
{
return dma_buf_export(obj, &evdi_dmabuf_ops, obj->size, flags);
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* Based on parts on udlfb.c:
* Copyright (C) 2009 its respective authors
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <linux/platform_device.h>
#include <drm/drmP.h>
#include "evdi_drv.h"
#include "evdi_cursor.h"
int evdi_driver_load(struct drm_device *dev,
__always_unused unsigned long flags)
{
struct platform_device *platdev = NULL;
struct evdi_device *evdi;
int ret;
EVDI_CHECKPT();
evdi = kzalloc(sizeof(struct evdi_device), GFP_KERNEL);
if (!evdi)
return -ENOMEM;
evdi->ddev = dev;
dev->dev_private = evdi;
ret = evdi_cursor_init(&evdi->cursor);
if (ret)
goto err;
EVDI_CHECKPT();
ret = evdi_modeset_init(dev);
if (ret)
goto err;
#ifdef CONFIG_FB
ret = evdi_fbdev_init(dev);
if (ret)
goto err;
#endif /* CONFIG_FB */
ret = drm_vblank_init(dev, 1);
if (ret)
goto err_fb;
ret = evdi_painter_init(evdi);
if (ret)
goto err_fb;
evdi_stats_init(evdi);
drm_kms_helper_poll_init(dev);
platdev = to_platform_device(dev->dev);
platform_set_drvdata(platdev, dev);
return 0;
err_fb:
#ifdef CONFIG_FB
evdi_fbdev_cleanup(dev);
#endif /* CONFIG_FB */
err:
kfree(evdi);
EVDI_ERROR("%d\n", ret);
if (evdi->cursor)
evdi_cursor_free(evdi->cursor);
return ret;
}
int evdi_driver_unload(struct drm_device *dev)
{
struct evdi_device *evdi = dev->dev_private;
EVDI_CHECKPT();
drm_vblank_cleanup(dev);
drm_kms_helper_poll_fini(dev);
drm_connector_unplug_all(dev);
#ifdef CONFIG_FB
evdi_fbdev_unplug(dev);
#endif /* CONFIG_FB */
if (evdi->cursor)
evdi_cursor_free(evdi->cursor);
evdi_painter_cleanup(evdi);
evdi_stats_cleanup(evdi);
#ifdef CONFIG_FB
evdi_fbdev_cleanup(dev);
#endif /* CONFIG_FB */
evdi_modeset_cleanup(dev);
kfree(evdi);
return 0;
}
static void evdi_driver_close(struct drm_device *drm_dev, struct drm_file *file)
{
struct evdi_device *evdi = drm_dev->dev_private;
if (evdi)
evdi_painter_close(evdi, file);
}
void evdi_driver_preclose(struct drm_device *drm_dev, struct drm_file *file)
{
evdi_driver_close(drm_dev, file);
}
void evdi_driver_postclose(struct drm_device *drm_dev, struct drm_file *file)
{
struct evdi_device *evdi = drm_dev->dev_private;
EVDI_DEBUG("(dev=%d) Process tries to close us, postclose\n",
evdi ? evdi->dev_index : -1);
evdi_log_process();
evdi_driver_close(drm_dev, file);
}

View File

@@ -0,0 +1,396 @@
/*
* Copyright (C) 2012 Red Hat
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* Based on parts on udlfb.c:
* Copyright (C) 2009 its respective authors
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <uapi/drm/evdi_drm.h>
#include "evdi_drv.h"
#include "evdi_cursor.h"
struct evdi_flip_queue {
struct mutex lock;
struct workqueue_struct *wq;
struct delayed_work work;
struct drm_crtc *crtc;
struct drm_pending_vblank_event *event;
u64 flip_time; /* in jiffies */
u64 vblank_interval; /* in jiffies */
};
static void evdi_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct evdi_device *evdi = crtc->dev->dev_private;
evdi_painter_dpms_notify(evdi, mode);
}
static bool evdi_crtc_mode_fixup(
__always_unused struct drm_crtc *crtc,
__always_unused const struct drm_display_mode *mode,
__always_unused struct drm_display_mode *adjusted_mode)
{
return true;
}
static int evdi_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode,
__always_unused int x,
__always_unused int y,
struct drm_framebuffer *old_fb)
{
struct drm_device *dev = NULL;
struct evdi_device *evdi = NULL;
struct evdi_framebuffer *efb = NULL;
struct evdi_flip_queue *flip_queue = NULL;
struct drm_clip_rect rect;
if (crtc->primary == NULL) {
EVDI_DEBUG("evdi_crtc_mode_set primary plane is NULL");
return 0;
}
EVDI_ENTER();
efb = to_evdi_fb(crtc->primary->fb);
if (old_fb) {
struct evdi_framebuffer *eold_fb = to_evdi_fb(old_fb);
eold_fb->active = false;
}
efb->active = true;
dev = efb->base.dev;
evdi = dev->dev_private;
evdi_painter_mode_changed_notify(evdi, &efb->base, adjusted_mode);
/* update flip queue vblank interval */
flip_queue = evdi->flip_queue;
if (flip_queue) {
mutex_lock(&flip_queue->lock);
flip_queue->vblank_interval = HZ / drm_mode_vrefresh(mode);
mutex_unlock(&flip_queue->lock);
}
/* damage all of it */
evdi_painter_set_new_scanout_buffer(evdi, efb);
evdi_painter_commit_scanout_buffer(evdi);
rect.x1 = 0;
rect.y1 = 0;
rect.x2 = efb->base.width;
rect.y2 = efb->base.height;
evdi_painter_mark_dirty(evdi, &rect);
EVDI_EXIT();
return 0;
}
static void evdi_crtc_disable(struct drm_crtc *crtc)
{
struct evdi_device *evdi = crtc->dev->dev_private;
EVDI_CHECKPT();
evdi_painter_crtc_state_notify(evdi, DRM_MODE_DPMS_OFF);
}
static void evdi_crtc_destroy(struct drm_crtc *crtc)
{
EVDI_CHECKPT();
drm_crtc_cleanup(crtc);
kfree(crtc);
}
static void evdi_sched_page_flip(struct work_struct *work)
{
struct evdi_flip_queue *flip_queue =
container_of(container_of(work, struct delayed_work, work),
struct evdi_flip_queue, work);
struct drm_crtc *crtc;
struct drm_device *dev;
struct drm_pending_vblank_event *event;
struct drm_framebuffer *fb;
mutex_lock(&flip_queue->lock);
crtc = flip_queue->crtc;
dev = crtc->dev;
event = flip_queue->event;
fb = crtc->primary->fb;
flip_queue->event = NULL;
mutex_unlock(&flip_queue->lock);
EVDI_CHECKPT();
if (fb) {
struct evdi_device *evdi = dev->dev_private;
const struct drm_clip_rect rect = {
0, 0, fb->width, fb->height };
evdi_painter_commit_scanout_buffer(evdi);
evdi_painter_mark_dirty(evdi, &rect);
}
if (event) {
unsigned long flags = 0;
spin_lock_irqsave(&dev->event_lock, flags);
drm_send_vblank_event(dev, 0, event);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
static int evdi_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
__always_unused uint32_t page_flip_flags)
{
struct drm_device *dev = crtc->dev;
struct evdi_device *evdi = dev->dev_private;
struct evdi_flip_queue *flip_queue = evdi->flip_queue;
if (!flip_queue || !flip_queue->wq) {
DRM_ERROR("Uninitialized page flip queue\n");
return -ENOMEM;
}
mutex_lock(&flip_queue->lock);
EVDI_CHECKPT();
atomic_inc(&evdi->frame_count);
flip_queue->crtc = crtc;
if (fb) {
struct evdi_framebuffer *efb = to_evdi_fb(fb);
struct drm_framebuffer *old_fb = crtc->primary->fb;
if (old_fb) {
struct evdi_framebuffer *eold_fb = to_evdi_fb(old_fb);
eold_fb->active = false;
}
efb->active = true;
crtc->primary->fb = fb;
evdi_painter_set_new_scanout_buffer(evdi, efb);
}
if (event) {
if (flip_queue->event) {
unsigned long flags = 0;
spin_lock_irqsave(&dev->event_lock, flags);
drm_send_vblank_event(dev, 0, flip_queue->event);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
flip_queue->event = event;
}
if (!delayed_work_pending(&flip_queue->work)) {
u64 now = jiffies;
u64 next_flip =
flip_queue->flip_time + flip_queue->vblank_interval;
flip_queue->flip_time = (next_flip < now) ? now : next_flip;
queue_delayed_work(flip_queue->wq, &flip_queue->work,
flip_queue->flip_time - now);
}
mutex_unlock(&flip_queue->lock);
return 0;
}
static int evdi_crtc_cursor_set(struct drm_crtc *crtc,
struct drm_file *file,
uint32_t handle,
uint32_t width,
uint32_t height,
int32_t hot_x,
int32_t hot_y)
{
struct drm_device *dev = crtc->dev;
struct evdi_device *evdi = dev->dev_private;
struct drm_gem_object *obj = NULL;
struct evdi_gem_object *eobj = NULL;
/*
* evdi_crtc_cursor_set is callback function using
* deprecated cursor entry point.
* There is no info about underlaying pixel format.
* Hence we are assuming that it is in ARGB 32bpp format.
* This format it the only one supported in cursor composition
* function.
* This format is also enforced during framebuffer creation.
*
* Proper format will be available when driver start support
* universal planes for cursor.
*/
uint32_t format = DRM_FORMAT_ARGB8888;
uint32_t stride = 4 * width;
EVDI_CHECKPT();
if (handle) {
mutex_lock(&dev->struct_mutex);
obj = drm_gem_object_lookup(crtc->dev, file, handle);
if (obj)
eobj = to_evdi_bo(obj);
else
EVDI_ERROR("Failed to lookup gem object.\n");
mutex_unlock(&dev->struct_mutex);
}
evdi_cursor_set(evdi->cursor,
eobj, width, height, hot_x, hot_y,
format, stride);
drm_gem_object_unreference_unlocked(obj);
if (evdi_enable_cursor_blending)
return evdi_crtc_page_flip(crtc, NULL, NULL, 0);
else
evdi_painter_send_cursor_set(evdi->painter, evdi->cursor);
return 0;
}
static int evdi_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
{
struct drm_device *dev = crtc->dev;
struct evdi_device *evdi = dev->dev_private;
evdi_cursor_move(evdi->cursor, x, y);
if (evdi_enable_cursor_blending)
return evdi_crtc_page_flip(crtc, NULL, NULL, 0);
else
evdi_painter_send_cursor_move(evdi->painter, evdi->cursor);
return 0;
}
static void evdi_crtc_prepare(__always_unused struct drm_crtc *crtc)
{
}
static void evdi_crtc_commit(struct drm_crtc *crtc)
{
struct evdi_device *evdi = crtc->dev->dev_private;
EVDI_CHECKPT();
evdi_painter_crtc_state_notify(evdi, DRM_MODE_DPMS_ON);
}
static struct drm_crtc_helper_funcs evdi_helper_funcs = {
.dpms = evdi_crtc_dpms,
.mode_fixup = evdi_crtc_mode_fixup,
.mode_set = evdi_crtc_mode_set,
.prepare = evdi_crtc_prepare,
.commit = evdi_crtc_commit,
.disable = evdi_crtc_disable,
};
static const struct drm_crtc_funcs evdi_crtc_funcs = {
.set_config = drm_crtc_helper_set_config,
.destroy = evdi_crtc_destroy,
.page_flip = evdi_crtc_page_flip,
.cursor_set2 = evdi_crtc_cursor_set,
.cursor_move = evdi_crtc_cursor_move,
};
static int evdi_crtc_init(struct drm_device *dev)
{
struct drm_crtc *crtc;
int status = 0;
EVDI_CHECKPT();
crtc = kzalloc(sizeof(struct drm_crtc), GFP_KERNEL);
if (crtc == NULL)
return -ENOMEM;
status = drm_crtc_init(dev, crtc, &evdi_crtc_funcs);
EVDI_DEBUG("drm_crtc_init: %d\n", status);
drm_crtc_helper_add(crtc, &evdi_helper_funcs);
return 0;
}
static int evdi_flip_workqueue_init(struct drm_device *dev)
{
struct evdi_device *evdi = dev->dev_private;
struct evdi_flip_queue *flip_queue =
kzalloc(sizeof(struct evdi_flip_queue), GFP_KERNEL);
EVDI_CHECKPT();
if (WARN_ON(!flip_queue))
return -ENOMEM;
mutex_init(&flip_queue->lock);
flip_queue->wq = create_singlethread_workqueue("flip");
if (WARN_ON(!flip_queue->wq)) {
mutex_destroy(&flip_queue->lock);
kfree(flip_queue);
return -ENOMEM;
}
INIT_DELAYED_WORK(&flip_queue->work, evdi_sched_page_flip);
flip_queue->flip_time = jiffies;
flip_queue->vblank_interval = HZ / 60;
evdi->flip_queue = flip_queue;
return 0;
}
static void evdi_flip_workqueue_cleanup(struct drm_device *dev)
{
struct evdi_device *evdi = dev->dev_private;
struct evdi_flip_queue *flip_queue = evdi->flip_queue;
if (!flip_queue)
return;
EVDI_CHECKPT();
if (flip_queue->wq) {
flush_workqueue(flip_queue->wq);
destroy_workqueue(flip_queue->wq);
}
mutex_destroy(&flip_queue->lock);
kfree(flip_queue);
}
static const struct drm_mode_config_funcs evdi_mode_funcs = {
.fb_create = evdi_fb_user_fb_create,
.output_poll_changed = NULL,
};
int evdi_modeset_init(struct drm_device *dev)
{
struct drm_encoder *encoder;
EVDI_CHECKPT();
drm_mode_config_init(dev);
dev->mode_config.min_width = 640;
dev->mode_config.min_height = 480;
dev->mode_config.max_width = 3840;
dev->mode_config.max_height = 2160;
dev->mode_config.prefer_shadow = 0;
dev->mode_config.preferred_depth = 24;
dev->mode_config.funcs = &evdi_mode_funcs;
drm_mode_create_dirty_info_property(dev);
evdi_crtc_init(dev);
encoder = evdi_encoder_init(dev);
evdi_connector_init(dev, encoder);
return evdi_flip_workqueue_init(dev);
}
void evdi_modeset_cleanup(struct drm_device *dev)
{
EVDI_CHECKPT();
evdi_flip_workqueue_cleanup(dev);
drm_mode_config_cleanup(dev);
}

View File

@@ -0,0 +1,850 @@
/*
* Copyright (c) 2013 - 2018 DisplayLink (UK) Ltd.
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include "linux/thread_info.h"
#include "linux/mm.h"
#include <drm/drmP.h>
#include <drm/drm_edid.h>
#include <uapi/drm/evdi_drm.h>
#include "evdi_drv.h"
#include "evdi_cursor.h"
#include <linux/mutex.h>
#include <linux/compiler.h>
struct evdi_event_cursor_set_pending {
struct drm_pending_event base;
struct drm_evdi_event_cursor_set cursor_set;
};
struct evdi_event_cursor_move_pending {
struct drm_pending_event base;
struct drm_evdi_event_cursor_move cursor_move;
};
struct evdi_event_update_ready_pending {
struct drm_pending_event base;
struct drm_evdi_event_update_ready update_ready;
};
struct evdi_event_dpms_pending {
struct drm_pending_event base;
struct drm_evdi_event_dpms dpms;
};
struct evdi_event_mode_changed_pending {
struct drm_pending_event base;
struct drm_evdi_event_mode_changed mode_changed;
};
struct evdi_event_crtc_state_pending {
struct drm_pending_event base;
struct drm_evdi_event_crtc_state crtc_state;
};
#define MAX_DIRTS 16
#define EDID_EXT_BLOCK_SIZE 128
#define MAX_EDID_SIZE (255 * EDID_EXT_BLOCK_SIZE + sizeof(struct edid))
struct evdi_painter {
bool is_connected;
struct edid *edid;
unsigned int edid_length;
struct mutex lock;
struct mutex new_scanout_fb_lock;
struct drm_clip_rect dirty_rects[MAX_DIRTS];
int num_dirts;
struct evdi_framebuffer *new_scanout_fb;
struct evdi_framebuffer *scanout_fb;
struct drm_file *drm_filp;
bool was_update_requested;
};
static void expand_rect(struct drm_clip_rect *a, const struct drm_clip_rect *b)
{
a->x1 = min(a->x1, b->x1);
a->y1 = min(a->y1, b->y1);
a->x2 = max(a->x2, b->x2);
a->y2 = max(a->y2, b->y2);
}
static int rect_area(const struct drm_clip_rect *r)
{
return (r->x2 - r->x1) * (r->y2 - r->y1);
}
static void merge_dirty_rects(struct drm_clip_rect *rects, int *count)
{
int a, b;
for (a = 0; a < *count - 1; ++a) {
for (b = a + 1; b < *count;) {
/* collapse to bounding rect if it is fewer pixels */
const int area_a = rect_area(&rects[a]);
const int area_b = rect_area(&rects[b]);
struct drm_clip_rect bounding_rect = rects[a];
expand_rect(&bounding_rect, &rects[b]);
if (rect_area(&bounding_rect) <= area_a + area_b) {
rects[a] = bounding_rect;
rects[b] = rects[*count - 1];
/* repass */
b = a + 1;
--*count;
} else {
++b;
}
}
}
}
static void collapse_dirty_rects(struct drm_clip_rect *rects, int *count)
{
int i;
EVDI_CHECKPT();
EVDI_WARN("Not enough space for clip rects! Rects will be collapsed");
for (i = 1; i < *count; ++i)
expand_rect(&rects[0], &rects[i]);
*count = 1;
}
static int copy_primary_pixels(struct evdi_framebuffer *ufb,
char __user *buffer,
int buf_byte_stride,
int num_rects, struct drm_clip_rect *rects,
int const max_x,
int const max_y)
{
struct drm_framebuffer *fb = &ufb->base;
struct drm_clip_rect *r;
EVDI_CHECKPT();
for (r = rects; r != rects + num_rects; ++r) {
const int byte_offset = r->x1 * 4;
const int byte_span = (r->x2 - r->x1) * 4;
const int src_offset = fb->pitches[0] * r->y1 + byte_offset;
const char *src = (char *)ufb->obj->vmapping + src_offset;
const int dst_offset = buf_byte_stride * r->y1 + byte_offset;
char __user *dst = buffer + dst_offset;
int y = r->y2 - r->y1;
/* rect size may correspond to previous resolution */
if (max_x < r->x2 || max_y < r->y2) {
EVDI_WARN("Rect size beyond expected dimensions\n");
return -EFAULT;
}
EVDI_VERBOSE("copy rect %d,%d-%d,%d\n", r->x1, r->y1, r->x2,
r->y2);
for (; y > 0; --y) {
if (copy_to_user(dst, src, byte_span))
return -EFAULT;
src += fb->pitches[0];
dst += buf_byte_stride;
}
}
return 0;
}
static void copy_cursor_pixels(struct evdi_framebuffer *efb,
char __user *buffer,
int buf_byte_stride,
struct evdi_cursor *cursor)
{
if (evdi_enable_cursor_blending) {
evdi_cursor_lock(cursor);
if (evdi_cursor_compose_and_copy(cursor,
efb,
buffer,
buf_byte_stride))
EVDI_ERROR("Failed to blend cursor\n");
evdi_cursor_unlock(cursor);
}
}
#define painter_lock(painter) \
do { \
EVDI_VERBOSE("Painter lock\n"); \
mutex_lock(&painter->lock); \
} while (0)
#define painter_unlock(painter) \
do { \
EVDI_VERBOSE("Painter unlock\n"); \
mutex_unlock(&painter->lock); \
} while (0)
bool evdi_painter_is_connected(struct evdi_device *evdi)
{
if (evdi && evdi->painter)
return evdi->painter->is_connected;
return false;
}
u8 *evdi_painter_get_edid_copy(struct evdi_device *evdi)
{
u8 *block = NULL;
EVDI_CHECKPT();
painter_lock(evdi->painter);
if (evdi_painter_is_connected(evdi) &&
evdi->painter->edid &&
evdi->painter->edid_length) {
block = kmalloc(evdi->painter->edid_length, GFP_KERNEL);
if (block) {
memcpy(block,
evdi->painter->edid,
evdi->painter->edid_length);
EVDI_DEBUG("(dev=%d) EDID valid\n", evdi->dev_index);
}
}
painter_unlock(evdi->painter);
return block;
}
static void evdi_painter_send_event(struct drm_file *drm_filp,
struct list_head *event_link)
{
list_add_tail(event_link, &drm_filp->event_list);
wake_up_interruptible(&drm_filp->event_wait);
}
static void evdi_painter_send_update_ready(struct evdi_painter *painter)
{
struct evdi_event_update_ready_pending *event;
if (painter->drm_filp) {
event = kzalloc(sizeof(*event), GFP_KERNEL);
event->update_ready.base.type = DRM_EVDI_EVENT_UPDATE_READY;
event->update_ready.base.length = sizeof(event->update_ready);
event->base.event = &event->update_ready.base;
event->base.file_priv = painter->drm_filp;
event->base.destroy =
(void (*)(struct drm_pending_event *))kfree;
evdi_painter_send_event(painter->drm_filp, &event->base.link);
} else {
EVDI_WARN("Painter is not connected!");
}
}
static uint32_t evdi_painter_get_gem_handle(struct evdi_painter *painter,
struct evdi_gem_object *obj)
{
uint32_t handle = 0;
if (!obj)
return 0;
handle = evdi_gem_object_handle_lookup(painter->drm_filp, &obj->base);
if (handle)
return handle;
if (drm_gem_handle_create(painter->drm_filp,
&obj->base, &handle)) {
EVDI_ERROR("Failed to create gem handle for %p\n",
painter->drm_filp);
}
return handle;
}
void evdi_painter_send_cursor_set(struct evdi_painter *painter,
struct evdi_cursor *cursor)
{
struct evdi_event_cursor_set_pending *event;
struct evdi_gem_object *eobj = NULL;
if (painter->drm_filp) {
event = kzalloc(sizeof(*event), GFP_KERNEL);
event->cursor_set.base.type = DRM_EVDI_EVENT_CURSOR_SET;
event->cursor_set.base.length =
sizeof(event->cursor_set);
evdi_cursor_lock(cursor);
event->cursor_set.enabled = evdi_cursor_enabled(cursor);
evdi_cursor_hotpoint(cursor,
&event->cursor_set.hot_x,
&event->cursor_set.hot_y);
evdi_cursor_size(cursor,
&event->cursor_set.width,
&event->cursor_set.height);
evdi_cursor_format(cursor,
&event->cursor_set.pixel_format);
evdi_cursor_stride(cursor,
&event->cursor_set.stride);
eobj = evdi_cursor_gem(cursor);
event->cursor_set.buffer_handle =
evdi_painter_get_gem_handle(painter, eobj);
if (eobj)
event->cursor_set.buffer_length = eobj->base.size;
if (!event->cursor_set.buffer_handle) {
event->cursor_set.enabled = false;
event->cursor_set.buffer_length = 0;
}
evdi_cursor_unlock(cursor);
event->base.event = &event->cursor_set.base;
event->base.file_priv = painter->drm_filp;
event->base.destroy =
(void (*)(struct drm_pending_event *))kfree;
evdi_painter_send_event(painter->drm_filp, &event->base.link);
} else {
EVDI_WARN("Painter is not connected!");
}
}
void evdi_painter_send_cursor_move(struct evdi_painter *painter,
struct evdi_cursor *cursor)
{
struct evdi_event_cursor_move_pending *event;
if (painter->drm_filp) {
event = kzalloc(sizeof(*event), GFP_KERNEL);
event->cursor_move.base.type = DRM_EVDI_EVENT_CURSOR_MOVE;
event->cursor_move.base.length = sizeof(event->cursor_move);
evdi_cursor_lock(cursor);
evdi_cursor_position(
cursor,
&event->cursor_move.x,
&event->cursor_move.y);
evdi_cursor_unlock(cursor);
event->base.event = &event->cursor_move.base;
event->base.file_priv = painter->drm_filp;
event->base.destroy =
(void (*)(struct drm_pending_event *))kfree;
evdi_painter_send_event(painter->drm_filp, &event->base.link);
} else {
EVDI_WARN("Painter is not connected!");
}
}
static void evdi_painter_send_dpms(struct evdi_painter *painter, int mode)
{
struct evdi_event_dpms_pending *event;
if (painter->drm_filp) {
event = kzalloc(sizeof(*event), GFP_KERNEL);
event->dpms.base.type = DRM_EVDI_EVENT_DPMS;
event->dpms.base.length = sizeof(event->dpms);
event->dpms.mode = mode;
event->base.event = &event->dpms.base;
event->base.file_priv = painter->drm_filp;
event->base.destroy =
(void (*)(struct drm_pending_event *))kfree;
evdi_painter_send_event(painter->drm_filp, &event->base.link);
} else {
EVDI_WARN("Painter is not connected!");
}
}
static void evdi_painter_send_crtc_state(struct evdi_painter *painter,
int state)
{
struct evdi_event_crtc_state_pending *event;
if (painter->drm_filp) {
event = kzalloc(sizeof(*event), GFP_KERNEL);
event->crtc_state.base.type = DRM_EVDI_EVENT_CRTC_STATE;
event->crtc_state.base.length = sizeof(event->crtc_state);
event->crtc_state.state = state;
event->base.event = &event->crtc_state.base;
event->base.file_priv = painter->drm_filp;
event->base.destroy =
(void (*)(struct drm_pending_event *))kfree;
evdi_painter_send_event(painter->drm_filp, &event->base.link);
} else {
EVDI_WARN("Painter is not connected!");
}
}
static void evdi_painter_send_mode_changed(
struct evdi_painter *painter,
struct drm_display_mode *current_mode,
int32_t bits_per_pixel,
uint32_t pixel_format)
{
struct evdi_event_mode_changed_pending *event;
if (painter->drm_filp) {
event = kzalloc(sizeof(*event), GFP_KERNEL);
event->mode_changed.base.type = DRM_EVDI_EVENT_MODE_CHANGED;
event->mode_changed.base.length = sizeof(event->mode_changed);
event->mode_changed.hdisplay = current_mode->hdisplay;
event->mode_changed.vdisplay = current_mode->vdisplay;
event->mode_changed.vrefresh =
drm_mode_vrefresh(current_mode);
event->mode_changed.bits_per_pixel = bits_per_pixel;
event->mode_changed.pixel_format = pixel_format;
event->base.event = &event->mode_changed.base;
event->base.file_priv = painter->drm_filp;
event->base.destroy =
(void (*)(struct drm_pending_event *))kfree;
evdi_painter_send_event(painter->drm_filp, &event->base.link);
} else {
EVDI_WARN("Painter is not connected!");
}
}
void evdi_painter_mark_dirty(struct evdi_device *evdi,
const struct drm_clip_rect *dirty_rect)
{
struct drm_clip_rect rect;
struct evdi_framebuffer *efb = NULL;
struct evdi_painter *painter = evdi->painter;
painter_lock(evdi->painter);
efb = evdi->painter->scanout_fb;
if (!efb) {
EVDI_WARN("(dev=%d) Skip clip rect. Scanout buffer not set.\n",
evdi->dev_index);
goto unlock;
}
rect = evdi_framebuffer_sanitize_rect(efb, dirty_rect);
EVDI_VERBOSE("(dev=%d) %d,%d-%d,%d\n", evdi->dev_index, rect.x1,
rect.y1, rect.x2, rect.y2);
if (painter->num_dirts == MAX_DIRTS)
merge_dirty_rects(&painter->dirty_rects[0],
&painter->num_dirts);
if (painter->num_dirts == MAX_DIRTS)
collapse_dirty_rects(&painter->dirty_rects[0],
&painter->num_dirts);
memcpy(&painter->dirty_rects[painter->num_dirts], &rect, sizeof(rect));
painter->num_dirts++;
if (painter->was_update_requested) {
evdi_painter_send_update_ready(painter);
painter->was_update_requested = false;
}
unlock:
painter_unlock(evdi->painter);
}
void evdi_painter_dpms_notify(struct evdi_device *evdi, int mode)
{
struct evdi_painter *painter = evdi->painter;
if (painter) {
EVDI_DEBUG("(dev=%d) Notifying dpms mode: %d\n",
evdi->dev_index, mode);
evdi_painter_send_dpms(painter, mode);
} else {
EVDI_WARN("Painter does not exist!");
}
}
void evdi_painter_crtc_state_notify(struct evdi_device *evdi, int state)
{
struct evdi_painter *painter = evdi->painter;
if (painter) {
EVDI_DEBUG("(dev=%d) Notifying crtc state: %d\n",
evdi->dev_index, state);
evdi_painter_send_crtc_state(painter, state);
} else {
EVDI_WARN("Painter does not exist!");
}
}
void evdi_painter_mode_changed_notify(struct evdi_device *evdi,
struct drm_framebuffer *fb,
struct drm_display_mode *new_mode)
{
struct evdi_painter *painter = evdi->painter;
int bits_per_pixel = fb->bits_per_pixel;
uint32_t pixel_format = fb->pixel_format;
EVDI_DEBUG(
"(dev=%d) Notifying mode changed: %dx%d@%d; bpp %d; ",
evdi->dev_index, new_mode->hdisplay, new_mode->vdisplay,
drm_mode_vrefresh(new_mode), bits_per_pixel);
EVDI_DEBUG("pixel format %d\n", pixel_format);
evdi_painter_send_mode_changed(painter,
new_mode,
bits_per_pixel,
pixel_format);
}
static int
evdi_painter_connect(struct evdi_device *evdi,
void const __user *edid_data, unsigned int edid_length,
uint32_t sku_area_limit,
struct drm_file *file, int dev_index)
{
struct evdi_painter *painter = evdi->painter;
struct edid *new_edid = NULL;
int expected_edid_size = 0;
EVDI_DEBUG("(dev=%d) Process is trying to connect\n",
evdi->dev_index);
evdi_log_process();
if (edid_length < sizeof(struct edid)) {
EVDI_ERROR("Edid length too small\n");
return -EINVAL;
}
if (edid_length > MAX_EDID_SIZE) {
EVDI_ERROR("Edid length too large\n");
return -EINVAL;
}
new_edid = kzalloc(edid_length, GFP_KERNEL);
if (!new_edid)
return -ENOMEM;
if (copy_from_user(new_edid, edid_data, edid_length)) {
EVDI_ERROR("(dev=%d) Failed to read edid\n", dev_index);
kfree(new_edid);
return -EFAULT;
}
expected_edid_size = sizeof(struct edid) +
new_edid->extensions * EDID_EXT_BLOCK_SIZE;
if (expected_edid_size != edid_length) {
EVDI_ERROR("Wrong edid size. Expected %d but is %d\n",
expected_edid_size, edid_length);
kfree(new_edid);
return -EINVAL;
}
if (painter->drm_filp)
EVDI_WARN("(dev=%d) Double connect - replacing %p with %p\n",
dev_index, painter->drm_filp, file);
painter_lock(painter);
evdi->dev_index = dev_index;
evdi->sku_area_limit = sku_area_limit;
painter->drm_filp = file;
kfree(painter->edid);
painter->edid_length = edid_length;
painter->edid = new_edid;
painter->is_connected = true;
painter_unlock(painter);
EVDI_DEBUG("(dev=%d) Connected with %p\n", evdi->dev_index,
painter->drm_filp);
drm_helper_hpd_irq_event(evdi->ddev);
drm_helper_resume_force_mode(evdi->ddev);
return 0;
}
static int evdi_painter_disconnect(struct evdi_device *evdi,
struct drm_file *file)
{
struct evdi_painter *painter = evdi->painter;
EVDI_CHECKPT();
painter_lock(painter);
if (file != painter->drm_filp) {
painter_unlock(painter);
return -EFAULT;
}
evdi_painter_set_new_scanout_buffer(evdi, NULL);
if (painter->scanout_fb) {
drm_framebuffer_unreference(&painter->scanout_fb->base);
painter->scanout_fb = NULL;
}
painter->is_connected = false;
EVDI_DEBUG("(dev=%d) Disconnected from %p\n", evdi->dev_index,
painter->drm_filp);
evdi_cursor_enable(evdi->cursor, false);
painter->drm_filp = NULL;
evdi->dev_index = -1;
painter->was_update_requested = false;
painter_unlock(painter);
drm_helper_hpd_irq_event(evdi->ddev);
return 0;
}
void evdi_painter_close(struct evdi_device *evdi, struct drm_file *file)
{
EVDI_CHECKPT();
if (evdi->painter)
evdi_painter_disconnect(evdi, file);
else
EVDI_WARN("Painter does not exist!");
}
int evdi_painter_connect_ioctl(struct drm_device *drm_dev, void *data,
struct drm_file *file)
{
int ret;
struct evdi_device *evdi = drm_dev->dev_private;
struct evdi_painter *painter = evdi->painter;
struct drm_evdi_connect *cmd = data;
EVDI_CHECKPT();
if (painter) {
if (cmd->connected)
ret = evdi_painter_connect(evdi,
cmd->edid,
cmd->edid_length,
cmd->sku_area_limit,
file,
cmd->dev_index);
else
ret = evdi_painter_disconnect(evdi, file);
if (ret) {
EVDI_WARN("(dev=%d) (pid=%d) disconnect failed\n",
evdi->dev_index, (int)task_pid_nr(current));
}
return ret;
}
EVDI_WARN("Painter does not exist!");
return -ENODEV;
}
int evdi_painter_grabpix_ioctl(struct drm_device *drm_dev, void *data,
__always_unused struct drm_file *file)
{
struct evdi_device *evdi = drm_dev->dev_private;
struct evdi_painter *painter = evdi->painter;
struct drm_evdi_grabpix *cmd = data;
struct drm_framebuffer *fb = NULL;
struct evdi_framebuffer *efb = NULL;
int err = 0;
EVDI_CHECKPT();
if (!painter)
return -ENODEV;
painter_lock(painter);
efb = painter->scanout_fb;
if (!efb) {
EVDI_ERROR("Scanout buffer not set\n");
err = -EAGAIN;
goto unlock;
}
if (painter->was_update_requested) {
EVDI_WARN("(dev=%d) Update ready not sent,",
evdi->dev_index);
EVDI_WARN(" but pixels are grabbed.\n");
}
fb = &efb->base;
if (!efb->obj->vmapping) {
if (evdi_gem_vmap(efb->obj) == -ENOMEM) {
EVDI_ERROR("Failed to map scanout buffer\n");
err = -EFAULT;
goto unlock;
}
if (!efb->obj->vmapping) {
EVDI_ERROR("Inexistent vmapping\n");
err = -EFAULT;
goto unlock;
}
}
if (cmd->buf_width != fb->width ||
cmd->buf_height != fb->height) {
EVDI_ERROR("Invalid buffer dimension\n");
err = -EINVAL;
goto unlock;
}
if (cmd->num_rects < 1) {
EVDI_ERROR("No space for clip rects\n");
err = -EINVAL;
goto unlock;
}
if (cmd->mode == EVDI_GRABPIX_MODE_DIRTY) {
if (painter->num_dirts < 0) {
err = -EAGAIN;
goto unlock;
}
merge_dirty_rects(&painter->dirty_rects[0],
&painter->num_dirts);
if (painter->num_dirts > cmd->num_rects)
collapse_dirty_rects(&painter->dirty_rects[0],
&painter->num_dirts);
cmd->num_rects = painter->num_dirts;
if (copy_to_user(cmd->rects, painter->dirty_rects,
cmd->num_rects * sizeof(cmd->rects[0])))
err = -EFAULT;
if (err == 0)
err = copy_primary_pixels(efb,
cmd->buffer,
cmd->buf_byte_stride,
painter->num_dirts,
painter->dirty_rects,
cmd->buf_width,
cmd->buf_height);
if (err == 0)
copy_cursor_pixels(efb,
cmd->buffer,
cmd->buf_byte_stride,
evdi->cursor);
painter->num_dirts = 0;
}
unlock:
painter_unlock(painter);
return err;
}
int evdi_painter_request_update_ioctl(struct drm_device *drm_dev,
__always_unused void *data,
__always_unused struct drm_file *file)
{
struct evdi_device *evdi = drm_dev->dev_private;
struct evdi_painter *painter = evdi->painter;
int result = 0;
if (painter) {
painter_lock(painter);
if (painter->was_update_requested) {
EVDI_WARN
("(dev=%d) Update was already requested - ignoring\n",
evdi->dev_index);
} else {
if (painter->num_dirts > 0)
result = 1;
else
painter->was_update_requested = true;
}
painter_unlock(painter);
return result;
} else {
return -ENODEV;
}
}
int evdi_painter_init(struct evdi_device *dev)
{
EVDI_CHECKPT();
dev->painter = kzalloc(sizeof(*dev->painter), GFP_KERNEL);
if (dev->painter) {
mutex_init(&dev->painter->lock);
mutex_init(&dev->painter->new_scanout_fb_lock);
dev->painter->edid = NULL;
dev->painter->edid_length = 0;
return 0;
}
return -ENOMEM;
}
void evdi_painter_cleanup(struct evdi_device *evdi)
{
struct evdi_painter *painter = evdi->painter;
EVDI_CHECKPT();
if (painter) {
painter_lock(painter);
kfree(painter->edid);
painter->edid_length = 0;
painter->edid = 0;
painter_unlock(painter);
} else {
EVDI_WARN("Painter does not exist\n");
}
}
/*
* This can be called from multiple threads so we need to lock during
* *new_scanout_fb* assignment.
* It is called from *evdi_crtc_page_flip* which must return immediately.
* If we lock here whole painter object it will interfere with grab_pics
* ioctl (which can take some time).
* Because of that we lock only on the *new_scanout_fb*.
*/
void evdi_painter_set_new_scanout_buffer(struct evdi_device *evdi,
struct evdi_framebuffer *newfb)
{
struct evdi_painter *painter = evdi->painter;
struct evdi_framebuffer *oldfb = NULL;
if (newfb)
drm_framebuffer_reference(&newfb->base);
mutex_lock(&painter->new_scanout_fb_lock);
oldfb = painter->new_scanout_fb;
painter->new_scanout_fb = newfb;
mutex_unlock(&painter->new_scanout_fb_lock);
if (oldfb)
drm_framebuffer_unreference(&oldfb->base);
}
void evdi_painter_commit_scanout_buffer(struct evdi_device *evdi)
{
struct evdi_painter *painter = evdi->painter;
struct evdi_framebuffer *newfb = NULL;
struct evdi_framebuffer *oldfb = NULL;
painter_lock(painter);
mutex_lock(&painter->new_scanout_fb_lock);
newfb = painter->new_scanout_fb;
if (newfb)
drm_framebuffer_reference(&newfb->base);
oldfb = painter->scanout_fb;
painter->scanout_fb = newfb;
mutex_unlock(&painter->new_scanout_fb_lock);
painter_unlock(painter);
if (oldfb)
drm_framebuffer_unreference(&oldfb->base);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
*
* This file is subject to the terms and conditions of the GNU General Public
* License v2. See the file COPYING in the main directory of this archive for
* more details.
*/
#include "evdi_drv.h"
static ssize_t frame_count_show(struct device *dev,
__always_unused struct device_attribute *attr,
char *buf)
{
struct drm_minor *drm_minor = dev_get_drvdata(dev);
struct drm_device *drm_dev = drm_minor->dev;
struct evdi_device *evdi = drm_dev->dev_private;
return snprintf(buf, PAGE_SIZE, "%d\n",
atomic_read(&evdi->frame_count));
}
static struct device_attribute evdi_device_attributes[] = {
__ATTR_RO(frame_count),
};
void evdi_stats_init(struct evdi_device *evdi)
{
int i, retval;
DRM_INFO("evdi: evdi_stats_init\n");
atomic_set(&evdi->frame_count, 0);
for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++) {
retval =
device_create_file(&evdi->ddev->primary->kdev,
&evdi_device_attributes[i]);
if (retval)
DRM_ERROR("evdi: device_create_file failed %d\n",
retval);
}
}
void evdi_stats_cleanup(struct evdi_device *evdi)
{
int i;
DRM_INFO("evdi: evdi_stats_cleanup\n");
for (i = 0; i < ARRAY_SIZE(evdi_device_attributes); i++)
device_remove_file(&evdi->ddev->primary->kdev,
&evdi_device_attributes[i]);
}