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_alloc(&evdi->cursor); if (ret) goto err; EVDI_CHECKPT(); ret = evdi_modeset_init(dev); if (ret) goto err; ret = evdi_fbdev_init(dev); if (ret) goto err; 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: evdi_fbdev_cleanup(dev); 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); #if KERNEL_VERSION(4, 8, 0) <= LINUX_VERSION_CODE #elif KERNEL_VERSION(4, 7, 0) <= LINUX_VERSION_CODE drm_connector_unregister_all(dev); #else drm_connector_unplug_all(dev); #endif evdi_fbdev_unplug(dev); if (evdi->cursor) evdi_cursor_free(evdi->cursor); evdi_painter_cleanup(evdi); evdi_stats_cleanup(evdi); evdi_fbdev_cleanup(dev); evdi_modeset_cleanup(dev); kfree(evdi); return 0; }
void evdi_driver_preclose(struct drm_device *drm_dev, struct drm_file *file) { struct evdi_device *evdi = drm_dev->dev_private; EVDI_CHECKPT(); if (evdi) evdi_painter_close(evdi, file); }
static void evdi_fb_copyarea(struct fb_info *info, const struct fb_copyarea *region) { struct evdi_fbdev *ufbdev = info->par; EVDI_CHECKPT(); 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(); evdi_handle_damage(&ufbdev->ufb, image->dx, image->dy, image->width, image->height); }
static void evdi_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { struct evdi_fbdev *ufbdev = info->par; EVDI_CHECKPT(); evdi_handle_damage(&ufbdev->ufb, rect->dx, rect->dy, rect->width, rect->height); }
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!"); }
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; }
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); dev->painter->edid = NULL; dev->painter->edid_length = 0; return 0; } return -ENOMEM; }
static void evdi_user_framebuffer_destroy(struct drm_framebuffer *fb) { struct evdi_framebuffer *ufb = to_evdi_fb(fb); EVDI_CHECKPT(); if (ufb->obj->vmapping) evdi_gem_vunmap(ufb->obj); if (ufb->obj) drm_gem_object_unreference_unlocked(&ufb->obj->base); drm_framebuffer_cleanup(fb); kfree(ufb); }
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, #if KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE 0, ufb->obj->base.size, #endif 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, #if KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE 0, ufb->obj->base.size, #endif DMA_FROM_DEVICE); atomic_add(1, &evdi->frame_count); unlock: drm_modeset_unlock_all(fb->dev); return ret; }
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"); } }
static int copy_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 evdi_cursor *cursor_copy) { 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 evdi_cursor_composing_and_copy(cursor_copy, ufb, buffer, buf_byte_stride, max_x, max_y); }
int evdi_handle_damage(struct evdi_framebuffer *fb, int x, int y, int width, int height) { struct drm_device *dev = fb->base.dev; struct evdi_device *evdi = dev->dev_private; EVDI_CHECKPT(); if (!fb->obj->vmapping) { if (evdi_gem_vmap(fb->obj) == -ENOMEM) { DRM_ERROR("failed to vmap fb\n"); return 0; } if (!fb->obj->vmapping) { DRM_ERROR("failed to vmapping\n"); return 0; } } { const int line_offset = fb->base.pitches[0] * y; const int byte_offset = line_offset + (x * 4); //RG24 const char* pix = (char*)fb->obj->vmapping + byte_offset; EVDI_VERBOSE("%p %d,%d-%dx%d %02x%02x%02x%02x%02x%02x%02x%02x\n", fb, x, y, width, height, ((int)(pix[0])&0xff), ((int)(pix[1])&0xff), ((int)(pix[2])&0xff), ((int)(pix[3])&0xff), ((int)(pix[4])&0xff), ((int)(pix[5])&0xff), ((int)(pix[6])&0xff), ((int)(pix[7])&0xff)); } { struct drm_clip_rect rect = { x, y, x + width, y + height }; evdi_painter_mark_dirty(evdi, fb, &rect); } return 0; }
static int evdi_user_framebuffer_dirty(struct drm_framebuffer *fb, struct drm_file *file, unsigned flags, unsigned color, struct drm_clip_rect *clips, unsigned 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->obj->base.import_attach) { ret = dma_buf_begin_cpu_access(ufb->obj->base.import_attach->dmabuf, 0, ufb->obj->base.size, 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, 0, ufb->obj->base.size, DMA_FROM_DEVICE); } atomic_add(1, &evdi->frame_count); unlock: drm_modeset_unlock_all(fb->dev); return ret; }
void 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) { EVDI_WARN ("(dev=%d) An unknown connection to %p tries to close us", evdi->dev_index, file); EVDI_WARN(" - ignoring\n"); painter_unlock(painter); return; } if (painter->new_scanout_fb) { drm_framebuffer_unreference(&painter->new_scanout_fb->base); painter->new_scanout_fb = 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); painter->drm_filp = NULL; evdi->dev_index = -1; memset(&painter->current_mode, '\0', sizeof(struct drm_display_mode)); painter->was_update_requested = false; painter_unlock(painter); drm_helper_hpd_irq_event(evdi->ddev); }
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_set_new_scanout_buffer(evdi, fb); evdi_flip_scanout_buffer(evdi); evdi_painter_mark_dirty(evdi, &rect); return 0; }
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) %02x %02x %02x\n", evdi->dev_index, block[0], block[1], block[2]); } } painter_unlock(evdi->painter); return block; }
int evdi_painter_connect_ioctl(struct drm_device *drm_dev, void *data, struct drm_file *file) { 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) evdi_painter_connect(evdi, cmd->edid, cmd->edid_length, file, cmd->dev_index); else evdi_painter_disconnect(evdi, file); return 0; } 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; struct evdi_cursor *cursor_copy = NULL; int err = 0; EVDI_CHECKPT(); if (!painter) return -ENODEV; mutex_lock(&drm_dev->struct_mutex); if (evdi_cursor_alloc(&cursor_copy) == 0) evdi_cursor_copy(cursor_copy, evdi->cursor); mutex_unlock(&drm_dev->struct_mutex); 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("Failed to map scanout buffer\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; else err = copy_pixels(efb, cmd->buffer, cmd->buf_byte_stride, painter->num_dirts, painter->dirty_rects, cmd->buf_width, cmd->buf_height, cursor_copy); painter->num_dirts = 0; } unlock: painter_unlock(painter); if (cursor_copy) evdi_cursor_free(cursor_copy); return err; }
int evdi_painter_connect(struct evdi_device *evdi, void const __user *edid_data, unsigned int edid_length, struct drm_file *file, int dev_index) { struct evdi_painter *painter = evdi->painter; struct edid *new_edid = NULL; int expected_edid_size = 0; EVDI_CHECKPT(); 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) LSP 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); EVDI_DEBUG("(dev=%d) Connected with %p\n", evdi->dev_index, painter->drm_filp); painter_lock(painter); evdi->dev_index = dev_index; painter->drm_filp = file; kfree(painter->edid); painter->edid_length = edid_length; painter->edid = new_edid; painter->is_connected = true; painter_unlock(painter); drm_helper_hpd_irq_event(evdi->ddev); drm_helper_resume_force_mode(evdi->ddev); return 0; }
static void painter_unlock(struct evdi_painter *painter) { EVDI_CHECKPT(); mutex_unlock(&painter->lock); }