linux fixmap

linux-fixmap

1.fixmap 定义

内核空间中有fixmap ,其va见上图(VA_BITS 40位,没有kasan,页表大小为4K(PAGE_SHIFT为12)只是一个例子大家参考就好)。这个fixmap 是做什么的呢?
请添加图片描述

fixmap 是指固定的虚拟地址.在内核的启动过程中,由于还没有建立页表映射.有些模块需要使用虚拟内存并mapping到指定的物理地址上.
这些物理地址中有的内容用来初始化例如FDT,有的是为了debug例如earlycon,有的是为了操作IO等等。

fixmap的地址区域位于FIXADDR_START和FIXADDR_TOP之间.

#define FIXADDR_TOP	(PCI_IO_START - SZ_2M)
#define FIXADDR_SIZE	(__end_of_permanent_fixed_addresses << PAGE_SHIFT)
#define FIXADDR_START	(FIXADDR_TOP - FIXADDR_SIZE)

2.fixed_address定义

关于这fixed_addresses描述如下:
fixmap虚拟地址空间又被平均分成两个部分permanent fixed addresses和temporary fixed addresses。permanent fixed addresses是永久映射,temporary fixed addresses是临时映射。永久映射是指在建立的映射关系在kernel阶段不会改变,仅供特定模块一直使用。临时映射就是模块使用前创建映射,使用后解除映射。

arch\arm64\include\asm\fixmap.h

enum fixed_addresses {
	FIX_HOLE,
	/*
	 * Reserve a virtual window for the FDT that is 2 MB larger than the
	 * maximum supported size, and put it at the top of the fixmap region.
	 * The additional space ensures that any FDT that does not exceed
	 * MAX_FDT_SIZE can be mapped regardless of whether it crosses any
	 * 2 MB alignment boundaries.
	 *
	 * Keep this at the top so it remains 2 MB aligned.
	 */
#define FIX_FDT_SIZE		(MAX_FDT_SIZE + SZ_2M)
	FIX_FDT_END,
	FIX_FDT = FIX_FDT_END + FIX_FDT_SIZE / PAGE_SIZE - 1,

	FIX_EARLYCON_MEM_BASE,
	FIX_TEXT_POKE0,

#ifdef CONFIG_ACPI_APEI_GHES
	/* Used for GHES mapping from assorted contexts */
	FIX_APEI_GHES_IRQ,
	FIX_APEI_GHES_SEA,
#ifdef CONFIG_ARM_SDE_INTERFACE
	FIX_APEI_GHES_SDEI_NORMAL,
	FIX_APEI_GHES_SDEI_CRITICAL,
#endif
#endif /* CONFIG_ACPI_APEI_GHES */

#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
	FIX_ENTRY_TRAMP_DATA,
	FIX_ENTRY_TRAMP_TEXT,
#define TRAMP_VALIAS		(__fix_to_virt(FIX_ENTRY_TRAMP_TEXT))
#endif /* CONFIG_UNMAP_KERNEL_AT_EL0 */
	__end_of_permanent_fixed_addresses,

	/*
	 * Temporary boot-time mappings, used by early_ioremap(),
	 * before ioremap() is functional.
	 */
#define NR_FIX_BTMAPS		(SZ_256K / PAGE_SIZE)
#define FIX_BTMAPS_SLOTS	7
#define TOTAL_FIX_BTMAPS	(NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)

	FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
	FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,

	/*
	 * Used for kernel page table creation, so unmapped memory may be used
	 * for tables.
	 */
	FIX_PTE,
	FIX_PMD,
	FIX_PUD,
	FIX_PGD,

	__end_of_fixed_addresses
};

permanent fixed addresses:
FIX_FDT: FDT映射使用,该值为4M。由于linux规定dtb的size需要小于2M,理论上用一个2M的虚拟地址空间即可完成映射,建立dtb页表需要使用section map,即其最后一级页表会直接指向2M block为边界的物理地址。此时若dtb的位置横跨物理地址2M边界时,显然需要为上下两个2M block都创建页表才能访问完整的image,正是基于这个考虑,此处内核为dtb保留了4M的虚拟地址空间。
FIX_EARLYCON_MEM_BASE: earlycom 映射使用,占一个页,需要映射一些串口相关的IO寄存器。
FIX_TEXT_POKE0:占一个页,它可以用于修改代码段指令的操作。其实现位于arch/arm64/kernel/insn.c中,在通过fixmap映射该地址时会将代码段的flag映射为可读写的,然后通过构造所需的指令,并将其覆盖掉原先的内容,从而实现代码段的修改。在kgdb中就是用这一机制插入和移除断点的,如需要在某处插入断点,可以将该地址通过fixmap映射,然后用断点指令替换原始指令,当需要继续执行时,则把原始指令再替换回去即可。

