Making Multiple Images

You can generate images as described in mkifs in the Utilities Reference and in OS Images in Building Embedded Systems.

When you use mkifs to build an OS filesystem, you can specify that particular executables will either execute in place (normally in flash) or be copied to RAM and executed there (see Notes on XIP versus copy,” in the mkifs entry in Utilities Reference). Executing in place saves a bit of RAM by avoiding the copying of the code and/or data segment of an object from one DRAM location to another.

But what if you want some executables to run from flash and others from RAM? That's what multiple images are used for.

Multiple image filesystems are typically used in execute-in-place (XIP) systems to separate executables that must run directly from flash (to conserve RAM) and those that should be copied and then executed from RAM (for performance).

Simply put, you create two separate images and then stitch them together. This is what the OS image will look like:

The boot image filesystem will be run from RAM to improve performance and the XIP image filesystem will be run from flash to conserve RAM.

Each of the three sections must begin at a memory page boundary (typically 4 KB). The IPL code will execute the OS in the first image.

Note:

Restrictions on XIP image filesystems

There are some restrictions in XIP image filesystems:

Mounting an IFS

Once the kernel is running, you can mount image filesystems. The syntax for this is:

mount -tifs -o offset=XXXXXXXX,size=YYYYYYYY /dev/mem /mountpoint

The variables are:

XXXXXXXX
The physical address of an area of contiguous memory (linear) that contains a binary image filesystem.
YYYYYYYY
The size of the image, rounded up to the next highest page boundary.
/mountpoint
The name of the mountpoint for the image filesystem.

For example, suppose an image filesystem starts at physical address 0x600000 and is a size of 0x23A5C. You can use the mkifs -v command to determine the size when you build the image. The next highest 4-KB page boundary is 0x24000. Suppose we want to mount this image as /ifs2. The syntax would be:

mount -tifs -o offset=0x600000,size=0x24000 /dev/mem /ifs2

Using a second IFS

There are several options for using a second image filesystem:

In this technical note, we'll use the third option.

Example: mounting an IFS on a board

We'll use a fictitious board in this example. This board has a flash part that's 32 MB in size and sits in memory space from 0xFE000000 to 0xFFFFFFFF. The reset vector jumps to 0xFE000000.

You can design the flash any way you wish. Here's the layout that we'll use in this example:

From To Contains Size
0xFE000000 0xFE03FFFF IPL 256 KB
0xFE040000 0xFE2FFFFF Boot image 1 (os1) 3 MB − 256 KB
0xFE300000 0xFE3FFFFF Boot image 2 (os2) 1 MB
0xFE400000 0xFEFFFFFF Flash filesystem mounted as / 28 MB

The IPL has been changed to start searching for os1 at 0xFE040000. By default, the IPL will search from 0xFE010000 to 0xFE030000.

Sample buildfiles

First we need to make the os1 and os2 boot images. Let's look at the buildfiles.

The os1.build file looks like this:

[image=0x20000]  
[virtual=armle-v7,binary +compress] .bootstrap = {

# reserve 4M of ram at the 6M physical address point. and zero it out (0 flag)

    startup-my_board-dual -vvv -r 0x600000,0x400000,0

   #######################################################################
   ## PATH set here is the *safe* path for executables.
   ## LD_LIBRARY_PATH set here is the *safe* path for libraries.
   ##     i.e. These are the paths searched by setuid/setgid binaries.
   ##          (confstr(_CS_PATH...) and confstr(_CS_LIBPATH...))
   #######################################################################
   PATH=:/proc/boot:/bin:/usr/bin LD_LIBRARY_PATH=:/proc/boot:/lib:/usr/lib:/lib/dll procnto-600 -v
}
[+script] .script = {
    procmgr_symlink ../../proc/boot/libc.so.2 /usr/lib/ldqnx.so.2

    #######################################################################
    ## my_board
    #######################################################################
    display_msg Welcome to QNX Neutrino on my_board (OS1)
    devc-ser8250 -c 132000000 -u 3 -e -F -S -b115200 0xf0002400,67
    waitfor /dev/ser3    
    reopen /dev/ser3    

    SYSNAME=nto
    TERM=qansi
    HOME=/
    PATH=:/proc/boot:/bin:/usr/bin:/opt/bin
    TERM=qansi
    LD_LIBRARY_PATH=:/proc/boot:/lib:/usr/lib:/lib/dll:/opt/lib

    [+session] ksh &
}

[type=link] /bin/sh=/proc/boot/ksh
[type=link] /dev/console=/dev/ser1
[type=link] /tmp=/dev/shmem

