diff mbox series

[v2,1/5] mm: Add write-protect and clean utilities for address space ranges

Message ID 20190926115548.44000-2-thomas_os@shipmail.org (mailing list archive)
State New, archived
Headers show
Series Emulated coherent graphics memory take 2 | expand

Commit Message

Thomas Hellström (Intel) Sept. 26, 2019, 11:55 a.m. UTC
From: Thomas Hellstrom <thellstrom@vmware.com>

Add two utilities to a) write-protect and b) clean all ptes pointing into
a range of an address space.
The utilities are intended to aid in tracking dirty pages (either
driver-allocated system memory or pci device memory).
The write-protect utility should be used in conjunction with
page_mkwrite() and pfn_mkwrite() to trigger write page-faults on page
accesses. Typically one would want to use this on sparse accesses into
large memory regions. The clean utility should be used to utilize
hardware dirtying functionality and avoid the overhead of page-faults,
typically on large accesses into small memory regions.

Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Rik van Riel <riel@surriel.com>
Cc: Minchan Kim <minchan@kernel.org>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Huang Ying <ying.huang@intel.com>
Cc: Souptick Joarder <jrdr.linux@gmail.com>
Cc: "Jérôme Glisse" <jglisse@redhat.com>
Cc: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org

Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
Reviewed-by: Ralph Campbell <rcampbell@nvidia.com>
---
 include/linux/mm.h    |  13 +-
 mm/Kconfig            |   3 +
 mm/Makefile           |   1 +
 mm/as_dirty_helpers.c | 392 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 408 insertions(+), 1 deletion(-)
 create mode 100644 mm/as_dirty_helpers.c

Comments

Thomas Hellström (Intel) Sept. 26, 2019, 12:03 p.m. UTC | #1
On 9/26/19 1:55 PM, Thomas Hellström (VMware) wrote:
> From: Thomas Hellstrom <thellstrom@vmware.com>
>
> Add two utilities to a) write-protect and b) clean all ptes pointing into
> a range of an address space.
> The utilities are intended to aid in tracking dirty pages (either
> driver-allocated system memory or pci device memory).
> The write-protect utility should be used in conjunction with
> page_mkwrite() and pfn_mkwrite() to trigger write page-faults on page
> accesses. Typically one would want to use this on sparse accesses into
> large memory regions. The clean utility should be used to utilize
> hardware dirtying functionality and avoid the overhead of page-faults,
> typically on large accesses into small memory regions.
>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Matthew Wilcox <willy@infradead.org>
> Cc: Will Deacon <will.deacon@arm.com>
> Cc: Peter Zijlstra <peterz@infradead.org>
> Cc: Rik van Riel <riel@surriel.com>
> Cc: Minchan Kim <minchan@kernel.org>
> Cc: Michal Hocko <mhocko@suse.com>
> Cc: Huang Ying <ying.huang@intel.com>
> Cc: Souptick Joarder <jrdr.linux@gmail.com>
> Cc: "Jérôme Glisse" <jglisse@redhat.com>
> Cc: linux-mm@kvack.org
> Cc: linux-kernel@vger.kernel.org
>
> Signed-off-by: Thomas Hellstrom <thellstrom@vmware.com>
> Reviewed-by: Ralph Campbell <rcampbell@nvidia.com>
> ---

Hi!

I wonder if I can get an ack from an mm maintainer to merge this through 
DRM along with the vmwgfx patches? Andrew? Matthew?

Thanks,
Thomas
Linus Torvalds Sept. 26, 2019, 7:19 p.m. UTC | #2
On Thu, Sep 26, 2019 at 5:03 AM Thomas Hellström (VMware)
<thomas_os@shipmail.org> wrote:
>
> I wonder if I can get an ack from an mm maintainer to merge this through
> DRM along with the vmwgfx patches? Andrew? Matthew?

It would have helped to actually point to the patch itself, instead of
just quoting the commit message.

Looks like this:

     https://lore.kernel.org/lkml/20190926115548.44000-2-thomas_os@shipmail.org/

but why is the code in question not just using the regular page
walkers. The commit log shows no explanation of what's so special
about this?

Is the only reason the locking magic? Because if that's the reason,
then afaik we already have a function for that: it's
__walk_page_range().

Yes, it's static right now, but that's imho not a reason to duplicate
all the walking (badly).

Is there some other magic reason that isn't documented?

              Linus
Thomas Hellström (Intel) Sept. 26, 2019, 8:09 p.m. UTC | #3
Hi,

On 9/26/19 9:19 PM, Linus Torvalds wrote:
> On Thu, Sep 26, 2019 at 5:03 AM Thomas Hellström (VMware)
> <thomas_os@shipmail.org> wrote:
>> I wonder if I can get an ack from an mm maintainer to merge this through
>> DRM along with the vmwgfx patches? Andrew? Matthew?
> It would have helped to actually point to the patch itself, instead of
> just quoting the commit message.
>
> Looks like this:
>
>       https://lore.kernel.org/lkml/20190926115548.44000-2-thomas_os@shipmail.org/
>
> but why is the code in question not just using the regular page
> walkers. The commit log shows no explanation of what's so special
> about this?
>
> Is the only reason the locking magic? Because if that's the reason,
> then afaik we already have a function for that: it's
> __walk_page_range().
>
> Yes, it's static right now, but that's imho not a reason to duplicate
> all the walking (badly).
>
> Is there some other magic reason that isn't documented?
>
>                Linus

There is a discussion around this subject in

https://lore.kernel.org/lkml/20190926115548.44000-1-thomas_os@shipmail.org/

The main point is that there is an assert in pud_trans_huge_lock() that 
the mmap_sem is held, and we don't have it. Presumably we should be able 
to get away with the i_mmap_lock, but in addition I would have had to 
include the walk_as_pte_range() as the walk::pmd_entry anyway, so the 
amount of duplicated page walk code isn't that big.

That said, if people are OK with me modifying the assert in 
pud_trans_huge_lock() and make __walk_page_range non-static, it should 
probably be possible to make it work, yes.

Thanks,
Thomas
Linus Torvalds Sept. 26, 2019, 8:16 p.m. UTC | #4
On Thu, Sep 26, 2019 at 1:09 PM Thomas Hellström (VMware)
<thomas_os@shipmail.org> wrote:
>
> That said, if people are OK with me modifying the assert in
> pud_trans_huge_lock() and make __walk_page_range non-static, it should
> probably be possible to make it work, yes.

