udl_fb.c 13 KB
Newer Older
Dave Airlie's avatar
Dave Airlie committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * Copyright (C) 2012 Red Hat
 *
 * based in parts on udlfb.c:
 * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
 * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
 * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
 *
 * 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/slab.h>
#include <linux/fb.h>
16
#include <linux/dma-buf.h>
17
#include <linux/mem_encrypt.h>
Dave Airlie's avatar
Dave Airlie committed
18

19 20 21
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
Dave Airlie's avatar
Dave Airlie committed
22 23
#include "udl_drv.h"

24
#include <drm/drm_fb_helper.h>
Dave Airlie's avatar
Dave Airlie committed
25

26
#define DL_DEFIO_WRITE_DELAY    (HZ/20) /* fb_deferred_io.delay in jiffies */
Dave Airlie's avatar
Dave Airlie committed
27

28
static int fb_defio = 0;  /* Optionally enable experimental fb_defio mmap support */
Dave Airlie's avatar
Dave Airlie committed
29 30 31 32 33 34 35 36 37 38 39 40
static int fb_bpp = 16;

module_param(fb_bpp, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);
module_param(fb_defio, int, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP);

struct udl_fbdev {
	struct drm_fb_helper helper;
	struct udl_framebuffer ufb;
	int fb_count;
};

#define DL_ALIGN_UP(x, a) ALIGN(x, a)
41
#define DL_ALIGN_DOWN(x, a) ALIGN_DOWN(x, a)
Dave Airlie's avatar
Dave Airlie committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

/** Read the red component (0..255) of a 32 bpp colour. */
#define DLO_RGB_GETRED(col) (uint8_t)((col) & 0xFF)

/** Read the green component (0..255) of a 32 bpp colour. */
#define DLO_RGB_GETGRN(col) (uint8_t)(((col) >> 8) & 0xFF)

/** Read the blue component (0..255) of a 32 bpp colour. */
#define DLO_RGB_GETBLU(col) (uint8_t)(((col) >> 16) & 0xFF)

/** Return red/green component of a 16 bpp colour number. */
#define DLO_RG16(red, grn) (uint8_t)((((red) & 0xF8) | ((grn) >> 5)) & 0xFF)

/** Return green/blue component of a 16 bpp colour number. */
#define DLO_GB16(grn, blu) (uint8_t)(((((grn) & 0x1C) << 3) | ((blu) >> 3)) & 0xFF)

/** Return 8 bpp colour number from red, green and blue components. */
#define DLO_RGB8(red, grn, blu) ((((red) << 5) | (((grn) & 3) << 3) | ((blu) & 7)) & 0xFF)

#if 0
static uint8_t rgb8(uint32_t col)
{
	uint8_t red = DLO_RGB_GETRED(col);
	uint8_t grn = DLO_RGB_GETGRN(col);
	uint8_t blu = DLO_RGB_GETBLU(col);

	return DLO_RGB8(red, grn, blu);
}

static uint16_t rgb16(uint32_t col)
{
	uint8_t red = DLO_RGB_GETRED(col);
	uint8_t grn = DLO_RGB_GETGRN(col);
	uint8_t blu = DLO_RGB_GETBLU(col);

	return (DLO_RG16(red, grn) << 8) + DLO_GB16(grn, blu);
}
#endif