temporary fixed addresses:
FIX_BTMAP:iomap映射使用,bitmap映射开始是非permanent fixmap的.因此其映射关系不是永久的。该区域一共占7 * 256k(1792k)空间。
FIX_PTE/FIX_PMD/FIX_PUD/FIX_PGD:创建kernel 的page table使用,每一项占用一个页。

3.virt_to_fix和fix_to_virt

fixed_addresses 枚举中默认对应的元素1对应FIXADDR中的一个PAGE_SIZE,因此有以下计算方式。

#define __fix_to_virt(x)	(FIXADDR_TOP - ((x) << PAGE_SHIFT))
#define __virt_to_fix(x)	((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)

/*
 * 'index to address' translation. If anyone tries to use the idx
 * directly without translation, we catch the bug with a NULL-deference
 * kernel oops. Illegal ranges of incoming indices are caught too.
 */
static __always_inline unsigned long fix_to_virt(const unsigned int idx)
{
	BUILD_BUG_ON(idx >= __end_of_fixed_addresses);//1
	return __fix_to_virt(idx);
}

static inline unsigned long virt_to_fix(const unsigned long vaddr)
{
	BUG_ON(vaddr >= FIXADDR_TOP || vaddr < FIXADDR_START);//2
	return __virt_to_fix(vaddr);
}

1.fix转换成virt的时候idx 大于等于_end_of_fixed_addresses即可,可以转换为temporary fixed addresses和permanent fixed addresses的映射。
2.virt转换成fix的时候VA要在permanent fixed addresses范围内。

下图为fixmap展开。
请添加图片描述

4.bm_pte,bm_pmd和bm_pud

其中kennel中定义了三个数组bm_pud,bm_pmd,bm_pte用来构建fixmap的页表。
为啥没有bm_pgd数组? 使用的是vmlinux.lds.S中的。

swapper_pg_dir = .;
	. += PAGE_SIZE;
swapper_pg_end = .;

linux\arch\arm64\include\asm\pgtable-hwdef.h

#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n)	((PAGE_SHIFT - 3) * (4 - (n)) + 3)
#define PTRS_PER_PTE		(1 << (PAGE_SHIFT - 3))
#define PTRS_PER_PMD		PTRS_PER_PTE
#define PTRS_PER_PGD		(1 << (VA_BITS - PGDIR_SHIFT))

对于qcom来说VA_BITS都是40位,PAGE_SHIFT 是12(4K)。
所以PTRS_PER_PTE=PTRS_PER_PMD=PTRS_PER_PGD=512。
数组中有512个8bit,总计大小是4K,__page_aligned_bss代表一个page对其,数据放在bss段。

static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;

5.fixmap_pud,fixmap_pmd和fixmap_pte

参考文件linux\arch\arm64\include\asm\pgtable.h
fixmap_pud,fixmap_pmd,fixmap_pte根据传入参数va返回各自(pud,pmd,pte)页表中对应index的(pud,pmd,pte) entry的va。
CONFIG_PGTABLE_LEVELS = 3的时候有:

#define __phys_to_kimg(x)	((unsigned long)((x) + kimage_voffset))//kimage中的PA地址通过加上kimage_voffset之后转换为VA。
#define pmd_offset_kimg(dir,addr)	((pmd_t *)__phys_to_kimg(pmd_offset_phys((dir), (addr))))//pmd的pa通过 + kimage_voffset转换为va。
#define pmd_offset_phys(dir, addr)	(pud_page_paddr(READ_ONCE(*(dir))) + pmd_index(addr) * sizeof(pmd_t)) //参考之前的page walk的文章,pud的pa加上pmd_index*sizeof(pmd_t)就得到了对应的pmd的pa
#define pud_offset_kimg(dir,addr)	((pud_t *)dir)//CONFIG_PGTABLE_LEVELS =3的时,采用3级页>表,没有pud。

CONFIG_PGTABLE_LEVELS >3的时候pgd->pud->pmd->pte。
CONFIG_PGTABLE_LEVELS =3的时候pgd->pmd->pte。