I don't think you need to modify that assert at all.

That thing only exists when there's a "pud_entry" op in the walker,
and then you absolutely need to have that mmap_lock.

As far as I can tell, you fundamentally only ever work on a pte level
in your address space walker already and actually have a WARN_ON() on
the pud_huge thing, so no pud entry can possibly apply.

So no, the assert in pud_trans_huge_lock() does not seem to be a
reason not to just use the existing page table walkers.

And once you get rid of the walking, what is left? Just the "iterate
over the inode mappings" part. Which could just be done in
mm/pagewalk.c, and then you don't even need to remove the static.

So making it be just another walking in pagewalk.c would seem to be
the simplest model.

Call it "walk_page_mapping()". And talk extensively about how the
locking differs a lot from the usual "walk_page_vma()" things.

The then actual "apply" functions (what a horrid name) could be in the
users. They shouldn't be mixed in with the walking functions anyway.
They are callbacks, not walkers.

             Linus
Thomas Hellström (Intel) Sept. 26, 2019, 8:55 p.m. UTC | #5
On 9/26/19 10:16 PM, Linus Torvalds wrote:
> On Thu, Sep 26, 2019 at 1:09 PM Thomas Hellström (VMware)
> <thomas_os@shipmail.org> wrote:
>> That said, if people are OK with me modifying the assert in
>> pud_trans_huge_lock() and make __walk_page_range non-static, it should
>> probably be possible to make it work, yes.
> I don't think you need to modify that assert at all.
>
> That thing only exists when there's a "pud_entry" op in the walker,
> and then you absolutely need to have that mmap_lock.
>
> As far as I can tell, you fundamentally only ever work on a pte level
> in your address space walker already and actually have a WARN_ON() on
> the pud_huge thing, so no pud entry can possibly apply.

Well, we're working on supporting huge puds and pmds in the graphics 
VMAs, although in the write-notify cases we're looking at here, we would 
probably want to split them down to PTE level. But if we would want to 
extend that or if we would want to make this interface general, we'd 
probably want to support also a pud_entry callback.

Looking at zap_pud_range() which when called from unmap_mapping_pages() 
uses identical locking (no mmap_sem), it seems we should be able to get 
away with i_mmap_lock(), making sure the whole page table doesn't 
disappear under us. So it's not clear to me why the mmap_sem is strictly 
needed here. Better to sort those restrictions out now rather than when 
huge entries start appearing.

>
> So no, the assert in pud_trans_huge_lock() does not seem to be a
> reason not to just use the existing page table walkers.
>
> And once you get rid of the walking, what is left? Just the "iterate
> over the inode mappings" part. Which could just be done in
> mm/pagewalk.c, and then you don't even need to remove the static.
>
> So making it be just another walking in pagewalk.c would seem to be
> the simplest model.
>
> Call it "walk_page_mapping()". And talk extensively about how the
> locking differs a lot from the usual "walk_page_vma()" things.

Sure. Can do that.

Thanks,

Thomas
Linus Torvalds Sept. 26, 2019, 10:20 p.m. UTC | #6
On Thu, Sep 26, 2019 at 1:55 PM Thomas Hellström (VMware)
<thomas_os@shipmail.org> wrote:
>
> Well, we're working on supporting huge puds and pmds in the graphics
> VMAs, although in the write-notify cases we're looking at here, we would
> probably want to split them down to PTE level.

Well, that's what the existing walker code does if you don't have that
"pud_entry()" callback.

That said, I assume you would *not* want to do that if the huge
pud/pmd is already clean and read-only, but just continue.

So you may want to have a special pud_entry() that handles that case.
Eventually. Maybe. Although honestly, if you're doing dirty tracking,
I doubt it makes much sense to use largepages.

> Looking at zap_pud_range() which when called from unmap_mapping_pages()
> uses identical locking (no mmap_sem), it seems we should be able to get
> away with i_mmap_lock(), making sure the whole page table doesn't
> disappear under us. So it's not clear to me why the mmap_sem is strictly
> needed here. Better to sort those restrictions out now rather than when
> huge entries start appearing.

zap_pud_range()actually does have that

               VM_BUG_ON_VMA(!rwsem_is_locked(&tlb->mm->mmap_sem), vma);

exactly for the case where it might have to split the pud entry.

Zapping the whole thing it does do without the assert.

I'm not going to swear the mmap_sem is absolutely required, since a
shared vma should be stable due to the i_mmap_lock, but splitting the
hugepage really is a fairly big deal.

It can't happen if you zap the *whole* mapping, but it can happen if
you have a start/end range. Like you do.

Also, in general it's probably not a great idea to look at
zap_page_range() (and copy_page_range()) for ideas.

They are kind of special, since they tend to be used for fundamental
whole-address-space operations (ie fork/exit) and so as a result they
get to do special things that a normal page walker generally shouldn't
do.

It's why they've never gotten translated to use the generic walker code.

           Linus
Thomas Hellström (Intel) Sept. 27, 2019, 5:55 a.m. UTC | #7
On 9/27/19 12:20 AM, Linus Torvalds wrote:
> On Thu, Sep 26, 2019 at 1:55 PM Thomas Hellström (VMware)
> <thomas_os@shipmail.org> wrote:
>> Well, we're working on supporting huge puds and pmds in the graphics
>> VMAs, although in the write-notify cases we're looking at here, we would
>> probably want to split them down to PTE level.
> Well, that's what the existing walker code does if you don't have that
> "pud_entry()" callback.
>
> That said, I assume you would *not* want to do that if the huge
> pud/pmd is already clean and read-only, but just continue.
>
> So you may want to have a special pud_entry() that handles that case.
> Eventually. Maybe. Although honestly, if you're doing dirty tracking,
> I doubt it makes much sense to use largepages.

The approach we're looking at in this case is to keep huge entries 
write-protected and split them in the wp_huge_xxx() code's fallback path 
with the mmap_sem held. This means that there will actually be huge 
entries in the page-walking code soon, but as you say, only entries that 
we want to ignore and not split. So we'd also need a way to avoid the 
pagewalk splitting for the situation when someone faults a huge entry in 
just before the call to split_huge_xxx.

