裸机中代码书写的细节总结
1、用匯編寫的函數,末尾應該添加mov pc,lr語句。
2、裸機程序的代碼編寫流程、文件的引用關系
3、關于鏈接地址和鏈接腳本的一些符號標識的理解
4、關于重定位的理解
(1)在sram內部重定位,因此不需要初始化DDR
鏈接腳本中鏈接地址是0xd0024000 在SRAM中
/** 文件名: led.s * 作者: 朱老師* 描述: 演示重定位(在SRAM內部重定位)*/#define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80.global _start // 把_start鏈接屬性改為外部,這樣其他文件就可以看見_start了_start:// 第1步:關看門狗(向WTCON的bit5寫入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:設置SVC棧ldr sp, =SVC_STACK// 第3步:開/關icachemrc p15,0,r0,c1,c0,0; // 讀出cp15的c1到r0中//bic r0, r0, #(1<<12) // bit12 置0 關icacheorr r0, r0, #(1<<12) // bit12 置1 開icachemcr p15,0,r0,c1,c0,0;// 第4步:重定位。(這里的代碼細節說明adr是與運行相關的,ldr是與鏈接相關的。)adr r0, _start // adr指令用于加載_start當前運行地址 // adr加載時就叫短加載 ldr r1, =_start // ldr指令用于加載_start的鏈接地址:0xd0024000 // ldr加載時如果目標寄存器是pc就叫長跳轉,如果目標寄存器是r1等就叫長加載// bss段的起始地址ldr r2, =bss_start // 就是我們重定位代碼的結束地址,重定位只需重定位代碼段和數據段即可cmp r0, r1 // 比較_start的運行時地址和鏈接地址是否相等beq clean_bss // 如果相等說明不需要重定位,所以跳過copy_loop,直接到clean_bss// 如果不相等說明需要重定位,那么直接執行下面的copy_loop進行重定位// 重定位完成后繼續執行clean_bss。// 用匯編來實現的一個while循環 copy_loop:ldr r3, [r0], #4 // 源str r3, [r1], #4 // 目的 這兩句代碼就完成了4個字節內容的拷貝cmp r1, r2 // r1和r2都是用ldr加載的,都是鏈接地址,所以r1不斷+4總能等于r2bne copy_loop// 清bss段,其實就是在鏈接地址處把bss段全部清零 clean_bss:ldr r0, =bss_start ldr r1, =bss_endcmp r0, r1 // 如果r0等于r1,說明bss段為空(即不存在bss段),直接下去beq run_on_dram // 清除bss完之后的地址mov r2, #0clear_loop:str r2, [r0], #4 // 先將r2中的值放入r0所指向的內存地址(r0中的值作為內存地址),cmp r0, r1 // 然后r0 = r0 + 4bne clear_looprun_on_dram: // 長跳轉到led_blink開始第二階段ldr pc, =led_blink // ldr指令實現長跳轉//bl led_blink // bl指令實現短跳轉// 匯編最后的這個死循環不能丟b .(2)重定位至DDR,因此需要初始化DDR
鏈接腳本中的鏈接地址是0x2000 0000 在DDR中
/** 文件名: led.s * 作者: 朱老師* 描述: 演示重定位*/#define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80.global _start _start://..............// 第4步:初始化ddrbl sdram_asm_init //此函數末尾記得添加mov pc,lr// 第5步:重定位,后面的代碼和之前的完全一樣。故不寫。(3)由此可以看出,其實兩者沒有什么區別,只是多了一個內存初始化操作而已。
5、Makefile中用 -Ttext 0x0 來指定鏈接地址是0x0。這意味著我們認為這個程序將來會放在0x0這個內存地址去運行。但是實際上我們運行時的地址是0xd0020010(我們用dnw下載時指定的下載地址)。這兩個地址看似不同,但是實際相同。這是因為S5PV210內部做了映射,把SRAM映射到了0x0地址去。
這段話的意思是,SRAM的0xd0020010地址,映射到了0x0地址,因此我們可以把程序的鏈接地址設為0x0。而0xd0020010這個地址,是三星這個開發板啟動時,BL0執行完后自動運行開始的地址,它是由CPU的設計決定的。
6、問題,BL0判斷啟動介質為SD卡后,怎么知道拷貝SD卡的第幾扇區?怎么知道拷貝到哪個地方?
我的猜想是,BL0內部的拷貝函數,它的參數之一肯定是SD卡的第一扇區,參數之二肯定是拷貝到哪個地方(這里肯定是0xd0020000),參數之三是拷貝的大小。
7、問題,如果文件太大,分為兩部分了,這個拷貝的過程是如何的。
首先,兩部分燒錄到SD卡的哪個扇區位置是已知的,第一部分肯定是在第一扇區開始的位置,至于第二部分,合理即可,但必須知道是哪里(后面這個位置當作參數,傳給第一部分中的拷貝函數)。
然后,第一部分被BL0拷貝到SRAM中運行,運行到第一部分中的拷貝函數時,拷貝函數從SD中拷貝第二部分內容。此函數之一肯定是第二部分在SD卡中的存儲位置,參數二肯定是第二部分的大小,參數三肯定是拷貝到哪個位置。
最后,第一部分代碼中有一個跳轉語句,跳轉到剛才拷貝的第二部分的位置,執行第二部分的代碼。
8、關于7中的問題驗證。
(1)此時write2sd中代碼
可知,兩部分分別燒錄至第1扇區、第45扇區開始的地方。
#!/bin/sh sudo dd iflag=dsync oflag=dsync if=./BL1/BL1.bin of=/dev/sdb seek=1 sudo dd iflag=dsync oflag=dsync if=./BL2/BL2.bin of=/dev/sdb seek=45
首先,鏈接腳本的鏈接地址是0xd0020010。很好,這個地址就是BL1該呆在的地方,因為由CPU設計時規定的一開始運行的地址就是0xd0020010。
SECTIONS {. = 0xd0020010;.text : {start.osdram_init.o* (.text)}.data : {* (.data)}bss_start = .; .bss : {* (.bss)}bss_end = .; }其次,BL1的makefile中,還是要把BL1做16字節填充的,這也很合理。
接著,start.S中初始化DDR后,使用copy_bl2_2_ddr函數,把BL2復制到DDR中的某個位置,并跳轉到該位置執行。
#define WTCON 0xE2700000#define SVC_STACK 0xd0037d80.global _start // 把_start鏈接屬性改為外部,這樣其他文件就可以看見_start了 _start:// 第1步:關看門狗(向WTCON的bit5寫入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:設置SVC棧ldr sp, =SVC_STACK// 第3步:開/關icachemrc p15,0,r0,c1,c0,0; // 讀出cp15的c1到r0中//bic r0, r0, #(1<<12) // bit12 置0 關icacheorr r0, r0, #(1<<12) // bit12 置1 開icachemcr p15,0,r0,c1,c0,0;// 第4步:初始化ddrbl sdram_asm_init// 第5步:重定位,從SD卡第45扇區開始,復制32個扇區內容到DDR的0x23E00000bl copy_bl2_2_ddr// 匯編最后的這個死循環不能丟b .copy_bl2_2_ddr函數。
關注下0xD0037F98,這個地址是BL0內置的SD卡拷貝函數地址。
#define SD_START_BLOCK 45 #define SD_BLOCK_CNT 32 #define DDR_START_ADDR 0x23E00000typedef unsigned int bool;// 通道號:0,或者2 // 開始扇區號:45 // 讀取扇區個數:32 // 讀取后放入內存地址:0x23E00000 // with_init:0 typedef bool(*pCopySDMMC2Mem)(int, unsigned int, unsigned short, unsigned int*, bool);typedef void (*pBL2Type)(void);// 從SD卡第45扇區開始,復制32個扇區內容到DDR的0x23E00000,然后跳轉到23E00000去執行 void copy_bl2_2_ddr(void) {// 第一步,讀取SD卡扇區到DDR中pCopySDMMC2Mem p1 = (pCopySDMMC2Mem)0xD0037F98);p1(2, SD_START_BLOCK, SD_BLOCK_CNT, (unsigned int *)DDR_START_ADDR, 0); // 讀取SD卡到DDR中// 第二步,跳轉到DDR中的BL2去執行pBL2Type p2 = (pBL2Type)DDR_START_ADDR;p2(); }(3)第二部分的邏輯關系
首先,鏈接地址應該由第一部分的拷貝函數拷貝將第二部分到哪里決定。由上面可知拷貝到0x23E00000,那么第二部分的連接地址也應該是0x23E00000。果真如此。
SECTIONS {. = 0x23E00000;.text : {start.o* (.text)}.data : {* (.data)}bss_start = .; .bss : {* (.bss)}bss_end = .; }其次,BL2的makefile中,不會再添加16字節填充的操作。果真如此。
然后,至于為什么會先執行start.S,是因為鏈接腳本中的.o文件的順序決定的。start.S中的內容如下。
#define WTCON 0xE2700000 #define SVC_STACK 0xd0037d80.global _start _start:ldr pc, =main // ldr指令實現長跳轉// 匯編最后的這個死循環不能丟b .此時,會到有main函數的文件中,執行main函數。
總結:
(1)拷貝函數還是使用BL0中的拷貝函數。
(2)第二階段的運行入口,是第二階段的鏈接腳本指定的位置(該位置由第一階段拷貝函數將內容拷貝到哪里決定)。
(3)第二階段的腳本中.o的順序,決定了第二階段眾多程序文件中,先執行哪些文件。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的裸机中代码书写的细节总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Fastjson实用工具类,List转J
- 下一篇: USB-WiFi在x210板子上的移植