fb_defio.c 3.95 KB
Newer Older
Jaya Kumar's avatar
Jaya Kumar committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 *  linux/drivers/video/fb_defio.c
 *
 *  Copyright (C) 2006 Jaya Kumar
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive
 * for more details.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/list.h>

/* to support deferred IO */
#include <linux/rmap.h>
#include <linux/pagemap.h>

/* this is to find and return the vmalloc-ed fb pages */
Nick Piggin's avatar
Nick Piggin committed
28 29
static int fb_deferred_io_fault(struct vm_area_struct *vma,
				struct vm_fault *vmf)
Jaya Kumar's avatar
Jaya Kumar committed
30 31 32 33
{
	unsigned long offset;
	struct page *page;
	struct fb_info *info = vma->vm_private_data;
34 35
	/* info->screen_base is in System RAM */
	void *screen_base = (void __force *) info->screen_base;
Jaya Kumar's avatar
Jaya Kumar committed
36

Nick Piggin's avatar
Nick Piggin committed
37
	offset = vmf->pgoff << PAGE_SHIFT;
Jaya Kumar's avatar
Jaya Kumar committed
38
	if (offset >= info->fix.smem_len)
Nick Piggin's avatar
Nick Piggin committed
39
		return VM_FAULT_SIGBUS;
Jaya Kumar's avatar
Jaya Kumar committed
40

41
	page = vmalloc_to_page(screen_base + offset);
Jaya Kumar's avatar
Jaya Kumar committed
42
	if (!page)
Nick Piggin's avatar
Nick Piggin committed
43
		return VM_FAULT_SIGBUS;
Jaya Kumar's avatar
Jaya Kumar committed
44 45

	get_page(page);
Nick Piggin's avatar
Nick Piggin committed
46 47
	vmf->page = page;
	return 0;
Jaya Kumar's avatar
Jaya Kumar committed
48 49
}

50 51 52 53 54 55 56 57 58 59 60 61
int fb_deferred_io_fsync(struct file *file, struct dentry *dentry, int datasync)
{
	struct fb_info *info = file->private_data;

	/* Kill off the delayed work */
	cancel_rearming_delayed_work(&info->deferred_work);

	/* Run it immediately */
	return schedule_delayed_work(&info->deferred_work, 0);
}
EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);

Jaya Kumar's avatar
Jaya Kumar committed
62
/* vm_ops->page_mkwrite handler */
63 64
static int fb_deferred_io_mkwrite(struct vm_area_struct *vma,
				  struct page *page)
Jaya Kumar's avatar
Jaya Kumar committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
{
	struct fb_info *info = vma->vm_private_data;
	struct fb_deferred_io *fbdefio = info->fbdefio;

	/* this is a callback we get when userspace first tries to
	write to the page. we schedule a workqueue. that workqueue
	will eventually mkclean the touched pages and execute the
	deferred framebuffer IO. then if userspace touches a page
	again, we repeat the same scheme */

	/* protect against the workqueue changing the page list */
	mutex_lock(&fbdefio->lock);
	list_add(&page->lru, &fbdefio->pagelist);
	mutex_unlock(&fbdefio->lock);

	/* come back after delay to process the deferred IO */
	schedule_delayed_work(&info->deferred_work, fbdefio->delay);
	return 0;
}

static struct vm_operations_struct fb_deferred_io_vm_ops = {
Nick Piggin's avatar
Nick Piggin committed
86
	.fault		= fb_deferred_io_fault,
Jaya Kumar's avatar
Jaya Kumar committed
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
	.page_mkwrite	= fb_deferred_io_mkwrite,
};

static int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
	vma->vm_ops = &fb_deferred_io_vm_ops;
	vma->vm_flags |= ( VM_IO | VM_RESERVED | VM_DONTEXPAND );
	vma->vm_private_data = info;
	return 0;
}

/* workqueue callback */
static void fb_deferred_io_work(struct work_struct *work)
{
	struct fb_info *info = container_of(work, struct fb_info,
						deferred_work.work);
	struct list_head *node, *next;
	struct page *cur;
	struct fb_deferred_io *fbdefio = info->fbdefio;

	/* here we mkclean the pages, then do all deferred IO */
	mutex_lock(&fbdefio->lock);
	list_for_each_entry(cur, &fbdefio->pagelist, lru) {
		lock_page(cur);
		page_mkclean(cur);
		unlock_page(cur);
	}

	/* driver's callback with pagelist */
	fbdefio->deferred_io(info, &fbdefio->pagelist);

	/* clear the list */
	list_for_each_safe(node, next, &fbdefio->pagelist) {
		list_del(node);
	}
	mutex_unlock(&fbdefio->lock);
}

void fb_deferred_io_init(struct fb_info *info)
{
	struct fb_deferred_io *fbdefio = info->fbdefio;

	BUG_ON(!fbdefio);
	mutex_init(&fbdefio->lock);
	info->fbops->fb_mmap = fb_deferred_io_mmap;
	INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
	INIT_LIST_HEAD(&fbdefio->pagelist);
	if (fbdefio->delay == 0) /* set a default of 1 s */
		fbdefio->delay = HZ;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_init);

void fb_deferred_io_cleanup(struct fb_info *info)
{
	struct fb_deferred_io *fbdefio = info->fbdefio;

	BUG_ON(!fbdefio);
	cancel_delayed_work(&info->deferred_work);
	flush_scheduled_work();
}
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);

MODULE_LICENSE("GPL");