fixed map是被linux kernel用来解决一类问题的机制,这类问题的共同特点是:
(1)在很早期的阶段需要进行地址映射,而此时,由于内存管理模块还没有完成初始化,不能动态分配内存,也就是无法动态分配创建映射需要的页表内存空间;
(2)物理地址是固定的,或者是在运行时就可以确定的。
1. 为什么需要fixmap
在start_kernel之前,内核会建立idmap_pg_dir和init_pg_dir页表,并使能MMU,从而将系统切换到虚拟地址空间。这时只有内核镜像部分的内存被映射,但是内存管理系统还没有初始化,因此在内核的视角只有内核镜像部分的内存可见。
出于内存管理系统的重要性,接下去的首先要考虑的就是要尽快初始化该模块。内存初始化前我们需要先从dtb中读取系统的内存配置信息,而这需要先为dtb创建页表,同时一些其它情形也需要执行内存的静态或动态映射。但是页表创建需要两个条件:
(1)需要为物理内存分配虚拟地址;
(2)需要为页表本身分配内存;
但是,此时还不能调用内核的通用内存接口,fixmap机制应运而生。为了解决该问题,内核为其提供了以下机制:
(1)为fixmap映射单独预留一段虚拟地址空间。在当前内核版本下其在内核总虚拟地址空间中的位置如下:
(2)在内核的全局变量中为fixmap页表静态定义一段页表项内存,它会被编译进内核镜像的bss段,因此其在镜像映射流程中会作为内核镜像的一部分被init_pg_dir映射。其定义位于arch/arm64/mm/mmu.c中:
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;
2. fixmap的虚拟地址分配
为物理地址在fixmap空间中找到一块虚拟地址空间,然后根据虚拟地址计算其在每级页表中对应的表项(entry),最后通过指针将各级页表连接起来即可。
为了更好地管理这段虚拟地址,fixmap又按功能对其做了进一步细分(并不是所有的虚拟内存都会分配出去做固定映射),其中每一部分都有特定的用途。以下为其定义:
/*
* Here we define all the compile-time 'special' virtual
* addresses. The point is to have a constant address at
* compile time, but to set the physical address only
* in the boot process.
*
* Each enum increment in these 'compile-time allocated'
* memory buffers is page-sized. Use set_fixmap(idx,phys)
* to associate physical memory with a fixmap index.
*/
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_TEXT3,
FIX_ENTRY_TRAMP_TEXT2,
FIX_ENTRY_TRAMP_TEXT1,
FIX_ENTRY_TRAMP_DATA,
#define TRAMP_VALIAS (__fix_to_virt(FIX_ENTRY_TRAMP_TEXT1))
#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
};
以上定义中,fixmap的地址可以分为几个部分:
第一部分 permanent fixmap:
在内核启动过程中,用于分配给指定物理地址设定映射关系,持续在系统启动到关机的整个生命周期。
-
DTB的虚拟地址空间(4M):由于linux规定dtb的size需要小于2M,理论上用一个2M的虚拟地址空间即可完成映射。但建立dtb页表时需要使用section map,即其最后一级页表会直接指向2M block为边界的物理地址,如下图所示:
此时若dtb的位置是如下图所示的横跨物理地址2M边界时,显然需要为上下两个2M block都创建页表才能访问完整的image,正是基于这个考虑,此处内核为dtb保留了4M的虚拟地址空间。
-
EARLYCON的虚拟地址空间:由于earlycon只需要映射一些串口相关的IO寄存器,故所占的空间很小,一个页的空间已经足够。
-
FIX_TEXT_POKE0:用于修改代码段指令的操作。其实现位于arch/arm64/kernel/insn.c中,在通过fixmap映射该地址时会将代码段的flag映射为可读写的,然后通过构造所需的指令,并将其覆盖掉原先的内容,从而实现代码段的修改。也只占一个页。这一机制在调试跟踪中非常有用,如kgdb可以利用这一机制插入和移除断点,若需要在某处插入断点,则可将该地址通过fixmap映射,然后用断点指令替换原始指令,当需要继续执行时,则把原始指令再替换回去。还有像kprobe之类的动态跟踪工具也可以通过该机制向被跟踪程序插入一些跟踪点等。
-
ACPI_APEI_GHES:一般在服务器领域用于RAS功能,其作用是通过firmware向内核报告硬件错误。如在armv8中这些硬件错误会通过事件路由机制被路由到运行于EL3的bl31中,然后由bl31处理后再通过sdei机制将事件发送给内核的ghes模块处理。其优势就是bl31的异常等级比内核更高,能够获取到更多硬件相关信息(如可以访问EL3的系统寄存器,访问secure内存等)。同时若将中断路由到EL3,则即使内核处于关中断或死锁等无法响应中断的状态时,bl31依然可以接收到该种类型的中断。
-
FIX_ENTRY_TRAMP_DATA和FIX_ENTRY_TRAMP_TEXT:用于内核security增强机制。其原理是在内核退出进入用户态前将自身的页表替换为一个只映射了包含系统调用等很少部分代码的页表,而隐藏内核的实际页表,在通过系统调用进入内核后再恢复实际的页表。其作用是减少了内核的攻击面。
第二部分 非permanent fixmap:
在内核启动过程中(甚至内核启动完成后)动态地给模块分配虚拟内存,模块退出后,释放该虚拟内存,动态建立映射关系。
- BITMAP空间:从定义可见以。对于这一部分我的理解是前面的部分经过页表映射以后,该页表在此后不再会被解除映射而会永久存在。其后的bitmap空间会被用于early ioremap,而ioremap的映射在使用完以后即可通过iounmap操作解除映射,因此其映射关系不是永久的。该区域一共占7 * 256k(1792k)空间。
第三部分 页表映射:
预留4*PAGE_SIZE的空间
- FIX_PTE等:该区域的空间用于存放页表entry,一共预留了4*PAGE_SIZE的虚拟内存(16k)。
结合代码和调试工具(CONFIG_PTDUMP_DEBUGFS)得知,Fixmap内存管理器所管理的内存地址:
3. early_fixmap_init
- 从链接脚本可以知道swapper_pg_dir的大小为一个PAGE_SIZE,每个PGD的entry占8字节,因此PGD页表最多可以含有512个entry(4k页大小,48位虚拟地址配置),而PGD entry的index为虚拟地址bit[47:39]的计算结果,因此index值的范围为0-511,一个PGD页表可以表示48位虚拟地址空间,因此FIXMAP的映射与swapper的映射共用同一个PGD页表,其基地址为swapper_pg_dir。只要将相应虚拟地址在PGD中所占的entry(页表项)指向不同的PUD即可。
- fixmap的pud页表有两种情况:(1)fixmap的P4D entry正好与swapper映射时kernel image的P4D entry相同,此时它与kernel 镜像共享PUD页表,其P4D页表也指向swapper_pud(这个表达可能不妥,意思是和kernel image的映射使用同一个PUD页表,即同一PGD页表项);(2)fixmap的P4D entry与kernel image的P4D entry不同,此时只要将fixmap地址对应的P4D entry指向其自身的pud页表bm_pud即可。
- pud的每个entry指向一个范围在bit[29:0]的PMD页表,即每个PMD页表包含一个1GB的地址范围,而从上一节fixmap地址分配所示图中,我们可以看到fixmap的虚拟地址与内核镜像地址不处于同一个1G范围中,即它们在pud页表中不位于同一个entry。pud中指向fixmap的entry为指向其特定的pmd页表bm_pmd,与bm_pud一样,它也是定义在kernel image的bss段中。
- fixmap的起始地址位于__end_of_permanent_fixed_addresses,位于dtb以下的2M空间中,由以上fixmap各部分的size可知,除了dtb之外,其余所有部分包括earlycon,text_poke0,bitmap以及pte等的地址总长度小于2M,故它们都可以通过一个PTE页表映射(bm_pte[512])完成。而dtb的映射则会使用section映射机制,通过block映射方式直接填充PMD entry完成,不会使用bm_pte。
代码如下:
void __init early_fixmap_init(void)
{
pgd_t *pgdp;
p4d_t *p4dp, p4d;
pud_t *pudp;
pmd_t *pmdp;
unsigned long addr = FIXADDR_START; //(3.2.1)
pgdp = pgd_offset_k(addr); //(3.2.2)
p4dp = p4d_offset(pgdp, addr);
p4d = READ_ONCE(*p4dp);
// (3.2.3)
if (CONFIG_PGTABLE_LEVELS > 3 && // (3.2.3-1)
!(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)); // (3.2.3-1-(1))
pudp = pud_offset_kimg(p4dp, addr); // (3.2.3-1-(2))
} else { // (3.2.3-2)
if (p4d_none(p4d)) // (3.2.3-2-(1))
__p4d_populate(p4dp, __pa_symbol(bm_pud), P4D_TYPE_TABLE); // (3.2.3-2-(2))
pudp = fixmap_pud(addr); // (3.2.3-2-(3))
}
if (pud_none(READ_ONCE(*pudp)))
__pud_populate(pudp, __pa_symbol(bm_pmd), PUD_TYPE_TABLE); // (3.2.3-2-(4))
pmdp = fixmap_pmd(addr); // (3.2.3-2-(5))
__pmd_populate(pmdp, __pa_symbol(bm_pte), PMD_TYPE_TABLE); // (3.2.3-2-(6))
/*
* 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))) // (3.2.4)
|| 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);
}
}
3.1 Linux内核的页表模型(只讲4级的情况)
Linux内核目前已经支持5级页表,可以通过CONFIG_PAGE_LEVELS去配置。如果是5级页表,会在PGD和PUD之间增加一个level叫P4D。如果是4级页表模型(这里只讲这种情况),P4D等于PGD。
对于4级分页模型:
- 页全局目录(Page Global Directory,PGD)
- 页上级目录(Page Upper Directory,PUD)
- 页中间目录(Page Middle Directory,PMD)
- 页表(Page Table,PT)
下图是一个4级分页模型:
3.2 early_fixmap_init代码解析
3.2.1 得到FIXADDR_START的VA地址
FIXADDR_START,定义了Fixed map区域的起始地址,位于arch/arm64/include/asm/fixmap.h。
#define FIXADDR_TOP (VMEMMAP_START - SZ_32M) //我的系统0xfffffbfffe000000
#define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT) //我的系统(4132 KB)
#define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE) //我的系统0xfffffbfffdbf7000
3.2.2 在内核的页全局目录查找虚拟地址address对应的表项地址
宏 pgd_offset_k(address)用来在内核的页全局目录找到虚拟地址address对应的表项的地址。
#define PGDIR_SHIFT 39
/* to find an entry in a page-table-directory */
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)
#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
struct mm_struct init_mm = {
.mm_rb = RB_ROOT,
.pgd = swapper_pg_dir,//swapper_pg_dir用于存放内核PGD页表的地方
.mm_users = ATOMIC_INIT(2),
.mm_count = ATOMIC_INIT(1),
.mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem),
.page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
.mmlist = LIST_HEAD_INIT(init_mm.mmlist),
INIT_MM_CONTEXT(init_mm)
};
pgd_offset_k(addr)得到VA地址的pgd页表项的虚拟地址;
3.2.3 填充PGD->PUD->PMD的页表项
在内核的全局变量中为fixmap页表静态定义一段页表项内存,它会被编译进内核镜像的bss段,因此其在镜像映射流程中会作为内核镜像的一部分被init_pg_dir映射,因此这里可以访问。
1. 进入if分支
if分支进入条件:
(1)page level大于3级(目前支持4/5级)&& pgd页表项不为空;
(2)或者page level大于3级(目前支持4/5级)&& pgd页表项中填写的phy addr不是bm_pud的物理地址;
如果上述条件之一成立,则说明fixmap这段VA和kernel image这段VA,使用同一个pgd页表项(但是这种情况只会发生在"16k/4 levels configurations"的配置下。如果不是CONFIG_ARM64_16K_PAGES就BUG_ON报错)。这个页表项已经在head.S文件的create_page里面填充过了,取这个PGD指向的pud表是kimage pud表。所以可以使用 pud_offset_kimage函数得到addr的pud表项的指针。
2. 进入else分支(重点分析)
(1)p4d_none(p4d)为真,代表p4d(pgd)页表项为空,即fixmap这段VA和kernel image这段VA,使用的不是同一个PGD页表项;
(2)将bm_pud的物理地址写到pgd全局页表项中;
(3)把addr对应pud页表的虚拟地址赋值给pudp指针;
(4)将bm_pmd的物理地址写到pud页表项中;
(5)把addr对应pmd页表的虚拟地址赋值给pmdp指针;
(6)将bm_pte的物理地址写到pmd页表项中;
注意这里未填充pte entry。
3.2.4 判断FIX_BTMAP_BEGIN-FIX_BTMAP_END的地址范围是否跨越多个pmd
如果跨过,则告警提示我们还没有准备好。
对于我的系统:
[ 0.000000] pmdp:0xffff80000a171f68,fixmap_pmd(fix_to_virt(FIX_BTMAP_BEGIN)): 0xffff80000a171f68,fixmap_pmd(fix_to_virt(FIX_BTMAP_END)):0xffff80000a171f68 //我添加的地址打印
[ 0.000000] pmdp (____ptrval____) != (____ptrval____), (____ptrval____)
[ 0.000000] fix_to_virt(FIX_BTMAP_BEGIN): fffffbfffda38000
[ 0.000000] fix_to_virt(FIX_BTMAP_END): fffffbfffdbf7000
[ 0.000000] FIX_BTMAP_END: 1033
[ 0.000000] FIX_BTMAP_BEGIN: 1480
显然是使用同一个PMD页表项(index=bit[29:21] = 493)。
3.2.5 总结
对FIXADDR_START地址建立PGD->PUD->PMD->PTE页表映射框架,这样后续在基于FIXADDR_START虚拟地址建立映射关系的时候,只需要填充对应的PTE页表项即可(比如像fixmap_remap_fdt()函数,就是典型的填充pte entry的过程,完成最后的一步映射,然后才能读取dtb文件)。其中,PGD使用的是init_task的页目录表,PUD和PMD以及PTE都是静态定义完成的页表,被编译进内核镜像的bss段,因此其在镜像映射流程中会作为内核镜像的一部分被init_pg_dir映射。
来一张图片就懂了,是透彻的懂了:
4. early ioremap
4.1 early ioremap的映射机制
early ioremap可使用btmap地址为io内存建立临时页表,根据上面的分析我们知道这段虚拟地址位于dtb地址之下2M范围内,而这2M内存的页表已经在fixmap初始化时创建好了。因此其映射流程就可简化如下:
(1)调用ioremap时,根据其虚拟地址的bit12 - bit20获取其pte entry指针
(2)将物理地址做页对齐操作,并与映射prot组合后填入pte entry
(3)将物理地址低12位与虚拟地址相加后即为最终的虚拟地址
4.2 early ioremap的初始化
early ioremap一共包含7 * 256k地址空间,它以256k为单位将其划分7个slot,每次early ioremap操作都会从中查找一个空闲的slot,并且使用该slot对应的地址映射。
它使用以下三个数据结构来管理slot的映射情况。slot_virt表示每个slot的起始虚拟地址,prev_map表示已分配出去slot的地址,prev_size表示已分配出去slot的size,其定义如下:
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;
slot_virt表示该slot占用的虚拟地址空间,其在ioremap初始化函数中设置,相应代码如下:
void __init early_ioremap_setup(void)
{
int i;
for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
if (WARN_ON(prev_map[i])) //(1)判断early ioremap是否已经被调用过
break;
/* 对于我的qemu环境:
FIX_BTMAP_END = 0x409
FIX_BTMAP_BEGIN = 0x5c8
NR_FIX_BTMAPS = 0x40 (256KB/4KB) ,每个slot占多少个页
TOTAL_FIX_BTMAPS = 0x1c0 (NR_FIX_BTMAPS * 7)
*/
for (i = 0; i < FIX_BTMAPS_SLOTS; i++) // FIX_BTMAPS_SLOTS = 7
slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i); //(2)设置slot的虚拟地址
}
其中__fix_to_virt实际上就是通过FIXADDR_TOP按照PAGE_SIZE的方式做偏移算法找到对应的虚拟地址空间。
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
#define __virt_to_fix(x) ((FIXADDR_TOP - ((x)&PAGE_MASK)) >> PAGE_SHIFT)
4.3 early ioremap映射函数
该函数主要包含查找一个空闲的slot,然后构造和填充pte entry,最后根据物理地址低12位偏移计算实际虚拟地址。其流程如下(mm/early_ioremap.c):
static void __init __iomem *
__early_ioremap(resource_size_t phys_addr, unsigned long size, pgprot_t prot)
{
unsigned long offset;
resource_size_t last_addr;
unsigned int nrpages;
enum fixed_addresses idx;
int i, slot;
WARN_ON(system_state >= SYSTEM_RUNNING);
slot = -1;
for (i = 0; i < FIX_BTMAPS_SLOTS; i++) { //(1)
if (!prev_map[i]) {
slot = i;
break;
}
}
if (WARN(slot < 0, "%s(%pa, %08lx) not found slot\n",
__func__, &phys_addr, size))
return NULL;
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1; //(2)
if (WARN_ON(!size || last_addr < phys_addr))
return NULL;
prev_size[slot] = size; //(3)
/*
* Mappings have to be page-aligned
*/
offset = offset_in_page(phys_addr); //(4)#define offset_in_page(p) ((unsigned long)(p) & ~PAGE_MASK)
phys_addr &= PAGE_MASK; //(5)
size = PAGE_ALIGN(last_addr + 1) - phys_addr; //(6)
/*
* Mappings have to fit in the FIX_BTMAP area.
*/
nrpages = size >> PAGE_SHIFT; //(7)
if (WARN_ON(nrpages > NR_FIX_BTMAPS))
return NULL;
/*
* Ok, go for it..
*/
idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot; //(8)
while (nrpages > 0) { //(9)
if (after_paging_init)
__late_set_fixmap(idx, phys_addr, prot);
else
__early_set_fixmap(idx, phys_addr, prot);
phys_addr += PAGE_SIZE;
--idx;
--nrpages;
}
WARN(early_ioremap_debug, "%s(%pa, %08lx) [%d] => %08lx + %08lx\n",
__func__, &phys_addr, size, slot, offset, slot_virt[slot]);
prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]); //(10)
return prev_map[slot]; //(11)
}
(1)从prev_map[i]中找到一个空闲的entry;
(2)计算需要映射的物理地址的结束值 ;
(3)将需要映射的size数值填入对应slot的prev_size结构中;
(4)根据需要映射的起始物理地址的值计算其在一个page内的偏移量;
(5)将起始物理地址做page对齐操作;
(6)计算页对齐后的size值;
(7)计算需要映射的页的数量;
(8)计算当前使用的slot的index值;
(9)逐页操作,在bm_pte页表中设置该地址对应的pte entry值;
(10)将其起始地址的虚拟地址保存到prev_map相应的slot中;
(11)返回该物理地址起始值对应虚拟地址的值;
4.4 early ioremap解除映射函数
由于early ioremap的虚拟地址池只有7个slot,因此若对使用完成的映射不解除映射,则可能会造成后续的early ioremap操作找不到空闲的slot。故与动态内存分配的malloc函数类似,early ioremap函数映射的地址在使用完成后,需要使用early_iounmap()函数解除映射,以释放虚拟地址资源。
early_iounmap()函数的实现及其注释如下:
void __init early_iounmap(void __iomem *addr, unsigned long size)
{
unsigned long virt_addr;
unsigned long offset;
unsigned int nrpages;
enum fixed_addresses idx;
int i, slot;
slot = -1;
for (i = 0; i < FIX_BTMAPS_SLOTS; i++) {
if (prev_map[i] == addr) {
slot = i;
break;
}
}
if (WARN(slot < 0, "%s(%p, %08lx) not found slot\n",
__func__, addr, size))
return;
if (WARN(prev_size[slot] != size,
"%s(%p, %08lx) [%d] size not consistent %08lx\n",
__func__, addr, size, slot, prev_size[slot]))
return;
WARN(early_ioremap_debug, "%s(%p, %08lx) [%d]\n",
__func__, addr, size, slot);
virt_addr = (unsigned long)addr;
if (WARN_ON(virt_addr < fix_to_virt(FIX_BTMAP_BEGIN)))
return;
offset = offset_in_page(virt_addr);
nrpages = PAGE_ALIGN(offset + size) >> PAGE_SHIFT;
idx = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*slot;
while (nrpages > 0) {
if (after_paging_init)
__late_clear_fixmap(idx);
else
__early_set_fixmap(idx, 0, FIXMAP_PAGE_CLEAR);
--idx;
--nrpages;
}
prev_map[slot] = NULL;
}
(1)从已分配slot数组prev_map中,查找与给定虚拟地址相等的slot
(2)校验给定size是否与保存在prev_size中的size相等
(3)校验给定虚拟地址是否处于BTMAP的地址空间
(4)计算该虚拟地址低12位的offset
(5)计算需要解除映射地址的页数
(6)逐页执行解除映射的操作
(7 - 8)清除对应index的pte entry,并刷新tlb
(9)清除prev_map的内容,以将该slot释放给新的early ioremap使用
4.5 总结
在传统的驱动模块中,通常使用ioremap函数来完成地址映射,但是ioremap函数的使用需要一定的前提条件的,在地址映射过程中,如果某个level的Translation tabe不存在,那么该函数需要调用伙伴系统模块的接口来分配一个page size的内存来创建某个level的Translation table,但是在启动阶段,内存管理的伙伴系统还没有ready,其实这时候,内核连系统中有多少内存都不知道的。而early io remap则在early_ioremap_init之后就可以被使用了。更具体的信息请参考mm/early_ioremap.c文件。
结论:如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制。
参考
标签:__,slot,管理器,addr,FIX,fixmap,页表,FIXMAP,内存 From: https://www.cnblogs.com/jianhua1992/p/16879187.html