ADS下C語言的入口方式和ROM鏡像文件的生成
這部分介紹下ADS下如何生成可以運行的ROM鏡像文件,我們知道當程序下載到flash中運行的時候,對於RW、ZI數據就存在着兩個環境,一個load環境,一個是exec環境,有時候由於速度的需要RO數據也要重新加載,那麼對RO數據也是有兩個環境。編譯器產生ROM鏡像文件時候,這三塊數據的存放依次爲RO、RW、ZI,並且地址空間時連續的。但是到了運行的時候,RW數據必須被拷貝到SDRAM(SRAM)中以支持讀寫,這就是我們所謂的運行環境。那麼就要有一段代碼去完成這個任務,在本章中我們介紹如何生成這段代碼。
玩過2410的朋友都知道2410初始化代碼中有一段搬運RW和ZI初始化的代碼,沒錯,它確實能夠在一定程度上完成上面所說的任務,只要我們在生成二進制可執行代碼的時候在編譯器鏈接項的地方填寫正確的RO&RW地址,(比如RO = 0, RW = 0x30000000), 那麼將程序下到 NOR flash的零地址並從nor flash啓動,啓動代碼會將RW&ZI數據弄到0x30000000,程序就能跑起來了。
但是各位有沒有想過,怎麼把RO代碼弄到SDRAM中(有時候這是必須的,比方後面我將提到用nor flash的bootloader燒寫nor flash)?如果直接設RO=0x30000000,那麼這段代碼下載到0地址肯定跑不起來,除非是ROPI,這個要求就高了。這裏我們有必要從介紹ADS中規定的C語言入口開始,ADS中從初始化彙編代碼跳到main函數有兩種方式,main和__main:
1,在__main入口的模式下,彙編代碼的指令爲 b __main, 編譯器在跳轉到main之前還要作一系列的工作,這其中就包括對運行環境的初始化,在<ADS COMPILE GUIDE>中提到: copies nonroot(RO&RW) execution regions from load addr to exec addr, and Zeros ZI region. 藉助編譯器,我們就可以定義更爲複雜的運行環境,這裏要用到scatter文件(.scf),比如我們要的目標運行環境是:將啓動代碼以外的所有代碼都 拷貝到SDRAM的初始地址中運行,比且把RW段設在0x30800000,那麼對應的scf文件如下:
FLASH 0x0 0x200000
{
EXEC1 0x0 0x200000
{
2410init.o(Init, +First)
__main.o(+RO) ; copy code
* (Region$$Table) ; RO/RW addresses to copy
* (ZISection$$Table) ; ZI addresses to zero
}
EXEC2 0x30000000 0x00800000
{
*(+RO)
}
SDRAM 0x30800000 0x00800000
{
*(+RW,+ZI)
}
}
;Sections named Region$$Table and ZISection$$Table which contain the addresses of the code/data to be copied.
當然,在這種模式下,有些入口函數必須自己重定義,比如__user_initial_stackheap,具體參見ADS文檔。
2, main入口模式即簡單的跳轉,這裏起始不用“main”這個名字也無所謂。那麼編譯器不會作任何的初始化,所有運行環境的建立都要靠 我們自己,這就是大家看到的那段搬運代碼存在的理由。但是它實現一些簡單的運行環境是可取的,如果用scf定義的複雜環境,雖然我 相信是可以做到的,但是可能會比較麻煩。我還沒深究。
另外,這裏提一下semihost,因爲我們在看ADS的東西的時候經常出現這個詞,我也一直受其困擾。這裏我簡單說一下自己的見解,semihost 僅僅是一種調試手段,它的機理就是利用MULTI_IDE等工具捕捉目標環境運行過程中產生的值爲0x123456的SWI中斷,然後向上位機的ADS 軟件發送對應的調試信息。對於我們最後的應用代碼來說,都是nonsemihost類型的。如果我們在調試中使用semihost,那麼只要在最後重定義 ADS中的一些使用到的庫函數(比如fputc),代碼就可以從semihost向nonsemihost的類型轉變。不過到目前爲止,我還沒體會到semihost的威力。
2410啓動代碼分析
這一章主要對目前廣泛流行的2410啓動代碼進行分析:S3C2410的初始化代碼主要涉及到對系統主要模塊的配置、運行環境的建立、系統時鐘、MMU等模塊的配置,下面按執行順序依次都各個部分進行分析:
程序入口:(ResetHandler)
在程序一開始,首先進行的一些操作主要保證初始化程序能夠順利的運行, 因此主要包括關閉WDT、中斷,配置鎖相環等。
配置memory接口
memory接口是確保數據訪問正確的基本保障,此處主要配置SFR寄存器中0x48000000開始的memory接口寄存器組, 確保每個bank的位寬、訪問類型(waitable)以及時序參數正確。如果沒有特別的要求,一般來說時序參數使用默認值即可。
初始化堆棧
ARM有6種運行模式,必須爲每一種模式提供獨立的堆棧空間,在堆棧設置之前是不能進行C函數的調用的。ARM的堆棧模式 是從高地址遞減的,我的所有代碼統一將堆棧的首地址設在0x33ff8000處,往低依次爲FIQ、IRQ、Abort、Undef、SVC,其中
SVC和User模式不予區分。堆棧大小一般可在頭文件或者當前文件中修改。
運行空間的初始化
這段代碼主要完成兩個功能,一是將RW數據搬運到RW空間(我們生成ROM鏡像時,RW數據是跟在RO數據之後的),二是 初始化ZI數據段。當然,這段代碼存在的前提是代碼的運行環境只是標準的兩段式:一段RO空間和一段RW空間;並且在C程序
入口時沒有調用編譯器的鏈接庫(__main)。後者已經提供相應的功能,並且支持更加複雜的運行環境定義(使用SCF文件),
(關於這一點,我在介紹ADS中C代碼的啓動模式時已經詳細介紹)。
__rt_lib_init
在ADS1.2的環境中,如果在C入口沒有調用編譯器的鏈接庫(__main),那麼在C程序一開始要調用該函數以初始化運行時的函數庫,以保證對ADS提供的某些庫函數能夠正常調用。從這個函數開始,我們已經在C語言環境下了。
MMU初始化
2410的MMU支持1級&2級地址映射,在我們目前大部分應用中均採用1級section模式的地址映射,一個section的大小爲1M,也就是說從邏輯地址到物理地址的轉變是這樣的一個過程:
一個32位的地址,高12位決定了該地址在頁表中的index,這個index的內容決定了該邏輯section對應的物理section; 低20位決定了該地址在section中的偏移(index)。
因此從0x0~0xffffffff的地址空間總共可以分成0x1000(4K)個section,頁表中每項的大小爲32個bit,因此頁表的大小爲0x4000(16K)。在我的代碼中所有程序的頁表統一存放在地址0x33ff8000。
每個頁表項的內容如下:
bit: 31 20 19 12 11 10 9 8 5 4 3 2 1 0
content: Section對應的物理地址 NULL AP 0 Domain 1 C B 1 0
最低兩位(10)是section分頁的標識。
AP:Access Permission,區分只讀、讀寫、SVC&其它模式。
Domain:每個section都屬於某個Domain,一個有16個Domain,每個Domain的屬性由CP15的R3寄存器控制。 在我得所有程序中,都只包含兩個Domain,一個是SFR地址以下(包括SFR)的空間,可訪問; 另一個是SFR以上的空間,不可訪問。
C、B:這兩位決定了該section的cache&write buffer屬性,這與該段的用途(RO or RW)有密切關係。不同的用途要做不同的設置。
C B 具體含義
0 0 無cache,無寫緩衝,任何對memory的讀寫都反映到ASB總線上。
對 memory 的操作過程中CPU需要等待。
0 1 無cache,有寫緩衝,讀操作直接反映到ASB總線上。寫操作CPU將數據寫
入 到寫緩衝後繼續運行,由寫緩衝進行ASB操作。
1 0 有cache,寫通模式,讀操作首先考慮cache hit;寫操作時直接將數據寫入
寫緩衝,如果同時出現cache hit,那麼也更新cache。
1 1 有cache,寫回模式,讀操作首先考慮cache hit;寫操作也首先考慮cache,
如果hit,則只修改cache,並將cache對應半行的dirty比特置位;如果miss,
則寫入寫緩衝,觸發ASB總線操作。
在我的程序中內存空間的分配統一採用了文末的MEMORY圖。雖然MMU只是使用了邏輯地址到物理地址的linear transfer(值不改變),但是由於MMU能夠引入cache&write buffer,因此系統性能有很大的提高!
配置時鐘比、重新設置PLL
2410內部有三個時鐘:FCLK、HCLK、PCLK,分別供CPU、AHB總線和APB總線使用,爲了降低功耗,一般都選擇週期比爲1:2:4的合理配置。 同時將PLL配置爲運行環境時鐘,一般都達到最高202M。
IO初始化
將IO口配置爲對應的功能選項,同時一般會點亮相應的LED燈。
中斷初始化
2410的內存空間沒有remap的機制,應該中斷入口時鐘位於零地址。因此中斷服務機制可以描述如下:
首先,不管使用那種啓動方式,必須確保一下代碼段位於內存的0x0地址:
b ResetHandler
b HandlerUndef ;handler for Undefined mode
b HandlerSWI ;handler for SWI interrupt
b HandlerPabort ;handler for PAbort
b HandlerDabort ;handler for DAbort
b . ;reserved
b HandlerIRQ ;handler for IRQ interrupt
b HandlerFIQ ;handler for FIQ interrupt
除ResetHandler外,其餘各項都是由如下的宏定義的一段代碼:
HandlerFIQ HANDLER HandleFIQ
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 ;decrement sp(to store jump address)
stmfd sp!,{r0} ;PUSH the work register to stack
ldr r0,=$HandleLabel ;load the address of HandleXXX to r0
ldr r0,[r0] ;load the contents
str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack
ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR)
MEND
這段代碼的含義是通過堆棧將中斷向量表中的內容賦給PC指針(如HandleFIQ是存放着FIQ服務程序入口地址的地址),自然程序就跳到相應的入口地址。
可見,中斷向量表存放的是各個中斷服務程序的入口地址,它是用來被加載的,而並不是可執行代碼。爲了統一,所有示例程序都將中斷向量表放在0x33ffff00開始的地址,並根據入口地址依次排列。
需要注意的是如果各種模式的服務程序用C語言定義,那麼類型必須用__irq定義,以保證能夠正確返回。
初始化串口
串口統一選用UART0,模式採用115200、1bit STOP、No Parity。
最後跳轉到我們自己的應用程序!
附:我得程序所使用的地址空間結構以及MMU中C、B的設置:
Blank Area: RW_FAULT 0x5b000000 ~ 0xffffffff
Sram & SFR: NCNB 0x40000000 ~ 0x4affffff
Blank Area: RW_FAULT 0x34000000 ~ 0x3fffffff
Int_Vec, Stack, MTT: CNB 0x33f00000 ~ 0x33ffffff
SDRAM Download: NCNB 0x31000000 ~ 0x33efffff
SDRAM Exec RW: CB 0x30800000 ~ 0x30ffffff
SDRAM Exec R CNB 0x30000000 ~ 0x307fffff
Bank5, FPGA: NCNB 0x28000000 ~ 0x2fffffff
Bank4, FPGA: NCNB 0x20000000 ~ 0x27ffffff
Bank3, Bottom NIC: NCNB 0x18000000 ~ 0x1fffffff
Bank2, Bottom Flash: CNB 0x10000000 ~ 0x17ffffff
Bank1, Bottom Sram: CNB 0x08000000 ~ 0x0fffffff
Bank0, Flash or Sram: CNB 0x00000000 ~ 0x07ffffff
Nor Flash Bootloader
這是我着手寫的第一個程序,我的想*是讓這個程序同時支持通過串口對Nand 和 Nor FLASH的燒寫,如果不進行任何燒寫,那麼就跳到Nor Flash的第二個section啓動應用程序,這樣一來,即使脫離JTGA,我也可以使用串口進行盲調。
由於有現成的初始化文件和flash燒寫的示例程序,開發起來還比較快。當然也遇到了一些問題,一開始連flash的device ID都讀不出來,後來發現我指針沒有定義成volatile類型,flash的操作時序被編譯器優化了;再者,在對Nor Flash進行操作時,bank0在MMU中的類型一定要設爲NCNB,這樣比較保險。
遇到最大的問題就是下面的了,一開始我用jtag把程序下載到0x30000000的地方運行,對Nor Flash的燒寫完全正常,但是當把程序下載到Nor Flash中啓動運行後,再對Nor Flash的section 2進行燒寫時,就出現了問題。所幸沒多久我就意識到了問題,將程序放在Nor Flash中運行,同時有對同一片flash進行操作,那麼操作時序勢必會被CPU的指令讀取時序所破壞,因此程序必須搬運到SDRAM中運行。
但是啓動地址有必須是零地址,所以我採用了前文提到的scatter文件的方*,將非必要的代碼全部搬到sdram中運行,scf文件格式就是前文中的那個。當然採用了__main的入口,調用了ADS的鏈接庫,讓它幫忙建立程序的運行環境。
至此,Nor Flash Bootloader可以順暢無憂的實現其功能了。
Nand Flash Bootloader
因爲Nor flash bootloader已經實現了對Nand Flash的燒寫,因此在Nand Flash Bootloader中實現flash燒寫並不是我的目的,況且,S3C2410運行在NAND BOOT模式下的時候,4K的SRAM位於0地址,上電時刻Nand Flash中block 0的前8個page的數據自動加載到SRAM後開始運行,Nor flash這個時候是不可見的。 因此,我做Nand Flash Bootloader的目的簡單而又直接,就是把block1開始的若干個block數據加載到sdram首地址,然後PC跳到那裏運行應用程序就可以了。比方說我把編譯好的ucos-ii代碼放在block1,那麼ucos-ii就可以跑起來了。
因此製作Nand Flash一個最重要的問題就是真個程序必須小於4K。應用程序應該是一個完整的應用代碼,只是在編譯時RO的起始地址應該定位成0x30000000,如果直接用JTAG將其下載到對應地址,程序照樣能夠跑起來(當然零地址要有中斷向量入口程序)。這裏我偷懶了一下,將應用程序的中斷向量表地址和Nand Flash Bootloader設得完全相同,那樣應用程序就可以借用bootloader的中斷跳轉程序以實現中斷的正確跳轉,當然應用程序也有自己相應的跳轉代碼,但是這段代碼位於SDRAM起始地址,是不會被執行的;至於堆棧,應用程序在自己得初始化代碼中可以重新設置堆棧。
在Nand Flash的硬件方面,我開發板使用的是K9F5608(32M),相對於K9S208(64M),後者的地址需要寫四次才能全部送出,而前者只要三次就夠了,2410的引腳中專門有nCON控制地址送出的次數。因此當硬件在這兩者之間變化時,既要注意外部電路圖的接*,又要注意軟件代碼的正確性。
RTL8019調試心得
一開始接觸8019真的是讓我頭暈,首先我沒有一點網絡基礎,另外,8019的datasheet稱不上最爛也算是極品了。當初作PCB的時候選用8019主要是因爲價格便宜以及lbbbb做過,能夠提供源代碼&技術支持。最後能搞定,我覺得還是很有成就感的。
8109AS的運行模式包括跳線模式、非跳線模式和PnP模式,PnP模式是在電腦上使用的即插即用模式,因此這裏我們可以不予考慮。8109AS的IO寄存器符合NE2000標準,分爲4個page,其中page3是8109AS自己定義的寄存器。所謂跳線模式,是指8019AS I/O寄存器page3中的大部分配置寄存器(CONFIGn)的值是在上電覆位時刻確定的,來源是在RESET上升沿時捕捉到的一些外部引腳的電平值。在非跳線模式下,這些寄存器值的配置由外部EEPROM 93C46完成。配置寄存器在運行過程中大部分值時不能改變的。
目前驅動程序目前只實現了最基本的收發功能。片內16K的SRAM劃分如下:40~46:發送緩衝區1;46~4c:發送緩衝區2;4c~80:接收緩衝區。
另外我在調試中發現片內的SRAM是不可按地址讀的,雖然我在原理圖上也象CS8900A那樣連了mem_wr&mem_rd,但是似乎不能訪問,希望哪位高人能夠給我一個明確的回答。
起初作硬件了時候我加了93C46,想使用非跳線模式,JP腳就懸空在那裏。後來93C46買不到,就一直空着,雖然8019的初始化沒有出問題,但是對這種不洋不土的模式,我還是心有餘悸,因此將JP腳接到了5V電源,板上唯一的飛線就是這麼來礟OSThttp://bbs.edw.com.cn/S**ePost.asp?A 現在還有一個鬱悶的問題就是linux 2.4.18是不支持8019的。天下的2410開發板都採用8900a也就是這個道理。所以,我還要完成驅動!!!
S3c2410 DMA介紹
之所以要介紹DMA,因爲它對性能太重要了!只有活用了DMA,CPU的性能才能上去!S3c2410有四個DMA,每個DMA支持工作方式基本相同,但支持的source Dest可能略有不同,具體見Datasheet。
這裏具體DMA CONTROL寄存器(DCON)的配置說明,進而引出DMA的各種工作方式。
Atomic transfer:指的是DMA的單次原子操作,它可以是Unit模式(傳輸1個data size),也可以是burst模式(傳輸4個data size),具體對應DCON[28]。
Data Size:指的是單次原子操作的數據位寬,8、16、32,具體對應DCON[21:20]。
Request Source:DMA請求的來源有兩種,軟件&硬件模塊,由DCON[23]控制;當爲前者時,由軟件對DMASKTRIG寄存器的位0置位觸發一次DMA 操作。當爲後者時,具體來源由DCON[26:24]控制,不同硬件模塊的某時間觸發一次DMA操作,具體要見不同的硬件模塊。
DMA service mode:DMA的工作模式有兩種,單一服務模式&整體服務模式。前一模式下,一次DMA請求完成一項原子操作,並且transfer count的值減1。後一模式下,一次DMA請求完成一批原子操作,直到transfer count等於0表示完成一次整體服務。具體對應DCON[27]。
RELOAD:在reload模式下,當transfer count的值變爲零時,將自動加src、dst、TC的值加載到CURR_DST、CURR_SRC、CURR_TC,並開始一次新的DMA傳輸。該模式一般和整體服務模式一起使用,也就是說當一次整體服務開始後,src、dst、TC的值都已經被加載,因此可以更改爲下一次
服務的地址,2410說明文檔中建議加入以下語句來判斷當前的服務開始,src、dst、TC的值可以被更改了:while((rDSTATn & 0xfffff) == 0) ;
Req&Ack:DMA請求和應答的協議有兩種,Demard mode 和 Handshake mode。兩者對Request和Ack的時序定義有所不同:在Demard模式下,如果
DMA完成一次請求如果Request仍然有效,那麼DMA就認爲這是下一次DMA請求;在Handshake模式下,DMA完成一次請求後等待Request信號無效,然後把ACK也置無效,再等待下一次Request。這個設計外部DMA請求時可能要用到。
傳輸總長度:DMA一次整體服務傳輸的總長度爲:
Data Size × Atomic transfer size × TC(字節)。