Home
General Guide
Renesas
Microchip
Texas Instruments
Analog Devices
ADSP21369 Register Header
ADSP21369 Design Notes
16-bit SDRAM
Plug-in Flash Driver
Boot Second Program
Mobile Devices
.NET Tips
Miscellaneous
Contact Us
Forum

How to Load and Run Second Program from First One

1 Introduction

 

In some cases, it is desirable to load and run another program from one program. For example, the first-run program is a utility that reads configurations through jumpers or commands from another machine, and determines operation modes, such as maintenance mode and normal operation mode. In maintenance mode, the utility may write application parameters to non-volatile memory, upgrade the application code, etc. In normal operation mode, the utility loads the real application and executes it. The app note EE-108 from Analog Devices addresses how to manage multiple applications in a single EPROM. That solution uses a multiple-application index table and whatever it takes to get a valid application number from 1 to 6. The main limitation is that the boot kernel size must not exceed 256 instructions. In our case, the first-run program is not as simple as a boot kernel, rather a complex application. Therefore, that solution is not ideal for this case.

You can develop the first-run program in the usual way, which means that you could utilize the library and the default kernel. The first-run program shall reside at the top of an external memory, such as flash device. The default boot kernel gets booted into the internal RAM first at power up or reset, it loads and parses the program to the RAM and runs it. The challenge is how to load and parse the second program from the first one. The default boot kernel can be used as a basis, and it needs to be included into your project, but certain modifications must be done properly to fulfill this task.


2 Boot Kernel Modification


To understand the assembly code of the boot kernel may not be easy, but I’ll show you what modifications are needed.

 

2.1 Two Labels

 

Here are the a few lines that appear at the beginning in the boot kernel source code 369_prom.asm.

         .SECTION/PM seg_ldr;       // the kernel must reside in a single input_section

          nop;nop;nop;
    reset:
          nop;                  //48-bit word at 0x90003
    final_init_loop:                   

          nop;                  //48-bit word at 0x90004


The seg_ldr section is loaded to the internal RAM block 0, 0x90000 – 0x900FF in 48 bits. The first three locations 0x90000 – 0x90002 are not used, that’s what the three NOP instructions are. Locations 0x90003 and 0x90004 are used as scratch registers for the boot kernel. The NOP instruction at “reset” and “final_init_loop” are placeholders. However, the two labels are significant, as the last section “final_init” in the boot kernel code refers to them to fulfill the magic that overwrites itself and restores the application code at EP0I interrupt vector location 0x90050. The external port channel 0 was used to load the image from the external memory to the internal RAM. Now since the boot kernel is part of the first-run program, it can’t be placed at the top of the RAM block 0, where the IVT of the first-run program resides. So the label “reset” and “final_init_loop” will not be at 0x90003 and 0x90004, respectively. Instead, we can remove the above lines from the source code, but define the labels in the ldf file as shown below.


    reset = 0x90003;
    final_init_loop = 0x90004;

Then, your boot kernel can refer to them by declaring them as externs.


    .extern reset;
    .extern final_init_loop;

The code that refers to the two labels is in the “final_init” section.

    DO final_init_loop UNTIL EQ;         // Set bottom-of-loop address (loopstack) to 0x90004
                                                       // and top-of-loop address (PC Stack) to the address of the next instruction
    PCSTK = reset;                            // Change top-of-loop value from the address of this instruction to 0x90003


You can verify that the two labels are at the correct locations from the Disassembly View as shown below.


                                                    


2.2 User Init Section

 

The user init section is where you shall initialize the DSP if that has not been done (you would usually do so in your program, so it shall have been initialized when the program reaches this point), and set up conditions for the DMA transfer.

There are two setups that are essential, AMICTL and EIEP0. Depending on your application, the AMICTL may have been configured differently. Anyway, in order to DMA the instructions from an external memory to the internal RAM, the data packing bit of the AMICTL must be enabled so that the DMA reads 32-bit data at a time. Secondly, the DMA external index register EIEP0 must be set to the start address of the application program. Please note that the byte index is different from the instruction index of the external memory, the former is 8-bit, and the later is 32-bit. For example, you application starts at sector 6 of the flash, say byte address 0x4030000, set EIEP0 = 0x4000000 + 0x30000 / 4 = 0x400C000.