int udl_handle_damage(struct udl_framebuffer *fb, int x, int y,
		      int width, int height)
{
	struct drm_device *dev = fb->base.dev;
85
	struct udl_device *udl = to_udl(dev);
Dave Airlie's avatar
Dave Airlie committed
86 87 88 89 90 91 92
	int i, ret;
	char *cmd;
	cycles_t start_cycles, end_cycles;
	int bytes_sent = 0;
	int bytes_identical = 0;
	struct urb *urb;
	int aligned_x;
Mikulas Patocka's avatar
Mikulas Patocka committed
93 94 95 96
	int log_bpp;

	BUG_ON(!is_power_of_2(fb->base.format->cpp[0]));
	log_bpp = __ffs(fb->base.format->cpp[0]);
Dave Airlie's avatar
Dave Airlie committed
97 98 99 100

	if (!fb->active_16)
		return 0;

101 102 103 104 105 106 107 108 109 110 111
	if (!fb->obj->vmapping) {
		ret = udl_gem_vmap(fb->obj);
		if (ret == -ENOMEM) {
			DRM_ERROR("failed to vmap fb\n");
			return 0;
		}
		if (!fb->obj->vmapping) {
			DRM_ERROR("failed to vmapping\n");
			return 0;
		}
	}
Dave Airlie's avatar
Dave Airlie committed
112 113 114 115 116 117 118 119 120 121

	aligned_x = DL_ALIGN_DOWN(x, sizeof(unsigned long));
	width = DL_ALIGN_UP(width + (x-aligned_x), sizeof(unsigned long));
	x = aligned_x;

	if ((width <= 0) ||
	    (x + width > fb->base.width) ||
	    (y + height > fb->base.height))
		return -EINVAL;

122 123
	start_cycles = get_cycles();

Dave Airlie's avatar
Dave Airlie committed
124 125 126 127 128
	urb = udl_get_urb(dev);
	if (!urb)
		return 0;
	cmd = urb->transfer_buffer;

129
	for (i = y; i < y + height ; i++) {
Dave Airlie's avatar
Dave Airlie committed
130
		const int line_offset = fb->base.pitches[0] * i;
Mikulas Patocka's avatar
Mikulas Patocka committed
131 132 133
		const int byte_offset = line_offset + (x << log_bpp);
		const int dev_byte_offset = (fb->base.width * i + x) << log_bpp;
		if (udl_render_hline(dev, log_bpp, &urb,
Dave Airlie's avatar
Dave Airlie committed
134
				     (char *) fb->obj->vmapping,
135
				     &cmd, byte_offset, dev_byte_offset,
Mikulas Patocka's avatar
Mikulas Patocka committed
136
				     width << log_bpp,
Dave Airlie's avatar
Dave Airlie committed
137 138 139 140 141 142
				     &bytes_identical, &bytes_sent))
			goto error;
	}

	if (cmd > (char *) urb->transfer_buffer) {
		/* Send partial buffer remaining before exiting */
143 144 145 146
		int len;
		if (cmd < (char *) urb->transfer_buffer + urb->transfer_buffer_length)
			*cmd++ = 0xAF;
		len = cmd - (char *) urb->transfer_buffer;
Dave Airlie's avatar
Dave Airlie committed
147 148 149 150 151 152 153 154
		ret = udl_submit_urb(dev, urb, len);
		bytes_sent += len;
	} else
		udl_urb_completion(urb);

error:
	atomic_add(bytes_sent, &udl->bytes_sent);
	atomic_add(bytes_identical, &udl->bytes_identical);
Mikulas Patocka's avatar
Mikulas Patocka committed
155
	atomic_add((width * height) << log_bpp, &udl->bytes_rendered);
Dave Airlie's avatar
Dave Airlie committed
156 157 158 159 160 161 162 163 164 165 166 167
	end_cycles = get_cycles();
	atomic_add(((unsigned int) ((end_cycles - start_cycles)
		    >> 10)), /* Kcycles */
		   &udl->cpu_kcycles_used);

	return 0;
}

static int udl_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;
168
	unsigned long offset;
Dave Airlie's avatar
Dave Airlie committed
169 170
	unsigned long page, pos;

171 172 173 174 175 176
	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
		return -EINVAL;

	offset = vma->vm_pgoff << PAGE_SHIFT;

	if (offset > info->fix.smem_len || size > info->fix.smem_len - offset)
Dave Airlie's avatar
Dave Airlie committed
177 178 179 180 181 182 183
		return -EINVAL;

	pos = (unsigned long)info->fix.smem_start + offset;

	pr_notice("mmap() framebuffer addr:%lu size:%lu\n",
		  pos, size);

184 185 186
	/* We don't want the framebuffer to be mapped encrypted */
	vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);

Dave Airlie's avatar
Dave Airlie committed
187 188 189 190 191 192 193 194 195 196 197 198 199
	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;
	}

200
	/* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by remap_pfn_range() */
Dave Airlie's avatar
Dave Airlie committed
201 202 203 204 205 206 207 208 209 210 211 212
	return 0;
}

/*
 * 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 udl_fb_open(struct fb_info *info, int user)
{
	struct udl_fbdev *ufbdev = info->par;
	struct drm_device *dev = ufbdev->ufb.base.dev;
213
	struct udl_device *udl = to_udl(dev);
Dave Airlie's avatar
Dave Airlie committed
214 215

	/* If the USB device is gone, we don't accept new opens */