static inline pud_t * fixmap_pud(unsigned long addr)
{
	pgd_t *pgdp = pgd_offset_k(addr);
	p4d_t *p4dp = p4d_offset(pgdp, addr);
	p4d_t p4d = READ_ONCE(*p4dp);

	BUG_ON(p4d_none(p4d) || p4d_bad(p4d));

	return pud_offset_kimg(p4dp, addr);
}

static inline pmd_t * fixmap_pmd(unsigned long addr)
{
	pud_t *pudp = fixmap_pud(addr);
	pud_t pud = READ_ONCE(*pudp);

	BUG_ON(pud_none(pud) || pud_bad(pud));

	return pmd_offset_kimg(pudp, addr);
}

static inline pte_t * fixmap_pte(unsigned long addr)
{
	return &bm_pte[pte_index(addr)];
}

6.early_fixmap_init

调用流程:
->start_kernel
->setup_arch
->early_fixmap_init();

CONFIG_PGTABLE_LEVELS = 3的时候:
将bm_pmd的pa添加到pgd->swapper_pg_dir中FIXADDR_START对应的index,将bm_pte的pa添加到pmd->bm_pmd中FIXADDR_START对应的index.

void __init early_fixmap_init(void)
{
	pgd_t *pgdp;
	p4d_t *p4dp, p4d;
	pud_t *pudp;
	pmd_t *pmdp;
	unsigned long addr = FIXADDR_START;//1

	pgdp = pgd_offset_k(addr);//2
	p4dp = p4d_offset(pgdp, addr);//3
	p4d = READ_ONCE(*p4dp);
	if (CONFIG_PGTABLE_LEVELS > 3 &&  //4
	    !(p4d_none(p4d) || p4d_page_paddr(p4d) == __pa_symbol(bm_pud))) {
		/*
		 * We only end up here if the kernel mapping and the fixmap
		 * share the top level pgd entry, which should only happen on
		 * 16k/4 levels configurations.
		 */
		BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
		pudp = pud_offset_kimg(p4dp, addr);
	} else {
		if (p4d_none(p4d))
			__p4d_populate(p4dp, __pa_symbol(bm_pud), PUD_TYPE_TABLE);//5
		pudp = fixmap_pud(addr);//6
	}
	if (pud_none(READ_ONCE(*pudp)))
		__pud_populate(pudp, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);//7
	pmdp = fixmap_pmd(addr);//8
	__pmd_populate(pmdp, __pa_symbol(bm_pte), PMD_TYPE_TABLE);//9

	/*
	 * The boot-ioremap range spans multiple pmds, for which
	 * we are not prepared:
	 */
	BUILD_BUG_ON((__fix_to_virt(FIX_BTMAP_BEGIN) >> PMD_SHIFT)
		     != (__fix_to_virt(FIX_BTMAP_END) >> PMD_SHIFT));

	if ((pmdp != fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)))
	     || pmdp != fixmap_pmd(fix_to_virt(FIX_BTMAP_`END))) {
		WARN_ON(1);
		pr_warn("pmdp %p != %p, %p\n",
			pmdp, fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)),
			fixmap_pmd(fix_to_virt(FIX_BTMAP_END)));
		pr_warn("fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n",
			fix_to_virt(FIX_BTMAP_BEGIN));
		pr_warn("fix_to_virt(FIX_BTMAP_END):   %08lx\n",
			fix_to_virt(FIX_BTMAP_END));

		pr_warn("FIX_BTMAP_END:       %d\n", FIX_BTMAP_END);
		pr_warn("FIX_BTMAP_BEGIN:     %d\n", FIX_BTMAP_BEGIN);
	}
}
  1. FIXADDR_START(VA)为起始地址映射。
  2. FIXADDR_START在pgd->swapper_pg_dir中的pgd entry对应的va。
  3. p4d不用认为该值等于pgd。
  4. 我们的CONFIG_PGTABLE_LEVELS=3不走这里。
  5. 将数组bm_pud的物理地址添加到p4d/pgd页表对应的page entry中。
  6. 传入参数addr返回pud页表中对应index的va(pud entry)。
  7. 将数组bm_pmd的物理地址添加到pud页表对应pud entry。
  8. 传入参数addr返回pmd页表中对应index的va(pmd entry)。
  9. 将数组bm_pte的物理地址添加到pmd页表对应的pmd entry。
include/linux/pgtable.h
#define pgd_offset_k(address)		pgd_offset(&init_mm, (address))
#define pgd_offset(mm, address)		pgd_offset_pgd((mm)->pgd, (address))//1
static inline pgd_t *pgd_offset_pgd(pgd_t *pgd, unsigned long address)
{
	return (pgd + pgd_index(address));//2
}

1.init_mm的pgd是swapper_pg_dir
2.注意返回的是VA

arch\arm64\include\asm\pgalloc.h

static inline void __pud_populate(pud_t *pudp, phys_addr_t pmdp, pudval_t prot)
{
	set_pud(pudp, __pud(__phys_to_pud_val(pmdp) | prot));
}

static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t ptep,
				  pmdval_t prot)
{
	set_pmd(pmdp, __pmd(__phys_to_pmd_val(ptep) | prot));
}

CONFIG_PGTABLE_LEVELS = 3static inline void __p4d_populate(p4d_t *p4dp, phys_addr_t pudp, p4dval_t prot)
{
	BUILD_BUG();
}

early_fixmap_init之后得到如下页表映射关系:
请添加图片描述

7.early_ioremap_setup

调用流程:
*->start_kernel
********->setup_arch
**************->early_fixmap_init();
**************->early_ioremap_init();
********************->early_ioremap_setup
**************->setup_machine_fdt
*************->paging_init

编译到init段的slot_virt

static void __iomem *prev_map[FIX_BTMAPS_SLOTS] __initdata;
static unsigned long prev_size[FIX_BTMAPS_SLOTS] __initdata;
static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __initdata;
void __init early_ioremap_setup(void)
{
	int i;

	for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
		if (WARN_ON(prev_map[i]))
			break;

	for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
		slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
}

回忆下

#define NR_FIX_BTMAPS		(SZ_256K / PAGE_SIZE)
#define FIX_BTMAPS_SLOTS	7
#define TOTAL_FIX_BTMAPS	(NR_FIX_BTMAPS * FIX_BTMAPS_SLOTS)

	FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
	FIX_BTMAP_BEGIN = FIX_BTMAP_END + TOTAL_FIX_BTMAPS - 1,

1.early ioremap利用slot管理映射,最多支持FIX_BTMAPS_SLOTS个映射,每个映射最大支持映射256KB。slot_virt数组存储每个slot的虚拟地址首地址。prev_map数组用来记录已经分配出去的虚拟地址,数组值为0代表没有分配。prev_size记录映射的size。
2.early ioremap映射大小为啥是7*256K?还能在大一点么?为了保证FIXADDR_START和FIX_PTE/FIX_PMD/FIX_PUD/FIX_PGD在2M对齐的地址空间内,这样可以利用early_fixmap_init创建的页表进行寻址。

8.fixmap_remap_fdt

调用流程:
-*>start_kernel
*********->setup_arch
*****************->early_fixmap_init();
*****************->early_ioremap_init();
*****************->setup_machine_fdt
*************************->fixmap_remap_fdt
***************->paging_init

static void __init setup_machine_fdt(phys_addr_t dt_phys)//1
{
	int size;
	void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);//2
	const char *name;

	if (dt_virt)
		memblock_reserve(dt_phys, size);//3

	if (!dt_virt || !early_init_dt_scan(dt_virt)) {//4
		pr_crit("\n"
			"Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
			"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
			"\nPlease check your bootloader.",
			&dt_phys, dt_virt);

		while (true)
			cpu_relax();
	}

	/* Early fixups are done, map the FDT as read-only now */
	fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);//5

	name = of_flat_dt_get_machine_name();
	if (!name)
		return;

	pr_info("Machine model: %s\n", name);
	dump_stack_set_arch_desc("%s (DT)", name);
}

1.init段代码,传入fdt的物理地址。
2.创建fixmap中fdt的页表映射,返回fdt的虚拟地址。页表属性为PAGE_KERNEL。
3.memblock中涉及。
4.memblock中涉及。
5.创建fixmap中fdt的映射页表属性PAGE_KERNEL_RO。

void *__init fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)//1
{
	const u64 dt_virt_base = __fix_to_virt(FIX_FDT);//2
	int 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 magic and size
	 * fields 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_noalloc() 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);//3

	BUILD_BUG_ON(__fix_to_virt(FIX_FDT_END) >> SWAPPER_TABLE_SHIFT !=
		     __fix_to_virt(FIX_BTMAP_BEGIN) >> SWAPPER_TABLE_SHIFT);

	offset = dt_phys % SWAPPER_BLOCK_SIZE; //4
	dt_virt = (void *)dt_virt_base + offset; //5

	/* map the first chunk so we can read the size from the header */
	create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE),//6
			dt_virt_base, SWAPPER_BLOCK_SIZE, prot);

	if (fdt_magic(dt_virt) != FDT_MAGIC)//7
		return NULL;

	*size = fdt_totalsize(dt_virt);//8
	if (*size > MAX_FDT_SIZE)//9
		return NULL;

	if (offset + *size > SWAPPER_BLOCK_SIZE)//10
		create_mapping_noalloc(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,
			       round_up(offset + *size, SWAPPER_BLOCK_SIZE), prot);

	return dt_virt;
}

1.init段的代码,参数为fdt的PA和页表属性。
2.fixmap中FDT的va,这里为(0xfffffffefea00000)(FIX_TOP)-0x40000(FIX_FDT<<12)=0xfffffffefe600000
3.fixmap中FDT的va是不是2M对齐。
4.offset为fdt的pa对于2M对齐的dt_virt_base VA中的偏移。2M对齐,物理地址的低21位。
5.dt_virt_base的地址0xfffffffefe600000+offset为实际存储fdt的va。
6.首先创建fdt的一个2M对齐的block页表。
7.fdt magic check。
8.fdt头中获取fdt的size。
9.最大的ftd size不可以超过2M。
10.处理fdt跨2M对齐的情况,需要在创建一个2M对齐的block页表。

#define SWAPPER_BLOCK_SHIFT	SECTION_SHIFT
#define SWAPPER_BLOCK_SIZE	      SECTION_SIZE
#define SWAPPER_TABLE_SHIFT	PUD_SHIFT
#define SECTION_SHIFT		21
#define SECTION_SIZE		(1UL << SECTION_SHIFT)
#define SECTION_MASK		(~((1 << SECTION_SHIFT) - 1))

1.SECTION_SHIFT和SWAPPER_BLOCK_SHIFT都是左移21位。
2.SECTION_SIZE和SWAPPER_BLOCK_SIZE左移21位之后就是2M,从而保证2M对齐。

static void __init create_mapping_noalloc(phys_addr_t phys, unsigned long virt,
				  phys_addr_t size, pgprot_t prot)
{
	if ((virt >= PAGE_END) && (virt < VMALLOC_START)) {
		pr_warn("BUG: not creating mapping for %pa at 0x%016lx - outside kernel range\n",
			&phys, virt);
		return;
	}
	__create_pgd_mapping(init_mm.pgd, phys, virt, size, prot, NULL,//1
			     NO_CONT_MAPPINGS);//2
}

1.pgtable_alloc参数为NULL,说明利用bm_pmd和bm_pte建立页表,不用额外申请内存。
2.NO_BLOCK_MAPPINGS代表不要申请block页表,NO_CONT_MAPPINGS代表要continuous页表,NO_EXEC_MAPPINGS代表不要申请exec的页表。

#define NO_BLOCK_MAPPINGS	BIT(0)
#define NO_CONT_MAPPINGS	BIT(1)
#define NO_EXEC_MAPPINGS	BIT(2)

调用流程:
*fixmap_remap_fdt
********->create_mapping_noalloc
************->__create_pgd_mapping
******************->alloc_init_pud
*************************->alloc_init_cont_pmd
******************************->init_pmd
**************************************->pmd_set_fixmap_offset
*************************************->pmd_set_huge

static void init_pmd(pud_t *pudp, unsigned long addr, unsigned long end,
		     phys_addr_t phys, pgprot_t prot,
		     phys_addr_t (*pgtable_alloc)(int), int flags)
{
	unsigned long next;
	pmd_t *pmdp;

	pmdp = pmd_set_fixmap_offset(pudp, addr);//1
	do {
		pmd_t old_pmd = READ_ONCE(*pmdp);

		next = pmd_addr_end(addr, end);

		/* try section mapping first */
		if (((addr | next | phys) & ~SECTION_MASK) == 0 &&//2
		    (flags & NO_BLOCK_MAPPINGS) == 0) {
			pmd_set_huge(pmdp, phys, prot);//3

			/*
			 * After the PMD entry has been populated once, we
			 * only allow updates to the permission attributes.
			 */
			BUG_ON(!pgattr_change_is_safe(pmd_val(old_pmd),
						      READ_ONCE(pmd_val(*pmdp))));
		} else {
			alloc_init_cont_pte(pmdp, addr, next, phys, prot,
					    pgtable_alloc, flags);

			BUG_ON(pmd_val(old_pmd) != 0 &&
			       pmd_val(old_pmd) != READ_ONCE(pmd_val(*pmdp)));
		}
		phys += next - addr;
	} while (pmdp++, addr = next, addr != end);

	pmd_clear_fixmap();
}

1.pmd_set_fixmap_offset 这个实现比较绕
返回值pmdp是FIX_PMD的VA,该VA对应的PA为bm_pmd页表中FIX_FDT地址对应的pmd entry。这样将该地址中的设置为fdt的pa之后就给fdt建立了页表。
对应的代码贴出来 大家有兴趣的看看

/* Return a pointer with offset calculated */
#define __set_fixmap_offset(idx, phys, flags) \
({ \
unsigned 
long 
________addr; \
__set_fixmap(idx, phys, flags); \
________addr = fix_to_virt(idx) + ((phys) & (PAGE_SIZE - 1)); \
________addr; \
})
 
#define set_fixmap_offset(idx, phys) \
__set_fixmap_offset(idx, phys, FIXMAP_PAGE_NORMAL)
 
void __set_fixmap(enum fixed_addresses idx,phys_addr_t phys, pgprot_t flags)
{
    unsigned  long addr = __fix_to_virt(idx);//FIX_PMD的va
    pte_t *ptep;
 
    BUG_ON(idx <= FIX_HOLE || idx >= __end_of_fixed_addresses);
 
    ptep = fixmap_pte(addr);//bm_pte中对应pte entry的va
 
    if (pgprot_val(flags)) {
    	set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, flags));//填pte entry
    } 
    else 
    {
    	pte_clear(&init_mm, addr, ptep);
    	flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
    }
}

2.根据地址SECTION_MASK(2M)对齐,优先配置block映射。

#define SECTION_SHIFT		PMD_SHIFT
#define SECTION_SIZE		(_AC(1, UL) << SECTION_SHIFT)
#define SECTION_MASK		(~(SECTION_SIZE-1))

3.设置pmd huge page

int pmd_set_huge(pmd_t *pmdp, phys_addr_t phys, pgprot_t prot)
{
	pmd_t new_pmd = pfn_pmd(__phys_to_pfn(phys), mk_pmd_sect_prot(prot));//1

	/* Only allow permission changes for now */
	if (!pgattr_change_is_safe(READ_ONCE(pmd_val(*pmdp)),
				   pmd_val(new_pmd)))
		return 0;

	VM_BUG_ON(phys & ~PMD_MASK);
	set_pmd(pmdp, new_pmd);//2
	return 1;
}

3.1.mk_pmd_sect_prot设置页表属性,其中最后两位置为0x01,__phys_to_pfn获取pa对应的pfn。

#define pfn_pmd(pfn,prot)	__pmd(__phys_to_pmd_val((phys_addr_t)(pfn) << PAGE_SHIFT) | pgprot_val(prot))

3.2.设置pmd对应页表的va中的值。
为啥FDT的大小不超过2M,假如使用4K gurad的页表,level 2 Block对应2M地址的输出范。

4KBlevel0 转换表不支持Block描述符
level 1 Block转换表k描述了相关的1GB输入地址范围的映射
level 2 Block转换表k描述了相关的2MB输入地址范围的映射
总结下:
1111111

1.所有fixmap address中的地址对应1个pgd entry(1个pgd entry可以映射的地址-5125124K=1G)。
2.FIX-START和FIX-PMD 在同一块2M对齐的地址范围内,所以二者共享1个pmd entry(1个pmd entry可以映射的地址-512*4K=2M),即FIX-START和FIX-PMD的pmd entry为同一个。
3.FIX_PMD的pte指向FIX_PMD的PA,FIX_PMD的PA会填写FDT PA(2M对齐的)地址。FIX_PMD的PA同时是FIX_FDT 的pmd entry。这块比较绕。所以使用FIX_PMD是因为MMU开启,cpu只能访问VA,所以利用FIX_PMD填写FIX_FDT的pmd entry。
参考t32中的FDT信息:
请添加图片描述
1.FIX_FDT通过fix_to_virt转换为地址0xfffffffefe600000,转换后得到地址0xAE800000,其中可以看到pmd entry中的值后三位为0x01代表2M block映射。
2.0xfffffffefe600000加上offset(FDT PA/2M)之后得到FDT的VA,可以到对应地址的FDT magic。

  • 29
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值