>
>> Looking at zap_pud_range() which when called from unmap_mapping_pages()
>> uses identical locking (no mmap_sem), it seems we should be able to get
>> away with i_mmap_lock(), making sure the whole page table doesn't
>> disappear under us. So it's not clear to me why the mmap_sem is strictly
>> needed here. Better to sort those restrictions out now rather than when
>> huge entries start appearing.
> zap_pud_range()actually does have that
>
>                 VM_BUG_ON_VMA(!rwsem_is_locked(&tlb->mm->mmap_sem), vma);
>
> exactly for the case where it might have to split the pud entry.

Yes. My take on this is that locking the PUD ptl can be done either with 
the mmap_sem or the i_mmap_lock if present and that we should update the 
asserts in xxx_trans_huge_lock to reflect that. But when actually 
splitting transhuge pages you don't want to race with khugepaged, so you 
need the mmap_sem. For the graphics VMAs (MIXEDMAP), khugepaged never 
touches them. Yet.

>
> It's why they've never gotten translated to use the generic walker code.

OK. Yes there are a number of various specialized pagewalks all over the 
mm code.

But another thing that worries me is that the page-table modifications 
that happen in the callback use functionality that is not guaranteed to 
be exported, and that mm people don't want them to be exported because 
you don't want the drivers to go hacking around in page tables, which 
means that the two callbacks used here would need to be a set of core 
helpers anyway.

So I figure what I would end up with would actually be an extern 
__walk_page_range anyway, and slightly modified asserts. Do you think 
that could be acceptible?

Thanks,

Thomas


>
>             Linus
Thomas Hellström (Intel) Sept. 27, 2019, 9:27 a.m. UTC | #8
On 9/27/19 7:55 AM, Thomas Hellström (VMware) wrote:
> On 9/27/19 12:20 AM, Linus Torvalds wrote:
>> On Thu, Sep 26, 2019 at 1:55 PM Thomas Hellström (VMware)
>> <thomas_os@shipmail.org> wrote:
>>> Well, we're working on supporting huge puds and pmds in the graphics
>>> VMAs, although in the write-notify cases we're looking at here, we 
>>> would
>>> probably want to split them down to PTE level.
>> Well, that's what the existing walker code does if you don't have that
>> "pud_entry()" callback.
>>
>> That said, I assume you would *not* want to do that if the huge
>> pud/pmd is already clean and read-only, but just continue.
>>
>> So you may want to have a special pud_entry() that handles that case.
>> Eventually. Maybe. Although honestly, if you're doing dirty tracking,
>> I doubt it makes much sense to use largepages.
>
> The approach we're looking at in this case is to keep huge entries 
> write-protected and split them in the wp_huge_xxx() code's fallback 
> path with the mmap_sem held. This means that there will actually be 
> huge entries in the page-walking code soon, but as you say, only 
> entries that we want to ignore and not split. So we'd also need a way 
> to avoid the pagewalk splitting for the situation when someone faults 
> a huge entry in just before the call to split_huge_xxx.
>
>>
>>> Looking at zap_pud_range() which when called from unmap_mapping_pages()
>>> uses identical locking (no mmap_sem), it seems we should be able to get
>>> away with i_mmap_lock(), making sure the whole page table doesn't
>>> disappear under us. So it's not clear to me why the mmap_sem is 
>>> strictly
>>> needed here. Better to sort those restrictions out now rather than when
>>> huge entries start appearing.
>> zap_pud_range()actually does have that
>>
>> VM_BUG_ON_VMA(!rwsem_is_locked(&tlb->mm->mmap_sem), vma);
>>
>> exactly for the case where it might have to split the pud entry.
>
> Yes. My take on this is that locking the PUD ptl can be done either 
> with the mmap_sem or the i_mmap_lock if present and that we should 
> update the asserts in xxx_trans_huge_lock to reflect that. But when 
> actually splitting transhuge pages you don't want to race with 
> khugepaged, so you need the mmap_sem. For the graphics VMAs 
> (MIXEDMAP), khugepaged never touches them. Yet.
>
>>
>> It's why they've never gotten translated to use the generic walker code.
>
> OK. Yes there are a number of various specialized pagewalks all over 
> the mm code.
>
> But another thing that worries me is that the page-table modifications 
> that happen in the callback use functionality that is not guaranteed 
> to be exported, and that mm people don't want them to be exported 
> because you don't want the drivers to go hacking around in page 
> tables, which means that the two callbacks used here would need to be 
> a set of core helpers anyway.
>
> So I figure what I would end up with would actually be an extern 
> __walk_page_range anyway, and slightly modified asserts. Do you think 
> that could be acceptible?


Actually, I'll give your original suggestion a try and see what I come 
up with.

Thanks,
Thomas
Kirill A. Shutemov Sept. 27, 2019, 12:17 p.m. UTC | #9
On Thu, Sep 26, 2019 at 01:16:55PM -0700, Linus Torvalds wrote:
> On Thu, Sep 26, 2019 at 1:09 PM Thomas Hellström (VMware)
> <thomas_os@shipmail.org> wrote:
> >
> > That said, if people are OK with me modifying the assert in
> > pud_trans_huge_lock() and make __walk_page_range non-static, it should
> > probably be possible to make it work, yes.
> 
> I don't think you need to modify that assert at all.
> 
> That thing only exists when there's a "pud_entry" op in the walker,
> and then you absolutely need to have that mmap_lock.
> 
> As far as I can tell, you fundamentally only ever work on a pte level
> in your address space walker already and actually have a WARN_ON() on
> the pud_huge thing, so no pud entry can possibly apply.
> 
> So no, the assert in pud_trans_huge_lock() does not seem to be a
> reason not to just use the existing page table walkers.
> 
> And once you get rid of the walking, what is left? Just the "iterate
> over the inode mappings" part. Which could just be done in
> mm/pagewalk.c, and then you don't even need to remove the static.
> 
> So making it be just another walking in pagewalk.c would seem to be
> the simplest model.
> 
> Call it "walk_page_mapping()". And talk extensively about how the
> locking differs a lot from the usual "walk_page_vma()" things.