216
	if (drm_dev_is_unplugged(udl->ddev))
Dave Airlie's avatar
Dave Airlie committed
217 218 219 220
		return -ENODEV;

	ufbdev->fb_count++;

221
#ifdef CONFIG_DRM_FBDEV_EMULATION
Dave Airlie's avatar
Dave Airlie committed
222 223 224 225 226
	if (fb_defio && (info->fbdefio == NULL)) {
		/* enable defio at last moment if not disabled by client */

		struct fb_deferred_io *fbdefio;

227
		fbdefio = kzalloc(sizeof(struct fb_deferred_io), GFP_KERNEL);
Dave Airlie's avatar
Dave Airlie committed
228 229 230

		if (fbdefio) {
			fbdefio->delay = DL_DEFIO_WRITE_DELAY;
231
			fbdefio->deferred_io = drm_fb_helper_deferred_io;
Dave Airlie's avatar
Dave Airlie committed
232 233 234 235 236
		}

		info->fbdefio = fbdefio;
		fb_deferred_io_init(info);
	}
237
#endif
Dave Airlie's avatar
Dave Airlie committed
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

	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 udl_fb_release(struct fb_info *info, int user)
{
	struct udl_fbdev *ufbdev = info->par;

	ufbdev->fb_count--;

255
#ifdef CONFIG_DRM_FBDEV_EMULATION
Dave Airlie's avatar
Dave Airlie committed
256 257 258 259 260 261
	if ((ufbdev->fb_count == 0) && (info->fbdefio)) {
		fb_deferred_io_cleanup(info);
		kfree(info->fbdefio);
		info->fbdefio = NULL;
		info->fbops->fb_mmap = udl_fb_mmap;
	}
262
#endif
Dave Airlie's avatar
Dave Airlie committed
263 264 265 266 267 268 269 270 271

	pr_warn("released /dev/fb%d user=%d count=%d\n",
		info->node, user, ufbdev->fb_count);

	return 0;
}

static struct fb_ops udlfb_ops = {
	.owner = THIS_MODULE,
272
	DRM_FB_HELPER_DEFAULT_OPS,
273 274 275
	.fb_fillrect = drm_fb_helper_sys_fillrect,
	.fb_copyarea = drm_fb_helper_sys_copyarea,
	.fb_imageblit = drm_fb_helper_sys_imageblit,
Dave Airlie's avatar
Dave Airlie committed
276 277 278 279 280 281 282 283 284 285 286 287 288
	.fb_mmap = udl_fb_mmap,
	.fb_open = udl_fb_open,
	.fb_release = udl_fb_release,
};

static int udl_user_framebuffer_dirty(struct drm_framebuffer *fb,
				      struct drm_file *file,
				      unsigned flags, unsigned color,
				      struct drm_clip_rect *clips,
				      unsigned num_clips)
{
	struct udl_framebuffer *ufb = to_udl_fb(fb);
	int i;
289
	int ret = 0;
Dave Airlie's avatar
Dave Airlie committed
290

291 292
	drm_modeset_lock_all(fb->dev);

Dave Airlie's avatar
Dave Airlie committed
293
	if (!ufb->active_16)
294
		goto unlock;
Dave Airlie's avatar
Dave Airlie committed
295

296 297 298 299
	if (ufb->obj->base.import_attach) {
		ret = dma_buf_begin_cpu_access(ufb->obj->base.import_attach->dmabuf,
					       DMA_FROM_DEVICE);
		if (ret)
300
			goto unlock;
301 302
	}

Dave Airlie's avatar
Dave Airlie committed
303
	for (i = 0; i < num_clips; i++) {
304
		ret = udl_handle_damage(ufb, clips[i].x1, clips[i].y1,
Dave Airlie's avatar
Dave Airlie committed
305 306
				  clips[i].x2 - clips[i].x1,
				  clips[i].y2 - clips[i].y1);
307
		if (ret)
308
			break;
Dave Airlie's avatar
Dave Airlie committed
309
	}
310 311

	if (ufb->obj->base.import_attach) {
312 313
		ret = dma_buf_end_cpu_access(ufb->obj->base.import_attach->dmabuf,
					     DMA_FROM_DEVICE);
314
	}
315 316 317 318

 unlock:
	drm_modeset_unlock_all(fb->dev);

319
	return ret;
Dave Airlie's avatar
Dave Airlie committed
320 321 322 323 324 325 326
}