2.3 RTI Instruction at EP0I Vector

 

The boot loader uses EP0 DMA to load the code and data from an external memory to the internal RAM. When a DMA transfer completes, it generates an interrupt. To service the interrupt and return immediately, it is best to set RTI instruction at the interrupt vector (0x90050). There are two ways to accomplish that. Let me show you how.


2.3.1 Set RTI Instruction at Runtime

 

To set the RTI instruction to EP0I vector location at runtime before the DMA mechanism starts, you don’t need to change the runtime header, i.e., the library can be used directly. Here is the assembly code.

   

    r11 = dm(SYSCTL);         //keep SYSCTL contents for restoration later
    r12 = BSET r11 BY 9;      //Set IMDW0 to 1 so that data width is 48-bit for instruction core write
    dm(SYSCTL) = r12;         //write to SYSCTL
    i12 = 0x90050;                //I12 holds the EP0 interrupt vector address 0x90050
    r0 = 0x0B3E0000;           //This is the upper 32 bits of rti opcode
    pm(i12, 0) = r0;               //executing this instruction writes 0x90050 with rti
    dm(SYSCTL) = r11;         //Must restore SYSCTL to ensure data width 32 bit again

To write instruction to memory, the data width must be 48 bits. Once the instruction is written, the data width must be restored to 32 bits.


2.3.2 Set RTI Instruction at Linker Time

 

If the first-run program does not use the EP0 interrupt, you may set the RTI instruction to EP0I vector at linker time. To do so, the default runtime header (36x_hdr.asm) must be replaced by your modified version.

    //___lib_P9I: INT(P9);         // Peripheral interrupt 9
      ___lib_P9I: rti;                 // used for EP0 DMA Interrupt

Please refer to ADSP-21369 Design Notes on how to modify the ldf file.


2.4 Memory Allocation

 

Since the boot kernel must be running while loading the application image, it is best to allocate a 256 (or less depending on the size) words (48-bit) at the bottom of the internal RAM block 3. It can’t be placed at the top of the RAM block 0, because that is where the default boot kernel resides at first and replaced by the IVT and startup code of the first-run program.

    MEMORY
    {
       …
       seg_ldr { TYPE(PM RAM) START(0x000e13d5) END(0x000e1554) WIDTH(48) }
    }

    SECTIONS
    {
        …
       seg_ldr
       {
            seg_ldr_space = .;          //start address of the modified boot kernel
            INPUT_SECTIONS(MyBootKernel.doj(seg_ldr))
       } > seg_ldr
    }


3 Jump to the Boot Kernel

 

Depending on your application, place a JUMP instruction to the modified boot kernel where you want to start loading the application image and running it.

If your caller is written in assembly, use the following code,

    .extern seg_ldr_space;
    jump seg_ldr_space;

If your caller is written in C language, use the following code:

    asm (".extern seg_ldr_space;");
    asm (" jump seg_ldr_space;");

The label seg_ldr_space is the start address of the modified boot kernel defined in the ldf file shown in the above section.

The boot kernel loads and parses the application image section by section according to the section header type, target address and section length. The last section is the application IVT and startup code that are placed to the top of the RAM block 0. Particularly, the instruction at EP0I vector is loaded to memory 0x90003 at the beginning of the last section transfer, and then saved to PX register. It is written to EP0I vector through an instruction

    

    PM(0, I8)=PX;              //I8 = 0x90050

 

at memory location 0x90004. The loop from “reset” to “final_init_loop” is executed twice by the following two instructions

   

    DO final_init_loop UNTIL EQ;

 

and

   

    JUMP reset (DB);

 

Right after the instruction PM(0, I8)=PX is executed, the second application program starts to run from location 0x90005.

 


 


* App notes and source code from Analog Devices are copyrighted by Analog Devices, and provided here for your reference only.