Walking mappings of a page is what rmap does. This code thas to be
integrated there.
Kirill A. Shutemov Sept. 27, 2019, 12:26 p.m. UTC | #10
On Thu, Sep 26, 2019 at 03:20:42PM -0700, Linus Torvalds wrote:
> On Thu, Sep 26, 2019 at 1:55 PM Thomas Hellström (VMware)
> <thomas_os@shipmail.org> wrote:
> >
> > Well, we're working on supporting huge puds and pmds in the graphics
> > VMAs, although in the write-notify cases we're looking at here, we would
> > probably want to split them down to PTE level.
> 
> Well, that's what the existing walker code does if you don't have that
> "pud_entry()" callback.
> 
> That said, I assume you would *not* want to do that if the huge
> pud/pmd is already clean and read-only, but just continue.
> 
> So you may want to have a special pud_entry() that handles that case.
> Eventually. Maybe. Although honestly, if you're doing dirty tracking,
> I doubt it makes much sense to use largepages.
> 
> > Looking at zap_pud_range() which when called from unmap_mapping_pages()
> > uses identical locking (no mmap_sem), it seems we should be able to get
> > away with i_mmap_lock(), making sure the whole page table doesn't
> > disappear under us. So it's not clear to me why the mmap_sem is strictly
> > needed here. Better to sort those restrictions out now rather than when
> > huge entries start appearing.
> 
> zap_pud_range()actually does have that
> 
>                VM_BUG_ON_VMA(!rwsem_is_locked(&tlb->mm->mmap_sem), vma);

The VM_BUG is a blind copy from PMD layer and it's bogus. i_mmap_lock()
works fine for file mappings.

The PMD was intended for THP case at the time when there were only
anon-THP. The check was relaxed and later dropped for file-THP on PMD
level. It has to be dropped on PUD too. We don't have anon-THP on PUD
level at all, only DAX played with them.
Linus Torvalds Sept. 27, 2019, 4:39 p.m. UTC | #11
On Fri, Sep 27, 2019 at 5:17 AM Kirill A. Shutemov <kirill@shutemov.name> wrote:
>
> > Call it "walk_page_mapping()". And talk extensively about how the
> > locking differs a lot from the usual "walk_page_vma()" things.
>
> Walking mappings of a page is what rmap does. This code thas to be
> integrated there.

Well, that's very questionable.

The rmap code mainly does the "page -> virtual" mapping.  One page at a time.

The page walker code does the "virtual -> pte" mapping. Always a whole
range at a time.

The new code wants a combination of both.

It very much is about walking ranges - as in mm/pagewalk.c. It's just
that it walks potentially multiple ranges, based on where the address
space is mapped.

I think it has way more commonalities with the page walking code than
it has with the rmap code. But yes, there is some of that "look up
mappings based on address space" in there too, but it's the least part
of it

And as Thomas pointed out, it also has commonalities with
unmap_mapping_pages() in mm/memory.c. In many ways that part is the
closest.

I'd say that from a code sharing standpoint, mm/rmap.c is absolutely
the wrong place. It's the furthest away from what Thomas wants to do.

The mm/pagewalk.c code has the most actual code that could be shared,
and the addition would be smallest there.

And conceptually the closest analogue in terms of what it _does_ is
unmap_mapping_range() in mm/memory.c, but I see no room for sharing
actual code there unless we completely change how we do
zap_page_range() and add a lot of configurability there (which we
don't want, because page table teardown at exit is really a pretty
critical operation - I commonly see copy_page_range() and
zap_page_range() on profiles if you have things like script-heavyu
traditional UNIX loads).

So I think conceptually, mm/memory.c and unmap_mapping_range() is
closest but I don't think it's practical to share code.

And between mm/pagewalk.c and mm/rmap.c, I think the page walking has
way more of actual practical code sharing, and is also conceptually
closer because most of the code is about walking a range, not looking
up the mapping of one page.

               Linus
Kirill A. Shutemov Sept. 30, 2019, 1:03 p.m. UTC | #12
On Fri, Sep 27, 2019 at 09:39:48AM -0700, Linus Torvalds wrote:
> On Fri, Sep 27, 2019 at 5:17 AM Kirill A. Shutemov <kirill@shutemov.name> wrote:
> >
> > > Call it "walk_page_mapping()". And talk extensively about how the
> > > locking differs a lot from the usual "walk_page_vma()" things.
> >
> > Walking mappings of a page is what rmap does. This code thas to be
> > integrated there.
> 
> Well, that's very questionable.
> 
> The rmap code mainly does the "page -> virtual" mapping.  One page at a time.
> 
> The page walker code does the "virtual -> pte" mapping. Always a whole
> range at a time.

Have you seen page_vma_mapped_walk()? I made it specifically for rmap code
to cover cases when a THP is mapped with PTEs. To me it's not a big
stretch to make it cover multiple pages too.

> So I think conceptually, mm/memory.c and unmap_mapping_range() is
> closest but I don't think it's practical to share code.
> 
> And between mm/pagewalk.c and mm/rmap.c, I think the page walking has
> way more of actual practical code sharing, and is also conceptually
> closer because most of the code is about walking a range, not looking
> up the mapping of one page.

I guess it's matter of personal preferences, but page table walkers based
on callback always felt wrong to me.
Linus Torvalds Sept. 30, 2019, 5:12 p.m. UTC | #13
On Mon, Sep 30, 2019 at 6:04 AM Kirill A. Shutemov <kirill@shutemov.name> wrote:
>
> Have you seen page_vma_mapped_walk()? I made it specifically for rmap code
> to cover cases when a THP is mapped with PTEs. To me it's not a big
> stretch to make it cover multiple pages too.

I agree that is closer, but it does make for calling that big complex
function for every iteration step.

Of course, you are right that the callback approach is problematic
too, now that we have retpoline issues, making those very expensive.
But at least that hopefully gets fixed some day and gets to be a rare
problem.

Matter ot taste, I guess.

              Linus
Thomas Hellström (Intel) Sept. 30, 2019, 5:38 p.m. UTC | #14
On 9/30/19 7:12 PM, Linus Torvalds wrote:
> On Mon, Sep 30, 2019 at 6:04 AM Kirill A. Shutemov <kirill@shutemov.name> wrote:
>> Have you seen page_vma_mapped_walk()? I made it specifically for rmap code
>> to cover cases when a THP is mapped with PTEs. To me it's not a big
>> stretch to make it cover multiple pages too.
> I agree that is closer, but it does make for calling that big complex
> function for every iteration step.
>
> Of course, you are right that the callback approach is problematic
> too, now that we have retpoline issues, making those very expensive.
> But at least that hopefully gets fixed some day and gets to be a rare
> problem.
>
> Matter ot taste, I guess.
>
>                Linus

