Replace the deprecated .mmap hook with its replacement .mmap_prepare. As part of this change, additionally take the approach of mapping pages upon mmap rather than providing a fault handler. The page span cannot be mutated when an mmap mapping is in place, so this is safe to do in advance (the MON_IOCT_RING_SIZE ioctl operation exits -EBUSY if it's attempted, gated by the rp->mmap_active reference count). Utilise the newly introduced mmap_action_map_discontig_kernel_pages() to do this, which allows for iteration over pages in mon_bin_discontig_get(). mon_bin_discontig_init() increments the rp->mmap_active reference count to stabilise page spans. Should an error arise the core unmaps the VMA and mon_bin_vma_close() drops the reference again. The vm_ops->close hook implemented in mon_bin_vma_close() will ensure correct reference count arithmetic upon unmap (with mon_bin_vma_open() accounting for splitting). The existing semantics are all retained, including not mapping past the range of available pages, with a SIGBUS being raised in a userland process that attempts to access past this point. Ultimately insert_page() is invoked to insert each page, which increments the reference count on each mapped page. This mimics what was being done previously, only we pre-map the entire range rather than doing so on demand. The existing fault handler did nothing that required demand paging, and was presumably implemented this way due for historic reasons. One behavioural difference: pages are no longer faulted in on demand, so a page discarded with MADV_DONTNEED is not repopulated and a subsequent access raises SIGBUS, as with other pre-populated kernel mappings. Signed-off-by: Lorenzo Stoakes (ARM) --- drivers/usb/mon/mon_bin.c | 82 ++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/drivers/usb/mon/mon_bin.c b/drivers/usb/mon/mon_bin.c index 687f6a8981f3..9d00b21a8153 100644 --- a/drivers/usb/mon/mon_bin.c +++ b/drivers/usb/mon/mon_bin.c @@ -1219,6 +1219,15 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait) return mask; } +static void __mon_bin_vma_open(struct mon_reader_bin *rp) +{ + unsigned long flags; + + spin_lock_irqsave(&rp->b_lock, flags); + rp->mmap_active++; + spin_unlock_irqrestore(&rp->b_lock, flags); +} + /* * open and close: just keep track of how many times the device is * mapped, to use the proper memory allocation function. @@ -1226,64 +1235,79 @@ mon_bin_poll(struct file *file, struct poll_table_struct *wait) static void mon_bin_vma_open(struct vm_area_struct *vma) { struct mon_reader_bin *rp = vma->vm_private_data; - unsigned long flags; - spin_lock_irqsave(&rp->b_lock, flags); - rp->mmap_active++; - spin_unlock_irqrestore(&rp->b_lock, flags); + __mon_bin_vma_open(rp); } -static void mon_bin_vma_close(struct vm_area_struct *vma) +static void __mon_bin_vma_close(struct mon_reader_bin *rp) { unsigned long flags; - struct mon_reader_bin *rp = vma->vm_private_data; spin_lock_irqsave(&rp->b_lock, flags); rp->mmap_active--; spin_unlock_irqrestore(&rp->b_lock, flags); } -/* - * Map ring pages to user space. - */ -static vm_fault_t mon_bin_vma_fault(struct vm_fault *vmf) +static void mon_bin_vma_close(struct vm_area_struct *vma) { - struct mon_reader_bin *rp = vmf->vma->vm_private_data; + struct mon_reader_bin *rp = vma->vm_private_data; + + __mon_bin_vma_close(rp); +} + +static const struct vm_operations_struct mon_bin_vm_ops = { + .open = mon_bin_vma_open, + .close = mon_bin_vma_close, +}; + +static int mon_bin_discontig_init(void *vm_private_data, void **private) +{ + struct mon_reader_bin *rp = vm_private_data; + + /* Dropped by mon_bin_vma_close() on unmap, including on error. */ + __mon_bin_vma_open(rp); + return 0; +} + +static int mon_bin_discontig_get(struct discontig_kernel_page_state *state) +{ + struct mon_reader_bin *rp = state->vm_private_data; unsigned long offset, chunk_idx; - struct page *pageptr; unsigned long flags; spin_lock_irqsave(&rp->b_lock, flags); - offset = vmf->pgoff << PAGE_SHIFT; + + offset = state->pgoff << PAGE_SHIFT; if (offset >= rp->b_size) { spin_unlock_irqrestore(&rp->b_lock, flags); - return VM_FAULT_SIGBUS; + discontig_kernel_map_abort(state); + return 0; } chunk_idx = offset / CHUNK_SIZE; - pageptr = rp->b_vec[chunk_idx].pg; - get_page(pageptr); - vmf->page = pageptr; + discontig_kernel_map_page(state, rp->b_vec[chunk_idx].pg); + spin_unlock_irqrestore(&rp->b_lock, flags); return 0; } -static const struct vm_operations_struct mon_bin_vm_ops = { - .open = mon_bin_vma_open, - .close = mon_bin_vma_close, - .fault = mon_bin_vma_fault, +static const struct discontig_kernel_page_ops mon_discontig_ops = { + .init = mon_bin_discontig_init, + .get = mon_bin_discontig_get, }; -static int mon_bin_mmap(struct file *filp, struct vm_area_struct *vma) +static int mon_bin_mmap_prepare(struct vm_area_desc *desc) { - /* don't do anything here: "fault" will set up page table entries */ - vma->vm_ops = &mon_bin_vm_ops; + const struct file *filp = desc->file; - if (vma->vm_flags & VM_WRITE) + if (vma_desc_test(desc, VMA_WRITE_BIT)) return -EPERM; - vm_flags_mod(vma, VM_DONTEXPAND | VM_DONTDUMP, VM_MAYWRITE); - vma->vm_private_data = filp->private_data; - mon_bin_vma_open(vma); + desc->vm_ops = &mon_bin_vm_ops; + vma_desc_clear_flags(desc, VMA_MAYWRITE_BIT); + vma_desc_set_flags(desc, VMA_DONTEXPAND_BIT, VMA_DONTDUMP_BIT); + desc->private_data = filp->private_data; + + mmap_action_map_discontig_kernel_pages(desc, NULL, &mon_discontig_ops); return 0; } @@ -1298,7 +1322,7 @@ static const struct file_operations mon_fops_binary = { .compat_ioctl = mon_bin_compat_ioctl, #endif .release = mon_bin_release, - .mmap = mon_bin_mmap, + .mmap_prepare = mon_bin_mmap_prepare, }; static int mon_bin_wait_event(struct file *file, struct mon_reader_bin *rp) -- 2.55.0