spark架构代码分析-head.s启动代码分析

基本介绍

        这是几年前在做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语言执行

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值