+/*
+ * It's worth taking a moment to describe how mmap is implemented
+ * for zfs because it differs considerably from other Linux filesystems.
+ * However, this issue is handled the same way under OpenSolaris.
+ *
+ * The issue is that by design zfs bypasses the Linux page cache and
+ * leaves all caching up to the ARC. This has been shown to work
+ * well for the common read(2)/write(2) case. However, mmap(2)
+ * is problem because it relies on being tightly integrated with the
+ * page cache. To handle this we cache mmap'ed files twice, once in
+ * the ARC and a second time in the page cache. The code is careful
+ * to keep both copies synchronized.
+ *
+ * When a file with an mmap'ed region is written to using write(2)
+ * both the data in the ARC and existing pages in the page cache
+ * are updated. For a read(2) data will be read first from the page
+ * cache then the ARC if needed. Neither a write(2) or read(2) will
+ * will ever result in new pages being added to the page cache.
+ *
+ * New pages are added to the page cache only via .readpage() which
+ * is called when the vfs needs to read a page off disk to back the
+ * virtual memory region. These pages may be modified without
+ * notifying the ARC and will be written out periodically via
+ * .writepage(). This will occur due to either a sync or the usual
+ * page aging behavior. Note because a read(2) of a mmap'ed file
+ * will always check the page cache first even when the ARC is out
+ * of date correct data will still be returned.
+ *
+ * While this implementation ensures correct behavior it does have
+ * have some drawbacks. The most obvious of which is that it
+ * increases the required memory footprint when access mmap'ed
+ * files. It also adds additional complexity to the code keeping
+ * both caches synchronized.
+ *
+ * Longer term it may be possible to cleanly resolve this wart by
+ * mapping page cache pages directly on to the ARC buffers. The
+ * Linux address space operations are flexible enough to allow
+ * selection of which pages back a particular index. The trick
+ * would be working out the details of which subsystem is in
+ * charge, the ARC, the page cache, or both. It may also prove
+ * helpful to move the ARC buffers to a scatter-gather lists
+ * rather than a vmalloc'ed region.
+ */
+static int
+zpl_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct inode *ip = filp->f_mapping->host;
+ znode_t *zp = ITOZ(ip);
+ int error;
+
+ error = -zfs_map(ip, vma->vm_pgoff, (caddr_t *)vma->vm_start,
+ (size_t)(vma->vm_end - vma->vm_start), vma->vm_flags);
+ if (error)
+ return (error);
+
+ error = generic_file_mmap(filp, vma);
+ if (error)
+ return (error);
+
+ mutex_enter(&zp->z_lock);
+ zp->z_is_mapped = 1;
+ mutex_exit(&zp->z_lock);
+
+ return (error);
+}
+
+static struct page **
+pages_vector_from_list(struct list_head *pages, unsigned nr_pages)
+{
+ struct page **pl;
+ struct page *t;
+ unsigned page_idx;
+
+ pl = kmalloc(sizeof(*pl) * nr_pages, GFP_NOFS);
+ if (!pl)
+ return ERR_PTR(-ENOMEM);
+
+ page_idx = 0;
+ list_for_each_entry_reverse(t, pages, lru) {
+ pl[page_idx] = t;
+ page_idx++;
+ }
+
+ return pl;
+}
+
+static int
+zpl_readpages(struct file *file, struct address_space *mapping,
+ struct list_head *pages, unsigned nr_pages)
+{
+ struct inode *ip;
+ struct page **pl;
+ struct page *p, *n;
+ int error;
+
+ ip = mapping->host;
+
+ pl = pages_vector_from_list(pages, nr_pages);
+ if (IS_ERR(pl))
+ return PTR_ERR(pl);
+
+ error = -zfs_getpage(ip, pl, nr_pages);
+ if (error)
+ goto error;
+
+ list_for_each_entry_safe_reverse(p, n, pages, lru) {
+
+ list_del(&p->lru);
+
+ flush_dcache_page(p);
+ SetPageUptodate(p);
+ unlock_page(p);
+ page_cache_release(p);
+ }
+
+error:
+ kfree(pl);
+ return error;
+}
+
+/*
+ * Populate a page with data for the Linux page cache. This function is
+ * only used to support mmap(2). There will be an identical copy of the
+ * data in the ARC which is kept up to date via .write() and .writepage().
+ *
+ * Current this function relies on zpl_read_common() and the O_DIRECT
+ * flag to read in a page. This works but the more correct way is to
+ * update zfs_fillpage() to be Linux friendly and use that interface.
+ */
+static int
+zpl_readpage(struct file *filp, struct page *pp)
+{
+ struct inode *ip;
+ struct page *pl[1];
+ int error = 0;
+
+ ASSERT(PageLocked(pp));
+ ip = pp->mapping->host;
+ pl[0] = pp;
+
+ error = -zfs_getpage(ip, pl, 1);
+
+ if (error) {
+ SetPageError(pp);
+ ClearPageUptodate(pp);
+ } else {
+ ClearPageError(pp);
+ SetPageUptodate(pp);
+ flush_dcache_page(pp);
+ }
+
+ unlock_page(pp);
+ return error;
+}
+
+int
+zpl_putpage(struct page *pp, struct writeback_control *wbc, void *data)
+{
+ int error;
+
+ error = -zfs_putpage(pp, wbc, data);
+
+ if (error) {
+ SetPageError(pp);
+ ClearPageUptodate(pp);
+ } else {
+ ClearPageError(pp);
+ SetPageUptodate(pp);
+ flush_dcache_page(pp);
+ }
+
+ unlock_page(pp);
+ return error;
+}
+
+static int
+zpl_writepages(struct address_space *mapping, struct writeback_control *wbc)
+{
+ return write_cache_pages(mapping, wbc, zpl_putpage, mapping);
+}
+
+/*
+ * Write out dirty pages to the ARC, this function is only required to
+ * support mmap(2). Mapped pages may be dirtied by memory operations
+ * which never call .write(). These dirty pages are kept in sync with
+ * the ARC buffers via this hook.
+ */
+static int
+zpl_writepage(struct page *pp, struct writeback_control *wbc)
+{
+ return zpl_putpage(pp, wbc, pp->mapping);
+}
+