Http://blog.csdn.net/zhanglianpin
First, describe the software and hardware before power-on. This Samsung company has integrated 64 m of SDRAM and 64 m of nandflash memory according to the S3C2410 generated by ARM920T soft core. Both Vivi and UCOS are stored in this nandflash because nandflash will not lose information after power failure. This vivi is a bootloader written by Samsung for ARM chips. It is used in the development stage as a system boot program.
Vivi is stored at the start of Flash 0x00000000 address, and UCOS is stored at the start of Flash 0x03f30000 address. ARM920T boot starts from flash. When started, copy the first 4 K of Flash (the first 4 K of vivi) to SDRAM (this method is started using nandflash, the first 4 K of VIVI Code contains the code used to copy the remaining Vivi. After executing the code, Vivi controls flash reading, serial port control, and User Shell interfaces. Of course, Vivi also has other functions. When you execute the bootucos command, Vivi will extract the uCOS-related code from the flash
0x03f30000 copy to the place where SDRAM 0x30008000 is located. Of course, you can also set Vivi to automatically guide UCOS execution. After code copy is complete, Vivi changes the Pc value to 0x30008000 for execution.
Let's first explain why we have to talk about the specific address. We mentioned earlier that the compiled program has a load address and a real run address, the address 0x30008000 is the loading address of the program. This address is the address specified by the compiler, that is, it is configured by the file suffixed with SCF in the ADS project. In this file, we have configured the loading address and running address of the program. Why do we need to specify these two addresses? The program of our entire project is a fixed absolute address at the end of the link, that is to say, the address of the final link is the same as the actual running address. However, we generally do not place the Code directly in the corresponding parts. One of the reasons is that, in order to save the program without power-on, we will put the program into a non-easy storage device, during running, we will copy the program to the SDRAM with a high running speed. That is to say, the execution addresses of these static link programs are fixed. We need to put these programs in the correct place before these programs run. We must know the address to which our program is loaded and the address to which it is actually running. In this way, we can know how to handle program segments with different loading addresses and operating addresses. As for the handling work, You can manually implement it, or use the library functions provided by ads.
Jump to this 0x30008000 to execute the command at this address. Who is the first command after this project is compiled? The programs we usually write are executed from the main () function, but our embedded development is not. After analyzing the startup code, you will know, you have to do a lot of work before executing the so-called main () function.
There are two types of arm image file endpoints: one is the image file runtime entry point, called the initial entry point ), the other is normal entry point ).
The initial entry point is the entry point for running the image file. Each image file has only one unique initial entry point, which is saved in the elf header file. If the image file is loaded by the operating system, the operating system will jump to the initial entry point to load the image file.
The normal entry point is defined by the entry pseudo operation in the Assembly. It is usually used to indicate that the code entered through the exception interrupt handler. In this way, the code of this segment is not deleted when the connector deletes useless segments. Multiple common entry points can be defined in an image file.
It should be noted that the initial entry point can make a common entry point, but it can also be not a common entry point.
The initial entry point must meet the following two conditions:
1. The initial entry point must be in the runtime time domain of the image file.
1.1 The runtime time domain full of the initial entry point cannot be overwritten. Its loading address and operation address must be the same.
You can use the connection option-entry address to specify the initial entry point of the image file. In this case, address specifies the address value of the initial entry point of the image file. For embedded application systems with the address 0x0 as Rom, you can use-entry 0x0 to specify the initial entry point of the image file. In this way, after the system is reset, the system automatically jumps to this entry to start execution. If the image file is loaded by a loader, the image file must contain an initialization entry point. This type of image file usually contains other common entry points, which are generally the entry addresses of the exception interrupt handler.
When the-entry address is not specified, the connector determines the initial entry point of the image file according to the following rules.
If there is only one normal entry point in the input target file, the normal entry point is treated as the initial entry point of the image file by the connector.
If no common entry point exists in the input target file or more than one normal entry point exists, the image generated by the connector is used.
The compiled Executable File removes the image file in the header format. We are talking about the operating system, so this program is not the First Command executed through the initial entry point, it should be executed through common entry points, usually the interrupt vector table. That is, the first command in the instruction segment specified by the pseudo-command entry in the program. We use ads1.2 to open the tenth experiment in the UCOS learning materials project (UCOS system transplantation experiment ). There is a startup. s assembler in the Startup Folder. This is the startup code of UCOS. The first command specified by the entry pseudo-command is B coldreset, so the first command is B coldreset.
Let's ignore the issue of this first command. My goal is to tell you the ucos I learned, but this requires a certain specification. I hope you can understand what I said, I want to do this:
First, describe the entire process, and then summarize the hardware and software systems at this stage in stages? Why are there these orders? Why? In this process, the thinking may immediately spread to any relevant knowledge point. Finally, I will analyze the source code one by one. All problems encountered during source code analysis will be solved, including the essence and beauty. I may also elaborate on my understanding and methods, as well as my understanding of learning. I want to write this stuff according to certain standards, but I don't want to write it completely according to one idea. After all, I write it freely. My overall idea is to focus on what hardware and software have done throughout the time stream? Why is this the main line. All questions and knowledge points involved in this line will be elaborated one by one. I try to be as natural as possible, rather than imposing some blunt concepts on you, because people do not like to be. Learned, worked, and remembered.
The best way to understand UCOS is to read its source code. A good reference book is the embedded real-time operating system uCOS-II.
Statement: I still have a lot to understand when writing this document, so I may not be clear about it in some places, but I will write down my questions, I will write it out when I want to understand it. If you know it, please let me know. I will be very happy.
To explain the current situation, all UCOS Code (including the bootloader started) is copied by Vivi to the place where the memory address of 0x30008000 starts, and then the Pc value is changed to 0x30008000, take out the arm command from this address and start to execute this command. The first instruction of the executable program compiled by the complete project has been analyzed.
Now let's start with the overall startup process of the entire team and UCOS. It's just a rough description of the process. As to why such problems will arise, we will explain them in detail.
Hardware initialization is mainly to make the hardware platform in a known State. An important point is to initialize the C language runtime environment.
UCOS Initialization
UCOS run and execute the application
Well, the whole process is quite simple.
The following describes the hardware initialization phase. This is really troublesome, but it doesn't matter. Let's talk about it slowly.
From the specific code, it mainly does these jobs:
Turn off the watchdog (a hardware used in the development phase, which is described in the Code Description)
Block Interrupt Mask register (the control of the entire hardware platform is now in UCOS. during initialization, we do not want to be disturbed, for specific reasons, we will discuss it later)
Initialize the stack space of each mode (stack space is very important)
Copy interrupt vector table (we will talk about why copy is required)
Initialize the C library Environment
Then jump to the main application (that is, the main () function we usually call)
The following code is compiled in a collection, with a semicolon followed by a comment.
The following uses the specific code as an example to explain the startup code in detail.
Each code block provides a description. for particularly important code, I will make a detailed comment after the code. Comments are written after //. If there are important knowledge points here, separate them.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
; Copyright (c) 2004-2007 threewater@up-tech.com, All rights reserved.
;;;
; Startup code
; S3C2410: startup. s
; By threewater 2005.2.22
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Get 2410addr. S // introduce the content in the 2410addr. s file, which acts like # include in C.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
; Some arm920 CPSR bit discriptions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
; Pre-defined Constants
Usermode equ 0x10
Fiqmode equ 0x11
Irqmode equ 0x12
Svcmode equ 0x13
Abortmode equ 0x17
Undefmode equ 0x1b
Modemask equ 0x1f
Noint equ 0xc0
I _bit * 0x80
F_bit * 0x40
// The above Code defines some constants, all of which are about the CPSR register of arm. We know this knowledge. If you do not know this, refer to the relevant content in the program model chapter of datasheet. The written code is used for communication between programmers, so it should be readable as much as possible and should not use numbers. When using numbers, define them as another readable symbol, as shown in the preceding figure, so that you can read the name notification.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
; Start here
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
Area init, code, readonly // defines a segment and specifies attributes. These are all for the compiler. For more information, see the relevant compiler knowledge section.
Import _ use_no_semihosting_swi // disable the semihosting mechanism. What is the semihosting mechanism? This is a development debugging mechanism provided by ads, that is, your program runs on the target platform, you can use the hardware resources of the host to debug the program. For example, you can use the screen and keyboard of the host. Semihosted environment (semihosting is a mechanism for ARM target machines. It can communicate with a host running debugging functions based on the input/output requests of application code. This technology allows the host to provide host resources for the target hardware without input and output functions. It is used to implement the objective-related functions in the semlhosted environment. In your application
Use stand Io functions such as printf in C library in code! Easy debugging! For more information, see arm DUI 0058d (debug target Guide !) Arm interprets semihosting in Chinese as a semi-host mechanism. Why is it half-host? It mainly refers to the application code running on the target system. When you need to input and output code in the console similar to that on the PC platform, semihosting is called to use the console input and output devices on the PC: for example, open and close files, PC display output, and keyboard input. For more detailed content, see semihosting.pdf
Here, our system runs on the hardware platform independently, that is, we do not need to use the semihosting mechanism. Import _ use_no_semihosting_swi declares that we do not use this mechanism. If the program uses this mechanism, the compiler reports an error. In the C language, we disable the # pragma import (_ use_no_semihosting_swi) statement.
Import enter_undef
Import enter_swi
Import enter_pabort
Import enter_dabort
Import enter_fiq // The above is the reference global label, which indicates that these labels have been declared elsewhere and are global. Declare the global variables in the assembly language with the keyword export, and use the extern in the C language. Of course, the function in the C language itself is the global label.
; Import main
Entry // specifies the normal entry point of the program. The following code is a jump command. The entry pseudo Command tells the compiler that the following code is useful and should not be optimized. The compiler may think that this is a bunch of code that does not exist.
B coldreset // The first is the jump command, which jumps to the coldreset to execute
B enter_undef; undefinedinstruction
B enter_swi; syscall_handler or SWI
B enter_pabort; prefetchabort
B enter_dabort; dataabort
B.; reservedhandler
B irq_handler; irqhandler
B enter_fiq; fiqhandler
; Deal with IRQ interrupt
Export irq_handler
Irq_handler
Import isr_irqhandler
; Subs LR, LR, #4; corrected the return address in advance
Stmfd SP !, {R0-r12, LR}
BL isr_irqhandler
; Mrs R10, spsr; get the SRS
; MSR cpsr_cxsf, R10; Recovery CPSR
; Ldmfd SP !, {R0-r12, PC} ^
; Bic R10, R10, #0xc0; on interrupt
Ldmfd SP !, {R0-r12, LR}
; Mrs R10, spsr; get the SRS
; MSR cpsr_cxsf, R10; Recovery CPSR
; MoV PC, LR
Subs PC, LR, #4; s indicates that CPSR is affected.
; Mrs R10, spsr; get the SRS
; MSR cpsr_cxsf, R10; Recovery CPSR
; ========
; Entry
; ========
Export coldreset
Coldreset
LDR r0, = wtcon; watch dog disable
LDR R1, = 0x0
STR R1, [R0] // shut down the watchdog. For more information about the watchdog, see Chapter 18th of datasheet.
LDR r0, = intmsk
LDR R1, = 0 xffffffff; all interrupt disable
STR R1, [R0]
LDR r0, = intsubmsk
LDR R1, = 0x7ff; All Sub interrupt disable, 2002/04/10
STR R1, [R0]
// The code above is used to block all external interruptions by shielding the External Interrupt Mask register. For more information about the principle, see Chapter 1 interrupt controllor of datasheet or the basics of ARM chip.
; **************************************** ************
; * Initialize stacks *
; **************************************** ************
BL initstacks; stack setup for each mode // jump command, jump to initstacks to execute
Now the system stack space in each mode has been initialized. To sum up the current processor situation, the processor is in SVC mode and the IRQ and FIQ bits of CPSR cannot be interrupted. Also, the watchdog is blocked (the entire system does not use the watchdog), and the shielding registers of the interrupt controller with external interruptions are blocked. That is to say, the application for external interruption cannot reach the CPU. Even if the external request arrives at the CPU, the CPU will not be handled because IRQ and FIQ bits are blocked. This is the case with double protection.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
; Copy excption table to SRAM at 0x0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
Import | load $ exception_exec $ base | // address at loading
Import | image $ exception_exec $ base | // runtime address
Import | image $ exception_exec $ length | // length of the exception segment
LDR r0, = | load $ exception_exec $ base |; source data
LDR R1, = | image $ exception_exec $ base |; place exception talbe at 0x0
LDR R2, = | image $ exception_exec $ length |
Prediction_cploop
Sub R2, R2, #4 // R2 (that is, the length value of exception minus 4, because the general register of ARM is 32 bits, that is, four bytes are moved each time)
Ldmia R0 !, {R3} // put the first four bytes of the source address in R3, and add 4 to the R0 address automatically. this is! . For details, refer to the arm instruction set.
Stmia R1 !, {R3} // use R3 to install it to the destination address
CMP R2, #0 // determine whether to move. If not, move again.
BGE prediction_cploop
// The above code is the copy interrupt vector table. The interrupt vector table here is the interrupt vector defined in exception. s in the project, not the jump commands starting in startup. S. The reason for copying the interrupt vector table is as follows: our current UCOS is located at the SDRAM address 0x30008000. We also know that UCOS needs to manage the entire hardware and, of course, manage the interrupt, the arm interrupt vector table specifies the place where the memory address starts from 0, and the 0 address of SDRAM is Vivi code, I think it must also be an interrupt vector table (but it is vivi). Now we need to copy the interrupt vector table of UCOS to the 0 address of SDRAM. 1. This provides preconditions for UCOS management interruption. 2. This also abolished the VIVI interruption mechanism.
Chapter 10. To put it simply, this distributed file is used to tell the compiler how to link the input file, how it is connected, where the linked file is loaded, and where it is actually running. The conversion from the loading address to the running address can be implemented by code or by using the functions provided by ads, that is, the following _ main () functions can help you do this job. _ Main () is a library function provided by ads. It automatically generates the handling code based on the distributed file that you write.
As for the specific mission of the _ main () function, let's talk about it later.
Next, let's take a look at the content of this decentralized file (we use the scat_ram.scf file ):
Load 0x30008000; load region
{
Ram_exec + 0; PC
{
Startup. O (init, + first)
* (+ RO)
}
Stacks + 0x100000 uninit; 64 Kbyte under l0 pagetable
{
Stack. O (+ zi)
}
Ram + 0
{
* (+ RW, + zi)
}
Heap + 0 uninit
{
Heap. O (+ zi)
}
Exception_exec 0 overlay; Exception Region
{
Exception. O (+ RO)
}
}
I will not explain the syntax and specific meanings of this decentralized file. This is a format file, which contains its specifications and is easy to understand. After the analysis, we found this problem, exception. O (that is, the interrupt vector table section) has different loading addresses and operation addresses. The _ main function should generate code automatically, because the _ main function is used below, however, the execution field of prediction_exec is overlay, and the code segment _ main function of this attribute does not matter. You can only implement the code handling function by yourself. Now let's go back and see how the handling code implements the handling.
First, several variables are introduced. These variables are provided by the compiler, that is, the load address and runtime address specified by the distributed file. Originally, if you want to migrate, you have to have the original home address and destination address. For detailed procedures, refer to the notes I wrote after the program.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
; Start main function in C Language
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
Import _ main
BL _ main; don't use main () Because ......
// Jump to the _ main () function execution. Check it out. Here is the _ main () function. It is not a common main () function. Why, the reason is as follows:
The _ main () function is a library function provided by the development environment of ads1.2. The main functions of this library function are as follows:
1. Generate the automatic handling code based on the distributed file you have written.
2. initializes database functions.
If the storage structure you use is complex, the _ main () function provides a powerful function, so you don't have to handle the code yourself.
In addition, the _ main () function also initializes the library function, which is also very important. After the library function is initialized, you can use the standard C library function for programming during programming, for example, printf () functions, and so on, of course, to implement these library functions on a fixed hardware platform, we also need to transplant the library functions. For more information about database migration, see the embedded application development .doc using the armstandard C library. For details about the _ main () function, refer to main
__Main.doc.
Regarding the _ main () issue, there is a library's initialization function _ rt_lib_init (). It is not very clear what initialization has been done. It is always assembly when debugging with axd, I didn't understand what it meant. I had time to debug it slowly, so I will summarize it.
_ Main () function will eventually jump to the main () function (that is, the common main function written by the user)
If the program you write contains the main () function, the compiler will use the default link _ main () function and main () function.
B. // jump to this command, that is, an endless loop. Generally, the program will not run here.
; **************************************** ************
; * The function for initializing stack *
; **************************************** ************
Import userstack
Import svcstack
Import undefstack
Import irqstack
Import abortstack
Import fiqstack
// The following code initializes the stack space in various modes of the ARM chip. It is used to allocate the allocated space address to the SP pointer in each mode, I think you should have understood the C language and stack in the basic knowledge, and also used the stack when the processor mode is changed. Remember, the system stack space is allocated now.
Initstacks
; Don't use dram, such as sans FD, ldmfd ......
; Svcstack is initialized before
; Under toolkit ver 2.50, 'msr CPSR, r1' can be used instead of 'msr cpsr_cxsf, r1'
Mrs r0, CPSR // read the CPSR value. The CPSR value can be modified in privileged mode, in privileged mode, you can use Mrs and MSR to modify the last five values of CPSR to change the mode.
Bic r0, R0, # modemask
ORR R1, R0, # undefmode | noint
MSR cpsr_cxsf, R1; undefmode
LDR sp, = undefstack // change to undefined mode, and then pay the high address of the allocated space to the SP pointer of this mode. Define these spaces in the stack. s file. You can check them out.
ORR R1, R0, # abortmode | noint
MSR cpsr_cxsf, R1; abortmode
LDR sp, = abortstack
ORR R1, R0, # irqmode | noint
MSR cpsr_cxsf, R1; irqmode
LDR sp, = irqstack
ORR R1, R0, # fiqmode | noint
MSR cpsr_cxsf, R1; fiqmode
LDR sp, = fiqstack
; Bic r0, R0, # modemask | noint
ORR R1, R0, # svcmode | noint
MSR cpsr_cxsf, R1; svcmode
LDR sp, = svcstack
// The setting method in other modes is the same as that in the first mode. The ARM chip runs in SVC mode. The default mode after the chip is started is SVC, now, the change is back to the SVC mode, and the IRQ and FIQ bits of CPSR are not interrupted.
; User mode is not initialized.
MoV PC, LR; the LR register may be not valid for the mode changes. // return. When BL initstacks is executed, the return address is saved in the LR register. It is accurate to the LR register stored in SVC mode, so the final switch mode is SVC to ensure that the mov PC is used, and the LR command can return normally.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
; End of startup. c
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;
End
Now, let's take a look at the current situation. The basic hardware initialization is complete, and the C language execution environment has been initialized. Now the main () function is skipped. Finally, we can see the cute C language. It's easy to understand. Haha. We are in the next stage. Important: it is in the disconnected state.