851 lines
21 KiB
C
851 lines
21 KiB
C
/*
|
|
* 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);
|
|
}
|