@@ -8,11 +8,38 @@
* for more details.
*/
+/*
+ * Deferred I/O ("defio") allows framebuffers that are mmap()'ed to user space
+ * to batch user space writes into periodic updates to the underlying
+ * framebuffer hardware or other implementation (such as with a virtualized
+ * framebuffer in a VM). At each batch interval, a callback is invoked in the
+ * framebuffer's kernel driver, and the callback is supplied with a list of
+ * pages that have been modified in the preceding interval. The callback can
+ * use this information to update the framebuffer hardware as necessary. The
+ * batching can improve performance and reduce the overhead of updating the
+ * hardware.
+ *
+ * Defio is supported on framebuffers allocated using vmalloc() and allocated
+ * as contiguous kernel memory using alloc_pages(), kmalloc(), or
+ * dma_alloc_coherent(), the latter of which might allocate from CMA. These
+ * memory allocations all have corresponding "struct page"s. Framebuffers
+ * in MMIO space are *not* supported because MMIO space does not have
+ * corrresponding "struct page"s.
+ *
+ * For framebuffers allocated using vmalloc(), struct fb_info must have
+ * "screen_buffer" set to the vmalloc address of the framebuffer. For
+ * framebuffers allocated from contiguous kernel memory, FBINFO_KMEMFB must
+ * be set, and "fix.smem_start" must be set to the physical address of the
+ * frame buffer. In both cases, "fix.smem_len" must be set to the framebuffer
+ * size in bytes.
+ */
+
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
+#include <linux/pfn_t.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
@@ -37,7 +64,7 @@ static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long
else if (info->fix.smem_start)
page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT);
- if (page)
+ if (page && !(info->flags & FBINFO_KMEMFB))
get_page(page);
return page;
@@ -137,6 +164,15 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
BUG_ON(!info->fbdefio->mapping);
+ if (info->flags & FBINFO_KMEMFB)
+ /*
+ * In this path, the VMA is marked VM_PFNMAP, so mm assumes
+ * there is no struct page associated with the page. The
+ * PFN must be directly inserted and the created PTE will be
+ * marked "special".
+ */
+ return vmf_insert_pfn(vmf->vma, vmf->address, page_to_pfn(page));
+
vmf->page = page;
return 0;
}
@@ -163,13 +199,14 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
/*
* Adds a page to the dirty list. Call this from struct
- * vm_operations_struct.page_mkwrite.
+ * vm_operations_struct.page_mkwrite or .pfn_mkwrite.
*/
-static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
+static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, struct vm_fault *vmf,
struct page *page)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
struct fb_deferred_io_pageref *pageref;
+ unsigned long offset = vmf->pgoff << PAGE_SHIFT;
vm_fault_t ret;
/* protect against the workqueue changing the page list */
@@ -182,20 +219,34 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
}
/*
- * We want the page to remain locked from ->page_mkwrite until
- * the PTE is marked dirty to avoid mapping_wrprotect_range()
- * being called before the PTE is updated, which would leave
- * the page ignored by defio.
- * Do this by locking the page here and informing the caller
- * about it with VM_FAULT_LOCKED.
+ * The PTE must be marked writable before the defio deferred work runs
+ * again and potentially marks the PTE write-protected. If the order
+ * should be switched, the PTE would become writable without defio
+ * tracking the page, leaving the page forever ignored by defio.
+ *
+ * For vmalloc() framebuffers, the associated struct page is locked
+ * before releasing the defio lock. mm will later mark the PTE writaable
+ * and release the struct page lock. The struct page lock prevents
+ * the page from being prematurely being marked write-protected.
+ *
+ * For FBINFO_KMEMFB framebuffers, mm assumes there is no struct page,
+ * so the PTE must be marked writable while the defio lock is held.
*/
- lock_page(pageref->page);
+ if (info->flags & FBINFO_KMEMFB) {
+ unsigned long pfn = page_to_pfn(pageref->page);
+
+ ret = vmf_insert_mixed_mkwrite(vmf->vma, vmf->address,
+ __pfn_to_pfn_t(pfn, PFN_SPECIAL));
+ } else {
+ lock_page(pageref->page);
+ ret = VM_FAULT_LOCKED;
+ }
mutex_unlock(&fbdefio->lock);
/* come back after delay to process the deferred IO */
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
- return VM_FAULT_LOCKED;
+ return ret;
err_mutex_unlock:
mutex_unlock(&fbdefio->lock);
@@ -207,10 +258,10 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
* @fb_info: The fbdev info structure
* @vmf: The VM fault
*
- * 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
+ * This is a callback we get when userspace first tries to write to a
+ * page. We schedule a workqueue. That workqueue will eventually do
+ * mapping_wrprotect_range() on the written pages and execute the
+ * deferred framebuffer IO. Then if userspace writes to a page
* again, we repeat the same scheme.
*
* Returns:
@@ -218,12 +269,11 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
*/
static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
{
- unsigned long offset = vmf->pgoff << PAGE_SHIFT;
struct page *page = vmf->page;
file_update_time(vmf->vma->vm_file);
- return fb_deferred_io_track_page(info, offset, page);
+ return fb_deferred_io_track_page(info, vmf, page);
}
/* vm_ops->page_mkwrite handler */
@@ -234,9 +284,25 @@ static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
return fb_deferred_io_page_mkwrite(info, vmf);
}
+/*
+ * Similar to fb_deferred_io_mkwrite(), but for first writes to pages
+ * in VMAs that have VM_PFNMAP set.
+ */
+static vm_fault_t fb_deferred_io_pfn_mkwrite(struct vm_fault *vmf)
+{
+ struct fb_info *info = vmf->vma->vm_private_data;
+ unsigned long offset = vmf->pgoff << PAGE_SHIFT;
+ struct page *page = phys_to_page(info->fix.smem_start + offset);
+
+ file_update_time(vmf->vma->vm_file);
+
+ return fb_deferred_io_track_page(info, vmf, page);
+}
+
static const struct vm_operations_struct fb_deferred_io_vm_ops = {
.fault = fb_deferred_io_fault,
.page_mkwrite = fb_deferred_io_mkwrite,
+ .pfn_mkwrite = fb_deferred_io_pfn_mkwrite,
};
static const struct address_space_operations fb_deferred_io_aops = {
@@ -246,11 +312,31 @@ static const struct address_space_operations fb_deferred_io_aops = {
int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
+ vm_flags_t flags = VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_ops = &fb_deferred_io_vm_ops;
- vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
- if (!(info->flags & FBINFO_VIRTFB))
- vm_flags_set(vma, VM_IO);
+ if (info->flags & FBINFO_KMEMFB) {
+ /*
+ * I/O fault path calls vmf_insert_pfn(), which bug checks
+ * if the vma is not marked shared. mmap'ing the framebuffer
+ * as PRIVATE doesn't really make sense anyway, though doing
+ * so isn't harmful for vmalloc() framebuffers. So there's
+ * no prohibition for that case.
+ */
+ if (!(vma->vm_flags & VM_SHARED))
+ return -EINVAL;
+ /*
+ * Set VM_PFNMAP so mm code will not try to manage the pages'
+ * lifecycles. We don't want individual pages to be freed
+ * based on refcount. Instead the memory must be returned to
+ * the free pool in the usual way. Cf. the implementation of
+ * remap_pfn_range() and remap_pfn_range_internal().
+ */
+ flags |= VM_PFNMAP | VM_IO;
+ } else if (!(info->flags & FBINFO_VIRTFB)) {
+ flags |= VM_IO;
+ }
+ vm_flags_set(vma, flags);
vma->vm_private_data = info;
return 0;
}
@@ -402,6 +402,7 @@ struct fb_tile_ops {
/* hints */
#define FBINFO_VIRTFB 0x0004 /* FB is System RAM, not device. */
+#define FBINFO_KMEMFB 0x0008 /* FB is allocated in contig kernel mem */
#define FBINFO_PARTIAL_PAN_OK 0x0040 /* otw use pan only for double-buffering */
#define FBINFO_READS_FAST 0x0080 /* soft-copy faster than rendering */