mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Kevin Brodsky <kevin.brodsky@arm.com>
To: "Mike Rapoport (Microsoft)" <rppt@kernel.org>,
	Andrew Morton <akpm@linux-foundation.org>,
	David Hildenbrand <david@kernel.org>,
	Jonathan Corbet <corbet@lwn.net>
Cc: "Liam R. Howlett" <liam@infradead.org>,
	Lorenzo Stoakes <ljs@kernel.org>, Michal Hocko <mhocko@suse.com>,
	Randy Dunlap <rdunlap@infradead.org>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Suren Baghdasaryan <surenb@google.com>,
	Vlastimil Babka <vbabka@kernel.org>,
	linux-arch@vger.kernel.org, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-mm@kvack.org
Subject: Re: [PATCH] docs/mm: describe set_memory() and set_direct_map() APIs
Date: Thu, 17 Sep 2026 20:44:17 +0200	[thread overview]
Message-ID: <2817ca55-cf04-4874-bed6-8408606e1a41@arm.com> (raw)
In-Reply-To: <20260909-set-memory-docs-v1-1-6065d3f208e4@kernel.org>

On 09/09/2026 11:45, Mike Rapoport (Microsoft) wrote:
> The set_memory() and set_direct_map() APIs change permissions of existing
> kernel mappings, but their semantics are only described by the code, and
> that code differs from architecture to architecture.
>
> Add Documentation/mm/kernel-page-tables.rst that briefly describes what the
> kernel page tables consist of, defines the semantics both APIs have in
> common, including the parts that are easy to get wrong, and lists the
> differences between the architecture implementations.
>
> Add kernel-doc comments for the generic set_memory() and set_direct_map()
> stubs and link them into Documentation/core-api/mm-api.rst.
>
> Assisted-by: copilot:claude-opus
> Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Thanks for doing this Mike, I wish there had been such a document when I
started using those APIs!

Much of that document feels like an excruciating FIXME list... which is
exactly the state of set_memory/set_direct_map, and better to have it
documented than letting every new user stumble upon the same gotchas.

Overall looks good to me, some minor comments below.

> [...]
>
> +Modifying the kernel page tables
> +================================
> +
> +Except for the vmalloc area, the kernel page tables are mostly static. Still,
> +there are cases when the permissions of existing kernel mappings have to be
> +updated, for instance when a module is loaded and its text becomes read-only
> +and executable, or when a page is temporarily removed from the direct map to
> +reduce its exposure.
> +
> +There are two families of functions for this, both declared in
> +`include/linux/set_memory.h`:
> +
> +* `set_memory_*()` change permissions of an arbitrary kernel mapping. They
> +  take a kernel virtual address and the number of pages.
> +
> +* `set_direct_map_*()` change permissions of the direct map alias of a
> +  `struct page`. They take a `struct page` pointer and the number of
> +  pages.

"direct map alias of a struct page" is rather confusing, are we talking
about the mapping of struct page itself?

> +
> +Architectures that implement `set_memory()` select `CONFIG_ARCH_HAS_SET_MEMORY`
> +
> +Architectures that implement `set_direct_map()` select
> +`CONFIG_ARCH_HAS_SET_DIRECT_MAP`.
> [...]
>
> +Architecture specific differences
> +=================================
> +
> +The APIs are implemented by seven architectures and, beyond the common
> +semantics described above, their behaviour differs in several respects.
> +
> +Which of the APIs are implemented:
> +
> +=========  =====================  =========================
> +Arch       `ARCH_HAS_SET_MEMORY`  `ARCH_HAS_SET_DIRECT_MAP`
> +=========  =====================  =========================
> +arm        yes                    no
> +arm64      yes                    yes
> +loongarch  yes                    yes
> +powerpc    yes                    no
> +riscv      yes (MMU only)         yes (MMU only)
> +s390       yes                    yes
> +x86        yes                    yes
> +=========  =====================  =========================
> +
> +Only set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() are
> +available everywhere, and even these are not universal: some architectures do
> +not implement any of them for the direct map, and the architectures that may

"do not implement any of them for the direct map" feels ambiguous - I
think what we really mean is that they reject direct map addresses, e.g.
what arm64 does (only accept addresses to kernel VMAs)?

> +run on hardware without an execute permission bit, like x86 and s390, silently
> +skip the update of the executable bit there.
> +
> +set_memory_rox() has a generic implementation that calls set_memory_ro() and
> +set_memory_x() in turn; PowerPC, s390 and x86 override it with a single-pass
> +version.
> +
> +Making a mapping present or not present is spelled differently: set_memory_p()
> +and set_memory_np() on x86 and PowerPC, set_memory_valid() on arm64 and arm.

I'm not sure we should even document set_memory_valid(). It doesn't at
all behave like the other set_memory_* on amr64 (no check whatsoever, no
handling of aliases) and is (fortunately) only used from arch code. I've
been meaning to make its name scarier (__set_memory_valid?) for that reason.

> +
> +The direct map and the kernel image are normally mapped with the largest
> +possible pages, and changing the permissions of a single page inside such a
> +mapping requires splitting it, which not every architecture can do.
> +
> +arm
> +---
> +
> +* Does not implement `set_direct_map()`.
> +* Provides set_memory_valid().
> +* set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() accept
> +  only vmalloc and module addresses.
> +* set_memory_valid() accepts any address.
> +* Does not update mapping aliases.
> +
> +arm64
> +-----
> +
> +* Provides set_memory_valid().
> +* Provides the memory encryption helpers, which are effective only when the
> +  kernel runs as a confidential guest.
> +* set_memory_ro(), set_memory_rw(), set_memory_x() and set_memory_nx() accept
> +  only vmalloc and module addresses:
> +
> +  - the range must fit in the VM area that contains its start
> +  - the VM area must have `VM_ALLOC` set and `VM_ALLOW_HUGE_VMAP` clear
> +
> +* set_memory_valid() accepts any address.
> +* The encryption helpers accept only the direct map addresses.

