ARM Programming

Running ARM Native Code on an OS5 Device

        New devices running the Palm operating system v5.0 run on ARM based processors, no secret there.  But writing code that takes advantage of this new processor can be an issue for us open source types.  Palm really isn't making much of an effort to help out developing the open source ARM tools.  No big surprize there, they really don't seem to be all that bright these days.  And the prc-tools folks are starting to get together an environment that should be workable, but they're still plugging at it.  So in the meantime I'm going to describe how to hack together your own environment for building ARM native code using free tools.  This pulls together info from Palm itself, the armlet-forum newsgroup at Palm, documentation at the Palm site about running native code (the Palm Programming Companion docs), and general embedded Linux programming info.  The docs made available by Palm are mostly from Owen Emry, who seems to be fighting a one man crusade from within Palm to get information made available.  His posts to the armlet-forum are particularly interesting, so if you're reading the old mesages there check them out.  But in general I think that having to read forums to get info about doing something just bites.

Running a test ARMlet

        So, after having read through the description of calling ARMlets given in the Programmers Companion Volume 1, it seems like it should be pretty simple to test out this ARMlet dealie.  They say that all we need to do is pass a pointer to the function to call into this new PceNativeCall() entry point and the code will get run.  Sounds simple enough, but I always like to test stuff out for myself to make sure that there's nothing else going on that got left out of the docs.  I also want to get something setup at some point for ARMlet support within OnBoardC, so to test out calling a native function I tried it within an OBC project.  There is a binary ARMlet included with the SDK5.0 examples, unfortunately it's only included with the larger downloads.  It's not in the prc-tools version of the sdk, so I downloaded the non-installer version of the 5.0 sdk and found the armlet-simple.bin file in there. 

        The sample armlet is very small, just 32 bytes in length.  The programmers companion says that including the function as data within the calling app works, there are no requirement that it be located within a special section or passed through any API calls with restrictions.  We just need to make sure that the data is aligned to a 4 byte boundary, which OnBoardC seems to do for us consistently.  The programmers companion also says you'll have to byte swap the integers, but I just included the code as a byte array instead.  There's no need for swapping if you do this.  OnBoardC doesn't include the new function call PceNativeCall(), and needed to have some of the types defined for it, but those are easy to add in.  I've uploaded the example code to the files area for the OnBoardC Yahoo group.  Here's the relevant code, which I just added to the HelloWorld sample OBC program.  First are the new definitions and inclusion of the actual applet text from a header file:

    #include "nativefunc.h"

    typedef unsigned long Call68KFuncType( const void *emulStateP,
                unsigned long trapOrFunction, const void *argsOnStackP,
                unsigned long argsSizeAndwantA0 );
    typedef unsigned long NativeFuncType( const void *emulStateP,
                void *userData68KP, Call68KFuncType *call68KFuncP ); 
    #define sysTrapPceNativeCall (0xA45A)
    UInt32 PceNativeCall( NativeFuncType *nativeFuncP, void *userDataP )
and then the call to the ARMlet, which I put in the application startup procedure:
    reslt = PceNativeCall((NativeFuncType *)&armFuncCode,
                        (void *)PilotMain);
The include file nativefunc.h holds the actual ARMlet function.  I made it a seperate file so that I could generate nativefunc.h under Linux, I just made a small tool to take a binary file and turn it into a byte array.  Then all I have to do is transfer the DOC file over to the palm to run it.  This is what the file generated from armlet-simple.bin looked like:
    unsigned char armFuncCode[] = {
        0x0d, 0xc0, 0xa0, 0xe1,
        0x00, 0xd8, 0x2d, 0xe9, 
        0x04, 0xb0, 0x4c, 0xe2,
        0x01, 0x00, 0xa0, 0xe1,
        0x00, 0xa8, 0x5b, 0xe9,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00
And that's it.  The sample ARMlet just passes back as the return value the same value that's passed into the PceNativeCall() function.  This is easy enough to verify by printing values to the screen.  But still, I wanted an ARMlet that did something different.  I wanted an ARMlet that returned a known value included nowhere else in the source so that I knew I wasn't seeing just random side effects.  I wanted further validation.  So I setup an ARM compiler to make my own ARMlets under Linux.  It's not very hard to do, but the information isn't really extremely easy to find.  And the information that does exist seems to be at least a little flawed in places.

Building an ARM Compiler

        Building a cross compile toolchain can be quite a difficult proposition.  But that's normally the case because getting the compiler setup to deal with the particularities of the environment you're compiling for can be a pain.  When we compile ARMlets all we want is bare bones raw ARM code.  It's actually a little bit easier to generate a compiler that does this than it is to build a cross compiler that needs to interact with a standard C library for the environment it's going into.  It's still not as simple as just building gcc for the same environment as it's being built on though.  We need to give gcc a few extra options when we configure it, and then only run specific targets for the make.  I downloaded a package from the Palm site which had an armlet library, example build scripts, and sample armlet code in it.  It has a building_arm-elf-gcc.txt file in the top level directory.  I used the same versions as it gave there for binutils and gcc, I just had to modify the build for gcc a bit.  Here's the process assuming you're installing and building under /home/reverend/armlet:
    mkdir -p /home/reverend/armlet/src
    mkdir /home/reverend/armlet/build
    mkdir /home/reverend/armlet/dist
    cd /home/reverend/armlet/src
    tar jxvf binutils-2.13.tar.bz2
    tar zxvf gcc-3.1.1.tar.gz
    mkdir /home/reverend/armlet/build/binutils
    cd /home/reverend/armlet/build/binutils
    ../../src/binutils-2.13/configure --target=arm-elf \
    make all
    make install
    mkdir /home/reverend/armlet/build/gcc
    cd /home/reverend/armlet/build/gcc
    export PATH=$PATH:/home/reverend/armlet/dist/bin
    # Up to this point we're exactly the same as the build instructions that
    # come with the armlet library and example.
    ../../src/gcc-3.1.1/configure --target=arm-elf \
        --prefix=/home/reverend/armlet/dist --without-headers --with-newlib \
    make all-gcc
    make install-gcc
What we've done for the configure of gcc is told it that it should compile the compiler in a way so that it doesn't expect to have access to any information about the target architecture runtime libraries at all.  It builds just a bare bones compiler which can only make little standalone lumps of object code.  Normally that's a problem, since you want to make executable programs with a compiler.  But not so for us!  All we want are little lumps of standalone ARM code, cause that's what ARMlets are.  I've tried those cross compile commands under RedHat 7.2 and on my desktop system (which doesn't run a distribution at all, so it makes a good test bed normally), and is seems to work perfectly.

Build Scripts

        The ARMlet example includes scripts which pass the necessary options into the compiler and linker to generate the raw object formats we want.  Even with our minimal compiler, when you compile a .c file to generate a .o it generates ELF format files.  We want raw flat position independant object code.  So there are a couple of options to pass in.  There are also some restrictions to let the compiler know about, like registers which are used by the PACE environment.  Proper options to the compiler keep the object code from touching stuff that it shouldn't.  Using the scripts given in the examples and getting the armlet library compiled was a pain though.  I had to manually modify all sorts of paths for header files and libraries.  It would be really cool to put different build scripts up here or a description of what needs to be done to compile the libs... need more time for that.
Last updated $Date: 2003/08/31 22:23:05 $