在arm平台学习linux时,会遇到arm汇编指令,arm汇编指令与8086汇编指令很多地方都不同,在此记下来以免后面忘了,同时在学习了汇编指令之后分析一些汇编指令编写的代码。
一、相对跳转指令b、bl
b、bl指令都实现短跳转,bl指令执行后会在链接寄存器r14中保存下一条指令的地址。
二、数据传送指令mov
mov指令会把一个寄存器的数赋值给另一个寄存器,或者把一个常数传递给另一个寄存器。
如:mov r0,r1 //将r1中的值传递给r0,mov r0,#0xff //将常数0xff传递给r0寄存器。
mov指令传递的常数必须能够用立即数表示,当不知道一个数是否能够用“立即数传递”时,可以用ldr指令进行传递。
如:ldr r0,=0xff。
三、内存访问指令str、ldr、ldm、stm
ldr指令从内存中读取数到寄存器中,str指令将寄存器中的数传递到内存中,ldr、str指令操作的数都是32位的。ldm、stm是批量内存访问指令,用一个指令就能访问多个数据。下面是从s3c2440 datasheet上面的截图
{cond}表示指令的执行条件
Rn中保存内存地址,如果后面有!指令执行后会更新为下一个内存单元的地址
<Rlist>寄存器列表对于ldm指令相当于将内存的数据取出放入列表中的寄存器中,stm指令相当于将列表中的寄存器中的值放入内存中。
{^}有两种含义:如果<Rlist>有PC寄存器时,它表示指令执行后,spsr寄存器的值会自动复制cpsr寄存器中,这个常用于从中断处理函数中返回。如果<Rlist>中没有PC寄存器时,{^}表示操作的是用户模式下的寄存器,不是特权模式下的寄存器。
指令中列表中的寄存器与内存对应关系为:编号底的寄存器对应低地址内存单元,编号搞的对应高地址内存单元。
四、加减指令add、sub
如:add r0,r1,#0xff //r0=r1+0xff sub r0,r1,#0xff //表示r0=r1-0xff
五、程序状态寄存器访问指令msr、mrs
arm有个程序状态寄存器cpsr,它用来控制处理器的工作模式和设置中断的总开关。
msr cpsr,r0 //复制r0到cpsr中
mrs r0,cpsr //复制cpsr到r0中
六、伪指令
.gloabl _start
_start:
.text
.extern main
.gloabl将本文件中的某个程序定义为全局的
.extern 将某个变量或者函数引用到本文件中
.text 表示下面的语句都属于代码段
八、uboot启动过程分析
在看了韦东山老师的书和视频后,对汇编指令及bootloader的工作流程有了一个新的认识,我们接触比较的bootloader就是电脑的bios了,bootloader就是一段将我们硬盘上的代码搬运到内存中指定位置运行的程序。
在说硬盘内存这些概念时我们首先要对s3c24xx或者其他的微处理器的存储空间有一个大致的了解。我做实验主要用的是2440,下面也就2440进行说明。在2440中我们一般使用三种存储设备:SDRAM、Nandflash、Norflash。这三个存储设备相对于我们平常的PC就是:SDRAM====>内存条,(Nandflash、Norflash)====>硬盘,对于Nandflash和Norflash的区别主要是前者不能直接运行代码,后者代码可以直接运行但是不能进行写数据,所以我们常常将Nandflash 存放程序,Norflash存放数据。
对于存储空间2440有一个专门的存储控制器,
一般我们把Norflash、Nandflash都与nGCS0相连,然后通过选择OM0、OM1选择哪种启动方式,如果选择Norflash我们的代码就会从Norflash的0地址开始执行,如果选择Nandflash我们的代码会被处理器将Nandflash的前4k拷贝到芯片内部的4k RAM中,然后程序在内部4k RAM中开始执行,这个拷贝过程是一个硬件过程,芯片内部自动完成。由于只有4k大小如果我们的程序小于4k,我们就直接让其在芯片内部RAM中执行,如果我们的代码大于4k,一般我们会用4k大小的程序将Nandflash里面的其余部分程序直接拷贝到SDRAM(0x3000 0000)中执行。
我们的bootloader肯定是大于4k的,说以如果在Nandflash中运行我们就必须在4k 大小的代码中将bootloader拷贝到SDRAM,然后在SDRAM中将Nandflash 中的Kernerl拷贝到SDRAM中完成内核的启动,大的流程就如前面所说,但是具体在实现的过程中有很多的小细节。
8.1 bootloader启动前的准备
1、关看门狗
2、设置时钟
3、初始化SDRAM
4、重定位将bootloader的代码从Nandflash中拷贝到SDRAM中
5、执行main
====》在main函数中所做的工作
1、初始化串口(因为要加载内核需要打印一些信息,内核没有串口初始化的代码所以需要在内核启动前进行初始化)
2、从Nandflash中将内核读入SDRAM
3、设置参数(这个工作非常重要,因为内核在启动过程中需要设置启动参数比如:设置内存标记(内存的起始地址、内存大小)、设置命令行标记(命令行就是一个字符串,用于控制内核的一些行为。比如"noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0" 表示根文件系统在MTD3分区上,启动系统后执行的第一个程序是linuxrc,控制台为ttySAC0(第一个串口)))。在启动时bootloader与Kernel的交互式单项的所以只能是在bootloader启动阶段将我们用于设置内核的参数放在内存的某个地方,bootloader与Kernel约定好内核启动时就在这个地方去读取这些配置参数完成启动,一般这些参数存放在SDRAM 0x3100 0000地址上。
1 /* 3. 跳转执行 */ 2 puts("Boot kernel "); 3 theKernel = (void (*)(int, int, unsigned int))0x30008000; 4 theKernel(0, 362, 0x30000100); 5 /* 6 * mov r0, #0 7 * ldr r1, =362 8 * ldr r2, =0x30000100 9 * mov pc, #0x30008000 10 */
第3行代码就是将函数指针的首地址放到内核存放的内存首地址中去执行,下面的参数因为是函数调用所以会把我们上面说的bootloader与Kernel交互的设置参数保存在内存中的地址0x3000 0100放在r2寄存器中,内核执行时会从r2寄存器中取出地址然后找到我们在bootloader启动阶段存放的设置参数,这个过程相当的巧妙,主要是利用了函数调用时参数一般会保存在r0-r3,被调用的子程序返回前无需回复r0-r3的内容。
参考主要代码:
1、bootloader启动汇编部分
1 #define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02)) 2 #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) 3 #define MEM_CTL_BASE 0x48000000 4 5 .text 6 .global _start 7 _start: 8 9 /* 1. 关看门狗 */ 10 ldr r0, =0x53000000 11 mov r1, #0 12 str r1, [r0] 13 14 /* 2. 设置时钟 */ 15 ldr r0, =0x4c000014 16 // mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1 17 mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8 18 str r1, [r0] 19 20 /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */ 21 mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */ 22 orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */ 23 mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */ 24 25 /* MPLLCON = S3C2440_MPLL_200MHZ */ 26 ldr r0, =0x4c000004 27 ldr r1, =S3C2440_MPLL_400MHZ 28 str r1, [r0] 29 30 /* 启动ICACHE */ 31 mrc p15, 0, r0, c1, c0, 0 @ read control reg 32 orr r0, r0, #(1<<12) 33 mcr p15, 0, r0, c1, c0, 0 @ write it back 34 35 36 /* 3. 初始化SDRAM */ 37 ldr r0, =MEM_CTL_BASE 38 adr r1, sdram_config /* sdram_config的当前地址 */ 39 add r3, r0, #(13*4) 40 1: 41 ldr r2, [r1], #4 42 str r2, [r0], #4 43 cmp r0, r3 44 bne 1b 45 46 /* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */ 47 ldr sp, =0x34000000 48 49 bl nand_init 50 51 mov r0, #0 52 ldr r1, =_start 53 ldr r2, =__bss_start 54 sub r2, r2, r1 55 56 bl copy_code_to_sdram 57 bl clear_bss 58 59 /* 5. 执行main */ 60 ldr lr, =halt 61 ldr pc, =main 62 halt: 63 b halt 64 65 sdram_config: 66 .long 0x22011110 //BWSCON 67 .long 0x00000700 //BANKCON0 68 .long 0x00000700 //BANKCON1 69 .long 0x00000700 //BANKCON2 70 .long 0x00000700 //BANKCON3 71 .long 0x00000700 //BANKCON4 72 .long 0x00000700 //BANKCON5 73 .long 0x00018005 //BANKCON6 74 .long 0x00018005 //BANKCON7 75 .long 0x008C04F4 // REFRESH 76 .long 0x000000B1 //BANKSIZE 77 .long 0x00000030 //MRSRB6 78 .long 0x00000030 //MRSRB7
2、main函数部分
1 #include "setup.h" 2 3 extern void uart0_init(void); 4 extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len); 5 extern void puts(char *str); 6 extern void puthex(unsigned int val); 7 8 9 static struct tag *params; 10 11 void setup_start_tag(void) 12 { 13 params = (struct tag *)0x30000100; 14 15 params->hdr.tag = ATAG_CORE; 16 params->hdr.size = tag_size (tag_core); 17 18 params->u.core.flags = 0; 19 params->u.core.pagesize = 0; 20 params->u.core.rootdev = 0; 21 22 params = tag_next (params); 23 } 24 25 void setup_memory_tags(void) 26 { 27 params->hdr.tag = ATAG_MEM; 28 params->hdr.size = tag_size (tag_mem32); 29 30 params->u.mem.start = 0x30000000; 31 params->u.mem.size = 64*1024*1024; 32 33 params = tag_next (params); 34 } 35 36 int strlen(char *str) 37 { 38 int i = 0; 39 while (str[i]) 40 { 41 i++; 42 } 43 return i; 44 } 45 46 void strcpy(char *dest, char *src) 47 { 48 while ((*dest++ = *src++) != '