static void udl_user_framebuffer_destroy(struct drm_framebuffer *fb)
{
	struct udl_framebuffer *ufb = to_udl_fb(fb);

	if (ufb->obj)
327
		drm_gem_object_put_unlocked(&ufb->obj->base);
Dave Airlie's avatar
Dave Airlie committed
328 329 330 331 332 333 334 335 336 337 338 339 340 341

	drm_framebuffer_cleanup(fb);
	kfree(ufb);
}

static const struct drm_framebuffer_funcs udlfb_funcs = {
	.destroy = udl_user_framebuffer_destroy,
	.dirty = udl_user_framebuffer_dirty,
};


static int
udl_framebuffer_init(struct drm_device *dev,
		     struct udl_framebuffer *ufb,
342
		     const struct drm_mode_fb_cmd2 *mode_cmd,
Dave Airlie's avatar
Dave Airlie committed
343 344 345 346 347
		     struct udl_gem_object *obj)
{
	int ret;

	ufb->obj = obj;
348
	drm_helper_mode_fill_fb_struct(dev, &ufb->base, mode_cmd);
349
	ret = drm_framebuffer_init(dev, &ufb->base, &udlfb_funcs);
Dave Airlie's avatar
Dave Airlie committed
350 351 352 353
	return ret;
}


354
static int udlfb_create(struct drm_fb_helper *helper,
Dave Airlie's avatar
Dave Airlie committed
355 356
			struct drm_fb_helper_surface_size *sizes)
{
357 358
	struct udl_fbdev *ufbdev =
		container_of(helper, struct udl_fbdev, helper);
Dave Airlie's avatar
Dave Airlie committed
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	struct drm_device *dev = ufbdev->helper.dev;
	struct fb_info *info;
	struct drm_framebuffer *fb;
	struct drm_mode_fb_cmd2 mode_cmd;
	struct udl_gem_object *obj;
	uint32_t size;
	int ret = 0;

	if (sizes->surface_bpp == 24)
		sizes->surface_bpp = 32;

	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 = udl_gem_alloc_object(dev, size);
	if (!obj)
		goto out;

	ret = udl_gem_vmap(obj);
	if (ret) {
		DRM_ERROR("failed to vmap fb\n");
		goto out_gfree;
	}

390 391 392
	info = drm_fb_helper_alloc_fbi(helper);
	if (IS_ERR(info)) {
		ret = PTR_ERR(info);
Dave Airlie's avatar
Dave Airlie committed
393 394 395 396 397 398
		goto out_gfree;
	}
	info->par = ufbdev;

	ret = udl_framebuffer_init(dev, &ufbdev->ufb, &mode_cmd, obj);
	if (ret)
399
		goto out_gfree;
Dave Airlie's avatar
Dave Airlie committed
400 401 402 403 404 405 406 407 408 409 410 411

	fb = &ufbdev->ufb.base;

	ufbdev->helper.fb = fb;

	strcpy(info->fix.id, "udldrmfb");

	info->screen_base = ufbdev->ufb.obj->vmapping;
	info->fix.smem_len = size;
	info->fix.smem_start = (unsigned long)ufbdev->ufb.obj->vmapping;

	info->fbops = &udlfb_ops;
Ville Syrjälä's avatar
Ville Syrjälä committed
412
	drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth);
Dave Airlie's avatar
Dave Airlie committed
413 414 415 416 417 418 419 420
	drm_fb_helper_fill_var(info, &ufbdev->helper, sizes->fb_width, sizes->fb_height);

	DRM_DEBUG_KMS("allocated %dx%d vmal %p\n",
		      fb->width, fb->height,
		      ufbdev->ufb.obj->vmapping);

	return ret;
out_gfree:
421
	drm_gem_object_put_unlocked(&ufbdev->ufb.obj->base);
Dave Airlie's avatar
Dave Airlie committed
422 423 424 425
out:
	return ret;
}

426
static const struct drm_fb_helper_funcs udl_fb_helper_funcs = {
427
	.fb_probe = udlfb_create,
Dave Airlie's avatar
Dave Airlie committed
428 429 430 431 432
};