Matthew Wilcox suggested something similar before the pagewalk.c rewrite:

https://lore.kernel.org/lkml/20190806190937.GD30179@bombadil.infradead.org/

Still, In this case I'd opt for using the pagewalk code: In the dirty 
helpers we don't ever use a struct page, but only deal with PTE entries, 
same as the pagewalk code does, but not page_vma_mapped_walk(). The 
underlying memory may well be IO memory.

/Thomas
Thomas Hellström (Intel) Oct. 2, 2019, 9:21 a.m. UTC | #15
On 9/26/19 10:16 PM, Linus Torvalds wrote:
> On Thu, Sep 26, 2019 at 1:09 PM Thomas Hellström (VMware)
> <thomas_os@shipmail.org> wrote:
>> That said, if people are OK with me modifying the assert in
>> pud_trans_huge_lock() and make __walk_page_range non-static, it should
>> probably be possible to make it work, yes.
> I don't think you need to modify that assert at all.
>
> That thing only exists when there's a "pud_entry" op in the walker,
> and then you absolutely need to have that mmap_lock.
>
> As far as I can tell, you fundamentally only ever work on a pte level
> in your address space walker already and actually have a WARN_ON() on
> the pud_huge thing, so no pud entry can possibly apply.
>
> So no, the assert in pud_trans_huge_lock() does not seem to be a
> reason not to just use the existing page table walkers.
>
> And once you get rid of the walking, what is left? Just the "iterate
> over the inode mappings" part. Which could just be done in
> mm/pagewalk.c, and then you don't even need to remove the static.
>
> So making it be just another walking in pagewalk.c would seem to be
> the simplest model.
>
> Call it "walk_page_mapping()". And talk extensively about how the
> locking differs a lot from the usual "walk_page_vma()" things.
>
> The then actual "apply" functions (what a horrid name) could be in the
> users. They shouldn't be mixed in with the walking functions anyway.
> They are callbacks, not walkers.
>
>               Linus

Linus, Kirill

I've pushed a reworked version based on the pagewalk code here:

https://cgit.freedesktop.org/~thomash/linux/log/?h=pagewalk

(top three patched)

with users included here:

https://cgit.freedesktop.org/~thomash/linux/log/?h=coherent-rebased

Do you think this could work? The reason that the "mm: Add write-protect 
and clean.." code is still in mm as a set of helpers, is of course that 
much of the needed functionality is not exported, presumably since we 
want to keep page table manipulation in mm.

Thanks,

Thomas
Kirill A. Shutemov Oct. 2, 2019, 1:18 p.m. UTC | #16
On Wed, Oct 02, 2019 at 11:21:01AM +0200, Thomas Hellström (VMware) wrote:
> On 9/26/19 10:16 PM, Linus Torvalds wrote:
> > On Thu, Sep 26, 2019 at 1:09 PM Thomas Hellström (VMware)
> > <thomas_os@shipmail.org> wrote:
> > > That said, if people are OK with me modifying the assert in
> > > pud_trans_huge_lock() and make __walk_page_range non-static, it should
> > > probably be possible to make it work, yes.
> > I don't think you need to modify that assert at all.
> > 
> > That thing only exists when there's a "pud_entry" op in the walker,
> > and then you absolutely need to have that mmap_lock.
> > 
> > As far as I can tell, you fundamentally only ever work on a pte level
> > in your address space walker already and actually have a WARN_ON() on
> > the pud_huge thing, so no pud entry can possibly apply.
> > 
> > So no, the assert in pud_trans_huge_lock() does not seem to be a
> > reason not to just use the existing page table walkers.
> > 
> > And once you get rid of the walking, what is left? Just the "iterate
> > over the inode mappings" part. Which could just be done in
> > mm/pagewalk.c, and then you don't even need to remove the static.
> > 
> > So making it be just another walking in pagewalk.c would seem to be
> > the simplest model.
> > 
> > Call it "walk_page_mapping()". And talk extensively about how the
> > locking differs a lot from the usual "walk_page_vma()" things.
> > 
> > The then actual "apply" functions (what a horrid name) could be in the
> > users. They shouldn't be mixed in with the walking functions anyway.
> > They are callbacks, not walkers.
> > 
> >               Linus
> 
> Linus, Kirill
> 
> I've pushed a reworked version based on the pagewalk code here:
> 
> https://cgit.freedesktop.org/~thomash/linux/log/?h=pagewalk
> 
> (top three patched)
> 
> with users included here:
> 
> https://cgit.freedesktop.org/~thomash/linux/log/?h=coherent-rebased
> 
> Do you think this could work? The reason that the "mm: Add write-protect and
> clean.." code is still in mm as a set of helpers, is of course that much of
> the needed functionality is not exported, presumably since we want to keep
> page table manipulation in mm.

