On 10/9/26 20:04, Lorenzo Stoakes (ARM) wrote:
> The existing kernel page mapping mmap actions allow for partial and full
> mapping of an array of struct page pointers.
>
> However some drivers require the mapping of discontiguous ranges. Permit
> this by providing discontig_kernel_page_ops which allows a driver to
> specify how the operation should begin and how batches of pages should be
> retrieved.
>
> It uses the minimum exposed interface to do so, providing address, page
> offset and both vm_private_data state and a local private state object.
>
> ops->init can establish state for the operation, and ops->get outputs the
> pages to map and their count. Should an error arise the core unmaps the
> VMA, invoking vm_ops->close, which is therefore where any state established
> by ops->init is released.
>
> Also update the compatibility layer to do this too via
> compat_mmap_action_abort().
>
> Batches may not exceed the VMA, but may map less than its full range in
> case the driver wishes to allow the user to map an area larger than the
> available data.
>
> To use it, users invoke mmap_action_map_discontig_kernel_pages() with
> initial local private state and a set of operations.
>
> Users can then use one of the provided helper functions to perform an
> action:
>
> * discontig_kernel_map_abort() - Abort and leave the mapping as it has
> been accumulated so far.
> * discontig_kernel_map_page() - Map a single page, or a compound page given
> its head page.
> * discontig_kernel_map_page_range() - Maps a struct page ** array of a
> specified count, mapping only those pages which fit in the VMA.
>
> While here, update vm_insert_page() and vm_insert_pages() to assert the
> mmap write lock is held rather than the bizarre assert !mmap_read_trylock()
> and use the new VMA API so all code paths are consistent.
>
> Also convert a BUG_ON() to a VM_WARN_ON_ONCE() in vm_insert_page().
>
> The userland VMA tests are updated accordingly.
>
> Acked-by: Zi Yan <[email protected]>
> Signed-off-by: Lorenzo Stoakes (ARM) <[email protected]>
Finally finding time to go over this. In the future I want to have more time to
review such massive series, as it causes severe stress to be forced to review
this last minute before it goes upstream.
So if I push back in order to give me some more room to breathe in the future
it's nothing personal, I just have to keep my sanity somehow.
I hate being forced to review because we pick up stuff way to early. There is
just way too much other stuff going on.
I'm planning on picking up most stuff I maintain in the next cycle. For
cross-component stuff (esp. VMA) I expect Andrew to pick stuff up until we
tackle this once cycle later.
> +static inline
> +void mmap_action_map_discontig_kernel_pages(struct vm_area_desc *desc,
> + void *init_private, const struct discontig_kernel_page_ops *ops)
> +{
> + struct mmap_action *action = &desc->action;
> +
> + action->type = MMAP_DISCONTIG_KERNEL_PAGES;
> + action->map_kernel_discontig.init_private = init_private;
> + action->map_kernel_discontig.ops = ops;
> +}
> +
> int mmap_action_prepare(struct vm_area_desc *desc);
> int mmap_action_complete(struct vm_area_struct *vma,
> struct mmap_action *action, bool is_compat);
There is a mixture of two tab vs. different in your changes.
>
> +static inline void
> +discontig_kernel_map_abort(struct discontig_kernel_page_state *state)
> +{
> + state->action = DISCONTIG_KERNEL_PAGE_ABORT;
> +}
> +
> +static inline void
> +discontig_kernel_map_page(struct discontig_kernel_page_state *state,
> + struct page *page)
> +{
> + struct folio *folio = page_folio(page);
> +
> + if (folio_test_large(folio)) {
> + VM_WARN_ON_ONCE(page != folio_page(folio, 0));
> + state->action = DISCONTIG_KERNEL_PAGE_MAP_COMPOUND_PAGE;
> + state->__folio = folio;
> + state->__nr_pages = min(state->nr_pages_remain,
> + folio_nr_pages(folio));
> + } else {
> + state->action = DISCONTIG_KERNEL_PAGE_MAP_PAGE;
> + state->__page = page;
> + state->__nr_pages = 1;
> + }
Why are both separate? Isn't it sufficient to represent that through _page +
_nr_pages > 1 ? See below.
> +}
> +
> +static inline void
> +discontig_kernel_map_page_range(struct discontig_kernel_page_state *state,
> + struct page **page_arr, unsigned long nr_pages)
Is this actually "map_pages"? A range is rather a number of consectuive pages
part of the same folio/compound page, no?
(we call insert_pages() later ... )
> +{
> + state->action = DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE;
> + state->__page_arr = page_arr;
> + state->__nr_pages = min(nr_pages, state->nr_pages_remain);
So we expect all of them to be non-compound?
[...]
> diff --git a/mm/internal.h b/mm/internal.h
> index fc4e249e2343..557e629db8c5 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -1515,6 +1515,9 @@ int simple_ioremap_prepare(struct vm_area_desc *desc);
> int map_kernel_pages_prepare(struct vm_area_desc *desc);
> int map_kernel_pages_complete(struct vm_area_struct *vma,
> struct mmap_action *action);
> +int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc);
> +int map_discontig_kernel_pages_complete(struct vm_area_struct *vma,
> + struct mmap_action *action);
>
> static inline int io_remap_pfn_range_prepare(struct vm_area_desc *desc)
> {
> diff --git a/mm/memory.c b/mm/memory.c
> index 448342883e9d..e5c1d1c35bc7 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -2599,27 +2599,34 @@ int vm_insert_pages(struct vm_area_struct *vma,
> unsigned long addr,
>
> if (!range_in_vma(vma, addr, end))
> return -EFAULT;
> - if (!(vma->vm_flags & VM_MIXEDMAP)) {
> - VM_WARN_ON_ONCE(mmap_read_trylock(vma->vm_mm));
> - VM_WARN_ON_ONCE(vma->vm_flags & VM_PFNMAP);
> - vm_flags_set(vma, VM_MIXEDMAP);
> + if (!vma_test(vma, VMA_MIXEDMAP_BIT)) {
> + mmap_assert_write_locked(vma->vm_mm);
> + VM_WARN_ON_ONCE(vma_test(vma, VMA_PFNMAP_BIT));
I would prefer to have these changes in a separate patch. Or is it strictly
required to have this in this very patch?
> + vma_start_write(vma);
> + vma_set_flags(vma, VMA_MIXEDMAP_BIT);
> }
> /* Defer page refcount checking till we're about to map that page. */
> return insert_pages(vma, addr, pages, num, vma->vm_page_prot);
> }
> EXPORT_SYMBOL(vm_insert_pages);
>
> +static void __map_kernel_pages_prepare(struct vm_area_desc *desc)
> +{
> + if (vma_desc_test(desc, VMA_MIXEDMAP_BIT))
> + return;
> +
> + mmap_assert_write_locked(desc->mm);
> + VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
> + vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
> +}
> +
> int map_kernel_pages_prepare(struct vm_area_desc *desc)
> {
> const struct mmap_action *action = &desc->action;
> const unsigned long addr = action->map_kernel.start;
> unsigned long nr_pages, end;
>
> - if (!vma_desc_test(desc, VMA_MIXEDMAP_BIT)) {
> - VM_WARN_ON_ONCE(mmap_read_trylock(desc->mm));
> - VM_WARN_ON_ONCE(vma_desc_test(desc, VMA_PFNMAP_BIT));
> - vma_desc_set_flags(desc, VMA_MIXEDMAP_BIT);
> - }
> + __map_kernel_pages_prepare(desc);
>
> nr_pages = action->map_kernel.nr_pages;
> end = addr + PAGE_SIZE * nr_pages;
> @@ -2640,6 +2647,98 @@ int map_kernel_pages_complete(struct vm_area_struct
> *vma,
> &nr_pages, vma->vm_page_prot);
> }
>
> +int map_discontig_kernel_pages_prepare(struct vm_area_desc *desc)
> +{
> + const struct mmap_action *action = &desc->action;
> + const struct discontig_kernel_page_ops *ops =
> + action->map_kernel_discontig.ops;
> +
> + /* At minimum need to be able to get pages. */
> + if (WARN_ON_ONCE(!ops || !ops->get))
> + return -EINVAL;
> +
> + __map_kernel_pages_prepare(desc);
> + return 0;
> +}
> +
> +static int apply_discontig_action(struct vm_area_struct *vma,
> + struct discontig_kernel_page_state *state)
> +{
> + unsigned long nr_pages = state->__nr_pages;
> + unsigned long addr = state->addr;
> + unsigned long i;
> +
> + if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGE)
> + return insert_page(vma, addr, state->__page,
> + vma->vm_page_prot, /*mkwrite=*/false);
> + if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGE_RANGE)
> + return insert_pages(vma, addr, state->__page_arr,
> + &nr_pages, vma->vm_page_prot);
> +
> + /* Compound folio - have to iterate through each page. */
> + for (i = 0; i < nr_pages; i++, addr += PAGE_SIZE) {
> + struct page *page = folio_page(state->__folio, i);
> + int err;
> +
> + err = insert_page(vma, addr, page, vma->vm_page_prot,
> + /*mkwrite=*/false);
> + if (err)
> + return err;
> + }
Summarizing some of my comments below, I wonder if this could be:
DISCONTIG_KERNEL_PAGE_MAP_PAGES - array of individual apges
DISCONTIG_KERNEL_PAGE_MAP_CONTIG_PAGES - 1..N contiguous pages
if (state->action == DISCONTIG_KERNEL_PAGE_MAP_PAGES)
return insert_pages(vma, addr, state->__page_arr,
&nr_pages, vma->vm_page_prot);
VM_WARN_ON_ONCE(state->action != DISCONTIG_KERNEL_PAGE_MAP_CONTIG_PAGES)
for (i = 0; i < nr_pages; i++, addr += PAGE_SIZE) {
struct page *page = state->__page + i;
int err;
err = insert_page(vma, addr, page, vma->vm_page_prot,
/*mkwrite=*/false);
if (err)
return err;
}
...
--
Cheers,
David