|
@@ -17,6 +17,7 @@
|
|
|
#include <linux/of_fdt.h>
|
|
|
#include <linux/mm.h>
|
|
|
#include <linux/string_helpers.h>
|
|
|
+#include <linux/stop_machine.h>
|
|
|
|
|
|
#include <asm/pgtable.h>
|
|
|
#include <asm/pgalloc.h>
|
|
@@ -685,6 +686,30 @@ static void free_pmd_table(pmd_t *pmd_start, pud_t *pud)
|
|
|
pud_clear(pud);
|
|
|
}
|
|
|
|
|
|
+struct change_mapping_params {
|
|
|
+ pte_t *pte;
|
|
|
+ unsigned long start;
|
|
|
+ unsigned long end;
|
|
|
+ unsigned long aligned_start;
|
|
|
+ unsigned long aligned_end;
|
|
|
+};
|
|
|
+
|
|
|
+static int stop_machine_change_mapping(void *data)
|
|
|
+{
|
|
|
+ struct change_mapping_params *params =
|
|
|
+ (struct change_mapping_params *)data;
|
|
|
+
|
|
|
+ if (!data)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ spin_unlock(&init_mm.page_table_lock);
|
|
|
+ pte_clear(&init_mm, params->aligned_start, params->pte);
|
|
|
+ create_physical_mapping(params->aligned_start, params->start);
|
|
|
+ create_physical_mapping(params->end, params->aligned_end);
|
|
|
+ spin_lock(&init_mm.page_table_lock);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static void remove_pte_table(pte_t *pte_start, unsigned long addr,
|
|
|
unsigned long end)
|
|
|
{
|
|
@@ -713,6 +738,52 @@ static void remove_pte_table(pte_t *pte_start, unsigned long addr,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * clear the pte and potentially split the mapping helper
|
|
|
+ */
|
|
|
+static void split_kernel_mapping(unsigned long addr, unsigned long end,
|
|
|
+ unsigned long size, pte_t *pte)
|
|
|
+{
|
|
|
+ unsigned long mask = ~(size - 1);
|
|
|
+ unsigned long aligned_start = addr & mask;
|
|
|
+ unsigned long aligned_end = addr + size;
|
|
|
+ struct change_mapping_params params;
|
|
|
+ bool split_region = false;
|
|
|
+
|
|
|
+ if ((end - addr) < size) {
|
|
|
+ /*
|
|
|
+ * We're going to clear the PTE, but not flushed
|
|
|
+ * the mapping, time to remap and flush. The
|
|
|
+ * effects if visible outside the processor or
|
|
|
+ * if we are running in code close to the
|
|
|
+ * mapping we cleared, we are in trouble.
|
|
|
+ */
|
|
|
+ if (overlaps_kernel_text(aligned_start, addr) ||
|
|
|
+ overlaps_kernel_text(end, aligned_end)) {
|
|
|
+ /*
|
|
|
+ * Hack, just return, don't pte_clear
|
|
|
+ */
|
|
|
+ WARN_ONCE(1, "Linear mapping %lx->%lx overlaps kernel "
|
|
|
+ "text, not splitting\n", addr, end);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ split_region = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (split_region) {
|
|
|
+ params.pte = pte;
|
|
|
+ params.start = addr;
|
|
|
+ params.end = end;
|
|
|
+ params.aligned_start = addr & ~(size - 1);
|
|
|
+ params.aligned_end = min_t(unsigned long, aligned_end,
|
|
|
+ (unsigned long)__va(memblock_end_of_DRAM()));
|
|
|
+ stop_machine(stop_machine_change_mapping, ¶ms, NULL);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ pte_clear(&init_mm, addr, pte);
|
|
|
+}
|
|
|
+
|
|
|
static void remove_pmd_table(pmd_t *pmd_start, unsigned long addr,
|
|
|
unsigned long end)
|
|
|
{
|
|
@@ -728,13 +799,7 @@ static void remove_pmd_table(pmd_t *pmd_start, unsigned long addr,
|
|
|
continue;
|
|
|
|
|
|
if (pmd_huge(*pmd)) {
|
|
|
- if (!IS_ALIGNED(addr, PMD_SIZE) ||
|
|
|
- !IS_ALIGNED(next, PMD_SIZE)) {
|
|
|
- WARN_ONCE(1, "%s: unaligned range\n", __func__);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- pte_clear(&init_mm, addr, (pte_t *)pmd);
|
|
|
+ split_kernel_mapping(addr, end, PMD_SIZE, (pte_t *)pmd);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
@@ -759,13 +824,7 @@ static void remove_pud_table(pud_t *pud_start, unsigned long addr,
|
|
|
continue;
|
|
|
|
|
|
if (pud_huge(*pud)) {
|
|
|
- if (!IS_ALIGNED(addr, PUD_SIZE) ||
|
|
|
- !IS_ALIGNED(next, PUD_SIZE)) {
|
|
|
- WARN_ONCE(1, "%s: unaligned range\n", __func__);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- pte_clear(&init_mm, addr, (pte_t *)pud);
|
|
|
+ split_kernel_mapping(addr, end, PUD_SIZE, (pte_t *)pud);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
@@ -791,13 +850,7 @@ static void remove_pagetable(unsigned long start, unsigned long end)
|
|
|
continue;
|
|
|
|
|
|
if (pgd_huge(*pgd)) {
|
|
|
- if (!IS_ALIGNED(addr, PGDIR_SIZE) ||
|
|
|
- !IS_ALIGNED(next, PGDIR_SIZE)) {
|
|
|
- WARN_ONCE(1, "%s: unaligned range\n", __func__);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- pte_clear(&init_mm, addr, (pte_t *)pgd);
|
|
|
+ split_kernel_mapping(addr, end, PGDIR_SIZE, (pte_t *)pgd);
|
|
|
continue;
|
|
|
}
|
|
|
|