Could you post it to the mailing list? It's easier to review this way.
Thomas Hellström (Intel) Oct. 2, 2019, 1:28 p.m. UTC | #17
On 10/2/19 3:18 PM, Kirill A. Shutemov wrote:
> On Wed, Oct 02, 2019 at 11:21:01AM +0200, Thomas Hellström (VMware) wrote:
>> On 9/26/19 10:16 PM, Linus Torvalds wrote:
>>> On Thu, Sep 26, 2019 at 1:09 PM Thomas Hellström (VMware)
>>> <thomas_os@shipmail.org> wrote:
>>>> That said, if people are OK with me modifying the assert in
>>>> pud_trans_huge_lock() and make __walk_page_range non-static, it should
>>>> probably be possible to make it work, yes.
>>> I don't think you need to modify that assert at all.
>>>
>>> That thing only exists when there's a "pud_entry" op in the walker,
>>> and then you absolutely need to have that mmap_lock.
>>>
>>> As far as I can tell, you fundamentally only ever work on a pte level
>>> in your address space walker already and actually have a WARN_ON() on
>>> the pud_huge thing, so no pud entry can possibly apply.
>>>
>>> So no, the assert in pud_trans_huge_lock() does not seem to be a
>>> reason not to just use the existing page table walkers.
>>>
>>> And once you get rid of the walking, what is left? Just the "iterate
>>> over the inode mappings" part. Which could just be done in
>>> mm/pagewalk.c, and then you don't even need to remove the static.
>>>
>>> So making it be just another walking in pagewalk.c would seem to be
>>> the simplest model.
>>>
>>> Call it "walk_page_mapping()". And talk extensively about how the
>>> locking differs a lot from the usual "walk_page_vma()" things.
>>>
>>> The then actual "apply" functions (what a horrid name) could be in the
>>> users. They shouldn't be mixed in with the walking functions anyway.
>>> They are callbacks, not walkers.
>>>
>>>                Linus
>> Linus, Kirill
>>
>> I've pushed a reworked version based on the pagewalk code here:
>>
>> https://cgit.freedesktop.org/~thomash/linux/log/?h=pagewalk
>>
>> (top three patched)
>>
>> with users included here:
>>
>> https://cgit.freedesktop.org/~thomash/linux/log/?h=coherent-rebased
>>
>> Do you think this could work? The reason that the "mm: Add write-protect and
>> clean.." code is still in mm as a set of helpers, is of course that much of
>> the needed functionality is not exported, presumably since we want to keep
>> page table manipulation in mm.
> Could you post it to the mailing list? It's easier to review this way.
>
Sure.

/Thomas
diff mbox series

Patch

diff --git a/include/linux/mm.h b/include/linux/mm.h
index 0334ca97c584..27ff341ecbdc 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2657,7 +2657,6 @@  typedef int (*pte_fn_t)(pte_t *pte, unsigned long addr, void *data);
 extern int apply_to_page_range(struct mm_struct *mm, unsigned long address,
 			       unsigned long size, pte_fn_t fn, void *data);
 
-
 #ifdef CONFIG_PAGE_POISONING
 extern bool page_poisoning_enabled(void);
 extern void kernel_poison_pages(struct page *page, int numpages, int enable);
@@ -2891,5 +2890,17 @@  void __init setup_nr_node_ids(void);
 static inline void setup_nr_node_ids(void) {}
 #endif
 
+#ifdef CONFIG_AS_DIRTY_HELPERS
+unsigned long apply_as_clean(struct address_space *mapping,
+			     pgoff_t first_index, pgoff_t nr,
+			     pgoff_t bitmap_pgoff,
+			     unsigned long *bitmap,
+			     pgoff_t *start,
+			     pgoff_t *end);
+
+unsigned long apply_as_wrprotect(struct address_space *mapping,
+				 pgoff_t first_index, pgoff_t nr);
+#endif
+
 #endif /* __KERNEL__ */
 #endif /* _LINUX_MM_H */
diff --git a/mm/Kconfig b/mm/Kconfig
index 56cec636a1fc..594350e9d78e 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -736,4 +736,7 @@  config ARCH_HAS_PTE_SPECIAL
 config ARCH_HAS_HUGEPD
 	bool
 
+config AS_DIRTY_HELPERS
+        bool
+
 endmenu
diff --git a/mm/Makefile b/mm/Makefile
index d0b295c3b764..4086f1eefbc6 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -105,3 +105,4 @@  obj-$(CONFIG_PERCPU_STATS) += percpu-stats.o
 obj-$(CONFIG_ZONE_DEVICE) += memremap.o
 obj-$(CONFIG_HMM_MIRROR) += hmm.o
 obj-$(CONFIG_MEMFD_CREATE) += memfd.o
