. 4 _ create_page_tables ()
The _ create_page_tables () function is also located in arch/ARM/kernel/head. S. The Code is as follows:
_ Create_page_tables:
Pgtbl R4 @ page table address
/*
* Clear the 16 K Level 1 Swapper page table
*/
MoV r0, r4
MoV R3, #0
Add R6, R0, #0X4000
1: Str R3, [R0], #4
STR R3, [R0], #4
STR R3, [R0], #4
STR R3, [R0], #4
TEQ r0, R6
BNE 1b
LDR R7, [R10, # procinfo_mm_mmuflags] @ mm_mmuflags
/*
* Create identity mapping for first MB of kernel
* Cater for the MMU enable. This identity mapping
* Will be removed by paging_init (). We use our current program
* Counter to determine corresponding section base address.
*/
MoV R6, PC, LSR #20 @ start of kernel Section
ORR R3, R7, R6, LSL #20 @ flags + kernel Base
STR R3, [R4, R6, LSL #2] @ identity mapping
/*
* Now setup the pagetables for our kernel direct
* Mapped region.
*/
Add r0, R4, # (kernel_start & 0xff000000)> 18
STR R3, [r0, # (kernel_start & 0x00f00000)> 18]!
LDR R6, = (kernel_end-1)
Add r0, R0, #4
Add R6, R4, R6, LSR #18
1: CMP r0, R6
Add R3, R3, #1 <20
Strls R3, [R0], #4
20171b
# Ifdef config_xip_kernel
/*
* Map some Ram to cover our. Data and. BSS areas.
*/
ORR R3, R7, # (kernel_ram_paddr & 0xff000000)
. If (kernel_ram_paddr & 0x00f00000)
ORR R3, R3, # (kernel_ram_paddr & 0x00f00000)
. Endif
Add r0, R4, # (kernel_ram_vaddr & 0xff000000)> 18
STR R3, [r0, # (kernel_ram_vaddr & 0x00f00000)> 18]!
LDR R6, = (_ end-1)
Add r0, R0, #4
Add R6, R4, R6, LSR #18
1: CMP r0, R6
Add R3, R3, #1 <20
Strls R3, [R0], #4
20171b
# Endif
/*
* Then map first 1 MB of RAM in case it contains our boot Params.
*/
Add r0, R4, # page_offset> 18
ORR R6, R7, # (phys_offset & 0xff000000)
. If (phys_offset & 0x00f00000)
ORR R6, R6, # (phys_offset & 0x00f00000)
. Endif
STR R6, [R0]
# Ifdef config_debug_ll
LDR R7, [R10, # procinfo_io_mmuflags] @ io_mmuflags
/*
* Map in Io space for serial debugging.
* This allows debug messages to be output
* Via a serial console before paging_init.
*/
LDR R3, [R8, # machinfo_pgoffio]
Add r0, R4, r3
RSB R3, R3, #0X4000 @ ptrs_per_pgd * sizeof (long)
CMP R3, #0x0800 @ limit to 512 MB
Movhi R3, #0x0800.
Add R6, R0, r3
LDR R3, [R8, # machinfo_physio]
ORR R3, R3, r7
1: Str R3, [R0], #4
Add R3, R3, #1 <20
TEQ r0, R6
BNE 1b
# If defined (config_arch_netwinder) | defined (config_arch_cats)
/*
* If we're using the netwinder or cats, we also need to map
* In the 16550-type serial port for the debug messages
*/
Add r0, R4, #0xff000000> 18
ORR R3, R7, #0x7c000000
STR R3, [R0]
# Endif
# Ifdef config_arch_rpc
Add r0, R4, #0x02000000> 18
ORR R3, R7, #0x02000000
STR R3, [R0]
Add r0, R4, #0xd8000000> 18
STR R3, [R0]
# Endif
# Endif
MoV PC, LR
Endproc (_ create_page_tables)
This code is used to create a level-1 page table. This initial page table is used for the kernel code to be run next. Because the kernel code uses virtual addresses, we must establish MMU before use. Here, MMU only needs to create a page table to identify the virtual address of the kernel code, that is, from kernel_start to kernel_end.
# Define kernel_ram_vaddr (page_offset + text_offset)
# Define kernel_ram_paddr (phys_offset + text_offset)
# If (kernel_ram_vaddr & 0 xFFFF )! = 0x8000
# Error kernel_ram_vaddr must start at 0xxxxx8000
# Endif
. Globl swapper_pg_dir
. Equ swapper_pg_dir, kernel_ram_vaddr-0X4000
. Macro pgtbl, RD
LDR \ RD, = (kernel_ram_paddr-0X4000)
. Endm
# Ifdef config_xip_kernel
# Define kernel_start xip_virt_addr (config_xip_phys_addr)
# Define kernel_end _ edata_loc
# Else
# Define kernel_start kernel_ram_vaddr
# Define kernel_end _ end
# Endif
From the code above, we can see that kernel_start is c0008000, and kernel_end is equal to _ end. _ End we can find traces from vmlinux. LDS. S.
In addition, the MMU page table created here is a level-1 page table, which is in the unit of 1 MB. The level-2 page table (4 K) is not created here. The conversion relationships between ARM Level 1 page tables are as follows:
It can be seen that the content of the level-1 page table descriptor is composed of the physical address segment (the first 12 digits of the physical address) and some MMU management bits; the first-level page table descriptor address is composed of the base address (31-14 bits) of the page table address and the first 12 bits (31-20) of the virtual address. The last two bits are zero, supports 32-bit address alignment.
The process of creating a level-1 page table is to fill in each level-1 page table Descriptor (1 MB) to the address of each level-1 page table descriptor.
1.5 _ enable_mmu ()
After creating a page table, the following code is provided:
LDR R13, _ switch_data @ address to jump to after
@ MMU has been enabled
Adr lr, _ enable_mmu @ return (PIC) Address
Add PC, R10, # procinfo_initfunc
The last sentence is to jump to the processor initialization function execution. Our processor is armv6, so the processor initialization function can be found in arch/ARM/MM/pro_v6.s:
Entry (cpu_v6_proc_init)
MoV PC, LR
OK. Now we know that the purpose is to jump to the _ enable_mmu () function execution. As for R13, he can also use it at the end of the _ enable_mmu () function.
After creating a level-1 page table, we can open MMU and use the virtual address with confidence. The code for enabling MMU is as follows:
_ Enable_mmu:
# Ifdef config_alignment_trap
ORR r0, R0, # cr_a
# Else
Bic r0, R0, # cr_a
# Endif
# Ifdef config_cpu_dcache_disable
Bic r0, R0, # cr_c
# Endif
# Ifdef config_cpu_bpredict_disable
Bic r0, R0, # cr_z
# Endif
# Ifdef config_cpu_icache_disable
Bic r0, R0, # cr_ I
# Endif
MoV R5, # (domain_val (domain_user, domain_manager) | \
Domain_val (domain_kernel, domain_manager) | \
Domain_val (domain_table, domain_manager) | \
Domain_val (domain_io, domain_client ))
MCR P15, 0, R5, C3, C0, 0 @ load domain access register
MCR P15, 0, R4, C2, C0, 0 @ load page table pointer
B _ turn_mmu_on
Endproc (_ enable_mmu)
_ Turn_mmu_on:
MoV r0, R0
MCR P15, 0, R0, C1, C0, 0 @ write control Reg
MRC P15, 0, R3, C0, C0, 0 @ read ID Reg
MoV R3, r3
MoV R3, r3
MoV PC, R13
Endproc (_ turn_mmu_on)
This code is very simple: Put the base address of the first-level page table in C2 of CP15, and then open MMU. At the end of the execution, assign R13 to the PC, that is, jump to _ swtich_data and execute it.
1.6 _ mmap_switched ()
We can find the _ switch_data definition in arch/ARM/kernel/head-common.S:
_ Switch_data:
. Long _ mmap_switched
. Long _ data_loc @ r4
. Long _ data_start @ R5
. Long _ bss_start @ R6
. Long _ end @ r7
. Long processor_id @ r4
. Long _ machine_arch_type @ R5
. Long _ atags_pointer @ R6
. Long cr_alignment @ r7
. Long init_thread_union + thread_start_sp @ sp
The visible value of _ switch_data is equivalent to the pointer address of the _ mmap_switched () function. The _ mmap_switch () function is defined as follows:
_ Mmap_switched:
ADR R3, _ switch_data + 4
Ldmia R3 !, {R4, R5, R6, R7}
CMP R4, R5 @ copy data segment if needed
1: cmpne R5, R6
Ldrne FP, [R4], #4
Strne FP, [R5], #4
BNE 1b
MoV FP, #0 @ clear BSS (and zero FP)
1: CMP R6, r7
Strcc FP, [R6], #4
BCC 1b
Ldmia R3, {R4, R5, R6, R7, SP}
STR R9, [R4] @ save processor ID
STR R1, [R5] @ save Machine Type
STR R2, [R6] @ save atags pointer
Bic R4, R0, # cr_a @ clear 'A' bit
Stmia R7, {r0, R4} @ save control register values
B start_kernel
Endproc (_ mmap_switched)
This code is very simple, that is, copying data to the data segment; clearing the BSS; then saving the processor ID, Machine Type and ATAG pointer to the corresponding location of the memory (because the next step is to jump to the C language environment for execution, you must save meaningful registers). Jump to the start_kernel () function to enter the operating system environment.