| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * 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; |
| } |
| |