+obj-$(CONFIG_AS_DIRTY_HELPERS) += as_dirty_helpers.o
diff --git a/mm/as_dirty_helpers.c b/mm/as_dirty_helpers.c
new file mode 100644
index 000000000000..d4cc37dcb144
--- /dev/null
+++ b/mm/as_dirty_helpers.c
@@ -0,0 +1,392 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/mm.h>
+#include <linux/mm_types.h>
+#include <linux/hugetlb.h>
+#include <linux/bitops.h>
+#include <linux/mmu_notifier.h>
+#include <asm/cacheflush.h>
+#include <asm/tlbflush.h>
+
+/**
+ * struct as_walk - Argument to struct as_walk_ops callbacks.
+ * @vma: Pointer to the struct vmw_area_struct currently being walked.
+ *
+ * Embeddable argument to struct as_walk_ops callbacks.
+ */
+struct as_walk {
+	struct vm_area_struct *vma;
+};
+
+/**
+ * struct as_walk_ops - Callbacks for entries of various page table levels.
+ * extend for additional level support.
+ */
+struct as_walk_ops {
+	/**
+	 * pte-entry: Callback for PTEs
+	 * @pte: Pointer to the PTE.
+	 * @addr: Virtual address.
+	 * @asw: Struct as_walk argument for the walk. Embed for additional
+	 * data.
+	 */
+	void (*const pte_entry) (pte_t *pte, unsigned long addr,
+				 struct as_walk *asw);
+};
+
+/* Page-walking code */
+static void walk_as_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
+			      const struct as_walk_ops *ops,
+			      struct as_walk *asw)
+{
+	struct mm_struct *mm = asw->vma->vm_mm;
+	pte_t *pte;
+	spinlock_t *ptl;
+
+	pte = (mm == &init_mm) ?
+		pte_offset_kernel(pmd, addr) :
+		pte_offset_map_lock(mm, pmd, addr, &ptl);
+
+	arch_enter_lazy_mmu_mode();
+
+	do {
+		ops->pte_entry(pte++, addr, asw);
+	} while (addr += PAGE_SIZE, addr != end);
+
+	arch_leave_lazy_mmu_mode();
+
+	if (mm != &init_mm)
+		pte_unmap_unlock(pte - 1, ptl);
+}
+
+static void walk_as_pmd_range(pud_t *pud, unsigned long addr, unsigned long end,
+			      const struct as_walk_ops *ops,
+			      struct as_walk *asw)
+{
+	pmd_t *pmd = pmd_offset(pud, addr);
+	unsigned long next;
+
+	do {
+		next = pmd_addr_end(addr, end);
+		if (pmd_none_or_clear_bad(pmd))
+			continue;
+		if (WARN_ON(pmd_huge(*pmd)))
+			continue;
+		walk_as_pte_range(pmd, addr, next, ops, asw);
+	} while (pmd++, addr = next, addr != end);
+}
+
+static void walk_as_pud_range(p4d_t *p4d, unsigned long addr, unsigned long end,
+			      const struct as_walk_ops *ops,
+			      struct as_walk *asw)
+{
+	pud_t *pud = pud_offset(p4d, addr);
+	unsigned long next;
+
+	do {
+		next = pud_addr_end(addr, end);
+		if (pud_none_or_clear_bad(pud))
+			continue;
+		if (WARN_ON(pud_huge(*pud)))
+			continue;
+		walk_as_pmd_range(pud, addr, next, ops, asw);
+	} while (pud++, addr = next, addr != end);
+}
+
+static void walk_as_p4d_range(pgd_t *pgd, unsigned long addr, unsigned long end,
+			      const struct as_walk_ops *ops,
+			      struct as_walk *asw)
+{
+	p4d_t *p4d = p4d_offset(pgd, addr);
+	unsigned long next;
+
+	do {
+		next = p4d_addr_end(addr, end);
+		if (p4d_none_or_clear_bad(p4d))
+			continue;
+		walk_as_pud_range(p4d, addr, next, ops, asw);
+	} while (p4d++, addr = next, addr != end);
+}
+
+static void walk_as_pfn_range(unsigned long addr, unsigned long end,
+			      const struct as_walk_ops *ops,
+			      struct as_walk *asw)
+{
+	pgd_t *pgd = pgd_offset(asw->vma->vm_mm, addr);
+	unsigned long next;
+
+	do {
+		next = pgd_addr_end(addr, end);
+		if (pgd_none_or_clear_bad(pgd))
+			continue;
+		walk_as_p4d_range(pgd, addr, next, ops, asw);
+	} while (pgd++, addr = next, addr != end);
+}
+
+
+/**
+ * struct as_walk_range - Argument for apply_as_range
+ * @asw: The struct as_walk we embed for the page walk
+ * @start: Address of first modified pte
+ * @end: Address of last modified pte + 1
+ * @total: Total number of modified ptes
+ */
+struct as_walk_range {
+	struct as_walk base;
+	unsigned long start;
+	unsigned long end;
+	unsigned long total;
+};
+
+#define to_as_walk_range(_asw) container_of(_asw, struct as_walk_range, base)
+
+/**
+ * apply_pt_wrprotect - Leaf pte callback to write-protect a pte
+ * @pte: Pointer to the pte
+ * @addr: The virtual page address
+ * @asw: Pointer to a struct as_walk embedded in a struct as_walk_range
+ *
+ * The function write-protects a pte and records the range in
+ * virtual address space of touched ptes for efficient range TLB flushes.
+ */
+static void apply_pt_wrprotect(pte_t *pte, unsigned long addr,
+			       struct as_walk *asw)
+{
+	struct as_walk_range *awr = to_as_walk_range(asw);
+	pte_t ptent = *pte;
+
+	if (pte_write(ptent)) {
+		pte_t old_pte = ptep_modify_prot_start(asw->vma, addr, pte);
+
+		ptent = pte_wrprotect(old_pte);
+		ptep_modify_prot_commit(asw->vma, addr, pte, old_pte, ptent);
+		awr->total++;
+		awr->start = min(awr->start, addr);
+		awr->end = max(awr->end, addr + PAGE_SIZE);
+	}
+}
+
+/**
+ * struct as_walk_clean - Argument structure for apply_pt_clean
+ * @base: struct as_walk we derive from
+ * @bitmap_pgoff: Address_space Page offset of the first bit in @bitmap
+ * @bitmap: Bitmap with one bit for each page offset in the address_space range
+ * covered.
+ * @start: Address_space page offset of first modified pte relative
+ * to @bitmap_pgoff
+ * @end: Address_space page offset of last modified pte relative
+ * to @bitmap_pgoff
+ */
+struct as_walk_clean {
+	struct as_walk_range base;
+	pgoff_t bitmap_pgoff;
+	unsigned long *bitmap;
+	pgoff_t start;
+	pgoff_t end;
+};
+
+#define to_as_walk_clean(_awr) container_of(_awr, struct as_walk_clean, base)
+
+/**
+ * apply_pt_clean - Leaf pte callback to clean a pte
+ * @pte: Pointer to the pte
+ * @addr: The virtual page address
+ * @asw: Pointer to a struct as_walk embedded in a struct as_walk_clean
+ *
+ * The function cleans a pte and records the range in
+ * virtual address space of touched ptes for efficient TLB flushes.
+ * It also records dirty ptes in a bitmap representing page offsets
+ * in the address_space, as well as the first and last of the bits
+ * touched.
+ */
+static void apply_pt_clean(pte_t *pte, unsigned long addr, struct as_walk *asw)
+{
+	struct as_walk_range *awr = to_as_walk_range(asw);
+	struct as_walk_clean *clean = to_as_walk_clean(awr);
+	pte_t ptent = *pte;
+
+	if (pte_dirty(ptent)) {
+		pgoff_t pgoff = ((addr - asw->vma->vm_start) >> PAGE_SHIFT) +
+			asw->vma->vm_pgoff - clean->bitmap_pgoff;
+		pte_t old_pte = ptep_modify_prot_start(asw->vma, addr, pte);
+
+		ptent = pte_mkclean(old_pte);
+		ptep_modify_prot_commit(asw->vma, addr, pte, old_pte, ptent);
+
+		awr->total++;
+		awr->start = min(awr->start, addr);
+		awr->end = max(awr->end, addr + PAGE_SIZE);
+
+		__set_bit(pgoff, clean->bitmap);
+		clean->start = min(clean->start, pgoff);
+		clean->end = max(clean->end, pgoff + 1);
+	}
+}
+
+/**
+ * apply_as_range - Apply a pte callback to all PTEs pointing into a range
+ * of an address_space.
+ * @mapping: Pointer to the struct address_space
+ * @aas: Closure structure
+ * @first_index: First page offset in the address_space
+ * @nr: Number of incremental page offsets to cover
+ *
+ * Return: Number of ptes touched. Note that this number might be larger
+ * than @nr if there are overlapping vmas
+ */
+static unsigned long apply_as_range(struct address_space *mapping,
+				    pgoff_t first_index, pgoff_t nr,
+				    const struct as_walk_ops *ops,
+				    struct as_walk_range *awr)
+{
+	struct vm_area_struct *vma;
+	pgoff_t vba, vea, cba, cea;
+	unsigned long start_addr, end_addr;
+	struct mmu_notifier_range range;
+
+	i_mmap_lock_read(mapping);
+	vma_interval_tree_foreach(vma, &mapping->i_mmap, first_index,
+				  first_index + nr - 1) {
+		unsigned long vm_flags = READ_ONCE(vma->vm_flags);
+
+		/*
+		 * We can only do advisory flag tests below, since we can't
+		 * require the mm's mmap_sem to be held to protect the flags.
+		 * Therefore, callers that strictly depend on specific vm_flags
+		 * to remain constant throughout the operation must ensure
+		 * those flags are immutable for all relevant vmas or can't use
+		 * this function. Fixing this properly would require the
+		 * vm_flags to be protected by a separate lock taken after the
+		 * i_mmap_lock
+		 */
+
+		/* Skip non-applicable VMAs */
+		if ((vm_flags & (VM_SHARED | VM_WRITE)) !=
+		    (VM_SHARED | VM_WRITE))
+			continue;
+
+		/* Warn on and skip VMAs whose flags indicate illegal usage */
+		if (WARN_ON((vm_flags & (VM_HUGETLB | VM_IO)) != VM_IO))
+			continue;
+
+		/* Clip to the vma */
+		vba = vma->vm_pgoff;
+		vea = vba + vma_pages(vma);
+		cba = first_index;
+		cba = max(cba, vba);
+		cea = first_index + nr;
+		cea = min(cea, vea);
+
+		/* Translate to virtual address */
+		start_addr = ((cba - vba) << PAGE_SHIFT) + vma->vm_start;
+		end_addr = ((cea - vba) << PAGE_SHIFT) + vma->vm_start;
+		if (start_addr >= end_addr)
+			continue;
+
+		awr->base.vma = vma;
+		awr->start = end_addr;
+		awr->end = start_addr;
+
+		mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_PAGE, 0,
+					vma, vma->vm_mm, start_addr, end_addr);
+		mmu_notifier_invalidate_range_start(&range);
+
+		/* Is this needed when we only change protection? */
+		flush_cache_range(vma, start_addr, end_addr);
+
+		/*
+		 * We're not using tlb_gather_mmu() since typically
+		 * only a small subrange of PTEs are affected, whereas
+		 * tlb_gather_mmu() records the full range.
+		 */
+		inc_tlb_flush_pending(vma->vm_mm);
+		walk_as_pfn_range(start_addr, end_addr, ops, &awr->base);
+		if (awr->end > awr->start)
+			flush_tlb_range(vma, awr->start, awr->end);
+
+		mmu_notifier_invalidate_range_end(&range);
+		dec_tlb_flush_pending(vma->vm_mm);
+	}
+	i_mmap_unlock_read(mapping);
+
+	return awr->total;
+}
+
+/**
+ * apply_as_wrprotect - Write-protect all ptes in an address_space range
+ * @mapping: The address_space we want to write protect
+ * @first_index: The first page offset in the range
+ * @nr: Number of incremental page offsets to cover
+ *
+ * WARNING: This function should only be used for address spaces whose
+ * vmas are marked VM_IO and that do not contain huge pages.
+ * To avoid interference with COW'd pages, vmas not marked VM_SHARED are
+ * simply skipped.
+ *
+ * Return: The number of ptes actually write-protected. Note that
+ * already write-protected ptes are not counted.
+ */
+unsigned long apply_as_wrprotect(struct address_space *mapping,
+				 pgoff_t first_index, pgoff_t nr)
+{
+	static const struct as_walk_ops ops = {
+		.pte_entry = apply_pt_wrprotect
+	};
+	struct as_walk_range awr = { .total = 0 };
+
+	return apply_as_range(mapping, first_index, nr, &ops, &awr);
+}
+EXPORT_SYMBOL_GPL(apply_as_wrprotect);
+
+/**
+ * apply_as_clean - Clean all ptes in an address_space range
+ * @mapping: The address_space we want to clean
+ * @first_index: The first page offset in the range
+ * @nr: Number of incremental page offsets to cover
+ * @bitmap_pgoff: The page offset of the first bit in @bitmap
+ * @bitmap: Pointer to a bitmap of at least @nr bits. The bitmap needs to
+ * cover the whole range @first_index..@first_index + @nr.
+ * @start: Pointer to number of the first set bit in @bitmap.
+ * is modified as new bits are set by the function.
+ * @end: Pointer to the number of the last set bit in @bitmap.
+ * none set. The value is modified as new bits are set by the function.
+ *
+ * Note: When this function returns there is no guarantee that a CPU has
+ * not already dirtied new ptes. However it will not clean any ptes not
+ * reported in the bitmap.
+ *
+ * If a caller needs to make sure all dirty ptes are picked up and none
+ * additional are added, it first needs to write-protect the address-space
+ * range and make sure new writers are blocked in page_mkwrite() or
+ * pfn_mkwrite(). And then after a TLB flush following the write-protection
+ * pick up all dirty bits.
+ *
+ * WARNING: This function should only be used for address spaces whose
+ * vmas are marked VM_IO and that do not contain huge pages.
+ * To avoid interference with COW'd pages, vmas not marked VM_SHARED are
+ * simply skipped.
+ *
+ * Return: The number of dirty ptes actually cleaned.
+ */
+unsigned long apply_as_clean(struct address_space *mapping,
+			     pgoff_t first_index, pgoff_t nr,
+			     pgoff_t bitmap_pgoff,
+			     unsigned long *bitmap,
+			     pgoff_t *start,
+			     pgoff_t *end)
+{
+	bool none_set = (*start >= *end);
+	static const struct as_walk_ops ops = { .pte_entry = apply_pt_clean };
+	struct as_walk_clean clean = {
+		.base = { .total = 0, },
+		.bitmap_pgoff = bitmap_pgoff,
+		.bitmap = bitmap,
+		.start = none_set ? nr : *start,
+		.end = none_set ? 0 : *end,
+	};
+	unsigned long ret = apply_as_range(mapping, first_index, nr, &ops,
+					   &clean.base);
+	*start = clean.start;
+	*end = clean.end;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(apply_as_clean);