一、 裸機程序的構成
1. 基本的裸機程序由啓動代碼和C函數文件構成。而啓動代碼包括:硬件設備初始化、調用C函數。
本次分析中代碼文件有:
start.S 啓動代碼,都是彙編寫的
commom.h 一些通用的函數,比如設置某寄存器的某位爲1或0
irq.c 中斷初始化,中斷處理等
regs.h 6410的寄存器地址,需要哪些寄存器可以在本文件中聲明和定義
sdram.c 有關sdram的一些操作,如sdram初始化等
time.c 系統時鐘的有關設置,如PLLclock等
led.c 這個就是主函數了,主程序就在這裏編寫,本次只是演示,控制開發板的led燈循環點亮,也就是流水燈
led.lds 該文件爲鏈接腳本,描述了各個輸入文件的各個section如何映射到輸出文件的各section中,並控制輸出文件中section和符號的內存佈局。
Makefile 這個文件就不用說了吧。。。
1.1 學習啓動代碼有助於我們以後開發uboot,uboot的啓動代碼跟裸機的差不多。
下面把start.S代碼貼出來,其中代碼中也有註釋。
@************************************** @ File: start.S @ Function: cpu initial and jump to c program @ author: lixiaoming @ time: 2012/7/27 21:40 @************************************** .extern main .text .global _start _start: b reset @ when reset, cpu jump to 0 address b halt @ldr pc, _undefined_instruction b halt @ldr pc, _software_interrupt b halt @ldr pc, _prefetch_abort b halt @ldr pc, _data_abort b halt @ldr pc, _not_used ldr pc, _irq b halt @ldr pc, _fiq _irq: .word vector_irq vector_irq: ldr sp, = 0x54000000 @ save location sub lr, lr, #4 stmdb sp!, {r0-r12, lr} bl do_irq @ deal with exception @ backing out ldmia sp!, {r0-r12, pc}^ reset: ldr r0, = 0x70000000 @ Peripheral port base address orr r0, r0, #0x13 mcr p15,0,r0,c15,c2,4 @ 256M ldr r0, = 0x7e004000 @ watchdog registeraddress mov r1, #0x0 str r1, [r0] @ write 0, disable watchdog ldr sp, =1024*8 @ set stack, notice: can't larger than 8K bl clock_init @ system clock initial bl sdram_init @ sdram initial adr r0, _start @ get _start's current address: 0 ldr r1, = _start @ _start's link address ldr r2, = bss_start @ bss section's begining link address cmp r0, r1 beq clean_bss
copy_loop: ldr r3, [r0], #4 str r3, [r1], #4 cmp r1, r2 bne copy_loop
@mrs r0, cpsr bic r0, r0, #0x9f orr r0, r0, #0x10 msr cpsr, r0 @ enter user mode ldr sp, = 0x57000000 @bl main @ call c program's main function
ldr pc, = main halt: b halt
#ifndef __COMMON_H #define __COMMON_H #define vi *( volatile unsigned int * ) #define set_zero( addr, bit ) ( (vi addr) &= ( ~ ( 1 << (bit) ) ) ) #define set_one( addr, bit ) ( (vi addr) |= ( 1 << ( bit ) ) ) #define set_bit( addr, bit, val ) ( (vi addr) = (( vi addr)&=(~(1<<(bit))) ) | ( (val)<<(bit) ) ) #define set_2bit( addr, bit, val ) ( (vi addr) = (( vi addr)&(~(3<<(bit))) ) | ( (val)<<(bit) ) ) #define set_nbit( addr, bit, len, val ) \ ( (vi addr) = ((( vi addr)&(~(( ((1<<(len))-1) )<<(bit)))) | ( (val)<<(bit) ) )) #define get_bit( addr, bit ) ( (( vi addr ) & ( 1 << (bit) )) > 0 ) #define get_val( addr, val ) ( (val) = vi addr ) #define read_val( addr ) ( vi ( addr ) ) #define set_val( addr, val ) ( (vi addr) = (val) ) #define or_val( addr, val ) ( (vi addr) |= (val) ) /////////////////////////////// typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; // function declare int delay( int ); #endif
#include "common.h" #define MEMCCMD 0x7e001004 #define P1REFRESH 0x7e001010 #define P1CASLAT 0x7e001014 #define MEM_SYS_CFG 0x7e00f120 #define P1MEMCFG 0x7e00100c #define P1T_DQSS 0x7e001018 #define P1T_MRD 0x7e00101c #define P1T_RAS 0x7e001020 #define P1T_RC 0x7e001024 #define P1T_RCD 0x7e001028 #define P1T_RFC 0x7e00102c #define P1T_RP 0x7e001030 #define P1T_RRD 0x7e001034 #define P1T_WR 0x7e001038 #define P1T_WTR 0x7e00103c #define P1T_XP 0x7e001040 #define P1T_XSR 0x7e001044 #define P1T_ESR 0x7e001048 #define P1MEMCFG2 0X7e00104c #define P1_chip_0_cfg 0x7e001200 #define P1MEMSTAT 0x7e001000 #define P1MEMCCMD 0x7e001004 #define P1DIRECTCMD 0x7e001008 #define HCLK 133000000 #define nstoclk(ns) (ns/( 1000000000/HCLK)+1) void sdram_init( void ) { // tell dramc to configureset_val(MEMCCMD, 0x4 ); // set refresh period set_val( P1REFRESH, nstoclk(7800) ); // set timing paraset_val( P1CASLAT, ( 3 << 1 ) ); set_val( P1T_DQSS, 0x1 ); // 0.75 - 1.25 set_val( P1T_MRD, 0x2 ); set_val( P1T_RAS, nstoclk(45) ); set_val( P1T_RC, nstoclk(68) ); u32 trcd = nstoclk( 23 ); set_val( P1T_RCD, trcd | (( trcd - 3 ) << 3 ) ); u32 trfc = nstoclk( 80 ); set_val( P1T_RFC, trfc | ( ( trfc-3 ) << 5 ) ); u32 trp = nstoclk( 23 ); set_val( P1T_RP, trp | ( ( trp - 3 ) << 3 ) ); set_val( P1T_RRD, nstoclk(15) ); set_val( P1T_WR, nstoclk(15) ); set_val( P1T_WTR, 0x7 ); set_val( P1T_XP, 0x2 ); set_val( P1T_XSR, nstoclk(120) ); set_val( P1T_ESR, nstoclk(120) ); // set mem cfg set_nbit( P1MEMCFG, 0, 3, 0x2 ); set_nbit( P1MEMCFG, 3, 3, 0x2 ); set_zero( P1MEMCFG, 6 ); set_nbit( P1MEMCFG, 15, 3, 0x2 ); set_nbit( P1MEMCFG2, 0, 4, 0x5 ); set_2bit( P1MEMCFG2, 6, 0x1 ); set_nbit( P1MEMCFG2, 8, 3, 0x3 ); set_2bit( P1MEMCFG2, 11, 0x1 ); set_one( P1_chip_0_cfg, 16 ); // memory init set_val( P1DIRECTCMD, 0xc0000 ); // NOP set_val( P1DIRECTCMD, 0x000 ); // precharge set_val( P1DIRECTCMD, 0x40000 );// auto refresh set_val( P1DIRECTCMD, 0x40000 );// auto refresh set_val( P1DIRECTCMD, 0xa0000 ); // EMRS set_val( P1DIRECTCMD, 0x80032 ); // MRS set_val( MEM_SYS_CFG, 0x0 ); // set dramc to "go" status set_val( P1MEMCCMD, 0x000 ); // wait ready while( !(( read_val( P1MEMSTAT ) & 0x3 ) == 0x1)); }
#define APLL_LOCK (*((volatile unsigned long *)0x7E00F000)) #define MPLL_LOCK (*((volatileunsigned long *)0x7E00F004)) #define EPLL_LOCK (*((volatile unsigned long *)0x7E00F008)) #define OTHERS (*((volatile unsigned long *)0x7e00f900)) #define CLK_DIV0 (*((volatile unsigned long*)0x7E00F020)) #define ARM_RATIO 0 #define HCLKX2_RATIO 4 #define HCLK_RATIO 0 #define PCLK_RATIO 1 #define MPLL_RATIO 0 #define APLL_CON (*((volatile unsigned long *)0x7E00F00C)) #define APLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1)) #define MPLL_CON (*((volatile unsigned long *)0x7E00F010)) #define MPLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1)) #define CLK_SRC (*((volatile unsigned long*)0x7E00F01C)) void clock_init(void) { APLL_LOCK = 0xffff; MPLL_LOCK = 0xffff; EPLL_LOCK = 0xffff; OTHERS &= ~(0xc0); while((OTHERS & 0xf00) != 0); CLK_DIV0 = (ARM_RATIO) | (MPLL_RATIO << 4) | (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) | (PCLK_RATIO << 12); APLL_CON = APLL_CON_VAL; MPLL_CON = MPLL_CON_VAL; CLK_SRC = 0x03; }
SECTIONS { . = 0x50000000; //當前地址 . = ALIGN(4); .text : { //段名稱,放置所有文件的代碼段 start.o (.text) time.o (.text) irq.o (.text) led.o (.text) } . = ALIGN(4); //4位對齊 .rodata : { * (.rodata) } . = ALIGN(4); .data : { * (.data) } . = ALIGN(4); bss_start = .; //bss段開始處 .bss : { //放置所用bss段 * (.bss) } bss_end = .; //bss段結束處 }
下面說一下,之前我們寫的簡單程序,沒有用到DDR,只是將程序在6410的8K片內內存中運行,但是如果程序很大,那就不能指望在片內內存中運行我們的程序了。下面就要用到SDRAM,就要涉及到鏈接地址。
簡單的說,一個程序分爲下面幾個部分:
(1)代碼段(text):就是我們所寫的代碼,指令
(2)數據段(data):有初始值的全局變量或靜態變量
(3)Bss段(Bss):未初始化或初始值爲0的全局變量或靜態變量
分析反彙編文件我們得出:訪問全局變量使用的是鏈接地址來訪問的。在系統上電後,系統會自動的把NandFlash中的前8K程序拷貝到片內8K內存當中去,而一個程序要執行,應該位於鏈接地址。當程序的鏈接地址不等於當前地址時,就需要重定位,將程序拷貝到相應的鏈接地址中去執行。
位置無關碼:相對跳轉指令,不訪問全局變量。下面看一下重定位代碼:
adr r0, _start @ get _start's current address: 0 ldr r1, = _start @ _start's link address ldr r2, = bss_start @ bss section's begining link address cmp r0, r1 @ compare isnot equal beq clean_bss
copy_loop: ldr r3, [r0], #4 str r3, [r1], #4 cmp r1, r2 bne copy_loop
clean_bss: ldr r0, = bss_start ldr r1, = bss_end mov r3, #0 cmp r0, r1 ldreq pc, = on_ddr clean_loop: str r3, [r0], #4 cmp r0, r1 bne clean_loop ldr pc, = on_ddr
1.9 Makefile
由於是在Linux下開發,瞭解Makefile也是很有必要的,下面是本模板的Makefile代碼:
CC = arm-linux-gcc LD = arm-linux-ld AR = arm-linux-ar OBJCOPY = arm-linux-objcopy OBJDUMP = arm-linux-objdump CFLAGS = -Wall -Os -fno-builtin-printf export CC LD AR OBJCOPY OBJDUMP CFLAGS objs := start.o time.o sdram.o irq.o led.o led.bin : $(objs) $(LD) -Tled.lds -o led_elf $^ $(OBJCOPY) -O binary -S led_elf $@ $(OBJDUMP) -D -m arm led_elf > led.dis %.o : %.c $(CC) $(CFLAGS) -c -o $@ $< %.o : %.S $(CC) $(CFLAGS) -c -o $@ $< clean: rm -f *.dis *.bin *_elf *.o