[perms=+r,+x]
libc.so 
fpemu.so.2

[data=c]
devc-serpsc

ls
ksh
pipe
pidin
uname
slogger2
slog2info
slay
mount
cp
mv
hd
spatch
#/usr/lib/terminfo=/usr/lib/terminfo
dumpmem=./dumpmem/dumpmem
umount
devf-my_board=/root/workspace/bsp-my_board_devf-my_board/my_board/arm/le/devf-my_board
flashctl

The os2.build buildfile is given below. Note that this image filesystem is always read-only.

# set search path for files
[search=/usr/qnx630/target/qnx6/armle-v7/bin:/usr/qnx630/target/qnx6/armle-v7/sbin:/usr/qnx630/target/qnx6/armle-v7/usr/bin:/usr/qnx630/target/qnx6/armle-v7/usr/sbin]

# code=uip means that the code will be run from the image filesystem 
# (Use In Place).
#
# +raw means to not strip the programs in any way

[data=copy code=uip]
[perms=+r,+x]

# my files to include

# these files will reside directly under the mountpoint as specified
# with the mount command

[+raw] /raw/hd=hd
       /unraw/hd=hd

devc-ser8250
io-pkt-v4-hc

# these files will be placed 

/bin/use=use

Unlike the build script for a bootable OS image, the XIP script doesn't have a boot section, nor does it have a boot script. If you run mkifs -v on this buildfile, you'll see that the first entry is the image filesystem header rather than the startup code normally found in a bootable image.

Programming into flash

In os1.build, the startup code reserves 4 MB of RAM at a physical address of 0x600000. This will be where the os2 image filesystem will reside. The operating system won't use this memory range for other programs.

You'll need to program the srec files that will be created into flash. We'll assume that the best way to do this with our fictitious board is to use the dBUG tool and the fp command.

To program os1.srec:

  1. Make sure your tftpd server is working.
  2. Change the filename parameter in dBUG to look for this filename. For example:
    dBUG> set filename /xfer/os1.srec
    

    You can store this setting using the store command.

  3. Download the S-records:
    dn
    
  4. Program the S-records:
    fp 0 fe040000 fe2fffff 20000
    

Both the os1.srec and os2.srec files are created with offsets of 0x20000. This allows them to be downloaded in to RAM using the dBUG tool and then programmed. Be sure to include the offset (20000) when you flash program (fp).

Use the same programming technique for os2.srec, but change the start and end address:

fp 0 fe300000 fe3fffff 20000

Putting the images together

Note the following about the os1.build file:

It's possible to mount multiple image filesystems at the same mountpoint. For example:

mount -tifs -o offset=0x600000,size=0x24000 /dev/mem /
mount -tifs -o offset=0xa00000,size=0x31000 /dev/mem /

This mounts two additional images at the root (/) mountpoint. QNX Neutrino supports union filesystems. If there are duplicate paths to the same file, then the last image to mount will be the one that's used.

Test program

Here's a testing program called dumpmem.c that you can include in your first boot image, so that you can look at physical memory locations:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <inttypes.h>
#include <string.h>
#include <sys/neutrino.h>

int
main( int argc, char **argv )
{

    char *ptr;
    size_t len;
    uint64_t addr; 
    long int ltmp;
    char c;
    
    int i;

    if ( argc < 3 ) {
        fprintf(stderr,"enter addr and size\n");
        exit(1);
    }

    addr = strtoull( argv[1], NULL, 0 ); 

    ltmp = strtol( argv[2], NULL, 0 ); 
    len  = ltmp; 
    
    fprintf(stderr,"Dumping %d (0x%x) bytes at addr 0x%llx\n",
        len, len, addr );


    ThreadCtl( _NTO_TCTL_IO, 0);

    ptr = mmap_device_memory( 0, len, PROT_READ|PROT_WRITE|PROT_NOCACHE, 0, addr );
    if ( ptr == MAP_FAILED ) {
        perror( "mmap_device_memory for physical address failed" );
        exit( EXIT_FAILURE );
    }

    for ( i=0; i < len; i++ ) {
        c =(*(ptr+i) & 0xff);
        if ( isprint(c) ) 
            fprintf(stderr, "%c", c );
        fprintf(stderr, "[%x] ", c );
        if ( ( i % 20 ) == 0 )
            fprintf(stderr,"\n");
    }
}

For example, to print the first 100 bytes at address 0x600000 (to look for the “imagefs” signature), type:

dumpmem 0x600000 100

See also

mkifs, mkimage, and mkrec in the Utilities Reference

OS Images in Building Embedded Systems