static void udl_fbdev_destroy(struct drm_device *dev,
			      struct udl_fbdev *ufbdev)
{
433
	drm_fb_helper_unregister_fbi(&ufbdev->helper);
Dave Airlie's avatar
Dave Airlie committed
434
	drm_fb_helper_fini(&ufbdev->helper);
435 436 437 438 439
	if (ufbdev->ufb.obj) {
		drm_framebuffer_unregister_private(&ufbdev->ufb.base);
		drm_framebuffer_cleanup(&ufbdev->ufb.base);
		drm_gem_object_put_unlocked(&ufbdev->ufb.obj->base);
	}
Dave Airlie's avatar
Dave Airlie committed
440 441 442 443
}

int udl_fbdev_init(struct drm_device *dev)
{
444
	struct udl_device *udl = to_udl(dev);
Dave Airlie's avatar
Dave Airlie committed
445 446 447 448 449 450 451 452 453
	int bpp_sel = fb_bpp;
	struct udl_fbdev *ufbdev;
	int ret;

	ufbdev = kzalloc(sizeof(struct udl_fbdev), GFP_KERNEL);
	if (!ufbdev)
		return -ENOMEM;

	udl->fbdev = ufbdev;
454 455

	drm_fb_helper_prepare(dev, &ufbdev->helper, &udl_fb_helper_funcs);
Dave Airlie's avatar
Dave Airlie committed
456

457
	ret = drm_fb_helper_init(dev, &ufbdev->helper, 1);
458 459
	if (ret)
		goto free;
Dave Airlie's avatar
Dave Airlie committed
460

461 462 463
	ret = drm_fb_helper_single_add_all_connectors(&ufbdev->helper);
	if (ret)
		goto fini;
464 465 466 467

	/* disable all the possible outputs/crtcs before entering KMS mode */
	drm_helper_disable_unused_functions(dev);

468 469 470 471
	ret = drm_fb_helper_initial_config(&ufbdev->helper, bpp_sel);
	if (ret)
		goto fini;

Dave Airlie's avatar
Dave Airlie committed
472
	return 0;
473 474 475 476 477 478

fini:
	drm_fb_helper_fini(&ufbdev->helper);
free:
	kfree(ufbdev);
	return ret;
Dave Airlie's avatar
Dave Airlie committed
479 480 481 482
}

void udl_fbdev_cleanup(struct drm_device *dev)
{
483
	struct udl_device *udl = to_udl(dev);
Dave Airlie's avatar
Dave Airlie committed
484 485 486 487 488 489 490 491 492 493
	if (!udl->fbdev)
		return;

	udl_fbdev_destroy(dev, udl->fbdev);
	kfree(udl->fbdev);
	udl->fbdev = NULL;
}

void udl_fbdev_unplug(struct drm_device *dev)
{
494
	struct udl_device *udl = to_udl(dev);
Dave Airlie's avatar
Dave Airlie committed
495 496 497 498 499
	struct udl_fbdev *ufbdev;
	if (!udl->fbdev)
		return;

	ufbdev = udl->fbdev;
500
	drm_fb_helper_unlink_fbi(&ufbdev->helper);
Dave Airlie's avatar
Dave Airlie committed
501 502 503 504 505
}

struct drm_framebuffer *
udl_fb_user_fb_create(struct drm_device *dev,
		   struct drm_file *file,
506
		   const struct drm_mode_fb_cmd2 *mode_cmd)
Dave Airlie's avatar
Dave Airlie committed
507 508 509 510
{
	struct drm_gem_object *obj;
	struct udl_framebuffer *ufb;
	int ret;
511
	uint32_t size;
Dave Airlie's avatar
Dave Airlie committed
512

513
	obj = drm_gem_object_lookup(file, mode_cmd->handles[0]);
Dave Airlie's avatar
Dave Airlie committed
514 515 516
	if (obj == NULL)
		return ERR_PTR(-ENOENT);

517 518 519 520 521 522 523 524
	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);
		return ERR_PTR(-ENOMEM);
	}

Dave Airlie's avatar
Dave Airlie committed
525 526 527 528 529 530 531 532 533 534 535
	ufb = kzalloc(sizeof(*ufb), GFP_KERNEL);
	if (ufb == NULL)
		return ERR_PTR(-ENOMEM);

	ret = udl_framebuffer_init(dev, ufb, mode_cmd, to_udl_bo(obj));
	if (ret) {
		kfree(ufb);
		return ERR_PTR(-EINVAL);
	}
	return &ufb->base;
}