|
@@ -21,6 +21,7 @@
|
|
|
#include <linux/kernel.h>
|
|
|
#include <linux/errno.h>
|
|
|
#include <linux/init.h>
|
|
|
+#include <linux/libfdt.h>
|
|
|
#include <linux/mman.h>
|
|
|
#include <linux/nodemask.h>
|
|
|
#include <linux/memblock.h>
|
|
@@ -643,3 +644,68 @@ void __set_fixmap(enum fixed_addresses idx,
|
|
|
flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
|
|
|
+{
|
|
|
+ const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
|
|
|
+ pgprot_t prot = PAGE_KERNEL | PTE_RDONLY;
|
|
|
+ int granularity, size, offset;
|
|
|
+ void *dt_virt;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Check whether the physical FDT address is set and meets the minimum
|
|
|
+ * alignment requirement. Since we are relying on MIN_FDT_ALIGN to be
|
|
|
+ * at least 8 bytes so that we can always access the size field of the
|
|
|
+ * FDT header after mapping the first chunk, double check here if that
|
|
|
+ * is indeed the case.
|
|
|
+ */
|
|
|
+ BUILD_BUG_ON(MIN_FDT_ALIGN < 8);
|
|
|
+ if (!dt_phys || dt_phys % MIN_FDT_ALIGN)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Make sure that the FDT region can be mapped without the need to
|
|
|
+ * allocate additional translation table pages, so that it is safe
|
|
|
+ * to call create_mapping() this early.
|
|
|
+ *
|
|
|
+ * On 64k pages, the FDT will be mapped using PTEs, so we need to
|
|
|
+ * be in the same PMD as the rest of the fixmap.
|
|
|
+ * On 4k pages, we'll use section mappings for the FDT so we only
|
|
|
+ * have to be in the same PUD.
|
|
|
+ */
|
|
|
+ BUILD_BUG_ON(dt_virt_base % SZ_2M);
|
|
|
+
|
|
|
+ if (IS_ENABLED(CONFIG_ARM64_64K_PAGES)) {
|
|
|
+ BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PMD_SHIFT !=
|
|
|
+ __fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT);
|
|
|
+
|
|
|
+ granularity = PAGE_SIZE;
|
|
|
+ } else {
|
|
|
+ BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> PUD_SHIFT !=
|
|
|
+ __fix_to_virt(FIX_BTMAP_BEGIN) >> PUD_SHIFT);
|
|
|
+
|
|
|
+ granularity = PMD_SIZE;
|
|
|
+ }
|
|
|
+
|
|
|
+ offset = dt_phys % granularity;
|
|
|
+ dt_virt = (void *)dt_virt_base + offset;
|
|
|
+
|
|
|
+ /* map the first chunk so we can read the size from the header */
|
|
|
+ create_mapping(round_down(dt_phys, granularity), dt_virt_base,
|
|
|
+ granularity, prot);
|
|
|
+
|
|
|
+ if (fdt_check_header(dt_virt) != 0)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ size = fdt_totalsize(dt_virt);
|
|
|
+ if (size > MAX_FDT_SIZE)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ if (offset + size > granularity)
|
|
|
+ create_mapping(round_down(dt_phys, granularity), dt_virt_base,
|
|
|
+ round_up(offset + size, granularity), prot);
|
|
|
+
|
|
|
+ memblock_reserve(dt_phys, size);
|
|
|
+
|
|
|
+ return dt_virt;
|
|
|
+}
|