mirror of
https://github.com/torvalds/linux.git
synced 2024-12-30 14:52:05 +00:00
49bbd815fd
Fix a problem observed while using fb_defio with a short delay on a PowerPC platform. It is possible that page_mkclean() is invoked in the deferred io work function _before_ a PTE has been marked dirty. In this case, the page is removed from the defio pagelist but page_mkclean() does not write-protect the page again. The end result is that defio ignores all subsequent writes to the page and the corresponding portions of the framebuffer never get updated. The fix consists in keeping track of the pages with non-dirty PTEs, re-checking them again on the next deferred io work iteration. Note that those pages are not passed to the defio callback as they are not written by userspace yet. Signed-off-by: Albert Herranz <albert_herranz@yahoo.es> Acked-by: Jaya Kumar <jayakumar.lkml@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
248 lines
6.6 KiB
C
248 lines
6.6 KiB
C
/*
|
|
* 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/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>
|
|
|
|
struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs)
|
|
{
|
|
void *screen_base = (void __force *) info->screen_base;
|
|
struct page *page;
|
|
|
|
if (is_vmalloc_addr(screen_base + offs))
|
|
page = vmalloc_to_page(screen_base + offs);
|
|
else
|
|
page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT);
|
|
|
|
return page;
|
|
}
|
|
|
|
/* this is to find and return the vmalloc-ed fb pages */
|
|
static int fb_deferred_io_fault(struct vm_area_struct *vma,
|
|
struct vm_fault *vmf)
|
|
{
|
|
unsigned long offset;
|
|
struct page *page;
|
|
struct fb_info *info = vma->vm_private_data;
|
|
|
|
offset = vmf->pgoff << PAGE_SHIFT;
|
|
if (offset >= info->fix.smem_len)
|
|
return VM_FAULT_SIGBUS;
|
|
|
|
page = fb_deferred_io_page(info, offset);
|
|
if (!page)
|
|
return VM_FAULT_SIGBUS;
|
|
|
|
get_page(page);
|
|
|
|
if (vma->vm_file)
|
|
page->mapping = vma->vm_file->f_mapping;
|
|
else
|
|
printk(KERN_ERR "no mapping available\n");
|
|
|
|
BUG_ON(!page->mapping);
|
|
page->index = vmf->pgoff;
|
|
|
|
vmf->page = page;
|
|
return 0;
|
|
}
|
|
|
|
int fb_deferred_io_fsync(struct file *file, struct dentry *dentry, int datasync)
|
|
{
|
|
struct fb_info *info = file->private_data;
|
|
|
|
/* Skip if deferred io is compiled-in but disabled on this fbdev */
|
|
if (!info->fbdefio)
|
|
return 0;
|
|
|
|
/* 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);
|
|
|
|
/* vm_ops->page_mkwrite handler */
|
|
static int fb_deferred_io_mkwrite(struct vm_area_struct *vma,
|
|
struct vm_fault *vmf)
|
|
{
|
|
struct page *page = vmf->page;
|
|
struct fb_info *info = vma->vm_private_data;
|
|
struct fb_deferred_io *fbdefio = info->fbdefio;
|
|
struct page *cur;
|
|
|
|
/* 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);
|
|
|
|
/* we loop through the pagelist before adding in order
|
|
to keep the pagelist sorted */
|
|
list_for_each_entry(cur, &fbdefio->pagelist, lru) {
|
|
/* this check is to catch the case where a new
|
|
process could start writing to the same page
|
|
through a new pte. this new access can cause the
|
|
mkwrite even when the original ps's pte is marked
|
|
writable */
|
|
if (unlikely(cur == page))
|
|
goto page_already_added;
|
|
else if (cur->index > page->index)
|
|
break;
|
|
}
|
|
|
|
list_add_tail(&page->lru, &cur->lru);
|
|
|
|
page_already_added:
|
|
mutex_unlock(&fbdefio->lock);
|
|
|
|
/* come back after delay to process the deferred IO */
|
|
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
|
|
return 0;
|
|
}
|
|
|
|
static const struct vm_operations_struct fb_deferred_io_vm_ops = {
|
|
.fault = fb_deferred_io_fault,
|
|
.page_mkwrite = fb_deferred_io_mkwrite,
|
|
};
|
|
|
|
static int fb_deferred_io_set_page_dirty(struct page *page)
|
|
{
|
|
if (!PageDirty(page))
|
|
SetPageDirty(page);
|
|
return 0;
|
|
}
|
|
|
|
static const struct address_space_operations fb_deferred_io_aops = {
|
|
.set_page_dirty = fb_deferred_io_set_page_dirty,
|
|
};
|
|
|
|
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_RESERVED | VM_DONTEXPAND );
|
|
if (!(info->flags & FBINFO_VIRTFB))
|
|
vma->vm_flags |= VM_IO;
|
|
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 fb_deferred_io *fbdefio = info->fbdefio;
|
|
struct page *page, *tmp_page;
|
|
struct list_head *node, *tmp_node;
|
|
struct list_head non_dirty;
|
|
|
|
INIT_LIST_HEAD(&non_dirty);
|
|
|
|
/* here we mkclean the pages, then do all deferred IO */
|
|
mutex_lock(&fbdefio->lock);
|
|
list_for_each_entry_safe(page, tmp_page, &fbdefio->pagelist, lru) {
|
|
lock_page(page);
|
|
/*
|
|
* The workqueue callback can be triggered after a
|
|
* ->page_mkwrite() call but before the PTE has been marked
|
|
* dirty. In this case page_mkclean() won't "rearm" the page.
|
|
*
|
|
* To avoid this, remove those "non-dirty" pages from the
|
|
* pagelist before calling the driver's callback, then add
|
|
* them back to get processed on the next work iteration.
|
|
* At that time, their PTEs will hopefully be dirty for real.
|
|
*/
|
|
if (!page_mkclean(page))
|
|
list_move_tail(&page->lru, &non_dirty);
|
|
unlock_page(page);
|
|
}
|
|
|
|
/* driver's callback with pagelist */
|
|
fbdefio->deferred_io(info, &fbdefio->pagelist);
|
|
|
|
/* clear the list... */
|
|
list_for_each_safe(node, tmp_node, &fbdefio->pagelist) {
|
|
list_del(node);
|
|
}
|
|
/* ... and add back the "non-dirty" pages to the list */
|
|
list_splice_tail(&non_dirty, &fbdefio->pagelist);
|
|
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_open(struct fb_info *info,
|
|
struct inode *inode,
|
|
struct file *file)
|
|
{
|
|
file->f_mapping->a_ops = &fb_deferred_io_aops;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fb_deferred_io_open);
|
|
|
|
void fb_deferred_io_cleanup(struct fb_info *info)
|
|
{
|
|
struct fb_deferred_io *fbdefio = info->fbdefio;
|
|
struct list_head *node, *tmp_node;
|
|
struct page *page;
|
|
int i;
|
|
|
|
BUG_ON(!fbdefio);
|
|
cancel_delayed_work(&info->deferred_work);
|
|
flush_scheduled_work();
|
|
|
|
/* the list may have still some non-dirty pages at this point */
|
|
mutex_lock(&fbdefio->lock);
|
|
list_for_each_safe(node, tmp_node, &fbdefio->pagelist) {
|
|
list_del(node);
|
|
}
|
|
mutex_unlock(&fbdefio->lock);
|
|
|
|
/* clear out the mapping that we setup */
|
|
for (i = 0 ; i < info->fix.smem_len; i += PAGE_SIZE) {
|
|
page = fb_deferred_io_page(info, i);
|
|
page->mapping = NULL;
|
|
}
|
|
|
|
info->fbops->fb_mmap = NULL;
|
|
mutex_destroy(&fbdefio->lock);
|
|
}
|
|
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
|
|
|
|
MODULE_LICENSE("GPL");
|