作为需要和硬件打交道的工程师来说,比较关注的是驱动和CPU初始化这一块。所以我沿着启动的路线,重点学习一下和硬件相关的代码。就从linux解压的入口说起。学习阶段,基本是参考大神文章http://blog.chinaunix.net/uid/20543672/cid-6411-list-7.html所写。
linux自解压完成后就跳转到了解压后的内核(也就是vmlinux的bin版本Image),具体的入口可以在arch/arm/kernel/vmlinux.lds.S(最终的链接脚本是通过这个文件产生的)中获得:
这个入口在arch/arm/kernel/head.S中,这个文件就是Linux内核真正启动的地方,是初始化部分的开始,用汇编写成。他必须为后面的C代码做好准备
1./* 2. * linux/arch/arm/kernel/head.S 3. * 4. * Copyright (C) 1994-2002 Russell King 5. * Copyright (c) 2003 ARM Limited 6. * All Rights Reserved 7. * 8. * This program is free software; you can redistribute it and/or modify 9. * it under the terms of the GNU General Public License version 2 as 10. * published by the Free Software Foundation. 11. * 12. * 所有32-bit CPU的内核启动代码 13. */ 14. #include <linux/linkage.h> 15. #include <linux/init.h> 16. 17. #include <asm/assembler.h> 18. #include <asm/domain.h> 19. #include <asm/ptrace.h> 20. #include <asm/asm-offsets.h> 21. #include <asm/memory.h> 22. #include <asm/thread_info.h> 23. #include <asm/system.h> 24. 25. #ifdef CONFIG_DEBUG_LL 26. #include <mach/debug-macro.S> 27. #endif 28. 29. /* 30. * swapper_pg_dir 是初始页表的虚拟地址. 31. * 我们将页表放在KERNEL_RAM_VADDR以下16K的空间中. 因此我们必须保证 32. * KERNEL_RAM_VADDR已经被正常设置.当前, 我们期望的是 33. * 这个地址的最后16 bits为0x8000, 但我们或许可以放宽这项限制到 34. * KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000. 35. */ 36. #define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET) 37. #if (KERNEL_RAM_VADDR & 0xffff) != 0x8000 38. #error KERNEL_RAM_VADDR must start at 0xXXXX8000 39. #endif 40. 41. .globl swapper_pg_dir 42. .equ swapper_pg_dir, KERNEL_RAM_VADDR - 0x4000 43. 44. /* 45. * TEXT_OFFSET 是内核代码(解压后)相对于RAM起始的偏移. 46. * 而#TEXT_OFFSET - 0x4000就是页表相对于RAM起始的偏移. 47. * 这个宏的作用是将phys(RAM的启示地址)加上页表的偏移, 48. * 而得到页表的起始物理地址 49. */ 50. .macro pgtbl, rd, phys 51. add \rd, \phys, #TEXT_OFFSET - 0x4000 52. .endm 53. 54. #ifdef CONFIG_XIP_KERNEL 55. #define KERNEL_START XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR) 56. #define KERNEL_END _edata_loc 57. #else 58. #define KERNEL_START KERNEL_RAM_VADDR 59. #define KERNEL_END _end 60. #endif 61. 62. /* 63. * 内核启动入口点. 64. * --------------------------- 65. * 66. * 这个入口正常情况下是在解压完成后被调用的. 67. * 调用条件: MMU = off, D-cache = off, I-cache = dont care, r0 = 0, 68. * r1 = machine nr, r2 = atags or dtb pointer. 69. * 这些条件在解压完成后会被逐一满足,然后才跳转过来。 70. * 71. * 这些代码大多数是位置无关的, 如果你的内核入口地址在连接时确定为 72. * 0xc0008000, 你调用此函数的物理地址就是 __pa(0xc0008000). 73. * 74. * 完整的machineID列表,请参见 linux/arch/arm/tools/mach-types 75. * 76. * 我们尽量让代码简洁; 不在此处添加任何设备特定的代码 77. * - 这些特定的初始化代码是boot loader的工作(或在极端情况下, 78. * 有充分理由的情况下, 可以由zImage完成)。 79. */ 80. __HEAD 81. ENTRY(stext) 82. setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ CPU模式设置宏 83. @ (进入svc模式并且关闭中断) 84. mrc p15, 0, r9, c0, c0 @ 获取处理器id-->r9 85. bl __lookup_processor_type @ 返回r5=procinfo r9=cpuid 86. movs r10, r5 @ r10=r5,并可以检测r5=0?注意当前r10的值 87. THUMB( it eq ) @ force fixup-able long branch encoding 88. beq __error_p @ yes, error 'p'如果r5=0,则内核处理器不匹配,出错~死循环 89. 90. /* 91. * 获取RAM的起始物理地址,并保存于 r8 = phys_offset 92. * XIP内核与普通在RAM中运行的内核不同 93. * (1)CONFIG_XIP_KERNEL 94. * 通过运行时计算???? 95. * (2)正常RAM中运行的内核 96. * 通过编译时确定(PLAT_PHYS_OFFSET 一般在arch/arm/mach-xxx/include/mach/memory.h定义) 97. * 98. */ 99. #ifndef CONFIG_XIP_KERNEL 100. adr r3, 2f 101. ldmia r3, {r4, r8} 102. sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET) 103. add r8, r8, r4 @ PHYS_OFFSET 104. #else 105. ldr r8, =PLAT_PHYS_OFFSET 106. #endif 107. 108. /* 109. * r1 = machine no, r2 = atags or dtb, 110. * r8 = phys_offset, r9 = cpuid, r10 = procinfo 111. */ 112. bl __vet_atags @ 判断r2(内核启动参数)指针的有效性 113. #ifdef CONFIG_SMP_ON_UP 114. bl __fixup_smp @ ???如果运行SMP内核在单处理器系统中启动,做适当调整 115. #endif 116. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT 117. bl __fixup_pv_table @ ????根据内核在内存中的位置修正物理地址与虚拟地址的转换机制 118. #endif 119. bl __create_page_tables @ 初始化页表! 120. 121. /* 122. * 以下使用位置无关的方法调用的是CPU特定代码。 123. * 详情请见arch/arm/mm/proc-*.S 124. * r10 = xxx_proc_info 结构体的基地址(在上面__lookup_processor_type函数中选中的) 125. * 返回时, CPU 已经为 MMU 的启动做好了准备, 126. * 且 r0 保存着CPU控制寄存器的值. 127. */ 128. ldr r13, =__mmap_switched @ 在MMU启动之后跳入的第一个虚拟地址 129. adr lr, BSYM(1f) @ 设置返回的地址(PIC) 130. mov r8, r4 @ 将swapper_pg_dir的物理地址放入r8, 131. @ 以备__enable_mmu中将其放入TTBR1 132. ARM( add pc, r10, #PROCINFO_INITFUNC ) @ 跳入构架相关的初始化处理器函数(例如A8的是__v7_setup) 133. THUMB( add r12, r10, #PROCINFO_INITFUNC ) @主要目的只配置CP15(包括缓存配置) 134. THUMB( mov pc, r12 ) 135. 1: b __enable_mmu @ 启动MMU 136. ENDPROC(stext) 137. .ltorg 138. #ifndef CONFIG_XIP_KERNEL 139. 2: .long . 140. .long PAGE_OFFSET 141. #endif 142. 143. /* 144. * 创建初始化页表.我们只创建最基本的页表, 145. * 以满足内核运行的需要, 146. * 这通常意味着仅映射内核代码本身. 147. * 148. * r8 = phys_offset, r9 = cpuid, r10 = procinfo 149. * 150. * 返回: 151. *r0, r3, r5-r7 被篡改 152. *r4 = 页表物理地址 153. */ 154. __create_page_tables: 155. pgtbl r4, r8 @ 现在r4 = 页表的起始物理地址 156. 157. /* 158. * 清零16K的一级初始页表区 159. * 这些页表在内核自解压时被设置过 160. * (此时MMU已关闭) 161. */ 162. mov r0, r4 163. mov r3, #0 164. add r6, r0, #0x4000 165. 1: str r3, [r0], #4 166. str r3, [r0], #4 167. str r3, [r0], #4 168. str r3, [r0], #4 169. teq r0, r6 170. bne 1b 171. 172. /* 173. * 获取节描述符的默认配置(除节基址外的其他配置) 174. * 这个数据依构架而不同,数据是用汇编文件配置的: 175. * arch/arm/mm/proc-xxx.S 176. * (此时MMU已关闭) 177. */ 178. ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ 获取mm_mmuflags(节描述符默认配置),保存于r7 179. 180. /* 181. * 创建特定映射,以满足__enable_mmu的需求。 182. * 此特定映射将被paging_init()删除。 183. * 184. * 其实这个特定的映射就是仅映射__enable_mmu功能函数区的页表 185. * 以保证在启用mmu时代码的正确执行--1:1映射(物理地址=虚拟地址) 186. */ 187. adr r0, __enable_mmu_loc 188. ldmia r0, {r3, r5, r6} 189. sub r0, r0, r3 @ 获取编译时确定的虚拟地址到当前物理地址的偏移 190. add r5, r5, r0 @ __enable_mmu的当前物理地址 191. add r6, r6, r0 @ __enable_mmu_end的当前物理地址 192. mov r5, r5, lsr #20 @ __enable_mmu的节基址 193. mov r6, r6, lsr #20 @ __enable_mmu_end的节基址 194. 195. 1: orr r3, r7, r5, lsl #20 @ 生成节描述符:flags + 节基址 196. str r3, [r4, r5, lsl #2] @ 设置节描述符,1:1映射(物理地址=虚拟地址) 197. teq r5, r6 @ 完成映射?(理论上一次就够了,这个函数应该不会大于1M吧~) 198. addne r5, r5, #1 @ r5 = 下一节的基址 199. bne 1b 200. 201. /* 202. * 现在创建内核的逻辑映射区页表(节映射) 203. * 创建范围:KERNEL_START---KERNEL_END 204. * KERNEL_START:内核最终运行的虚拟地址 205. * KERNEL_END:内核代码结束的虚拟地址(bss段之后,但XIP不是) 206. */ 207. mov r3, pc @ 获取当前物理地址 208. mov r3, r3, lsr #20 @ r3 = 当前物理地址的节基址 209. orr r3, r7, r3, lsl #20 @ r3 为当前物理地址的节描述符 210. /* 211. * 下面是为了确定页表项的入口地址 212. * 其实页表入口项的偏移就反应了对应的虚拟地址的高位 213. * 214. * 由于ARM指令集的8bit位图问题,只能分两次得到 215. * KERNEL_START:内核最终运行的虚拟地址 216. * 217. */ 218. add r0, r4, #(KERNEL_START & 0xff000000) >> 18 219. str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]! 220. ldr r6, =(KERNEL_END - 1) 221. add r0, r0, #4 222. add r6, r4, r6, lsr #18 @ r6 = 内核逻辑映射结束的节基址 223. 1: cmp r0, r6 224. add r3, r3, #1 << 20 @ 生成节描述符(只需做基址递增) 225. strls r3, [r0], #4 @ 设置节描述符 226. bls 1b 227. 228. #ifdef CONFIG_XIP_KERNEL 229. /* 230. * 如果是XIP技术的内核,上面的映射只能映射内核代码和只读数据部分 231. * 这里我们再映射一些RAM来作为 .data and .bss 空间. 232. */ 233. add r3, r8, #TEXT_OFFSET 234. orr r3, r3, r7 @ 生成节描述符:flags + 节基址 235. add r0, r4, #(KERNEL_RAM_VADDR & 0xff000000) >> 18 236. str r3, [r0, #(KERNEL_RAM_VADDR & 0x00f00000) >> 18]! 237. ldr r6, =(_end - 1) 238. add r0, r0, #4 239. add r6, r4, r6, lsr #18 240. 1: cmp r0, r6 241. add r3, r3, #1 << 20 242. strls r3, [r0], #4 243. bls 1b 244. #endif 245. 246. /* 247. * 然后映射启动参数区(现在r2中的atags物理地址) 248. * 或者 249. * 如果启动参数区的虚拟地址没有确定(或者无效),则会映射RAM的头1MB. 250. */ 251. mov r0, r2, lsr #20 252. movs r0, r0, lsl #20 253. moveq r0, r8 @ 如果atags指针无效,则r0 = r8(映射RAM的头1MB) 254. sub r3, r0, r8 255. add r3, r3, #PAGE_OFFSET @ 转换为虚拟地址 256. add r3, r4, r3, lsr #18 @ 确定页表项(节描述符)入口地址 257. orr r6, r7, r0 @ 生成节描述符 258. str r6, [r3] @ 设置节描述符 259. 260. /* 261. * 下面是调试信息的输出函数区 262. * 这里做了IO内存空间的节映射 263. */ 264. #ifdef CONFIG_DEBUG_LL 265. #ifndef CONFIG_DEBUG_ICEDCC 266. /* 267. * 为串口调试映射IO内存空间(将串口IO内存之上的所有地址都映射了) 268. * 这允许调试信息(在paging_init之前)从串口控制台输出 269. * 270. */ 271. addruart r7, r3 @ 宏代码,位于arch/arm/mach-xxx/include/mach/debug-macro.S 272. @ 作用是将串口控制寄存器的基址放入r7(物理地址)和r3(虚拟地址) 273. mov r3, r3, lsr #20 274. mov r3, r3, lsl #2 275. 276. add r0, r4, r3 @ r0为串口IO内存映射页表项的入口地址 277. rsb r3, r3, #0x4000 @ 16K(PTRS_PER_PGD*sizeof(long))-r3 278. cmp r3, #0x0800 @ limit to 512MB,入口地址有效性检查(只能在最后#0x0800内) 279. movhi r3, #0x0800 @ 也就是说虚拟地址被限制在3.5G以上 280. add r6, r0, r3 @ r6为页表结束地址 281. mov r3, r7, lsr #20 282. ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags 283. orr r3, r7, r3, lsl #20 @ 生成节描述符 284. 1: str r3, [r0], #4 285. add r3, r3, #1 << 20 286. teq r0, r6 287. bne 1b 288. 289. #else /* CONFIG_DEBUG_ICEDCC */ 290. /* 我们无需任何串口调试映射 for ICEDCC */ 291. ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags 292. #endif /* !CONFIG_DEBUG_ICEDCC */ 293. 294. #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS) 295. /* 296. * 如果我们在使用 NetWinder 或 CATS,我们也需要为调试信息映射 297. * 16550-type 串口 298. */ 299. add r0, r4, #0xff000000 >> 18 300. orr r3, r7, #0x7c000000 301. str r3, [r0] 302. #endif 303. #ifdef CONFIG_ARCH_RPC 304. /* 305. * Map in screen at 0x02000000 & SCREEN2_BASE 306. * Similar reasons here - for debug. This is 307. * only for Acorn RiscPC architectures. 308. */ 309. add r0, r4, #0x02000000 >> 18 310. orr r3, r7, #0x02000000 311. str r3, [r0] 312. add r0, r4, #0xd8000000 >> 18 313. str r3, [r0] 314. #endif 315. #endif 316. mov pc, lr @页表创建结束,返回 317. ENDPROC(__create_page_tables) 318. .ltorg 319. .align 320. __enable_mmu_loc: 321. .long . 322. .long __enable_mmu 323. .long __enable_mmu_end 324. 325. #if defined(CONFIG_SMP) 326. __CPUINIT 327. ENTRY(secondary_startup) 328. /* 329. * Common entry point for secondary CPUs. 330. * 331. * Ensure that we're in SVC mode, and IRQs are disabled. Lookup 332. * the processor type - there is no need to check the machine type 333. * as it has already been validated by the primary processor. 334. */ 335. setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 336. mrc p15, 0, r9, c0, c0 @ get processor id 337. bl __lookup_processor_type 338. movs r10, r5 @ invalid processor? 339. moveq r0, #'p' @ yes, error 'p' 340. THUMB( it eq ) @ force fixup-able long branch encoding 341. beq __error_p 342. 343. /* 344. * Use the page tables supplied from __cpu_up. 345. */ 346. adr r4, __secondary_data 347. ldmia r4, {r5, r7, r12} @ address to jump to after 348. sub lr, r4, r5 @ mmu has been enabled 349. ldr r4, [r7, lr] @ get secondary_data.pgdir 350. add r7, r7, #4 351. ldr r8, [r7, lr] @ get secondary_data.swapper_pg_dir 352. adr lr, BSYM(__enable_mmu) @ return address 353. mov r13, r12 @ __secondary_switched address 354. ARM( add pc, r10, #PROCINFO_INITFUNC ) @ initialise processor 355. @ (return control reg) 356. THUMB( add r12, r10, #PROCINFO_INITFUNC ) 357. THUMB( mov pc, r12 ) 358. ENDPROC(secondary_startup) 359. 360. /* 361. * r6 = &secondary_data 362. */ 363. ENTRY(__secondary_switched) 364. ldr sp, [r7, #4] @ get secondary_data.stack 365. mov fp, #0 366. b secondary_start_kernel 367. ENDPROC(__secondary_switched) 368. 369. .align 370. 371. .type __secondary_data, %object 372. __secondary_data: 373. .long . 374. .long secondary_data 375. .long __secondary_switched 376. #endif /* defined(CONFIG_SMP) */ 377. 378. 379. 380. /* 381. * 在最后启动MMU前,设置一些常用位 Essentially 382. * 其实,这里只是加载了页表指针和域访问控制数据寄存器 383. * 384. * 385. *r0 = cp#15 control register 386. * r1 = machine ID 387. * r2 = atags or dtb pointer 388. * r4 = page table pointer 389. * r9 = processor ID 390. * r13 = 最后要跳入的虚拟地址 391. */ 392. __enable_mmu: 393. #ifdef CONFIG_ALIGNMENT_TRAP 394. orr r0, r0, #CR_A 395. #else 396. bic r0, r0, #CR_A 397. #endif 398. #ifdef CONFIG_CPU_DCACHE_DISABLE 399. bic r0, r0, #CR_C 400. #endif 401. #ifdef CONFIG_CPU_BPREDICT_DISABLE 402. bic r0, r0, #CR_Z 403. #endif 404. #ifdef CONFIG_CPU_ICACHE_DISABLE 405. bic r0, r0, #CR_I 406. #endif 407. mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \ 408. domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \ 409. domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \ 410. domain_val(DOMAIN_IO, DOMAIN_CLIENT)) @设置域访问控制数据 411. mcr p15, 0, r5, c3, c0, 0 @ 载入域访问控制数据到DACR 412. mcr p15, 0, r4, c2, c0, 0 @ 载入页表基址到TTBR0 413. b __turn_mmu_on @ 开启MMU 414. ENDPROC(__enable_mmu) 415. 416. /* 417. * 使能 MMU.这完全改变了可见的内存地址空间结构。 418. * 您将无法通过这里跟踪执行。 419. * 如果你已对此进行探究, *请*在向邮件列表发送另一个新帖之前, 420. * 检查linux-arm-kernel的邮件列表归档 421. * 422. *r0 = cp#15 control register 423. * r1 = machine ID 424. * r2 = atags or dtb pointer 425. * r9 = processor ID 426. * r13 = 最后要跳入的*虚拟*地址 427. * 428. * 其他寄存器依赖上面的调用函数 429. */ 430. .align 5 431. __turn_mmu_on: 432. mov r0, r0 433. mcr p15, 0, r0, c1, c0, 0 @ 设置cp#15控制寄存器(启用MMU) 434. mrc p15, 0, r3, c0, c0, 0 @ read id reg 435. mov r3, r3 436. mov r3, r13 @ r3中装入最后要跳入的*虚拟*地址 437. mov pc, r3 @ 跳转到__mmap_switched 438. __enable_mmu_end: 439. ENDPROC(__turn_mmu_on) 440. 441. 442. #ifdef CONFIG_SMP_ON_UP 443. __INIT 444. __fixup_smp: 445. and r3, r9, #0x000f0000 @ architecture version 446. teq r3, #0x000f0000 @ CPU ID supported? 447. bne __fixup_smp_on_up @ no, assume UP 448. 449. bic r3, r9, #0x00ff0000 450. bic r3, r3, #0x0000000f @ mask 0xff00fff0 451. mov r4, #0x41000000 452. orr r4, r4, #0x0000b000 453. orr r4, r4, #0x00000020 @ val 0x4100b020 454. teq r3, r4 @ ARM 11MPCore? 455. moveq pc, lr @ yes, assume SMP 456. 457. mrc p15, 0, r0, c0, c0, 5 @ read MPIDR 458. and r0, r0, #0xc0000000 @ multiprocessing extensions and 459. teq r0, #0x80000000 @ not part of a uniprocessor system? 460. moveq pc, lr @ yes, assume SMP 461. 462. __fixup_smp_on_up: 463. adr r0, 1f 464. ldmia r0, {r3 - r5} 465. sub r3, r0, r3 466. add r4, r4, r3 467. add r5, r5, r3 468. b __do_fixup_smp_on_up 469. ENDPROC(__fixup_smp) 470. 471. .align 472. 1: .word . 473. .word __smpalt_begin 474. .word __smpalt_end 475. 476. .pushsection .data 477. .globl smp_on_up 478. smp_on_up: 479. ALT_SMP(.long 1) 480. ALT_UP(.long 0) 481. .popsection 482. #endif 483. 484. .text 485. __do_fixup_smp_on_up: 486. cmp r4, r5 487. movhs pc, lr 488. ldmia {r0, r6} 489. ARM( str r6, [r0, r3] ) 490. THUMB( add r0, r0, r3 ) 491. #ifdef __ARMEB__ 492. THUMB( mov r6, r6, ror #16 ) @ Convert word order for big-endian. 493. #endif 494. THUMB( strh r6, [r0], #2 ) @ For Thumb-2, store as two halfwords 495. THUMB( mov r6, r6, lsr #16 ) @ to be robust against misaligned r3. 496. THUMB( strh r6, [r0] ) 497. b __do_fixup_smp_on_up 498. ENDPROC(__do_fixup_smp_on_up) 499. 500. ENTRY(fixup_smp) 501. stmfd {r4 - r6, lr} 502. mov r4, r0 503. add r5, r0, r1 504. mov r3, #0 505. bl __do_fixup_smp_on_up 506. ldmfd {r4 - r6, pc} 507. ENDPROC(fixup_smp) 508. 509. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT 510. 511. /* __fixup_pv_table - patch the stub instructions with the delta between 512. * PHYS_OFFSET and PAGE_OFFSET, which is assumed to be 16MiB aligned and 513. * can be expressed by an immediate shifter operand. The stub instruction 514. * has a form of '(add|sub) rd, rn, #imm'. 515. */ 516. __HEAD 517. __fixup_pv_table: 518. adr r0, 1f 519. ldmia r0, {r3-r5, r7} 520. sub r3, r0, r3 @ PHYS_OFFSET - PAGE_OFFSET 521. add r4, r4, r3 @ adjust table start address 522. add r5, r5, r3 @ adjust table end address 523. add r7, r7, r3 @ adjust __pv_phys_offset address 524. str r8, [r7] @ save computed PHYS_OFFSET to __pv_phys_offset 525. #ifndef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT 526. mov r6, r3, lsr #24 @ constant for add/sub instructions 527. teq r3, r6, lsl #24 @ must be 16MiB aligned 528. #else 529. mov r6, r3, lsr #16 @ constant for add/sub instructions 530. teq r3, r6, lsl #16 @ must be 64kiB aligned 531. #endif 532. THUMB( it ne @ cross section branch ) 533. bne __error 534. str r6, [r7, #4] @ save to __pv_offset 535. b __fixup_a_pv_table 536. ENDPROC(__fixup_pv_table) 537. 538. .align 539. 1: .long . 540. .long __pv_table_begin 541. .long __pv_table_end 542. 2: .long __pv_phys_offset 543. 544. .text 545. __fixup_a_pv_table: 546. #ifdef CONFIG_THUMB2_KERNEL 547. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT 548. lsls r0, r6, #24 549. lsr r6, #8 550. beq 1f 551. clz r7, r0 552. lsr r0, #24 553. lsl r0, r7 554. bic r0, 0x0080 555. lsrs r7, #1 556. orrcs r0, #0x0080 557. orr r0, r0, r7, lsl #12 558. #endif 559. 1: lsls r6, #24 560. beq 4f 561. clz r7, r6 562. lsr r6, #24 563. lsl r6, r7 564. bic r6, #0x0080 565. lsrs r7, #1 566. orrcs r6, #0x0080 567. orr r6, r6, r7, lsl #12 568. orr r6, #0x4000 569. b 4f 570. 2: @ at this point the C flag is always clear 571. add r7, r3 572. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT 573. ldrh ip, [r7] 574. tst ip, 0x0400 @ the i bit tells us LS or MS byte 575. beq 3f 576. cmp r0, #0 @ set C flag, and ... 577. biceq ip, 0x0400 @ immediate zero value has a special encoding 578. streqh ip, [r7] @ that requires the i bit cleared 579. #endif 580. 3: ldrh ip, [r7, #2] 581. and ip, 0x8f00 582. orrcc ip, r6 @ mask in offset bits 31-24 583. orrcs ip, r0 @ mask in offset bits 23-16 584. strh ip, [r7, #2] 585. 4: cmp r4, r5 586. ldrcc r7, [r4], #4 @ use branch for delay slot 587. bcc 2b 588. bx lr 589. #else 590. #ifdef CONFIG_ARM_PATCH_PHYS_VIRT_16BIT 591. and r0, r6, #255 @ offset bits 23-16 592. mov r6, r6, lsr #8 @ offset bits 31-24 593. #else 594. mov r0, #0 @ just in case... 595. #endif 596. b 3f 597. 2: ldr ip, [r7, r3] 598. bic ip, ip, #0x000000ff 599. tst ip, #0x400 @ rotate shift tells us LS or MS byte 600. orrne ip, ip, r6 @ mask in offset bits 31-24 601. orreq ip, ip, r0 @ mask in offset bits 23-16 602. str ip, [r7, r3] 603. 3: cmp r4, r5 604. ldrcc r7, [r4], #4 @ use branch for delay slot 605. bcc 2b 606. mov pc, lr 607. #endif 608. ENDPROC(__fixup_a_pv_table) 609. 610. ENTRY(fixup_pv_table) 611. stmfd {r4 - r7, lr} 612. ldr r2, 2f @ get address of __pv_phys_offset 613. mov r3, #0 @ no offset 614. mov r4, r0 @ r0 = table start 615. add r5, r0, r1 @ r1 = table size 616. ldr r6, [r2, #4] @ get __pv_offset 617. bl __fixup_a_pv_table 618. ldmfd {r4 - r7, pc} 619. ENDPROC(fixup_pv_table) 620. 621. .align 622. 2: .long __pv_phys_offset 623. 624. .data 625. .globl __pv_phys_offset 626. .type __pv_phys_offset, %object 627. __pv_phys_offset: 628. .long 0 629. .size __pv_phys_offset, . - __pv_phys_offset 630. __pv_offset: 631. .long 0 632. #endif 633. 634. #include "head-common.S"
arch/arm/kernel/head-common.S
1./* 2. * linux/arch/arm/kernel/head-common.S 3. * 4. * Copyright (C) 1994-2002 Russell King 5. * Copyright (c) 2003 ARM Limited 6. * All Rights Reserved 7. * 8. * This program is free software; you can redistribute it and/or modify 9. * it under the terms of the GNU General Public License version 2 as 10. * published by the Free Software Foundation. 11. * 12. */ 13. 14. #define ATAG_CORE 0x54410001 15. #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2) 16. #define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2) 17. 18. #ifdef CONFIG_CPU_BIG_ENDIAN 19. #define OF_DT_MAGIC 0xd00dfeed 20. #else 21. #define OF_DT_MAGIC 0xedfe0dd0 /* 0xd00dfeed in big-endian */ 22. #endif 23. 24. /* 25. * 异常处理.一些我们无法处理的错误. 26. * 我们应当告诉用户(这些错误信息),但因为我们甚至无法保证是在正确的架构上运行, 27. * 所以我们什么都不做(死循环)。 28. * 29. * 如果 CONFIG_DEBUG_LL 被设置,我们试图打印出错误信息, 30. * 并希望这可以对我们有帮助 (例如这对bootloader没有提供适当的处理器ID 31. * 是有帮助的). 32. */ 33. __HEAD 34. 35. /* 确定r2(内核启动参数)指针的有效性。 The heuristic 要求 36. * 是4Byte对齐的、在物理内存的头16K中,且以ATAG_CORE标记开头。 37. * 如果选择了CONFIG_OF_FLATTREE,dtb指针也是可以接受的. 38. * 39. * 在这个函数的未来版本中 可能会对物理地址的要求更为宽松, 40. * 且如果有必要的话,可能可以移动ATAGS数据块. 41. * 42. * 返回: 43. *r2 可能是有效的 atags 指针, 有效的 dtb 指针,或者0 44. * r5, r6 被篡改 45. */ 46. __vet_atags: 47. tst r2, #0x3 @ 是否4Byte对齐? 48. bne 1f @ 不是则认为指针无效,返回 49. 50. ldr r5, [r2, #0] @获取r2指向的前4Byte,用于下面测试 51. #ifdef CONFIG_OF_FLATTREE 52. ldr r6, =OF_DT_MAGIC @ is it a DTB? 53. cmp r5, r6 54. beq 2f 55. #endif 56. 57. /* 内核启动参数块的规范是: 58. * (wait for updata) 59. */ 60. cmp r5, #ATAG_CORE_SIZE @ 第一个tag是ATAG_CORE吗?测试的是tag_header中的size 61. @ 如果为ATAG_CORE,那么必为ATAG_CORE_SIZE 62. cmpne r5, #ATAG_CORE_SIZE_EMPTY @ 如果第一个tag的tag_header中的size为ATAG_CORE_SIZE_EMPTY 63. @ 说明此处也有atags 64. bne 1f 65. ldr r5, [r2, #4] @ 第一个tag_header的tag(魔数) 66. ldr r6, =ATAG_CORE @ 获取ATAG_CORE的魔数 67. cmp r5, r6 @ 判断第一个tag是否为ATAG_CORE 68. bne 1f @ 不是则认为指针无效,返回 69. 70. 2: mov pc, lr @ atag/dtb 指针有效 71. 72. 1: mov r2, #0 73. mov pc, lr 74. ENDPROC(__vet_atags) 75. 76. /* 77. * 以下的代码段是在MMU开启的状态下执行的, 78. * 而且使用的是绝对地址; 这不是位置无关代码. 79. * 80. *r0 = cp#15 控制寄存器值 81. *r1 = machine ID 82. * r2 = atags/dtb pointer 83. * r9 = processor ID 84. */ 85. __INIT 86. __mmap_switched: 87. adr r3, __mmap_switched_data 88. 89. ldmia {r4, r5, r6, r7} 90. cmp r4, r5 @ 如果有必要,拷贝数据段。 91. @ 对比__data_loc和_sdata 92. @ __data_loc是数据段在内核代码映像中的存储位置 93. @ _sdata是数据段的链接位置(在内存中的位置) 94. @ 如果是XIP技术的内核,这两个数据肯定不同 95. 1: cmpne r5, r6 @ 检测数据是否拷贝完成 96. ldrne fp, [r4], #4 97. strne fp, [r5], #4 98. bne 1b 99. 100. mov fp, #0 @ 清零 BSS 段(and zero fp) 101. 1: cmp r6, r7 @ 检测是否完成 102. strcc fp, [r6],#4 103. bcc 1b 104. 105. /* 这里将需要的数据从寄存器中转移到全局变量中, 106. * 因为最后会跳入C代码,寄存器会被使用。 107. */ 108. ARM( ldmia r3, {r4, r5, r6, r7, sp}) 109. THUMB( ldmia r3, {r4, r5, r6, r7} ) 110. THUMB( ldr sp, [r3, #16] ) 111. str r9, [r4] @ 保存 processor ID到全局变量processor_id 112. str r1, [r5] @ 保存 machine type到全局变量__machine_arch_type 113. str r2, [r6] @ 保存 atags指针到全局变量__atags_pointer 114. bic r4, r0, #CR_A @ 清除cp15 控制寄存器值的 'A' bit(禁用对齐错误检查) 115. stmia r7, {r0, r4} @ 保存控制寄存器值到全局变量cr_alignment(在arch/arm/kernel/entry-armv.S) 116. b start_kernel @ 跳入C代码(init/main.c) 117. ENDPROC(__mmap_switched) 118. 119. .align 2 120. .type __mmap_switched_data, %object 121. __mmap_switched_data: 122. .long __data_loc @ r4 123. .long _sdata @ r5 124. .long __bss_start @ r6 125. .long _end @ r7 126. .long processor_id @ r4 127. .long __machine_arch_type @ r5 128. .long __atags_pointer @ r6 129. .long cr_alignment @ r7 130. .long init_thread_union + THREAD_START_SP @ sp 131. .size __mmap_switched_data, . - __mmap_switched_data 132. 133. /* 134. * 这里提供一个 C-API 版本的 __lookup_processor_type 135. */ 136. ENTRY(lookup_processor_type) 137. stmfd {r4 - r6, r9, lr} 138. mov r9, r0 139. bl __lookup_processor_type 140. mov r0, r5 141. ldmfd {r4 - r6, r9, pc} 142. ENDPROC(lookup_processor_type) 143. 144. /* 145. * 读取处理器ID寄存器 (CP#15, CR0), 并且查找编译时确定的处理器 146. * 支持列表.注意:我们不能对__proc_info使用绝对地址, 147. * 因为我们还没有重新初始化页表(MMU已关闭,之前是解压时使用的1:1映射)。 148. * (我们不在正确的地址空间:内核是按虚拟地址(0xc00008000)编译的, 149. * 而现在我们运行在MMU关闭的情况下)。 150. * 我们必须计算偏移量。 151. * 152. * r9 = cpuid 153. * Returns: 154. * r3, r4, r6 被篡改 155. * r5 = proc_info 指针(物理地址空间) 156. * r9 = cpuid (保留) 157. */ 158. __CPUINIT 159. __lookup_processor_type: 160. adr r3, __lookup_processor_type_data @获取运行时的地址数据 161. ldmia r3, {r4 - r6} @获取编译时确定的地址数据(虚拟地址) 162. sub r3, r3, r4 @ 获取地址偏移 virt&phys(r3) 163. add r5, r5, r3 @ 将虚拟地址空间转换为物理地址空间 164. add r6, r6, r3 @ r5=__proc_info_begin r6=__proc_info_end 165. 1: ldmia r5, {r3, r4} @ 获取proc_info_list结构体中的value, mask 166. and r4, r4, r9 @ 利用掩码处理从CP15获取的处理器ID 167. teq r3, r4 @ 对比编译时确定的处理器ID 168. beq 2f @ 若处理器ID匹配,返回 169. add r5, r5, #PROC_INFO_SZ @ 利用sizeof(proc_info_list)跳入下一个处理器ID的匹配 170. cmp r5, r6 @ 是否已经处理完proc_info_list数据 171. blo 1b @ 如果还有proc_info_list数据,再次检查匹配 172. mov r5, #0 @ 否则,编译的内核与此处理器不匹配,r5 = #0 173. 2: mov pc, lr 174. ENDPROC(__lookup_processor_type) 175. 176. /* 177. * 参见 <asm/procinfo.h> 中关于 __proc_info 结构体的信息. 178. */ 179. .align 2 180. .type __lookup_processor_type_data, %object 181. __lookup_processor_type_data: 182. .long . 183. .long __proc_info_begin 184. .long __proc_info_end 185. .size __lookup_processor_type_data, . - __lookup_processor_type_data 186. 187. /* 188. * 处理器ID不匹配时的入口 189. * 如果启用了调试信息,会从consol打印提示信息 190. * 之后会进入__error的死循环 191. */ 192. __error_p: 193. #ifdef CONFIG_DEBUG_LL 194. adr r0, str_p1 195. bl printascii 196. mov r0, r9 197. bl printhex8 198. adr r0, str_p2 199. bl printascii 200. b __error 201. str_p1: .asciz "\nError: unrecognized/unsupported processor variant (0x" 202. str_p2: .asciz ").\n" 203. .align 204. #endif 205. ENDPROC(__error_p) 206. 207. /* 208. * 出错时的死循环入口 209. */ 210. __error: 211. #ifdef CONFIG_ARCH_RPC 212. /* 213. * 出错时屏幕变红 - RiscPC only. 214. */ 215. mov r0, #0x02000000 216. mov r3, #0x11 217. orr r3, r3, r3, lsl #8 218. orr r3, r3, r3, lsl #16 219. str r3, [r0], #4 220. str r3, [r0], #4 221. str r3, [r0], #4 222. str r3, [r0], #4 223. #endif 224. 1: mov r0, r0 225. b 1b 226. ENDPROC(__error)