* [PATCH v9 01/10] memblock: Introduce MEMBLOCK_LLMAP
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 02/10] of: reserved_mem: Introduce "ll-map" property Vincent Donnefort
` (9 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
Keeping last-level mappings is interesting on some architectures as it
allows mapping/unmapping pages from the kernel direct map without the
risk of splitting blocks which, under the break-before-make rule, may
trigger page-faults the kernel can't handle.
However, mapping the entire direct map at PTE-level is costly. So
instead, create a new memblock flag MEMBLOCK_LLMAP to enable the system
to decide which region must be covered by mappings up to the last-level.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/include/linux/memblock.h b/include/linux/memblock.h
index d62db9e776cf..d40a5ded188d 100644
--- a/include/linux/memblock.h
+++ b/include/linux/memblock.h
@@ -52,6 +52,7 @@ extern unsigned long long max_possible_pfn;
* kernel that we know is good to use. It is the only memory that
* allocations may happen from in this phase.
* @MEMBLOCK_RSRV_HUGETLB: memory is reserved for hugetlb pages
+ * @MEMBLOCK_LLMAP: memory region to be mapped using last-level mapping
*/
enum memblock_flags {
MEMBLOCK_NONE = 0x0, /* No special request */
@@ -63,6 +64,7 @@ enum memblock_flags {
MEMBLOCK_RSRV_KERN = 0x20, /* memory reserved for kernel use */
MEMBLOCK_KHO_SCRATCH = 0x40, /* scratch memory for kexec handover */
MEMBLOCK_RSRV_HUGETLB = 0x80, /* memory reserved for hugetlb pages */
+ MEMBLOCK_LLMAP = 0x100,/* last-level mapping */
};
/**
@@ -160,6 +162,8 @@ int memblock_reserved_mark_noinit(phys_addr_t base, phys_addr_t size);
int memblock_reserved_mark_kern(phys_addr_t base, phys_addr_t size);
int memblock_mark_kho_scratch(phys_addr_t base, phys_addr_t size);
int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size);
+int memblock_mark_llmap(phys_addr_t base, phys_addr_t size);
+int memblock_clear_llmap(phys_addr_t base, phys_addr_t size);
void memblock_free(void *ptr, size_t size);
void reset_all_zones_managed_pages(void);
@@ -306,6 +310,11 @@ static inline bool memblock_is_kho_scratch(struct memblock_region *m)
return m->flags & MEMBLOCK_KHO_SCRATCH;
}
+static inline bool memblock_is_llmap(struct memblock_region *m)
+{
+ return m->flags & MEMBLOCK_LLMAP;
+}
+
int memblock_search_pfn_nid(unsigned long pfn, unsigned long *start_pfn,
unsigned long *end_pfn);
void __next_mem_pfn_range(int *idx, int nid, unsigned long *out_start_pfn,
diff --git a/mm/memblock.c b/mm/memblock.c
index 9ce86349a29f..1591b50503ed 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -1119,6 +1119,16 @@ int __init_memblock memblock_mark_mirror(phys_addr_t base, phys_addr_t size)
*/
int __init_memblock memblock_mark_nomap(phys_addr_t base, phys_addr_t size)
{
+ struct memblock_region *r;
+
+ memblock_cap_size(base, &size);
+
+ for_each_mem_region(r) {
+ if (memblock_is_llmap(r) &&
+ memblock_addrs_overlap(base, size, r->base, r->size))
+ return -EINVAL;
+ }
+
return memblock_setclr_flag(&memblock.memory, base, size, 1, MEMBLOCK_NOMAP);
}
@@ -1204,6 +1214,45 @@ __init int memblock_clear_kho_scratch(phys_addr_t base, phys_addr_t size)
MEMBLOCK_KHO_SCRATCH);
}
+/**
+ * memblock_mark_llmap - Mark a memory region with flag MEMBLOCK_LLMAP.
+ * @base: the base phys addr of the region
+ * @size: the size of the region
+ *
+ * If supported by the architecture, such region is mapped at the last-level in
+ * the kernel direct map.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int __init_memblock memblock_mark_llmap(phys_addr_t base, phys_addr_t size)
+{
+ struct memblock_region *r;
+
+ memblock_cap_size(base, &size);
+
+ for_each_mem_region(r) {
+ if (memblock_is_nomap(r) &&
+ memblock_addrs_overlap(base, size, r->base, r->size))
+ return -EINVAL;
+ }
+
+ return memblock_setclr_flag(&memblock.memory, base, size, 1,
+ MEMBLOCK_LLMAP);
+}
+
+/**
+ * memblock_clear_llmap - Clear flag MEMBLOCK_LLMAP for a specified region.
+ * @base: the base phys addr of the region
+ * @size: the size of the region
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+int __init_memblock memblock_clear_llmap(phys_addr_t base, phys_addr_t size)
+{
+ return memblock_setclr_flag(&memblock.memory, base, size, 0,
+ MEMBLOCK_LLMAP);
+}
+
static bool should_skip_region(struct memblock_type *type,
struct memblock_region *m,
int nid, int flags)
@@ -2886,6 +2935,7 @@ static const char * const flagname[] = {
[ilog2(MEMBLOCK_RSRV_NOINIT)] = "RSV_NIT",
[ilog2(MEMBLOCK_RSRV_KERN)] = "RSV_KERN",
[ilog2(MEMBLOCK_KHO_SCRATCH)] = "KHO_SCRATCH",
+ [ilog2(MEMBLOCK_LLMAP)] = "LLMAP",
};
static int memblock_debug_show(struct seq_file *m, void *private)
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 02/10] of: reserved_mem: Introduce "ll-map" property
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 01/10] memblock: Introduce MEMBLOCK_LLMAP Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 17:24 ` Rob Herring
2026-09-02 10:47 ` [PATCH v9 03/10] set_memory.h: Introduce can_set_direct_map_range() Vincent Donnefort
` (8 subsequent siblings)
10 siblings, 1 reply; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
Keeping last-level mappings is interesting on some architectures as it
allows mapping/unmapping pages from the kernel direct map without the
risk of splitting blocks which, under the break-before-make rule, may
trigger page-faults the kernel can't handle.
Add an "ll-map" property for reserved-memory regions. When set, it
splits the underlying memblock and sets the MEMBLOCK_LLMAP flag.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 8c9d6395d6a3..9ff2d02cb149 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -32,9 +32,32 @@ static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
static int reserved_mem_count;
+enum of_rmem_flags {
+ OF_RMEM_NONE = 0,
+ OF_RMEM_NOMAP = BIT(0),
+ OF_RMEM_LLMAP = BIT(1),
+};
+
+static int __init of_reserved_mem_flags(unsigned long node, enum of_rmem_flags *flags)
+{
+ *flags = OF_RMEM_NONE;
+
+ if (of_get_flat_dt_prop(node, "no-map", NULL))
+ *flags |= OF_RMEM_NOMAP;
+ if (of_get_flat_dt_prop(node, "ll-map", NULL))
+ *flags |= OF_RMEM_LLMAP;
+
+ if ((*flags & OF_RMEM_NOMAP) && (*flags & OF_RMEM_LLMAP)) {
+ pr_err("Reserved memory: no-map and ll-map are mutually exclusive\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
- phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
- phys_addr_t *res_base)
+ phys_addr_t align, phys_addr_t start, phys_addr_t end,
+ enum of_rmem_flags flags, phys_addr_t *res_base)
{
phys_addr_t base;
int err = 0;
@@ -46,10 +69,21 @@ static int __init early_init_dt_alloc_reserved_memory_arch(phys_addr_t size,
return -ENOMEM;
*res_base = base;
- if (nomap) {
- err = memblock_mark_nomap(base, size);
- if (err)
+ if (flags & OF_RMEM_LLMAP) {
+ err = memblock_mark_llmap(base, size);
+ if (err) {
memblock_phys_free(base, size);
+ return err;
+ }
+ }
+
+ if (flags & OF_RMEM_NOMAP) {
+ err = memblock_mark_nomap(base, size);
+ if (err) {
+ if (flags & OF_RMEM_LLMAP)
+ memblock_clear_llmap(base, size);
+ memblock_phys_free(base, size);
+ }
}
if (!err)
@@ -119,17 +153,29 @@ static int fdt_fixup_reserved_mem_node(unsigned long node,
phys_addr_t base, phys_addr_t size);
static int __init early_init_dt_reserve_memory(phys_addr_t base,
- phys_addr_t size, bool nomap)
+ phys_addr_t size,
+ enum of_rmem_flags flags)
{
- if (nomap) {
+ int err;
+
+ if (flags & OF_RMEM_LLMAP) {
+ err = memblock_mark_llmap(base, size);
+ if (err)
+ return err;
+ }
+
+ if (flags & OF_RMEM_NOMAP) {
/*
* If the memory is already reserved (by another region), we
* should not allow it to be marked nomap, but don't worry
* if the region isn't memory as it won't be mapped.
*/
if (memblock_overlaps_region(&memblock.memory, base, size) &&
- memblock_is_region_reserved(base, size))
+ memblock_is_region_reserved(base, size)) {
+ if (flags & OF_RMEM_LLMAP)
+ memblock_clear_llmap(base, size);
return -EBUSY;
+ }
return memblock_mark_nomap(base, size);
}
@@ -143,10 +189,10 @@ static int __init early_init_dt_reserve_memory(phys_addr_t base,
static int __init __reserved_mem_reserve_reg(unsigned long node,
const char *uname)
{
+ enum of_rmem_flags flags;
phys_addr_t base, size;
- int len, err;
const __be32 *prop;
- bool nomap;
+ int len, err;
u64 b, s;
prop = of_flat_dt_get_addr_size_prop(node, "reg", &len);
@@ -157,7 +203,9 @@ static int __init __reserved_mem_reserve_reg(unsigned long node,
pr_warn("Reserved memory: node '%s' has %d <base size> entries, only the first is used\n",
uname, len);
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ err = of_reserved_mem_flags(node, &flags);
+ if (err)
+ return err;
err = fdt_validate_reserved_mem_node(node, NULL);
if (err && err != -ENODEV)
@@ -167,7 +215,7 @@ static int __init __reserved_mem_reserve_reg(unsigned long node,
base = b;
size = s;
- if (size && early_init_dt_reserve_memory(base, size, nomap) == 0) {
+ if (size && early_init_dt_reserve_memory(base, size, flags) == 0) {
fdt_fixup_reserved_mem_node(node, base, size);
pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
@@ -399,8 +447,8 @@ int __init fdt_scan_reserved_mem(void)
* reserved regions to keep the reserved memory contiguous if possible.
*/
static int __init __reserved_mem_alloc_in_range(phys_addr_t size,
- phys_addr_t align, phys_addr_t start, phys_addr_t end, bool nomap,
- phys_addr_t *res_base)
+ phys_addr_t align, phys_addr_t start, phys_addr_t end,
+ enum of_rmem_flags flags, phys_addr_t *res_base)
{
bool prev_bottom_up = memblock_bottom_up();
bool bottom_up = false, top_down = false;
@@ -435,7 +483,7 @@ static int __init __reserved_mem_alloc_in_range(phys_addr_t size,
memblock_set_bottom_up(bottom_up);
ret = early_init_dt_alloc_reserved_memory_arch(size, align,
- start, end, nomap, res_base);
+ start, end, flags, res_base);
/* Restore old setting if needed */
if (bottom_up != top_down)
@@ -452,9 +500,9 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam
{
phys_addr_t start = 0, end = 0;
phys_addr_t base = 0, align = 0, size;
+ enum of_rmem_flags flags;
int i, len;
const __be32 *prop;
- bool nomap;
int ret;
prop = of_get_flat_dt_prop(node, "size", &len);
@@ -477,7 +525,9 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam
align = dt_mem_next_cell(dt_root_addr_cells, &prop);
}
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ ret = of_reserved_mem_flags(node, &flags);
+ if (ret)
+ return ret;
ret = fdt_validate_reserved_mem_node(node, &align);
if (ret && ret != -ENODEV)
@@ -495,7 +545,7 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam
base = 0;
ret = __reserved_mem_alloc_in_range(size, align,
- start, end, nomap, &base);
+ start, end, flags, &base);
if (ret == 0) {
pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base,
@@ -505,7 +555,7 @@ static int __init __reserved_mem_alloc_size(unsigned long node, const char *unam
}
} else {
ret = early_init_dt_alloc_reserved_memory_arch(size, align,
- 0, 0, nomap, &base);
+ 0, 0, flags, &base);
if (ret == 0)
pr_debug("allocated memory for '%s' node: base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
@@ -635,8 +685,8 @@ static int __init __reserved_mem_init_node(struct reserved_mem *rmem,
static void __init fdt_init_reserved_mem_node(unsigned long node, const char *uname,
phys_addr_t base, phys_addr_t size)
{
+ enum of_rmem_flags flags;
int err = 0;
- bool nomap;
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
@@ -650,14 +700,19 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
rmem->base = base;
rmem->size = size;
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ err = of_reserved_mem_flags(node, &flags);
+ if (err)
+ return;
err = __reserved_mem_init_node(rmem, node);
if (err != 0 && err != -ENODEV) {
pr_info("node %s compatible matching fail\n", rmem->name);
rmem->name = NULL;
- if (nomap)
+ if (flags & OF_RMEM_LLMAP)
+ memblock_clear_llmap(rmem->base, rmem->size);
+
+ if (flags & OF_RMEM_NOMAP)
memblock_clear_nomap(rmem->base, rmem->size);
else
memblock_phys_free(rmem->base, rmem->size);
@@ -667,9 +722,10 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
bool reusable =
(of_get_flat_dt_prop(node, "reusable", NULL)) != NULL;
- pr_info("%pa..%pa (%lu KiB) %s %s %s\n",
+ pr_info("%pa..%pa (%lu KiB) %s %s %s %s\n",
&rmem->base, &end, (unsigned long)(rmem->size / SZ_1K),
- nomap ? "nomap" : "map",
+ (flags & OF_RMEM_LLMAP) ? "last-level " : "",
+ (flags & OF_RMEM_NOMAP) ? "nomap" : "map",
reusable ? "reusable" : "non-reusable",
rmem->name ? rmem->name : "unknown");
}
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v9 02/10] of: reserved_mem: Introduce "ll-map" property
2026-09-02 10:47 ` [PATCH v9 02/10] of: reserved_mem: Introduce "ll-map" property Vincent Donnefort
@ 2026-09-02 17:24 ` Rob Herring
2026-09-03 10:03 ` Vincent Donnefort
0 siblings, 1 reply; 16+ messages in thread
From: Rob Herring @ 2026-09-02 17:24 UTC (permalink / raw)
To: Vincent Donnefort
Cc: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi,
mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel
On Wed, Sep 02, 2026 at 11:47:04AM +0100, Vincent Donnefort wrote:
> Keeping last-level mappings is interesting on some architectures as it
> allows mapping/unmapping pages from the kernel direct map without the
> risk of splitting blocks which, under the break-before-make rule, may
> trigger page-faults the kernel can't handle.
>
> Add an "ll-map" property for reserved-memory regions. When set, it
> splits the underlying memblock and sets the MEMBLOCK_LLMAP flag.
Where is this documented? Any DT property must have a schema.
Though I'm not thrilled about more flag properties. Handling all the
combinations of properties has proven to be painful. Can this be implied
from the compatible string instead?
Rob
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v9 02/10] of: reserved_mem: Introduce "ll-map" property
2026-09-02 17:24 ` Rob Herring
@ 2026-09-03 10:03 ` Vincent Donnefort
0 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-03 10:03 UTC (permalink / raw)
To: Rob Herring
Cc: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi,
mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel
On Wed, Sep 02, 2026 at 12:24:36PM -0500, Rob Herring wrote:
> On Wed, Sep 02, 2026 at 11:47:04AM +0100, Vincent Donnefort wrote:
> > Keeping last-level mappings is interesting on some architectures as it
> > allows mapping/unmapping pages from the kernel direct map without the
> > risk of splitting blocks which, under the break-before-make rule, may
> > trigger page-faults the kernel can't handle.
> >
> > Add an "ll-map" property for reserved-memory regions. When set, it
> > splits the underlying memblock and sets the MEMBLOCK_LLMAP flag.
>
> Where is this documented? Any DT property must have a schema.
Ack.
>
> Though I'm not thrilled about more flag properties. Handling all the
> combinations of properties has proven to be painful. Can this be implied
> from the compatible string instead?
>
> Rob
We need this before the kernel direct map has been created (paging_init()).
I would have moved that directly into the driver lend_pool.c, but then it is
probed too late (unflatten_device_tree()).
--
Vincent
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v9 03/10] set_memory.h: Introduce can_set_direct_map_range()
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 01/10] memblock: Introduce MEMBLOCK_LLMAP Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 02/10] of: reserved_mem: Introduce "ll-map" property Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 04/10] set_memory.h: Introduce __set_direct_map*() Vincent Donnefort
` (7 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
While can_set_direct_map() tells whether the direct map can be modified
globally, can_set_direct_map_range() checks if a specific range can be
modified. e.g. if mapped at PTE-level.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 3030d9245f5a..88175c3fa751 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -44,6 +44,12 @@ static inline bool kernel_page_present(struct page *page)
{
return true;
}
+
+static inline bool can_set_direct_map_range(struct page *page,
+ unsigned long nr_pages)
+{
+ return false;
+}
#else /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
/*
* Some architectures, e.g. ARM64 can disable direct map modifications at
@@ -56,6 +62,14 @@ static inline bool can_set_direct_map(void)
}
#define can_set_direct_map can_set_direct_map
#endif
+
+#ifndef can_set_direct_map_range
+static inline bool can_set_direct_map_range(struct page *page, unsigned long nr_pages)
+{
+ return can_set_direct_map();
+}
+#define can_set_direct_map_range can_set_direct_map_range
+#endif
#endif /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
#ifdef CONFIG_X86_64
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 04/10] set_memory.h: Introduce __set_direct_map*()
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (2 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 03/10] set_memory.h: Introduce can_set_direct_map_range() Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 05/10] arm64: can_set_direct_map() if BBML3 Vincent Donnefort
` (6 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
set_direct_map_*() implementations verify wheter the direct map can be
effectively set. This verification can be costly. Add an "advanced" API
__set_direct_map_*() where the direct map is modified without any check.
The caller must have verified it is safe earlier with one of the
can_set_direct_map() or can_set_direct_map_range() functions.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 88175c3fa751..5a1917a3fd52 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -34,6 +34,15 @@ static inline int set_direct_map_default_noflush(struct page *page)
return 0;
}
+static inline int __set_direct_map_invalid_noflush(struct page *page)
+{
+ return 0;
+}
+static inline int __set_direct_map_default_noflush(struct page *page)
+{
+ return 0;
+}
+
static inline int set_direct_map_valid_noflush(struct page *page,
unsigned nr, bool valid)
{
@@ -70,6 +79,22 @@ static inline bool can_set_direct_map_range(struct page *page, unsigned long nr_
}
#define can_set_direct_map_range can_set_direct_map_range
#endif
+
+#ifndef __set_direct_map_invalid_noflush
+static inline int __set_direct_map_invalid_noflush(struct page *page)
+{
+ return set_direct_map_invalid_noflush(page);
+}
+#define __set_direct_map_invalid_noflush __set_direct_map_invalid_noflush
+#endif
+
+#ifndef __set_direct_map_default_noflush
+static inline int __set_direct_map_default_noflush(struct page *page)
+{
+ return set_direct_map_default_noflush(page);
+}
+#define __set_direct_map_default_noflush __set_direct_map_default_noflush
+#endif
#endif /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
#ifdef CONFIG_X86_64
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 05/10] arm64: can_set_direct_map() if BBML3
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (3 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 04/10] set_memory.h: Introduce __set_direct_map*() Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 06/10] arm64: Implement can_set_direct_map_range() Vincent Donnefort
` (5 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
Systems supporting BBML3 can split blocks without risking unhandled
page-faults. It is therefore safe for them to set the direct map
unconditionally.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c..58898ae53f5d 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -99,7 +99,8 @@ bool can_set_direct_map(void)
* Realms need to make pages shared/protected at page granularity.
*/
return rodata_full || debug_pagealloc_enabled() ||
- arm64_kfence_can_set_direct_map() || is_realm_world();
+ arm64_kfence_can_set_direct_map() || is_realm_world() ||
+ system_supports_bbml3();
}
static int update_range_prot(unsigned long start, unsigned long size,
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 06/10] arm64: Implement can_set_direct_map_range()
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (4 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 05/10] arm64: can_set_direct_map() if BBML3 Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 07/10] arm64: Implement __set_direct_map*() Vincent Donnefort
` (4 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
For systems where can_set_direct_map() is false, it is still possible
that a subregion of the direct map can be modified if it is mapped at
the last-level. Add an implementation for can_set_direct_map_range(). It
falls back to a page table walk to verify the mapping level if
can_set_direct_map() is false.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 90f61b17275e..1a37f4ef130b 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -9,6 +9,9 @@
bool can_set_direct_map(void);
#define can_set_direct_map can_set_direct_map
+bool can_set_direct_map_range(struct page *page, unsigned long nr_pages);
+#define can_set_direct_map_range can_set_direct_map_range
+
int set_memory_valid(unsigned long addr, int numpages, int enable);
int set_direct_map_invalid_noflush(struct page *page);
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index 58898ae53f5d..c59ef17eb0d0 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -103,6 +103,48 @@ bool can_set_direct_map(void)
system_supports_bbml3();
}
+bool can_set_direct_map_range(struct page *page, unsigned long nr_pages)
+{
+ unsigned long addr = (unsigned long)page_address(page);
+ unsigned long end = addr + nr_pages * PAGE_SIZE;
+
+ if (can_set_direct_map())
+ return true;
+
+ /*
+ * If !can_set_direct_map() then no one can split blocks and it is safe
+ * to walk the page-table lockless.
+ */
+ while (addr < end) {
+ pud_t *pudp, pud;
+ pmd_t *pmdp, pmd;
+ pgd_t *pgdp;
+ p4d_t *p4dp;
+
+ pgdp = pgd_offset_k(addr);
+ if (pgd_none(READ_ONCE(*pgdp)))
+ return false;
+
+ p4dp = p4d_offset(pgdp, addr);
+ if (p4d_none(READ_ONCE(*p4dp)))
+ return false;
+
+ pudp = pud_offset(p4dp, addr);
+ pud = READ_ONCE(*pudp);
+ if (pud_none(pud) || pud_leaf(pud))
+ return false;
+
+ pmdp = pmd_offset(pudp, addr);
+ pmd = READ_ONCE(*pmdp);
+ if (pmd_none(pmd) || pmd_leaf(pmd))
+ return false;
+
+ addr = pmd_addr_end(addr, end);
+ }
+
+ return true;
+}
+
static int update_range_prot(unsigned long start, unsigned long size,
pgprot_t set_mask, pgprot_t clear_mask)
{
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 07/10] arm64: Implement __set_direct_map*()
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (5 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 06/10] arm64: Implement can_set_direct_map_range() Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 08/10] arm64: Add support for MEMBLOCK_LLMAP Vincent Donnefort
` (3 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
Add implementation for the unsafe functions __set_direct_map*(). They do
not verify for can_set_direct_map() and expect the caller to do so
beforehand.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 1a37f4ef130b..080a40846bbe 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -16,6 +16,10 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);
int set_direct_map_invalid_noflush(struct page *page);
int set_direct_map_default_noflush(struct page *page);
+int __set_direct_map_invalid_noflush(struct page *page);
+#define __set_direct_map_invalid_noflush __set_direct_map_invalid_noflush
+int __set_direct_map_default_noflush(struct page *page);
+#define __set_direct_map_default_noflush __set_direct_map_default_noflush
int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);
bool kernel_page_present(struct page *page);
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index c59ef17eb0d0..28adab0edace 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -294,28 +294,38 @@ int set_memory_valid(unsigned long addr, int numpages, int enable)
__pgprot(PTE_PRESENT_VALID_KERNEL));
}
-int set_direct_map_invalid_noflush(struct page *page)
+int __set_direct_map_invalid_noflush(struct page *page)
{
pgprot_t clear_mask = __pgprot(PTE_PRESENT_VALID_KERNEL);
pgprot_t set_mask = __pgprot(PTE_PRESENT_INVALID);
+ return update_range_prot((unsigned long)page_address(page),
+ PAGE_SIZE, set_mask, clear_mask);
+}
+
+int set_direct_map_invalid_noflush(struct page *page)
+{
if (!can_set_direct_map())
return 0;
+ return __set_direct_map_invalid_noflush(page);
+}
+
+int __set_direct_map_default_noflush(struct page *page)
+{
+ pgprot_t set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE);
+ pgprot_t clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY);
+
return update_range_prot((unsigned long)page_address(page),
PAGE_SIZE, set_mask, clear_mask);
}
int set_direct_map_default_noflush(struct page *page)
{
- pgprot_t set_mask = __pgprot(PTE_PRESENT_VALID_KERNEL | PTE_WRITE);
- pgprot_t clear_mask = __pgprot(PTE_PRESENT_INVALID | PTE_RDONLY);
-
if (!can_set_direct_map())
return 0;
- return update_range_prot((unsigned long)page_address(page),
- PAGE_SIZE, set_mask, clear_mask);
+ return __set_direct_map_default_noflush(page);
}
static int __set_memory_enc_dec(unsigned long addr,
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 08/10] arm64: Add support for MEMBLOCK_LLMAP
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (6 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 07/10] arm64: Implement __set_direct_map*() Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 10:47 ` [PATCH v9 09/10] firmware: arm_ffa: Introduce ffa-lend-pool Vincent Donnefort
` (2 subsequent siblings)
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
With the MEMBLOCK_LLMAP flag, force a last-level mapping.
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/arch/arm64/mm/mmu.c b/arch/arm64/mm/mmu.c
index 79d90226fd5d..c0fcc76914d1 100644
--- a/arch/arm64/mm/mmu.c
+++ b/arch/arm64/mm/mmu.c
@@ -1184,9 +1184,8 @@ static void __init map_mem(void)
phys_addr_t init_begin = __pa_symbol(__init_begin);
phys_addr_t init_end = __pa_symbol(__init_end);
phys_addr_t kernel_end = __pa_symbol(__bss_stop);
- phys_addr_t start, end;
int flags = NO_EXEC_MAPPINGS;
- u64 i;
+ struct memblock_region *r;
/*
* Setting hierarchical PXNTable attributes on table entries covering
@@ -1226,9 +1225,13 @@ static void __init map_mem(void)
flags);
/* map all the memory banks */
- for_each_mem_range(i, &start, &end) {
+ for_each_mem_region(r) {
+ phys_addr_t start, end;
+
+ if (memblock_is_nomap(r))
+ continue;
/*
- * for_each_mem_range may return sub-page-aligned boundaries
+ * for_each_mem_region may return sub-page-aligned boundaries
* after memblock_mark_nomap() splits regions at byte precision.
* __create_pgd_mapping_locked aligns phys down to PAGE_MASK,
* which could accidentally map no-map memory on the boundary.
@@ -1237,17 +1240,23 @@ static void __init map_mem(void)
* regions. The cost is at most one page of unmapped gap at
* each boundary.
*/
- start = PAGE_ALIGN(start);
- end = end & PAGE_MASK;
+ start = PAGE_ALIGN(r->base);
+ end = (r->base + r->size) & PAGE_MASK;
if (start >= end)
continue;
+
+ int rflags = flags;
+
+ if (memblock_is_llmap(r))
+ rflags |= NO_BLOCK_MAPPINGS | NO_CONT_MAPPINGS;
+
/*
* The linear map must allow allocation tags reading/writing
* if MTE is present. Otherwise, it has the same attributes as
* PAGE_KERNEL.
*/
__map_memblock(start, end, pgprot_tagged(PAGE_KERNEL),
- flags);
+ rflags);
}
}
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v9 09/10] firmware: arm_ffa: Introduce ffa-lend-pool
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (7 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 08/10] arm64: Add support for MEMBLOCK_LLMAP Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 17:38 ` Rob Herring
2026-09-02 10:47 ` [PATCH v9 10/10] optee: Add support for arm,ffa-lend-pool Vincent Donnefort
2026-09-02 13:27 ` [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
10 siblings, 1 reply; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
When memory is lent to the Secure world via FF-A, fatal CPU speculative
reads from Non-Secure can still occur as long as it retains a cacheable
mapping to that memory. Introduce the "arm,ffa-lend-pool"
reserved-memory CMA driver to unmap pages before lending
(ffa_prepare_lend()) and restore them upon reclaim
(ffa_lend_reclaimed()). Devices bind to the pool via the "memory-region"
DT property or via ffa_lend_pool_attach().
reserved-memory {
#address-cells = <0x2>;
#size-cells = <0x2>;
ranges;
ffa_lend: ffa-lend-pool {
compatible = "arm,ffa-lend-pool";
reusable;
ll-map;
size = <0x0 0x4000000>;
};
};
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/drivers/firmware/arm_ffa/Kconfig b/drivers/firmware/arm_ffa/Kconfig
index 5e3ae5cf82e8..66dbf74c37c3 100644
--- a/drivers/firmware/arm_ffa/Kconfig
+++ b/drivers/firmware/arm_ffa/Kconfig
@@ -19,3 +19,8 @@ config ARM_FFA_SMCCC
bool
default ARM_FFA_TRANSPORT
depends on ARM64 && HAVE_ARM_SMCCC_DISCOVERY
+
+config ARM_FFA_LEND_POOL
+ bool
+ default y
+ depends on ARM_FFA_TRANSPORT && CMA && OF_RESERVED_MEM
diff --git a/drivers/firmware/arm_ffa/Makefile b/drivers/firmware/arm_ffa/Makefile
index 168990a7e792..5ea3019c407b 100644
--- a/drivers/firmware/arm_ffa/Makefile
+++ b/drivers/firmware/arm_ffa/Makefile
@@ -6,3 +6,4 @@ ffa-core-objs := $(ffa-bus-y)
ffa-module-objs := $(ffa-driver-y) $(ffa-transport-y)
obj-$(CONFIG_ARM_FFA_TRANSPORT) = ffa-core.o
obj-$(CONFIG_ARM_FFA_TRANSPORT) += ffa-module.o
+obj-$(CONFIG_ARM_FFA_LEND_POOL) += lend_pool.o
diff --git a/drivers/firmware/arm_ffa/lend_pool.c b/drivers/firmware/arm_ffa/lend_pool.c
new file mode 100644
index 000000000000..67a36f5d5aa6
--- /dev/null
+++ b/drivers/firmware/arm_ffa/lend_pool.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Arm FF-A Reserved Memory CMA driver for Memory Lending
+ *
+ * Prevents CPU speculative reads to secure memory by unmapping it from the
+ * kernel direct map. This only works if the reserved-memory is mapped at the
+ * last-level ("ll-map;" or "rodata=full") or if all CPUs in the system support
+ * BBML3.
+ *
+ * Copyright (C) 2026 Google LLC
+ * Author: Vincent Donnefort <vdonnefort@google.com>
+ */
+
+#include <linux/arm_ffa.h>
+#include <linux/cleanup.h>
+#include <linux/cma.h>
+#include <linux/dma-map-ops.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/rcupdate.h>
+#include <linux/set_memory.h>
+#include <linux/xarray.h>
+
+static DEFINE_XARRAY(ffa_lend_devices);
+
+static bool ffa_lend_pool_contains(struct device *dev, phys_addr_t addr, size_t size)
+{
+ phys_addr_t base, end;
+
+ if (!dev)
+ return false;
+
+ guard(rcu)();
+
+ if (xa_load(&ffa_lend_devices, (unsigned long)dev) != dev)
+ return false;
+
+ if (WARN_ON_ONCE(!dev->cma_area))
+ return false;
+
+ base = cma_get_base(dev->cma_area);
+ end = base + cma_get_size(dev->cma_area);
+
+ return addr >= base && (addr + size) <= end;
+}
+
+/**
+ * ffa_prepare_lend() - Prepare a memory region to be lent in FF-A
+ * @dev: Device attached to the lend pool
+ * @addr: Physical start address of the memory region
+ * @size: Size in bytes
+ *
+ * When memory is lent via FF-A, TrustZone transitions it to the secure state.
+ * As long as Arm CPUs retain a valid mapping to that now-secure memory, they
+ * can speculatively read it, which is fatal on some systems.
+ *
+ * ffa_prepare_lend() prevents this by unmapping the memory range from the
+ * kernel's direct map.
+ *
+ * Return: 0 on success, negative error code otherwise.
+ */
+int ffa_prepare_lend(struct device *dev, phys_addr_t addr, size_t size)
+{
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+ struct page *page;
+ unsigned long i;
+ int ret;
+
+ if (!ffa_lend_pool_contains(dev, addr, size))
+ return -ENODEV;
+
+ page = pfn_to_page(PHYS_PFN(addr));
+ for (i = 0; i < nr_pages; i++) {
+ ret = __set_direct_map_invalid_noflush(page + i);
+
+ if (ret) {
+ while (i--)
+ __set_direct_map_default_noflush(page + i);
+
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(ffa_prepare_lend);
+
+/**
+ * ffa_lend_reclaimed() - Restore a reclaimed FF-A memory region
+ * @dev: Device attached to the lend pool
+ * @addr: Physical start address of the memory region
+ * @size: Size in bytes
+ *
+ * Restores a memory range into the kernel's direct mapping. It must be called
+ * after a successful FF-A memory reclaim invocation.
+ */
+void ffa_lend_reclaimed(struct device *dev, phys_addr_t addr, size_t size)
+{
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+ struct page *page;
+ unsigned long i;
+
+ if (!ffa_lend_pool_contains(dev, addr, size))
+ return;
+
+ page = pfn_to_page(PHYS_PFN(addr));
+ for (i = 0; i < nr_pages; i++)
+ __set_direct_map_default_noflush(page + i);
+}
+EXPORT_SYMBOL_GPL(ffa_lend_reclaimed);
+
+/**
+ * ffa_lend_pool_attach() - Attach a device to the FF-A lend pool
+ * @dev: Device to attach
+ *
+ * FF-A devices are dynamically discovered and might not have an associated
+ * device tree node with a "memory-region" phandle. In that case, drivers must
+ * use this function to attach to the "arm,ffa-lend-pool" reserved memory
+ * region.
+ *
+ * Return: 0 on success, negative error code otherwise.
+ */
+int ffa_lend_pool_attach(struct device *dev)
+{
+ struct device_node *np;
+ struct reserved_mem *rmem;
+
+ np = of_find_compatible_node(NULL, NULL, "arm,ffa-lend-pool");
+ if (!np)
+ return -ENODEV;
+
+ rmem = of_reserved_mem_lookup(np);
+ of_node_put(np);
+ if (!rmem || !rmem->ops || !rmem->ops->device_init)
+ return -EINVAL;
+
+ return rmem->ops->device_init(rmem, dev);
+}
+EXPORT_SYMBOL_GPL(ffa_lend_pool_attach);
+
+/**
+ * ffa_lend_pool_detach() - Detach a device from the FF-A lend pool
+ * @dev: Device to detach
+ *
+ * Releases the device from the "arm,ffa-lend-pool" reserved memory region.
+ */
+void ffa_lend_pool_detach(struct device *dev)
+{
+ struct device_node *np;
+ struct reserved_mem *rmem;
+
+ np = of_find_compatible_node(NULL, NULL, "arm,ffa-lend-pool");
+ if (!np)
+ return;
+
+ rmem = of_reserved_mem_lookup(np);
+ of_node_put(np);
+
+ if (rmem && rmem->ops && rmem->ops->device_release)
+ rmem->ops->device_release(rmem, dev);
+}
+EXPORT_SYMBOL_GPL(ffa_lend_pool_detach);
+
+static int __init ffa_lend_pool_setup(unsigned long node, struct reserved_mem *rmem)
+{
+ struct cma *cma;
+ int ret;
+
+ if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
+ of_get_flat_dt_prop(node, "no-map", NULL)) {
+ pr_err("FF-A lend pool: node must be 'reusable' and not 'no-map'\n");
+ return -EINVAL;
+ }
+
+ if (!IS_ALIGNED(rmem->base | rmem->size, CMA_MIN_ALIGNMENT_BYTES)) {
+ pr_err("FF-A lend pool: incorrect alignment of CMA region\n");
+ return -EINVAL;
+ }
+
+ ret = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name, &cma);
+ if (ret) {
+ pr_err("FF-A lend pool: unable to setup CMA region (%d)\n", ret);
+ return ret;
+ }
+
+ rmem->priv = cma;
+
+ return 0;
+}
+
+static int ffa_lend_pool_device_init(struct reserved_mem *rmem, struct device *dev)
+{
+ int ret;
+
+ if (!can_set_direct_map_range(pfn_to_page(PHYS_PFN(rmem->base)), rmem->size / PAGE_SIZE)) {
+ pr_err("FF-A lend pool: reserved memory cannot be unmapped in direct map\n");
+ return -EINVAL;
+ }
+
+ dev->cma_area = rmem->priv;
+
+ ret = xa_err(xa_store(&ffa_lend_devices, (unsigned long)dev, dev, GFP_KERNEL));
+ if (ret)
+ dev->cma_area = NULL;
+
+ return ret;
+}
+
+static void ffa_lend_pool_device_release(struct reserved_mem *rmem, struct device *dev)
+{
+ xa_erase(&ffa_lend_devices, (unsigned long)dev);
+ dev->cma_area = NULL;
+}
+
+static const struct reserved_mem_ops ffa_lend_pool_ops = {
+ .node_init = ffa_lend_pool_setup,
+ .device_init = ffa_lend_pool_device_init,
+ .device_release = ffa_lend_pool_device_release,
+};
+RESERVEDMEM_OF_DECLARE(ffa_lend_pool, "arm,ffa-lend-pool", &ffa_lend_pool_ops);
diff --git a/include/linux/arm_ffa.h b/include/linux/arm_ffa.h
index e71d83ee0aef..fa3570f85ff7 100644
--- a/include/linux/arm_ffa.h
+++ b/include/linux/arm_ffa.h
@@ -519,4 +519,25 @@ struct ffa_ops {
const struct ffa_notifier_ops *notifier_ops;
};
+#if IS_ENABLED(CONFIG_ARM_FFA_LEND_POOL)
+int ffa_lend_pool_attach(struct device *dev);
+void ffa_lend_pool_detach(struct device *dev);
+int ffa_prepare_lend(struct device *dev, phys_addr_t paddr, size_t size);
+void ffa_lend_reclaimed(struct device *dev, phys_addr_t paddr, size_t size);
+#else
+static inline int ffa_lend_pool_attach(struct device *dev)
+{
+ return -ENODEV;
+}
+static inline void ffa_lend_pool_detach(struct device *dev)
+{
+}
+static inline int ffa_prepare_lend(struct device *dev, phys_addr_t paddr, size_t size)
+{
+ return -ENODEV;
+}
+static inline void ffa_lend_reclaimed(struct device *dev, phys_addr_t paddr, size_t size)
+{
+}
+#endif
#endif /* _LINUX_ARM_FFA_H */
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v9 09/10] firmware: arm_ffa: Introduce ffa-lend-pool
2026-09-02 10:47 ` [PATCH v9 09/10] firmware: arm_ffa: Introduce ffa-lend-pool Vincent Donnefort
@ 2026-09-02 17:38 ` Rob Herring
2026-09-03 10:10 ` Vincent Donnefort
0 siblings, 1 reply; 16+ messages in thread
From: Rob Herring @ 2026-09-02 17:38 UTC (permalink / raw)
To: Vincent Donnefort
Cc: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi,
mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel
On Wed, Sep 02, 2026 at 11:47:11AM +0100, Vincent Donnefort wrote:
> When memory is lent to the Secure world via FF-A, fatal CPU speculative
> reads from Non-Secure can still occur as long as it retains a cacheable
> mapping to that memory. Introduce the "arm,ffa-lend-pool"
> reserved-memory CMA driver to unmap pages before lending
> (ffa_prepare_lend()) and restore them upon reclaim
> (ffa_lend_reclaimed()). Devices bind to the pool via the "memory-region"
> DT property or via ffa_lend_pool_attach().
>
> reserved-memory {
> #address-cells = <0x2>;
> #size-cells = <0x2>;
> ranges;
>
> ffa_lend: ffa-lend-pool {
> compatible = "arm,ffa-lend-pool";
Needs a binding schema...
> reusable;
> ll-map;
> size = <0x0 0x4000000>;
> };
> };
>
> Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
[...]
> +static int __init ffa_lend_pool_setup(unsigned long node, struct reserved_mem *rmem)
> +{
> + struct cma *cma;
> + int ret;
> +
> + if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
> + of_get_flat_dt_prop(node, "no-map", NULL)) {
> + pr_err("FF-A lend pool: node must be 'reusable' and not 'no-map'\n");
> + return -EINVAL;
Your schema should enforce/check this.
[...]
> +static const struct reserved_mem_ops ffa_lend_pool_ops = {
> + .node_init = ffa_lend_pool_setup,
> + .device_init = ffa_lend_pool_device_init,
> + .device_release = ffa_lend_pool_device_release,
> +};
> +RESERVEDMEM_OF_DECLARE(ffa_lend_pool, "arm,ffa-lend-pool", &ffa_lend_pool_ops);
Do you need this early? If not, you can create a platform driver for
"arm,ffa-lend-pool". ramoops is implemented that way for example.
Rob
^ permalink raw reply [flat|nested] 16+ messages in thread* Re: [PATCH v9 09/10] firmware: arm_ffa: Introduce ffa-lend-pool
2026-09-02 17:38 ` Rob Herring
@ 2026-09-03 10:10 ` Vincent Donnefort
0 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-03 10:10 UTC (permalink / raw)
To: Rob Herring
Cc: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi,
mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel
On Wed, Sep 02, 2026 at 12:38:26PM -0500, Rob Herring wrote:
> On Wed, Sep 02, 2026 at 11:47:11AM +0100, Vincent Donnefort wrote:
> > When memory is lent to the Secure world via FF-A, fatal CPU speculative
> > reads from Non-Secure can still occur as long as it retains a cacheable
> > mapping to that memory. Introduce the "arm,ffa-lend-pool"
> > reserved-memory CMA driver to unmap pages before lending
> > (ffa_prepare_lend()) and restore them upon reclaim
> > (ffa_lend_reclaimed()). Devices bind to the pool via the "memory-region"
> > DT property or via ffa_lend_pool_attach().
> >
> > reserved-memory {
> > #address-cells = <0x2>;
> > #size-cells = <0x2>;
> > ranges;
> >
> > ffa_lend: ffa-lend-pool {
> > compatible = "arm,ffa-lend-pool";
>
> Needs a binding schema...
Ack
>
> > reusable;
> > ll-map;
> > size = <0x0 0x4000000>;
> > };
> > };
> >
> > Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
>
> [...]
>
> > +static int __init ffa_lend_pool_setup(unsigned long node, struct reserved_mem *rmem)
> > +{
> > + struct cma *cma;
> > + int ret;
> > +
> > + if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
> > + of_get_flat_dt_prop(node, "no-map", NULL)) {
> > + pr_err("FF-A lend pool: node must be 'reusable' and not 'no-map'\n");
> > + return -EINVAL;
>
> Your schema should enforce/check this.
>
> [...]
>
> > +static const struct reserved_mem_ops ffa_lend_pool_ops = {
> > + .node_init = ffa_lend_pool_setup,
> > + .device_init = ffa_lend_pool_device_init,
> > + .device_release = ffa_lend_pool_device_release,
> > +};
> > +RESERVEDMEM_OF_DECLARE(ffa_lend_pool, "arm,ffa-lend-pool", &ffa_lend_pool_ops);
>
> Do you need this early? If not, you can create a platform driver for
> "arm,ffa-lend-pool". ramoops is implemented that way for example.
>
> Rob
Only memblock_mark_llmap() must be called very early.
The rest just need to be in place before the TEE driver probed. So a platform
driver should work.
Thanks!
--
Vincent
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v9 10/10] optee: Add support for arm,ffa-lend-pool
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (8 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 09/10] firmware: arm_ffa: Introduce ffa-lend-pool Vincent Donnefort
@ 2026-09-02 10:47 ` Vincent Donnefort
2026-09-02 13:27 ` [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 10:47 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel, Vincent Donnefort
Hook OP-TEE dynamically allocated protected memory pools to the
"arm,ffa-lend-pool" driver. While the SMC transport platform device
resolves the pool through its DT "memory-region" property, the FF-A
transport lacks a device tree node and binds via ffa_lend_pool_attach().
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
diff --git a/drivers/tee/optee/ffa_abi.c b/drivers/tee/optee/ffa_abi.c
index 633715b98625..4d6555171918 100644
--- a/drivers/tee/optee/ffa_abi.c
+++ b/drivers/tee/optee/ffa_abi.c
@@ -979,6 +979,7 @@ static void optee_ffa_remove(struct ffa_device *ffa_dev)
mutex_destroy(&optee->ffa.mutex);
rhashtable_free_and_destroy(&optee->ffa.global_ids, rh_free_fn, NULL);
+ ffa_lend_pool_detach(&optee->teedev->dev);
kfree(optee);
}
@@ -1042,13 +1043,21 @@ static int optee_ffa_protmem_pool_init(struct optee *optee, u32 sec_caps)
int rc = 0;
if (sec_caps & OPTEE_FFA_SEC_CAP_PROTMEM) {
+ rc = ffa_lend_pool_attach(&optee->teedev->dev);
+ if (rc && rc != -ENODEV)
+ return rc;
+
pool = optee_protmem_alloc_dyn_pool(optee, id);
- if (IS_ERR(pool))
+ if (IS_ERR(pool)) {
+ ffa_lend_pool_detach(&optee->teedev->dev);
return PTR_ERR(pool);
+ }
rc = tee_device_register_dma_heap(optee->teedev, id, pool);
- if (rc)
+ if (rc) {
pool->ops->destroy_pool(pool);
+ ffa_lend_pool_detach(&optee->teedev->dev);
+ }
}
return rc;
diff --git a/drivers/tee/optee/protmem.c b/drivers/tee/optee/protmem.c
index be3abf6e8aa6..9b64db9b4e64 100644
--- a/drivers/tee/optee/protmem.c
+++ b/drivers/tee/optee/protmem.c
@@ -42,14 +42,6 @@ static int init_dyn_protmem(struct optee_protmem_dyn_pool *rp)
goto err_null_protmem;
}
- /*
- * TODO unmap the memory range since the physical memory will
- * become inaccesible after the lend_protmem() call.
- *
- * If the platform supports a hypervisor at EL2, it will unmap the
- * intermediate physical memory for us and stop cache pre-fetch of
- * the memory.
- */
rc = rp->optee->ops->lend_protmem(rp->optee, rp->protmem,
rp->mem_attrs,
rp->mem_attr_count, rp->use_case);
diff --git a/drivers/tee/optee/smc_abi.c b/drivers/tee/optee/smc_abi.c
index b8a2bdac3208..5949aa717cad 100644
--- a/drivers/tee/optee/smc_abi.c
+++ b/drivers/tee/optee/smc_abi.c
@@ -19,6 +19,7 @@
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
+#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/rpmb.h>
#include <linux/sched.h>
@@ -1528,6 +1529,8 @@ static void optee_smc_remove(struct platform_device *pdev)
if (optee->smc.memremaped_shm)
memunmap(optee->smc.memremaped_shm);
+ of_reserved_mem_device_release(&optee->teedev->dev);
+
kfree(optee);
}
@@ -1712,16 +1715,22 @@ static int optee_protmem_pool_init(struct optee *optee)
if (!protm && !dyn_protm)
return 0;
+ of_reserved_mem_device_init_by_idx(&optee->teedev->dev,
+ dev_of_node(optee->teedev->dev.parent), 0);
if (protm)
pool = static_protmem_pool_init(optee);
if (dyn_protm && IS_ERR(pool))
pool = optee_protmem_alloc_dyn_pool(optee, heap_id);
- if (IS_ERR(pool))
+ if (IS_ERR(pool)) {
+ of_reserved_mem_device_release(&optee->teedev->dev);
return PTR_ERR(pool);
+ }
rc = tee_device_register_dma_heap(optee->teedev, heap_id, pool);
- if (rc)
+ if (rc) {
pool->ops->destroy_pool(pool);
+ of_reserved_mem_device_release(&optee->teedev->dev);
+ }
return rc;
}
@@ -1833,14 +1842,14 @@ static int optee_probe(struct platform_device *pdev)
(sec_caps & OPTEE_SMC_SEC_CAP_RPMB_PROBE))
optee->in_kernel_rpmb_routing = true;
- teedev = tee_device_alloc(&optee_clnt_desc, NULL, pool, optee);
+ teedev = tee_device_alloc(&optee_clnt_desc, &pdev->dev, pool, optee);
if (IS_ERR(teedev)) {
rc = PTR_ERR(teedev);
goto err_free_optee;
}
optee->teedev = teedev;
- teedev = tee_device_alloc(&optee_supp_desc, NULL, pool, optee);
+ teedev = tee_device_alloc(&optee_supp_desc, &pdev->dev, pool, optee);
if (IS_ERR(teedev)) {
rc = PTR_ERR(teedev);
goto err_unreg_teedev;
diff --git a/drivers/tee/tee_shm.c b/drivers/tee/tee_shm.c
index 6742b3579c86..49a9b2993c83 100644
--- a/drivers/tee/tee_shm.c
+++ b/drivers/tee/tee_shm.c
@@ -3,6 +3,7 @@
* Copyright (c) 2015-2017, 2019-2021 Linaro Limited
*/
#include <linux/anon_inodes.h>
+#include <linux/arm_ffa.h>
#include <linux/device.h>
#include <linux/dma-buf.h>
#include <linux/dma-mapping.h>
@@ -43,6 +44,7 @@ static void tee_shm_release(struct tee_device *teedev, struct tee_shm *shm)
dma_mem = container_of(shm, struct tee_shm_dma_mem, shm);
p = dma_mem;
+ ffa_lend_reclaimed(&teedev->dev, shm->paddr, shm->size);
dma_free_pages(&teedev->dev, shm->size, dma_mem->page,
dma_mem->dma_addr, DMA_BIDIRECTIONAL);
#endif
@@ -288,6 +290,7 @@ struct tee_shm *tee_shm_alloc_dma_mem(struct tee_context *ctx,
struct tee_shm_dma_mem *dma_mem;
dma_addr_t dma_addr;
struct page *page;
+ int ret;
if (!tee_device_get(teedev))
return ERR_PTR(-EINVAL);
@@ -297,9 +300,13 @@ struct tee_shm *tee_shm_alloc_dma_mem(struct tee_context *ctx,
if (!page)
goto err_put_teedev;
+ ret = ffa_prepare_lend(&teedev->dev, page_to_phys(page), page_count * PAGE_SIZE);
+ if (ret && ret != -ENODEV)
+ goto err_free_pages;
+
dma_mem = kzalloc_obj(*dma_mem);
if (!dma_mem)
- goto err_free_pages;
+ goto err_map_pages;
refcount_set(&dma_mem->shm.refcount, 1);
dma_mem->shm.ctx = ctx;
@@ -313,6 +320,8 @@ struct tee_shm *tee_shm_alloc_dma_mem(struct tee_context *ctx,
return &dma_mem->shm;
+err_map_pages:
+ ffa_lend_reclaimed(&teedev->dev, page_to_phys(page), page_count * PAGE_SIZE);
err_free_pages:
dma_free_pages(&teedev->dev, page_count * PAGE_SIZE, page, dma_addr,
DMA_BIDIRECTIONAL);
--
2.55.0.970.g62bdec98f9-goog
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map
2026-09-02 10:47 [PATCH v9 00/10] arm64: Unmap FF-A lent memory from direct map Vincent Donnefort
` (9 preceding siblings ...)
2026-09-02 10:47 ` [PATCH v9 10/10] optee: Add support for arm,ffa-lend-pool Vincent Donnefort
@ 2026-09-02 13:27 ` Vincent Donnefort
10 siblings, 0 replies; 16+ messages in thread
From: Vincent Donnefort @ 2026-09-02 13:27 UTC (permalink / raw)
To: catalin.marinas, will, rppt, akpm, sudeep.holla, jenswi, robh
Cc: mark.rutland, sumit.garg, ardb, thierry.reding, david,
danielmentz, linux-arm-kernel, linux-mm, op-tee, devicetree,
linux-kernel
This is of course v1, not v9...
On Wed, Sep 02, 2026 at 11:47:02AM +0100, Vincent Donnefort wrote:
> This series is a follow-up to the discussion that has started here [1].
> While standalone, it also provides primitives reusable for the VPR DMA
> heap.
>
> When memory is lent to the Secure world via FF-A, CPU speculative
> accesses from NS to the lent pages can still occur as long as it retains
> a cacheable mapping to it.
>
> Ideally, lent memory would be "no-map" but that would mean giving up
> MiBs of useful memory, so let's try to do better with the help of a CMA
> pool.
>
> On arm64, modifying the direct map at runtime is generally restricted
> because the linear map defaults to block mapping and splitting blocks at
> runtime may trigger fatal page fault, unless the CPU implements BBML3
> or the entire direct map was mapped at page granularity from boot.
> Forcing last-level mappings system-wide incurs a severe penalty we want
> to avoid. Instead, this series introduces targeted last-level mappings
> for designated memory regions, along with the "arm,ffa-lend-pool" CMA
> driver to manage unmapping and remapping on lend/reclaim transitions:
>
> 1. memblock & OF reserved memory ("ll-map"):
> - Introduce MEMBLOCK_LLMAP and the DT "ll-map" property for reserved-memory
> nodes to force last-level (PTE) mappings only for a specific region.
>
> 2. set_memory infrastructure:
> - Introduce can_set_direct_map_range() to check if a specific address
> range is mapped with last-level entries and can be modified safely.
> - Introduce __set_direct_map_*() variants that bypass redundant checks
> when the caller has already validated the range.
>
> 3. "arm,ffa-lend-pool" driver
> - Introduce the "arm,ffa-lend-pool" CMA reserved-memory driver, which
> unmaps pages prior to lending (ffa_prepare_lend()) and restores them
> when reclaimed (ffa_lend_reclaimed()).
>
> 4. Optee support
> - Hook OP-TEE dynamic protected memory pools to "arm,ffa-lend-pool" for
> both SMC (via DT memory-region phandle) and FF-A (via
> ffa_lend_pool_attach()) transports.
>
> Testing:
> ========
>
> Tested with QEMU v8 using OP-TEE OS (built with CFG_CORE_DYN_PROTMEM=y)
> under both SMC and FF-A transports [2]
>
> static void dump_direct_map(const char *label)
> {
> printf("\n=== %s ===\n", label);
> fflush(stdout);
> system("sed -n '/Linear Mapping start/,/Linear Mapping end/p' /sys/kernel/debug/kernel_page_tables");
> fflush(stdout);
> }
>
> int main(int argc, char *argv[])
> {
> int heap_fd;
> int dmabuf_fd;
> struct dma_heap_allocation_data data = { 0 };
> size_t size = 1024 * 1024; /* 1MB */
>
> if (argc > 1)
> size = strtoul(argv[1], NULL, 0);
>
> dump_direct_map("BEFORE ALLOCATION");
>
> heap_fd = open("/dev/dma_heap/protected,secure-video", O_RDWR);
> if (heap_fd < 0) {
> perror("open /dev/dma_heap/protected,secure-video");
> return 1;
> }
>
> printf("\nOpened /dev/dma_heap/protected,secure-video\n");
> printf("Allocating %zu bytes of protected memory via DMA heap...\n", size);
>
> data.len = size;
> data.fd_flags = O_RDWR | O_CLOEXEC;
> if (ioctl(heap_fd, DMA_HEAP_IOCTL_ALLOC, &data) < 0) {
> perror("ioctl DMA_HEAP_IOCTL_ALLOC");
> close(heap_fd);
> return 1;
> }
>
> dmabuf_fd = data.fd;
> printf("Successfully allocated %zu bytes! dmabuf_fd = %d\n", size, dmabuf_fd);
>
> dump_direct_map("DURING LEND (EXPECT HOLE IN DIRECT MAP)");
>
> printf("\nReleasing dmabuf_fd...\n");
> close(dmabuf_fd);
> close(heap_fd);
>
> dump_direct_map("AFTER RECLAIM (RESTORED DIRECT MAP)");
>
> return 0;
> }
>
> [1] https://lore.kernel.org/all/20260807-tegra-vpr-v4-7-5510d16af89e@nvidia.com/
> [2] https://optee.readthedocs.io/en/latest/building/gits/build.html#qemu-v8
>
> Vincent Donnefort (10):
> memblock: Introduce MEMBLOCK_LLMAP
> of: reserved_mem: Introduce "ll-map" property
> set_memory.h: Introduce can_set_direct_map_range()
> set_memory.h: Introduce __set_direct_map*()
> arm64: can_set_direct_map() if BBML3
> arm64: Implement can_set_direct_map_range()
> arm64: Implement __set_direct_map*()
> arm64: Add support for MEMBLOCK_LLMAP
> firmware: arm_ffa: Introduce ffa-lend-pool
> optee: Add support for arm,ffa-lend-pool
>
> arch/arm64/include/asm/set_memory.h | 7 +
> arch/arm64/mm/mmu.c | 23 ++-
> arch/arm64/mm/pageattr.c | 67 +++++++-
> drivers/firmware/arm_ffa/Kconfig | 5 +
> drivers/firmware/arm_ffa/Makefile | 1 +
> drivers/firmware/arm_ffa/lend_pool.c | 223 +++++++++++++++++++++++++++
> drivers/of/of_reserved_mem.c | 104 ++++++++++---
> drivers/tee/optee/ffa_abi.c | 13 +-
> drivers/tee/optee/protmem.c | 8 -
> drivers/tee/optee/smc_abi.c | 17 +-
> drivers/tee/tee_shm.c | 11 +-
> include/linux/arm_ffa.h | 21 +++
> include/linux/memblock.h | 9 ++
> include/linux/set_memory.h | 39 +++++
> mm/memblock.c | 50 ++++++
> 15 files changed, 545 insertions(+), 53 deletions(-)
> create mode 100644 drivers/firmware/arm_ffa/lend_pool.c
>
>
> base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
> --
> 2.55.0.970.g62bdec98f9-goog
>
--
Vincent
^ permalink raw reply [flat|nested] 16+ messages in thread