[PATCHv6] arm64: add better page protections to arm64
Catalin Marinas
catalin.marinas at arm.com
Tue Dec 2 10:28:27 PST 2014
On Fri, Nov 21, 2014 at 09:50:45PM +0000, Laura Abbott wrote:
> --- a/arch/arm64/mm/mmu.c
> +++ b/arch/arm64/mm/mmu.c
> @@ -26,6 +26,7 @@
> #include <linux/memblock.h>
> #include <linux/fs.h>
> #include <linux/io.h>
> +#include <linux/stop_machine.h>
>
> #include <asm/cputype.h>
> #include <asm/fixmap.h>
> @@ -137,17 +138,50 @@ static void __init *early_alloc(unsigned long sz)
> return ptr;
> }
>
> -static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
> +/*
> + * remap a PMD into pages
> + */
> +static noinline void split_pmd(pmd_t *pmd,
> + void *(*alloc)(unsigned long size))
Please drop the inline/noinline annotations, just let the compiler do
the work.
> +{
> + pte_t *pte, *start_pte;
> + unsigned long pfn;
> + int i = 0;
> +
> + start_pte = pte = alloc(PTRS_PER_PTE*sizeof(pte_t));
> + BUG_ON(!pte);
> +
> + pfn = pmd_pfn(*pmd);
> +
> + do {
> + /*
> + * Need to have the least restrictive permissions available
> + * permissions will be fixed up later
> + */
> + set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
> + pfn++;
> + } while (pte++, i++, i < PTRS_PER_PTE);
> +
> +
> + __pmd_populate(pmd, __pa(start_pte), PMD_TYPE_TABLE);
> + flush_tlb_all();
> +}
For consistency, can we not have split_pmd similar to split_pud (i.e.
avoid allocation in split_pmd, just populate with the given pmd)?
> +
> +static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
> unsigned long end, unsigned long pfn,
> - pgprot_t prot)
> + pgprot_t prot,
> + void *(*alloc)(unsigned long size))
> {
> pte_t *pte;
>
> if (pmd_none(*pmd)) {
> - pte = early_alloc(PTRS_PER_PTE * sizeof(pte_t));
> + pte = alloc(PTRS_PER_PTE * sizeof(pte_t));
> + BUG_ON(!pte);
I wonder whether we should put the BUG_ON in the alloc functions to keep
the code simpler.
> __pmd_populate(pmd, __pa(pte), PMD_TYPE_TABLE);
> }
> - BUG_ON(pmd_bad(*pmd));
> +
> + if (pmd_bad(*pmd))
> + split_pmd(pmd, alloc);
I would use pmd_sect(*pmd) instead, it gives an indication on why it
needs splitting.
> +static inline bool use_1G_block(unsigned long addr, unsigned long next,
> + unsigned long phys, pgprot_t sect_prot,
> + pgprot_t pte_prot)
> +{
> + if (PAGE_SHIFT != 12)
> + return false;
> +
> + if (((addr | next | phys) & ~PUD_MASK) != 0)
> + return false;
> +
> + /*
> + * The assumption here is that if the memory is anything other
> + * than normal we should not be using a block type
> + */
Why? I know the original code had a !map_io check but I don't remember
why we actually need this limitation.
> +#ifdef CONFIG_DEBUG_RODATA
> +static void __init __map_memblock(phys_addr_t start, phys_addr_t end)
> +{
> + /*
> + * Set up the executable regions using the existing section mappings
> + * for now. This will get more fine grained later once all memory
> + * is mapped
> + */
> + unsigned long kernel_x_start = round_down(__pa(_stext), SECTION_SIZE);
> + unsigned long kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);
> +
> + if (end < kernel_x_start) {
> + create_mapping(start, __phys_to_virt(start),
> + end - start, PROT_SECT_NORMAL, PAGE_KERNEL);
> + } else if (start >= kernel_x_end) {
> + create_mapping(start, __phys_to_virt(start),
> + end - start, PROT_SECT_NORMAL, PAGE_KERNEL);
> + } else {
> + if (start < kernel_x_start)
> + create_mapping(start, __phys_to_virt(start),
> + kernel_x_start - start,
> + PROT_SECT_NORMAL,
> + PAGE_KERNEL);
> + create_mapping(kernel_x_start,
> + __phys_to_virt(kernel_x_start),
> + kernel_x_end - kernel_x_start,
> + PROT_SECT_NORMAL_EXEC, PAGE_KERNEL_EXEC);
> + if (kernel_x_end < end)
> + create_mapping(kernel_x_end,
> + __phys_to_virt(kernel_x_end),
> + end - kernel_x_end,
> + PROT_SECT_NORMAL,
> + PAGE_KERNEL);
> + }
Do we care about the init section? It will get freed eventually (though
I guess we could spot early bugs modifying such code).
Anyway, can you not do something similar here for the rest of the kernel
text to avoid re-adjusting the page attributes later?
> +struct flush_addr {
> + unsigned long start;
> + unsigned long end;
> +};
> +
> +static int __flush_mappings(void *val)
> +{
> + struct flush_addr *data = val;
> +
> + flush_tlb_kernel_range(data->start, data->end);
> + return 0;
> +}
> +
> +static void adjust_mem(unsigned long vstart, unsigned long vend,
> + phys_addr_t phys,
> + pgprot_t sect_prot, pgprot_t pte_prot)
> +{
> + struct flush_addr f;
> +
> + create_mapping_late(phys, vstart, vend - vstart,
> + sect_prot, pte_prot);
> +
> + if (!IS_ALIGNED(vstart, SECTION_SIZE) || !IS_ALIGNED(vend, SECTION_SIZE)) {
> + f.start = vstart;
> + f.end = vend;
> + stop_machine(__flush_mappings, &f, NULL);
> + }
Maybe this was discussed on earlier versions of this patch but why do
you need stop_machine() here? A TLB flush would be broadcast by hardware
automatically to all the other CPUs.
I need to go again through this patch and see if there is a better
option than overloading the alloc_init_*() functions for adjusting ptes
(or maybe avoid adjusting altogether if we create them with the right
attributes from the beginning).
--
Catalin
More information about the linux-arm-kernel
mailing list