s/the//

> +* Propagates the read-only and the read-write changes to the direct map alias
> +  when `rodata=full` is in effect.

That is, set_memory_<perms> propagate those changes.

> +* Splits leaf mappings before the update on the hardware that supports it.
> +  Without such support an update that covers a leaf entry only partially fails
> +  with a WARN()ing and `-EINVAL`.

What does "partially fails" mean?  The splitting may be partial, but no
permission change should occur.

> +* The `set_direct_map()` functions return 0 without doing anything when the
> +  direct map cannot be modified, see can_set_direct_map().
> +* Skips the TLB flush in `set_memory()` when the update only turns an invalid
> +  mapping into a valid one.
> +* Does not flush TLB in `set_direct_map()`.
> +
> +LoongArch
> +---------
> +
> +* Accepts only the addresses above the hardware window and silently returns
> +  success for the rest, see `Direct map`_.
> +* Does not update mapping aliases.
> +* Does not split anything: a leaf entry is updated as a whole, which changes
> +  the permissions of the entire large mapping.

Ouch!

> +* Flushes the TLB in `set_direct_map()`.
> +
> +PowerPC
> +-------
> +
> +* Does not implement `set_direct_map()`.
> +* Provides set_memory_np() and set_memory_p().
> +* Rejects huge vmalloc mappings.
> +* With the hash MMU on 64-bit systems accepts nothing but the vmalloc and the
> +  I/O regions.
> +* With the radix MMU accepts direct map addresses, but still cannot split a
> +  large mapping.
> +* Does not update mapping aliases.
> +
> +riscv
> +-----
> +
> +* Implements both APIs only when the MMU is enabled.
> +* Provides set_memory_rw_nx().
> +* The `set_memory()` functions accept any mapped kernel address, including the
> +  direct map, but a vmalloc range must have the `pages` array of its VM area
> +  populated, which rules out vmap() and ioremap() mappings.
> +* On 64-bit systems updates the direct map alias of a vmalloc range, including
> +  the executable bit.
> +* Does not split vmalloc ranges: a leaf entry is updated as a whole, which
> +  changes the permissions of the entire large mapping.
> +* Splits the direct map on 64-bit systems.
> +* Flushes the TLB in `set_direct_map()`.
> +
> +s390
> +----
> +
> +* Provides set_memory_4k(), set_memory_rwnx() and the
> +  `__set_memory_*(start, end)` variants that take a range rather than a page
> +  count.
> +* The `set_memory()` functions accept any mapped kernel address, including the
> +  direct map.
> +* Skips the update of the executable bit when the hardware has no support for
> +  it.
> +* Propagates only the read-only and read-write changes to the direct map alias
> +  of a `VM_ALLOC` area, and deliberately not the executable bit.
> +* Splits leaf PUD and PMD entries when the range is not aligned to them or when
> +  set_memory_4k() is requested.
> +* Updates the page table entries with instructions that invalidate the
> +  corresponding TLB entries, so no separate flush is needed anywhere.
> +
> +x86
> +---
> +
> +* Provides the largest set of operations on top of the common ones:
> +
> +  - the cache attribute helpers: set_memory_uc(), set_memory_wc(),
> +    set_memory_wb()
> +  - presence control: set_memory_np() and set_memory_p()
> +  - set_memory_4k()
> +  - set_memory_global() and set_memory_nonglobal()
> +  - the array variants that operate on `struct page` arrays or arrays of
> +    virtual addresses
> +  - memory encryption: set_memory_encrypted() and set_memory_decrypted()
> +
> +* The `set_memory()` functions accept any mapped kernel address, including the
> +  direct map, and silently succeed for the unmapped holes inside it.
> +* Does nothing in set_memory_x() and set_memory_nx() when the CPU has no
> +  execute permission bit.
> +* Applies the change to the direct map alias and, for the kernel image, to the
> +  high kernel mapping. The NX bit is never propagated, so that the direct map
> +  stays non-executable.

Same as above, bettermake the subject explicit (set_memory()?).

- Kevin

> +* Splits large mappings on demand and can collapse them back when the
> +  permissions become uniform again.
> +* Does not flush the TLB in `set_direct_map()`, but splitting a large
> +  mapping flushes it anyway.
>
> [...]

  parent reply	other threads:[~2026-09-17 18:44 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-09  9:45 Mike Rapoport (Microsoft)
2026-09-09 10:30 ` David Hildenbrand (Arm)
2026-09-09 10:44   ` Mike Rapoport
2026-09-09 13:49     ` David Hildenbrand (Arm)
2026-09-17 18:44 ` Kevin Brodsky [this message]
2026-09-18  7:21   ` Mike Rapoport

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=2817ca55-cf04-4874-bed6-8408606e1a41@arm.com \
    --to=kevin.brodsky@arm.com \
    --cc=akpm@linux-foundation.org \
    --cc=corbet@lwn.net \
    --cc=david@kernel.org \
    --cc=liam@infradead.org \
    --cc=linux-arch@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=ljs@kernel.org \
    --cc=mhocko@suse.com \
    --cc=rdunlap@infradead.org \
    --cc=rppt@kernel.org \
    --cc=skhan@linuxfoundation.org \
    --cc=surenb@google.com \
    --cc=vbabka@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®