在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地址上。
/* 3. 跳转执行 */
puts("Boot kernelnr");
theKernel = (void (*)(int, int, unsigned int))0x30008000;
theKernel(0, 362, 0x30000100);
/*
* mov r0, #0
* ldr r1, =362
* ldr r2, =0x30000100
* mov pc, #0x30008000
*/
第3行代码就是将函数指针的首地址放到内核存放的内存首地址中去执行,下面的参数因为是函数调用所以会把我们上面说的bootloader与Kernel交互的设置参数保存在内存中的地址0x3000 0100放在r2寄存器中,内核执行时会从r2寄存器中取出地址然后找到我们在bootloader启动阶段存放的设置参数,这个过程相当的巧妙,主要是利用了函数调用时参数一般会保存在r0-r3,被调用的子程序返回前无需回复r0-r3的内容。
参考主要代码:
1、bootloader启动汇编部分
.text
.global _start
_start:
/* 1. 关看门狗 */
ldr r0, =0x53000000
mov r1,
str r1, [r0]
/* 2. 设置时钟 */
ldr r0, =0x4c000014
// mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
mov r1,
str r1, [r0]
/* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
orr r1, r1,
mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
/* MPLLCON = S3C2440_MPLL_200MHZ */
ldr r0, =0x4c000004
ldr r1, =S3C2440_MPLL_400MHZ
str r1, [r0]
/* 启动ICACHE */
mrc p15, 0, r0, c1, c0, 0 @ read control reg
orr r0, r0,
mcr p15, 0, r0, c1, c0, 0 @ write it back
/* 3. 初始化SDRAM */
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config /* sdram_config的当前地址 */
add r3, r0,
1:
ldr r2, [r1],
str r2, [r0],
cmp r0, r3
bne 1b
/* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */
ldr sp, =0x34000000
bl nand_init
mov r0,
ldr r1, =_start
ldr r2, =__bss_start
sub r2, r2, r1
bl copy_code_to_sdram
bl clear_bss
/* 5. 执行main */
ldr lr, =halt
ldr pc, =main
halt:
b halt
sdram_config:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3
.long 0x00000700 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 // REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
2、main函数部分
extern void uart0_init(void);
extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
extern void puts(char *str);
extern void puthex(unsigned int val);
static struct tag *params;
void setup_start_tag(void)
{
params = (struct tag *)0x30000100;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
void setup_memory_tags(void)
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = 0x30000000;
params->u.mem.size = 64*1024*1024;
params = tag_next (params);
}
int strlen(char *str)
{
int i = 0;
while (str[i])
{
i++;
}
return i;
}
void strcpy(char *dest, char *src)
{
while ((*dest++ = *src++) != ' ');
}
void setup_commandline_tag(char *cmdline)
{
int len = strlen(cmdline) + 1;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;
strcpy (params->u.cmdline.cmdline, cmdline);
params = tag_next (params);
}
void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
int main(void)
{
void (*theKernel)(int zero, int arch, unsigned int params);
volatile unsigned int *p = (volatile unsigned int *)0x30008000;
/* 0. 帮内核设置串口: 内核启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 */
uart0_init();
/* 1. 从NAND FLASH里把内核读入内存 */
puts("Copy kernel from nandnr");
nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
puthex(0x1234ABCD);
puts("nr");
puthex(*p);
puts("nr");
/* 2. 设置参数 */
puts("Set boot paramsnr");
setup_start_tag();
setup_memory_tags();
setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
setup_end_tag();
/* 3. 跳转执行 */
puts("Boot kernelnr");
theKernel = (void (*)(int, int, unsigned int))0x30008000;
theKernel(0, 362, 0x30000100);
/*
* mov r0, #0
* ldr r1, =362
* ldr r2, =0x30000100
* mov pc, #0x30008000
*/
puts("Error!nr");
/* 如果一切正常, 不会执行到这里 */
return -1;
}
3、对设备初始化部分(Nandflash、Uart等)
/* NAND FLASH控制器 */
/* GPIO */
/* UART registers*/
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)0;
int val;
val = *p;
*p = 0x12345678;
if (*p == 0x12345678)
{
/* 写成功, 是nand启动 */
*p = val;
return 0;
}
else
{
/* NOR不能像内存一样写 */
return 1;
}
}
void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{
int i = 0;
/* 如果是NOR启动 */
if (isBootFromNorFlash())
{
while (i < len)
{
dest[i] = src[i];
i++;
}
}
else
{
//nand_init();
nand_read((unsigned int)src, dest, len);
}
}
void clear_bss(void)
{
extern int __bss_start, __bss_end;
int *p = &__bss_start;
for (; p < &__bss_end; p++)
*p = 0;
}
void nand_init(void)
{
/* 设置时序 */
NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
/* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
}
void nand_select(void)
{
NFCONT &= ~(1<<1);
}
void nand_deselect(void)
{
NFCONT |= (1<<1);
}
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for (i = 0; i < 10; i++);
}
void nand_addr(unsigned int addr)
{
unsigned int col = addr % 2048;
unsigned int page = addr / 2048;
volatile int i;
NFADDR = col & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (col >> 8) & 0xff;
for (i = 0; i < 10; i++);
NFADDR = page & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (page >> 8) & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (page >> 16) & 0xff;
for (i = 0; i < 10; i++);
}
void nand_wait_ready(void)
{
while (!(NFSTAT & 1));
}
unsigned char nand_data(void)
{
return NFDATA;
}
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
int col = addr % 2048;
int i = 0;
/* 1. 选中 */
nand_select();
while (i < len)
{
/* 2. 发出读命令00h */
nand_cmd(0x00);
/* 3. 发出地址(分5步发出) */
nand_addr(addr);
/* 4. 发出读命令30h */
nand_cmd(0x30);
/* 5. 判断状态 */
nand_wait_ready();
/* 6. 读数据 */
for (; (col < 2048) && (i < len); col++)
{
buf[i] = nand_data();
i++;
addr++;
}
col = 0;
}
/* 7. 取消选中 */
nand_deselect();
}
/*
* 初始化UART0
* 115200,8N1,无流控
*/
void uart0_init(void)
{
GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
GPHUP = 0x0c; // GPH2,GPH3内部上拉
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UFCON0 = 0x00; // 不使用FIFO
UMCON0 = 0x00; // 不使用流控
UBRDIV0 = UART_BRD; // 波特率为115200
}
/*
* 发送一个字符
*/
void putc(unsigned char c)
{
/* 等待,直到发送缓冲区中的数据已经全部发送出去 */
while (!(UTRSTAT0 & TXD0READY));
/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
UTXH0 = c;
}
void puts(char *str)
{
int i = 0;
while (str[i])
{
putc(str[i]);
i++;
}
}
void puthex(unsigned int val)
{
/* 0x1234abcd */
int i;
int j;
puts("0x");
for (i = 0; i < 8; i++)
{
j = (val >> ((7-i)*4)) & 0xf;
if ((j >= 0) && (j <= 9))
putc('0' + j);
else
putc('A' + j - 0xa);
}
}
4、寄存器地址设置、内核标记列表数据结构实现等
/*
* linux/include/asm/setup.h
*
* Copyright (C) 1997-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Structure passed to kernel to tell it about the
* hardware it's running on. See linux/Documentation/arm/Setup
* for more info.
*
* NOTE:
* This file contains two ways to pass information from the boot
* loader to the kernel. The old struct param_struct is deprecated,
* but it will be kept in the kernel for 5 years from now
* (2001). This will allow boot loaders to convert to the new struct
* tag way.
*/
/*
* Usage:
* - do not go blindly adding fields, add them at the end
* - when adding fields, don't rely on the address until
* a patch from me has been released
* - unused fields should be zero (for future expansion)
* - this structure is relatively short-lived - only
* guaranteed to contain useful data in setup_arch()
*/
/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
union {
struct {
unsigned long page_size; /* 0 */
unsigned long nr_pages; /* 4 */
unsigned long ramdisk_size; /* 8 */
unsigned long flags; /* 12 */
unsigned long rootdev; /* 16 */
unsigned long video_num_cols; /* 20 */
unsigned long video_num_rows; /* 24 */
unsigned long video_x; /* 28 */
unsigned long video_y; /* 32 */
unsigned long memc_control_reg; /* 36 */
unsigned char sounddefault; /* 40 */
unsigned char adfsdrives; /* 41 */
unsigned char bytes_per_char_h; /* 42 */
unsigned char bytes_per_char_v; /* 43 */
unsigned long pages_in_bank[4]; /* 44 */
unsigned long pages_in_vram; /* 60 */
unsigned long initrd_start; /* 64 */
unsigned long initrd_size; /* 68 */
unsigned long rd_start; /* 72 */
unsigned long system_rev; /* 76 */
unsigned long system_serial_low; /* 80 */
unsigned long system_serial_high; /* 84 */
unsigned long mem_fclk_21285; /* 88 */
} s;
char unused[256];
} u1;
union {
char paths[8][128];
struct {
unsigned long magic;
char n[1024 - sizeof(unsigned long)];
} s;
} u2;
char commandline[COMMAND_LINE_SIZE];
};
/*
* The new way of passing information: a list of tagged entries
*/
/* The list ends with an ATAG_NONE node. */
struct tag_header {
u32 size;
u32 tag;
};
/* The list must start with an ATAG_CORE node */
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
/* it is allowed to have multiple ATAG_MEM nodes */
struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};
/* VGA text type displays */
struct tag_videotext {
u8 x;
u8 y;
u16 video_page;
u8 video_mode;
u8 video_cols;
u16 video_ega_bx;
u8 video_lines;
u8 video_isvga;
u16 video_points;
};
/* describes how the ramdisk will be used in kernel */
struct tag_ramdisk {
u32 flags; /* bit 0 = load, bit 1 = prompt */
u32 size; /* decompressed ramdisk size in _kilo_ bytes */
u32 start; /* starting block of floppy-based RAM disk image */
};
/* describes where the compressed ramdisk image lives (virtual address) */
/*
* this one accidentally used virtual addresses - as such,
* its depreciated.
*/
/* describes where the compressed ramdisk image lives (physical address) */
struct tag_initrd {
u32 start; /* physical start address */
u32 size; /* size of compressed ramdisk image in bytes */
};
/* board serial number. "64 bits should be enough for everybody" */
struct tag_serialnr {
u32 low;
u32 high;
};
/* board revision */
struct tag_revision {
u32 rev;
};
/* initial values for vesafb-type framebuffers. see struct screen_info
* in include/linux/tty.h
*/
struct tag_videolfb {
u16 lfb_width;
u16 lfb_height;
u16 lfb_depth;
u16 lfb_linelength;
u32 lfb_base;
u32 lfb_size;
u8 red_size;
u8 red_pos;
u8 green_size;
u8 green_pos;
u8 blue_size;
u8 blue_pos;
u8 rsvd_size;
u8 rsvd_pos;
};
/* command line: terminated string */
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
/* acorn RiscPC specific information */
struct tag_acorn {
u32 memc_control_reg;
u32 vram_pages;
u8 sounddefault;
u8 adfsdrives;
};
/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
struct tag_memclk {
u32 fmemclk;
};
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
struct tagtable {
u32 tag;
int (*parse)(const struct tag *);
};
((unsigned long)(&((struct tag *)0L)->member + 1)
for (t = base; t->hdr.size; t = tag_next(t))
/*
* Memory map description
*/
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
extern struct meminfo meminfo;
原文始发于微信公众号(汇编语言):linux驱动系列之arm汇编
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论