基本介绍
这是几年前在做sparc架构开发学习的时候写的技术文档,时间过了很久了,可能很多细节自己已经忘记了,这次把他分享在这里,现在国内估计很少还有人关注sparc架构。这里是第一片,分析的是head.S这段内核执行的第一段汇编代码,是基于2.6.32版本内核的。
正文
分析是从第一条内核指令开始的,不说了,直接贴代码:
39 .text
40 .globl start, _start, stext, _stext
41 _start:
42 start:
43 _stext:
44 stext:
45 ! 0x0000000000404000
46 b sparc64_boot
47 flushw /* Flush register file. */
第39行,定义代码段
第40行,定义了几个全局符号,他们标志着内核代码段的开始
第41到44行,它们就是第40行定义的几个符号
第45行,注释语句表示这个位置的虚拟地址(内核代码开始的虚拟地址0x0000000000404000 )以后注释语句就不解释了。
第46行,内核代码段的第一条指令,跳转到sparc64_boot执行
第47行,这条指令是在第46行跳转指令的命令槽中,熟悉sparc体系结构的人都知道,命令槽 中的指令会在跳转指令之前执行,除非在跳转指令中指明命令槽无效,这条指令刷新寄存器窗口。
49 /* This stuff has to be in sync with SILO and other potential boot loaders
50 * Fields should be kept upward compatible and whenever any change is made,
51 * HdrS version should be incremented.
52 */
53 .global root_flags, ram_flags, root_dev
54 .global sparc_ramdisk_image, sparc_ramdisk_size
55 .global sparc_ramdisk_image64
56
57 .ascii "HdrS"
58 .word LINUX_VERSION_CODE
59
60 /* History:
61 *
62 * 0x0300 : Supports being located at other than 0x4000
63 * 0x0202 : Supports kernel params string
64 * 0x0201 : Supports reboot_command
65 */
66 .half 0x0301 /* HdrS version */
67
68 root_flags:
69 .half 1
70 root_dev:
71 .half 0
72 ram_flags:
73 .half 0
74 sparc_ramdisk_image:
75 .word 0
76 sparc_ramdisk_size:
77 .word 0
78 .xword reboot_command
79 .xword bootstr_info
80 sparc_ramdisk_image64:
81 .xword 0
82 .word _end
上面这一段定义了一些变量,这些变量是用来和silo等引导程序交互用的,我们不用关心。
84 /* PROM cif handler code address is in %o4. */
85 sparc64_boot:
86 mov %o4, %l7
内核的第一条指令就是跳转到这里执行的。
第86行,把固件传递给内核的第4个参数保存到l7寄存器中。注意在sparc Linux里,固件为内核做了很多事情,此外由于处理器执行权限的问题,很多事情必须由固件来完成,因为固件运行在超特权级,而内核运行在特权级,所以很多事情必须由内核请求固件服务来完成,可以这样说,内核是运行在固件的基础上。这里的第4个参数就是固件提供给内核的服务函数,就是内核请求固件服务的接口。
88 /* We need to remap the kernel. Use position independant
89 * code to remap us to KERNBASE.
90 *
91 * SILO can invoke us with 32-bit address masking enabled,
92 * so make sure that's clear.
93 */
94 rdpr %pstate, %g1
95 andn %g1, PSTATE_AM, %g1
96 wrpr %g1, 0x0, %pstate
97 ba,a,pt %xcc, 1f
第94到96行,这几行所完成的动作就是清除PSTATA(处理器状态寄存器)中的AM标志,该标志,该标志被置位表示指令地址的高32为被mask,即设为0,这在64位体系结构中显然是不应该的,所以要清除该标志。在silo等引导程序加载内核时有可能会设置该标志。
第97行,跳转到1处执行,ba表示无条件跳转,,a表示废除命令槽,即如果跳转的话,命令槽中的指令不会执行,,pt表示进行分支预测,分支预测是与处理器流水线相关的概念,用来提高处理器的执行效率。
99 .globl prom_finddev_name, prom_chosen_path, prom_root_node
100 .globl prom_getprop_name, prom_mmu_name, prom_peer_name
101 .globl prom_callmethod_name, prom_translate_name, prom_root_compatible
102 .globl prom_map_name, prom_unmap_name, prom_mmu_ihandle_cache
103 .globl prom_boot_mapped_pc, prom_boot_mapping_mode
104 .globl prom_boot_mapping_phys_high, prom_boot_mapping_phys_low
105 .globl prom_compatible_name, prom_cpu_path, prom_cpu_compatible
106 .globl is_sun4v, sun4v_chip_type, prom_set_trap_table_name
107 prom_peer_name:
108 .asciz "peer"
109 prom_compatible_name:
110 .asciz "compatible"
111 prom_finddev_name:
112 .asciz "finddevice"
113 prom_chosen_path:
114 .asciz "/chosen"
115 prom_cpu_path:
116 .asciz "/cpu"
117 prom_getprop_name:
118 .asciz "getprop"
119 prom_mmu_name:
120 .asciz "mmu"
121 prom_callmethod_name:
122 .asciz "call-method"
123 prom_translate_name:
124 .asciz "translate"
125 prom_map_name:
126 .asciz "map"
127 prom_unmap_name:
128 .asciz "unmap"
129 prom_set_trap_table_name:
130 .asciz "SUNW,set-trap-table"
131 prom_sun4v_name:
132 .asciz "sun4v"
133 prom_niagara_prefix:
134 .asciz "SUNW,UltraSPARC-T"
135 .align 4
136 prom_root_compatible:
137 .skip 64
138 prom_cpu_compatible:
139 .skip 64
140 prom_root_node:
141 .word 0
142 prom_mmu_ihandle_cache:
143 .word 0
144 prom_boot_mapped_pc:
145 .word 0
146 prom_boot_mapping_mode:
147 .word 0
148 .align 8
149 prom_boot_mapping_phys_high:
150 .xword 0
151 prom_boot_mapping_phys_low:
152 .xword 0
153 is_sun4v:
154 .word 0
155 sun4v_chip_type:
156 .word SUN4V_CHIP_INVALID
上面的一大段定义了很多全局变量,至于这些变量是干什么用的,等用到的时候再说吧,需要注意的是这一大段虽然是定义变量,但仍然在代码段中,而没有存放在数据段中,即他们在内存中位置是在程序代码之间,跟它们在本文件中的位置一样,这样做是为后面的代码调用这些变量提供方便。
157 1:
158 rd %pc, %l0
159
160 mov (1b - prom_peer_name), %l1
161 sub %l0, %l1, %l1
162 mov 0, %l2
第157行,标记1,第97行就是跳到这里执行。
第158行,读取程序计数器的值到l0中。
第160行,1b-prom_peer_name的值放在l1中,1b是标记,它代表了该位置的逻辑地址,prom_peer_name,即是前面那一大段定义的全局变量中的一个,你也可以把它看作一个标记,即变量的值在内存中位置的逻辑地址,两值相减,就是变量在内存中的位置相对于1标记处即第158行指令在内存中地址的位移的负数
第161行,l0中存放的是第158行指令的实际地址(可能是虚拟地址或者实地址,这取决与固件有没有启动mmu的相应功能,我现在还不知道),减去偏移,即是变量prom_peer_name的实际地址了,存放在l1中。
第162行,把0存放在l2中
164 /* prom_root_node = prom_peer(0) */
165 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "peer"
166 mov 1, %l3
167 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 1
168 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
169 stx %l2, [%sp + 2047 + 128 + 0x18] ! arg1, 0
170 stx %g0, [%sp + 2047 + 128 + 0x20] ! ret1
171 call %l7
172 add %sp, (2047 + 128), %o0 ! argument array
这几行就是内核请求固件提供的服务了,在固件中为系统中的硬件设备,cpu等资源建立了一个统一的抽象,这即使设备节点树了,内核启动过程中的很多工作都是通过这个节点树来完成的,因为节点树能够提供系统中硬件资源的信息,这样内核就不需要取遍历硬件资源了。既然是树,当然就有一个根节点,这一段就是调用固件服务获得节点树的根节点。
内核调用固件服务接口时的参数是通过堆栈来传递的,即内核把参数按顺序存放在堆栈中,然后固件从堆栈中读取参数进行相应计算,注意从内核开始执行后,堆栈指针sp的值一直都没有变过,即sp一直指向内核运行前固件执行时使用的堆栈。
第165行,把l1的值存放在堆栈中,这是第一个参数,是要请求的服务的名称,是一个字符串,这里就是字符串变量prom_peer_name的值”peer”
第166,167行,把1存放在堆栈中,这是第二个参数,代表服务‘peer’的参数的个数
第168行,又把1存放在堆栈中,这是第三个参数,表示服务’peer’的返回值的个数
第169行,服务’peer’的参数0,值为0
第170行,服务’peer’的返回值,清为0
第171行,调用固件服务接口,前面的分析可知,该接口存放在l7中
第172行,命令槽中的指令,跟新堆栈指针,使指向传递的参数
这段指令的执行结果等效于 prom_root_node = prom_peer(0),调用固件服务得到节点树的根节点。
174 ldx [%sp + 2047 + 128 + 0x20], %l4 ! prom root node
175 mov (1b - prom_root_node), %l1
176 sub %l0, %l1, %l1
177 stw %l4, [%l1]
第174行,从堆栈中取出‘peer’的返回值即prom root node的值到l4中
第175到176行,跟前面的分析一样,得出 prom_root_node变量的实际地址
第177行,把得到的值保存到prom_root_node变量中
179 mov (1b - prom_getprop_name), %l1
180 mov (1b - prom_compatible_name), %l2
181 mov (1b - prom_root_compatible), %l5
182 sub %l0, %l1, %l1
183 sub %l0, %l2, %l2
184 sub %l0, %l5, %l5
第179到184行,得到变量 prom_getprop_name, prom_compatible_name, prom_root_compatible变量的地址,分别放在l1,l2,l5寄存器中。
186 /* prom_getproperty(prom_root_node, "compatible",
187 * &prom_root_compatible, 64)
188 */
189 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "getprop"
190 mov 4, %l3
191 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 4
192 mov 1, %l3
193 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
194 stx %l4, [%sp + 2047 + 128 + 0x18] ! arg1, prom_root_node
195 stx %l2, [%sp + 2047 + 128 + 0x20] ! arg2, "compatible"
196 stx %l5, [%sp + 2047 + 128 + 0x28] ! arg3, &prom_root_compatible
197 mov 64, %l3
198 stx %l3, [%sp + 2047 + 128 + 0x30] ! arg4, size
199 stx %g0, [%sp + 2047 + 128 + 0x38] ! ret1
200 call %l7
201 add %sp, (2047 + 128), %o0 ! argument array
这一段跟上面请求peer服务一样,请求getprop服务,就不再一行行分析了,如果有不懂的,可以看前面的分析。这一段等效于:
prom_getproperty(prom_root_node, "compatible",
&prom_root_compatible, 64);
这个服务是获得节点的属性,在设备节点树中,每一个节点都有很多属性,它们描述了该节点所代表资源的一些信息,可以说,内核使用设备节点树就是为了得到节点的属性,从而获知硬件资源的信息以管理硬件。
这里是获得根节点的”compatible”属性的,放在 prom_root_compatible结构中,看这个属性的名字就知道,是用来判断兼容性的,这里得到,后面会使用的,到用到的时候再说。
203 mov (1b - prom_finddev_name), %l1
204 mov (1b - prom_chosen_path), %l2
205 mov (1b - prom_boot_mapped_pc), %l3
206 sub %l0, %l1, %l1
207 sub %l0, %l2, %l2
208 sub %l0, %l3, %l3
209 stw %l0, [%l3]
210 sub %sp, (192 + 128), %sp
211
212 /* chosen_node = prom_finddevice("/chosen") */
213 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "finddevice"
214 mov 1, %l3
215 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 1
216 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
217 stx %l2, [%sp + 2047 + 128 + 0x18] ! arg1, "/chosen"
218 stx %g0, [%sp + 2047 + 128 + 0x20] ! ret1
219 call %l7
220 add %sp, (2047 + 128), %o0 ! argument array
221
222 ldx [%sp + 2047 + 128 + 0x20], %l4 ! chosen device node
上面这一段调用固件的finddevice服务得到/chosen路径所代表的节点,这里的路径是节点在节点树中的路径,即根节点下的chosen节点,最后把得到的节点放在l4中,等效于:
chosen_node = prom_finddevice("/chosen")
至于chosen节点代表的是什么我现在也不知道。
注意在这段汇编中的第209行,把l0中的值存放在变量 prom_boot_mapped_pc,这应该是初始化全局变量 prom_boot_mapped_pc的,l0的值是内核代码段开始时的第几条指令的地址。至于到底是几条,你自己去数吧。
224 mov (1b - prom_getprop_name), %l1
225 mov (1b - prom_mmu_name), %l2
226 mov (1b - prom_mmu_ihandle_cache), %l5
227 sub %l0, %l1, %l1
228 sub %l0, %l2, %l2
229 sub %l0, %l5, %l5
230
231 /* prom_mmu_ihandle_cache = prom_getint(chosen_node, "mmu") */
232 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "getprop"
233 mov 4, %l3
234 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 4
235 mov 1, %l3
236 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
237 stx %l4, [%sp + 2047 + 128 + 0x18] ! arg1, chosen_node
238 stx %l2, [%sp + 2047 + 128 + 0x20] ! arg2, "mmu"
239 stx %l5, [%sp + 2047 + 128 + 0x28] ! arg3, &prom_mmu_ihandle_cache
240 mov 4, %l3
241 stx %l3, [%sp + 2047 + 128 + 0x30] ! arg4, sizeof(arg3)
242 stx %g0, [%sp + 2047 + 128 + 0x38] ! ret1
243 call %l7
244 add %sp, (2047 + 128), %o0 ! argument array
上面这一段是得到chosen节点的’mmu’属性,放在 prom_mmu_ihandle_cache变量中,等效于
prom_mmu_ihandle_cache = prom_getint(chosen_node, "mmu")
得到这个属性,后面会用
246 mov (1b - prom_callmethod_name), %l1
247 mov (1b - prom_translate_name), %l2
248 sub %l0, %l1, %l1
249 sub %l0, %l2, %l2
250 lduw [%l5], %l5 ! prom_mmu_ihandle_cache
251
252 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "call-method"
253 mov 3, %l3
254 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 3
255 mov 5, %l3
256 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 5
257 stx %l2, [%sp + 2047 + 128 + 0x18] ! arg1: "translate"
258 stx %l5, [%sp + 2047 + 128 + 0x20] ! arg2: prom_mmu_ihandle_cache
259 /* PAGE align */
260 srlx %l0, 13, %l3
261 sllx %l3, 13, %l3
262 stx %l3, [%sp + 2047 + 128 + 0x28] ! arg3: vaddr, our PC
263 stx %g0, [%sp + 2047 + 128 + 0x30] ! res1
264 stx %g0, [%sp + 2047 + 128 + 0x38] ! res2
265 stx %g0, [%sp + 2047 + 128 + 0x40] ! res3
266 stx %g0, [%sp + 2047 + 128 + 0x48] ! res4
267 stx %g0, [%sp + 2047 + 128 + 0x50] ! res5
268 call %l7
269 add %sp, (2047 + 128), %o0 ! argument array
上面这段就是用上面得到的mmu属性 prom_mmu_ihandle_cache,调用服务 call-method:
call-method(“translate”,prom_mmu_ihandle_cache,vaddr)
vaddr 是当前页的基地址
该服务有三个返回值。
271 ldx [%sp + 2047 + 128 + 0x40], %l1 ! translation mode
272 mov (1b - prom_boot_mapping_mode), %l4
273 sub %l0, %l4, %l4
274 stw %l1, [%l4]
275 mov (1b - prom_boot_mapping_phys_high), %l4
276 sub %l0, %l4, %l4
277 ldx [%sp + 2047 + 128 + 0x48], %l2 ! physaddr high
278 stx %l2, [%l4 + 0x0]
279 ldx [%sp + 2047 + 128 + 0x50], %l3 ! physaddr low
280 /* 4MB align */
281 srlx %l3, 22, %l3
282 sllx %l3, 22, %l3
283 stx %l3, [%l4 + 0x8]
这一段就是把call-method方法得到的返回值放到相应变量中,分别是prom_boot_mapping_mode, prom_boot_mapping_phys_high和rom_boot_mapping_phys_low
这里call-method服务所完成的功能应该时根据mmu的模式把传入的vaddr转换程相应的物理地址
285 /* Leave service as-is, "call-method" */
286 mov 7, %l3
287 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 7
288 mov 1, %l3
289 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
290 mov (1b - prom_map_name), %l3
291 sub %l0, %l3, %l3
292 stx %l3, [%sp + 2047 + 128 + 0x18] ! arg1: "map"
293 /* Leave arg2 as-is, prom_mmu_ihandle_cache */
294 mov -1, %l3
295 stx %l3, [%sp + 2047 + 128 + 0x28] ! arg3: mode (-1 default)
296 /* 4MB align the kernel image size. */
297 set (_end - KERNBASE), %l3
298 set ((4 * 1024 * 1024) - 1), %l4
299 add %l3, %l4, %l3
300 andn %l3, %l4, %l3
301 stx %l3, [%sp + 2047 + 128 + 0x30] ! arg4: roundup(ksize, 4MB)
302 sethi %hi(KERNBASE), %l3
303 stx %l3, [%sp + 2047 + 128 + 0x38] ! arg5: vaddr (KERNBASE)
304 stx %g0, [%sp + 2047 + 128 + 0x40] ! arg6: empty
305 mov (1b - prom_boot_mapping_phys_low), %l3
306 sub %l0, %l3, %l3
307 ldx [%l3], %l3
308 stx %l3, [%sp + 2047 + 128 + 0x48] ! arg7: phys addr
309 call %l7
310 add %sp, (2047 + 128), %o0 ! argument array
上面这一段继续调用call-method服务,
call-method(“map”,prom_mmu_ihandle_cache,-1,kernel-size,KERNELBASE,0,paddr)
这次请求该服务是把传入的物理地址映射到传入的虚拟地址
其中第297行到300行是上面的分析中没有见过的,这里分析一下
第297行,计算内核代码段大小,放入l3中
第298行,设置掩码,4M-1,放入l4中
第299,300行,进行按位取反,再相与运算,
这几行实际上是把内核代码段大小转换程4M对齐的大小
综合前面的分析,先获得固件的mmu属性,然后根据该属性把内核代码的当前虚拟地址(实际地址)通过call-method服务转换物理地址,然后再调用call-method服务把该物理地址映射到传入的虚拟地址(即根据内核代码段的大小和指定的虚拟地址建立映射关系),即把内核代码段映射KERNELBASE开始的4M大小的虚拟地址空间,这就是上面一大段汇编的作用,当然,同时初始化了一些变量。
312 add %sp, (192 + 128), %sp
313
314 sethi %hi(prom_root_compatible), %g1
315 or %g1, %lo(prom_root_compatible), %g1
316 sethi %hi(prom_sun4v_name), %g7
317 or %g7, %lo(prom_sun4v_name), %g7
318 mov 5, %g3
319 90: ldub [%g7], %g2
320 ldub [%g1], %g4
321 cmp %g2, %g4
322 bne,pn %icc, 80f
323 add %g7, 1, %g7
324 subcc %g3, 1, %g3
325 bne,pt %xcc, 90b
326 add %g1, 1, %g1
第314,315行,把 prom_root_compatible变量的地址存放在g1中,这个变量前面见过把,就是从根节点中获得的“compatible”属性,这里就要使用它了
第316,317行,把 prom_sun4v_name变量的地址存放在g7中,与prom_root_compatible变量不同,prom_sun4v_name变量在定义的时候就已经初始化好了,你可以到变量定义的代码去看,它被初始化为字符串”sun4v”
不知道你发现没有,这里取变量的地址和前面获得节点属性时取变量的地址所用的方法不同,前面还要用pc的值计算出变量的地址,而这里则直接使用了编译时为变量指定的逻辑地址,知道为什么吗,因为在前面已经为内核代码进行了重新映射,已经把内核代码映射到了编译器制定的逻辑地址了,所以这里就可以直接使用了。
第318行,在g3中放入常熟5
第319行,把g7所指地址的一个字节的内容放入g2中,从分析可知,就是”sun4v”字符串的第一个字符’s’
第320行,把g1所指地址即 prom_root_compatible变量的第一个字符放入g4中
第321行,比较g2和g4,即比较prom_root_compatible和字符串“sun4v”的第一个字符,
第322行,如果不等,则跳到标记80处执行,但是显然我们这里是会相等的,因为我们所用的sparc cpu正式sun4v模式的,因此,这条分支指令我值分析我们系统实际所走的分支,另外一个分支就不分析了,如果你有兴趣的话可以自己分析
第323行,如果相等,则g7值加一,一看就知道是比较下一个字符的
第324行,g3减1,并设置标志,g3的值是5,也就是说,只有减5次才会去真的设置xcc中的标志
第325行,如果g3不为0,则跳转到标记90处执行,标记90处就是取出两个字符串变量的字符,
第326行,命令槽中的指令,g2加1
这样从标记90处即第319行到326行的指令就构成了一个循环,循环的次数是5,就是比较
prom_root_compatible变量和“sun4v字符串”,如果不行等,就跳转到80处执行,否则顺序执行下条指令,我们这里就是去顺序执行下条指令
328 sethi %hi(is_sun4v), %g1
329 or %g1, %lo(is_sun4v), %g1
330 mov 1, %g7
331 stw %g7, [%g1]
接下来就是执行这一段了。
第328,329行,把 is_sun4v变量的地址存放在g1中
第330行,在g7中存放常量1
第331行,把1存入变量is_sun4v中,通过前面一段prom_root_compatible变量与”sun4v”字符串比较,这里已经知道我们的平台就是sun4v的,因此这里把is_sun4v变量设为1
333 /* cpu_node = prom_finddevice("/cpu") */
334 mov (1b - prom_finddev_name), %l1
335 mov (1b - prom_cpu_path), %l2
336 sub %l0, %l1, %l1
337 sub %l0, %l2, %l2
338 sub %sp, (192 + 128), %sp
339
340 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "finddevice"
341 mov 1, %l3
342 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 1
343 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
344 stx %l2, [%sp + 2047 + 128 + 0x18] ! arg1, "/cpu"
345 stx %g0, [%sp + 2047 + 128 + 0x20] ! ret1
346 call %l7
347 add %sp, (2047 + 128), %o0 ! argument array
上面这两段很熟悉了吧,就是请求固件服务得到/cpu路径制定的节点
cpu_node = prom_finddevice("/cpu")
不知道你注意没有,这里取变量的地址仍然使用内核代码没有映射之前的方式,这是因为,这个变量地址是给固件使用的,固件仍然使用以前的地址。
349 ldx [%sp + 2047 + 128 + 0x20], %l4 ! cpu device node
第349行,把cpu节点的节点号存在l4里
351 mov (1b - prom_getprop_name), %l1
352 mov (1b - prom_compatible_name), %l2
353 mov (1b - prom_cpu_compatible), %l5
354 sub %l0, %l1, %l1
355 sub %l0, %l2, %l2
356 sub %l0, %l5, %l5
357
358 /* prom_getproperty(cpu_node, "compatible",
359 * &prom_cpu_compatible, 64)
360 */
361 stx %l1, [%sp + 2047 + 128 + 0x00] ! service, "getprop"
362 mov 4, %l3
363 stx %l3, [%sp + 2047 + 128 + 0x08] ! num_args, 4
364 mov 1, %l3
365 stx %l3, [%sp + 2047 + 128 + 0x10] ! num_rets, 1
366 stx %l4, [%sp + 2047 + 128 + 0x18] ! arg1, cpu_node
367 stx %l2, [%sp + 2047 + 128 + 0x20] ! arg2, "compatible"
368 stx %l5, [%sp + 2047 + 128 + 0x28] ! arg3, &prom_cpu_compatible
369 mov 64, %l3
370 stx %l3, [%sp + 2047 + 128 + 0x30] ! arg4, size
371 stx %g0, [%sp + 2047 + 128 + 0x38] ! ret1
372 call %l7
373 add %sp, (2047 + 128), %o0 ! argument array
这两段仍然很熟悉,还是请求固件服务,这里是请求 “getprop”服务,得到cpu节点的 “compatible”属性,存放在 prom_cpu_compatible变量中prom_getproperty(cpu_node, “compatible”, &prom_cpu_compatible, 64)
前面根节点的 compatible属性为”sun4v”,“sun4v”是平台mmu的类型
这里要得到cpu的"compatible"属性
375 add %sp, (192 + 128), %sp
376
377 sethi %hi(prom_cpu_compatible), %g1
378 or %g1, %lo(prom_cpu_compatible), %g1
379 sethi %hi(prom_niagara_prefix), %g7
380 or %g7, %lo(prom_niagara_prefix), %g7
381 mov 17, %g3
382 90: ldub [%g7], %g2
383 ldub [%g1], %g4
384 cmp %g2, %g4
385 bne,pn %icc, 4f
386 add %g7, 1, %g7
387 subcc %g3, 1, %g3
388 bne,pt %xcc, 90b
389 add %g1, 1, %g1
这一段是不是同样很熟悉?前面分析过比较根节点的属性 prom_root_compatible和“sun4v”,这里同样的是比较 prom_cpu_compatible和"SUNW,UltraSPARC-T",这里就不再一行行的分析了,忘记了可以看前面的分析。
这里就是判断cpu是不是 SUNW,UltraSPARC-T,如果不是就跳到标记4处执行,在标记4处会把sun4v_chip_type设置成SUN4V_CHIP_UNKNOWN,显然我们不会是SUN4V_CHIP_UNKNOWN,即我们是 SUNW,UltraSPARC-T,这里我们就不会跳转了
391 sethi %hi(prom_cpu_compatible), %g1
392 or %g1, %lo(prom_cpu_compatible), %g1
393 ldub [%g1 + 17], %g2
394 cmp %g2, '1'
395 be,pt %xcc, 5f
396 mov SUN4V_CHIP_NIAGARA1, %g4
397 cmp %g2, '2'
398 be,pt %xcc, 5f
399 mov SUN4V_CHIP_NIAGARA2, %g4
第391,392行, prom_cpu_compatible变量的地址放在g1中
第393行,把 prom_cpu_compatible字符串的第17个字符放在g2中,
第394行,把该字符与‘1’比较
第395行,如果相等,跳到5处执行
第396行,命令槽指令,跳到5之前把 SUN4V_CHIP_NIAGARA1放到g4中
第397行,如果不等,则继续把该字符与‘2’比较
第398行,如果相等,则跳到5处执行
第399行,命令槽指令,如果相等,则把 SUN4V_CHIP_NIAGARA2保存在g4中
如果不等,则会执行标记4处的指令
从上面的分析可以看出,UNW,UltraSPARC-T也是分两种类型的,分别是UNW,UltraSPARC-T1和UNW,UltraSPARC-T2中,这一段就是判断到底是那种类型,然后根据这一类型把相应的SUN4V_CHIP_NIAGARAn 保存到g4中
400 4:
401 mov SUN4V_CHIP_UNKNOWN, %g4
402 5: sethi %hi(sun4v_chip_type), %g2
403 or %g2, %lo(sun4v_chip_type), %g2
404 stw %g4, [%g2]
上面这一段则是根据前面的类型设置 sun4v_chip_type变量的值,当然我们的系统中则设置的是
SUN4V_CHIP_NIAGARA2类型了,因为我们是T2
406 80:
407 BRANCH_IF_SUN4V(g1, jump_to_sun4u_init)
408 BRANCH_IF_CHEETAH_BASE(g1,g7,cheetah_boot)
409 BRANCH_IF_CHEETAH_PLUS_OR_FOLLOWON(g1,g7,cheetah_plus_boot)
410 ba,pt %xcc, spitfire_boot
411 nop
第406行,标记80,还记得前面的分析吗,如果prom_root_compatible不是“sun4v”,则跳到此处执行,原来sparc体系结果有T1,T2和后来的T3也许再后来还会有T4几种版本,当然后来的版本比前面的版本要先进了。其中T1和T2应该是与“sun4v”对应,也许它们的代号就叫“sun4v”,至于是不是,我不知道,我对sparc体系结构的版本不是很熟悉。后来的T3版本的代号应该就是“cheetah”及升级版“cheetah_plus”
第407行,是一个宏 BRANCH_IF_SUN4V,它的定义如下:
#define BRANCH_IF_SUN4V(tmp1,label) \
sethi %hi(is_sun4v), %tmp1; \
lduw [%tmp1 + %lo(is_sun4v)], %tmp1; \
brnz,pn %tmp1, label; \
nop
这个宏的意思就是判断is_sun4v变量的值,如果不为0,则跳转到 label标记处执行,显然is_sun4v变量的值早就设置为1了,这里就会跳转jump_to_sun4u_init处执行
那么后面的代码就不用管了,我们直接从jump_to_sun4u_init标记处分析
453
454 jump_to_sun4u_init:
455 /*
456 * Make sure we are in privileged mode, have address masking,
457 * using the ordinary globals and have enabled floating
458 * point.
459 *
460 * Again, typically PROM has left %pil at 13 or similar, and
461 * (PSTATE_PRIV | PSTATE_PEF | PSTATE_IE) in %pstate.
462 */
463 wrpr %g0, (PSTATE_PRIV|PSTATE_PEF|PSTATE_IE), %pstate
464 wr %g0, 0, %fprs
第463行,设置pstate寄存器到一个初始状态 PSTATE_PRIV | PSTATE_PEF | PSTATE_IE
第464行,禁止浮点单元
466 set sun4u_init, %g2
467 jmpl %g2 + %g0, %g0
468 nop
469
470 __REF
跳到 sun4u_init处执行
471 sun4u_init:
472 BRANCH_IF_SUN4V(g1, sun4v_init)
473
474 /* Set ctx 0 */
475 mov PRIMARY_CONTEXT, %g7
476 stxa %g0, [%g7] ASI_DMMU
477 membar #Sync
478
479 mov SECONDARY_CONTEXT, %g7
480 stxa %g0, [%g7] ASI_DMMU
481 membar #Sync
482
483 ba,pt %xcc, sun4u_continue
484 nop
第472行,该宏上面分析过,跳到 sun4v_init处,即第486行执行
486 sun4v_init:
487 /* Set ctx 0 */
488 mov PRIMARY_CONTEXT, %g7
489 stxa %g0, [%g7] ASI_MMU
490 membar #Sync
第488行,把 宏PRIMARY_CONTEXT的值放入g7,这个宏的值实际上就是MMU上primary context 寄存器的地址
第489行,把0 存入mmu上的primary context寄存器
第490行,是进行指令流控制的
492 mov SECONDARY_CONTEXT, %g7
493 stxa %g0, [%g7] ASI_MMU
494 membar #Sync
495 ba,pt %xcc, niagara_tlb_fixup
496 nop
第492到494行,同上面的一段,不过这里是把MMU上的secondary context寄存器清0
第495行,跳转到niagara_tlb_fixup
处执行
498 sun4u_continue:
499 BRANCH_IF_ANY_CHEETAH(g1, g7, cheetah_tlb_fixup)
500
501 ba,pt %xcc, spitfire_tlb_fixup
502 nop
503
504 niagara_tlb_fixup:
505 mov 3, %g2 /* Set TLB type to hypervisor. */
506 sethi %hi(tlb_type), %g1
507 stw %g2, [%g1 + %lo(tlb_type)]
第505行,在g2中放入常数3
第506,507行,把3存入变量tlb_type中,sun4v对应的tlb_type是hypervisor,而cheetah对于的tlb_type则是cheetah
509 /* Patch copy/clear ops. */
510 sethi %hi(sun4v_chip_type), %g1
511 lduw [%g1 + %lo(sun4v_chip_type)], %g1
512 cmp %g1, SUN4V_CHIP_NIAGARA1
513 be,pt %xcc, niagara_patch
514 cmp %g1, SUN4V_CHIP_NIAGARA2
515 be,pt %xcc, niagara2_patch
516 nop
第510,511行,把 sun4v_chip_type变量的值存放在g1中, sun4v_chip_type变量的值我们已经初始化过了,当时初始化成了 SUN4V_CHIP_NIAGARA2
第512,513行,比较 SUN4V_CHIP_NIAGARA2与sun4v_chip_type变量的值,如果相等,则跳到 niagara_patch处执行,我们这里显然不会跳转
第514,515行,这里就要跳转了,跳到niagara2_patch
处执行,即第526行执行,我们直接去看第526行
518 call generic_patch_copyops
519 nop
520 call generic_patch_bzero
521 nop
522 call generic_patch_pageops
523 nop
524
525 ba,a,pt %xcc, 80f
526 niagara2_patch:
527 call niagara2_patch_copyops
528 nop
529 call niagara_patch_bzero
530 nop
531 call niagara2_patch_pageops
532 nop
第527行,调用 niagara2_patch_copyops函数执行,我们看一看这个函数的定义:
26 .type niagara2_patch_copyops,#function
27 niagara2_patch_copyops:
28 NG_DO_PATCH(memcpy, NG2memcpy)
29 NG_DO_PATCH(___copy_from_user, NG2copy_from_user)
30 NG_DO_PATCH(___copy_to_user, NG2copy_to_user)
31 retl
32 nop
第26,27行,是定义,就不用管了
第28,29,30行,有同样的形式,我们就只分析其中的一行,这3行就是这个函数所能 完成的主要功能
第31行,返回
我们看看28,29,30行所用的宏 NG_DO_PATCH,定义如下:
6 #define BRANCH_ALWAYS 0x10680000
7 #define NOP 0x01000000
8 #define NG_DO_PATCH(OLD, NEW) \
9 sethi %hi(NEW), %g1; \
10 or %g1, %lo(NEW), %g1; \
11 sethi %hi(OLD), %g2; \
12 or %g2, %lo(OLD), %g2; \
13 sub %g1, %g2, %g1; \
14 sethi %hi(BRANCH_ALWAYS), %g3; \
15 sll %g1, 11, %g1; \
16 srl %g1, 11 + 2, %g1; \
17 or %g3, %lo(BRANCH_ALWAYS), %g3; \
18 or %g3, %g1, %g3; \
19 stw %g3, [%g2]; \
20 sethi %hi(NOP), %g3; \
21 or %g3, %lo(NOP), %g3; \
22 stw %g3, [%g2 + 0x4]; \
23 flush %g2;
第9,10行,把传入的参数NEW放到g1中
第11,12行,把传入的参数OLD放到g2中
第13行,前面的参数NEW,OLD实际上都是相关函数在内存中的逻辑地址值,所以这 里就是计算出,NEW相对与OLD的偏移
第14行,把 BRANCH_ALWAYS放到g3中, BRANCH_ALWAYS宏在上面有定义, 它实际上就是无条件跳转指令的操作码
第15,16行,把g1,即函数NEW的地址右移2位放在g1中
第17行, 把 BRANCH_ALWAYS放到g3中
第18行,g1|g3>g3中,这样在g3中就放了一条完整的指令了,该指令可以表示成如下 汇编指令:
ba, %xcc NEW
第19行,把这条跳转指令放到函数OLD的第一条指令的地址中去
第20到22行,把nop指令放到OLD的第二指令的地址中取,这实际上是第一条跳转指 令的命令槽
第23行,指令流控制
经过这样的替换后,当调用OLD函数时,就会跳转到NEW函数处执行,即相当于用NEW函数OLD函数打patch,这样这段汇编的功能就是根据 sun4v_chip_type的类型 给memcpy,___copy_from_user,___copy_to_user 3个函数打上相应的patch
534 ba,a,pt %xcc, 80f
跳转到80处执
536 niagara_patch:
537 call niagara_patch_copyops
538 nop
539 call niagara_patch_bzero
540 nop
541 call niagara_patch_pageops
542 nop
543
544 80:
545 /* Patch TLB/cache ops. */
546 call hypervisor_patch_cachetlbops
547 nop
第546行,也是根据类型打patch就不分析了,上面是给内存拷贝操作打patch,这里是给TLB和cache相关的操作打patch
549 ba,pt %xcc, tlb_fixup_done
550 nop
第549行,跳转到 tlb_fixup_done处执行,我们直接到哪里去看
552 cheetah_tlb_fixup:
553 mov 2, %g2 /* Set TLB type to cheetah+.
554 BRANCH_IF_CHEETAH_PLUS_OR_FOLLOWON(g1,g7,1f)
555
556 mov 1, %g2 /* Set TLB type to cheetah. */
557
558 1: sethi %hi(tlb_type), %g1
559 stw %g2, [%g1 + %lo(tlb_type)]
560
561 /* Patch copy/page operations to cheetah optimized versions. */
562 call cheetah_patch_copyops
563 nop
564 call cheetah_patch_copy_page
565 nop
566 call cheetah_patch_cachetlbops
567 nop
568
569 ba,pt %xcc, tlb_fixup_done
570 nop
571
572 spitfire_tlb_fixup:
573 /* Set TLB type to spitfire. */
574 mov 0, %g2
575 sethi %hi(tlb_type), %g1
576 stw %g2, [%g1 + %lo(tlb_type)]
577
578 tlb_fixup_done:
579 sethi %hi(init_thread_union), %g6
580 or %g6, %lo(init_thread_union), %g6
581 ldx [%g6 + TI_TASK], %g4
582 mov %sp, %l6
第579,580行,把init_thread_union变量的地址放到g6中,该结构定义如下:
union thread_union init_thread_union __init_task_data =
{ INIT_THREAD_INFO(init_task) };
该定义表明它是一个union thread_union共用体类型的变量,这个共用体的变量定义如下:
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
如果你对Linux的进程熟悉的话,就不会对这个变量感到陌生,Linux中每个进程都有一个描述符struct task_struct结构体类型的变量,每个进程还有一个保存与进程相关信息的变量 struct thread_info结构体类型的变量,该结构体的第一个成员就指向进程的进程描述符,每个进程的 struct thread_info都与进程的内核态堆栈放在一起,就是这里的 union thread_union共用体,这样做的好处是可以根据堆栈指针很容易的找到 struct thread_info变量,从而很容易的找到进程描述符号。
这里的 init_thread_union已经被初始化了,它的第一个成员指向init_task,init_task就是系统的第一个进程,也就是俗称的0号进程的进程描述符。
第581行,取出g6中即0号进程的struct thread_info结构的 TI_TASK位置的值到g4中,实际上 TI_TASK的值是0,所以这里就是把0号进程的进程描述符的地址存放在g4中
第582行,把sp的值保存到l6中,注意在此之前sp的值从来没有更新过,一直指向prom代码执行时的堆栈
584 wr %g0, ASI_P, %asi
585 mov 1, %g1
586 sllx %g1, THREAD_SHIFT, %g1
587 sub %g1, (STACKFRAME_SZ + STACK_BIAS), %g1
588 add %g6, %g1, %sp
589 mov 0, %fp
第584行,写ASI_P的值存入asi寄存器中,ASI_P地址空间标志符实际上就是指正常的虚拟地址空间
第585行,在g1寄存器中存入1
第586行,1左移 THREAD_SHIFT存入g1中,这样实际上g1中存放的就是进程的内核堆栈的大小,我们这里是8K
第587,588行,在前面一段汇编中,g6中存放的是0号进程的struct thread_info结构的地址,实际上就是0号进程的内核堆栈的基地址。这里就是更新堆栈指针sp,使之指向内核堆栈中,至于指向哪个位置,就要对内核堆栈的结构有所了解了,这里就不分析了
591 /* Set per-cpu pointer initially to zero, this makes
592 * the boot-cpu use the in-kernel-image per-cpu areas
593 * before setup_per_cpu_area() is invoked.
594 */
595 clr %g5
596
597 wrpr %g0, 0, %wstate
598 wrpr %g0, 0x0, %tl
第595行,清g5寄存器
第597行,清wstate寄存器
第598行,清tl寄存器
600 /* Clear the bss */
601 sethi %hi(__bss_start), %o0
602 or %o0, %lo(__bss_start), %o0
603 sethi %hi(_end), %o1
604 or %o1, %lo(_end), %o1
605 call __bzero
606 sub %o1, %o0, %o1
第601,602行,把 __bss_start存入参数0中
第603,604行,把_end的值存入参数1中
第605行,调用函数__bzero
第606行,经过计算,__bzero参数1中实际上存放的是bss段的长度,当然参数0中则是存放的bss段的基地址了
bss段是未初始化数据段,里面存放的是内核镜像的未初始化数据
这段汇编的作用就是把bss段清0
608 #ifdef CONFIG_LOCKDEP
609 /* We have this call this super early, as even prom_init can grab
610 * spinlocks and thus call into the lockdep code.
611 */
612 call lockdep_init
613 nop
614 #endif
上面这段是初始化lockdep,lockdep机制我不了解,所以这段就略过把
616 mov %l6, %o1 ! OpenPROM stack
617 call prom_init
618 mov %l7, %o0 ! OpenPROM cif handler
第616行,把l6的值存入参数1中,前面第582行,把prom堆栈的指针存入了l6中,这里把它作为参数传入prom_init
第617行,调用prom_init
第618行,把l7中的值作为prom_init的参数0,还记得最开始的时候吗,l7中存放的是内核调用固件服务的接口。
在内核初始化阶段,汇编函数执行完后进入c语言函数,可能还会调用固件提供的接口,倒时候其实还是调用这里使用的接口函数和prom堆栈,所以这里要把他们使用一种机制保存起来形成一个c语言函数调用固件服务的接口,这里就是初始化这个接口的。
620 /* Initialize current_thread_info()->cpu as early as possible.
621 * In order to do that accurately we have to patch up the get_cpuid()
622 * assembler sequences. And that, in turn, requires that we know
623 * if we are on a Starfire box or not. While we're here, patch up
624 * the sun4v sequences as well.
625 */
626 call check_if_starfire
627 nop
628 call per_cpu_patch
629 nop
630 call sun4v_patch
631 nop
第626行,调用check_if_starfire判断我们 if we are on a Starfire box,starfile是干什么的我不知道,但是我可以确定我们不属于starfire,因此我们不必关心它
第628行,per_cpu_patch是一个c语言函数,实际上就是感觉sun4v_chip_type和tlb_tpye来使用相应的__GET_CPUID宏,也就是得到当前cpu的id号,即打与cpuid相关的patch
第630行,同样是一个C语言函数,打与sun4v相关的patch
633 #ifdef CONFIG_SMP
634 call hard_smp_processor_id
635 nop
636 cmp %o0, NR_CPUS
637 blu,pt %xcc, 1f
638 nop
639 call boot_cpu_id_too_large
640 nop
641 /* Not reached... */
第634行,调用hard_smp_processor_id,这个函数比较简单,代码就不贴了,它实际上就是调用__GET_CPUID宏得到当前的cpuid
第636行,就是比较当前的cpuid和系统中允许的最大cpu数量
第637行,如果正常,即当前cpuid小于NR_CPUS,则跳到1处执行,显然我们这里会跳转
第639行,如果不正常,则进行一定处理然后退出
643 1:
644 #else
645 mov 0, %o0
646 #endif
647 sth %o0, [%g6 + TI_CPU]
第646行,到这里说明得到了得到了正确的cpuid,把该cpuid存入当前进程的struct thread_info结构的cpu字段中,代表当前进程在哪个cpu上执行
649 call prom_init_report
650 nop
这一段调用prom_init_report,实际上就是打印一些与版本相关的信息
652 /* Off we go.... */
653 call start_kernel
654 nop\
655 /* Not reached... */
656
657 .previous
终于到了最后,调用start_kernel进入c语言执行