PortingHowto

Getting a working toolchain and DSLinux build environment

Start out by following the instructions at CompilingDSLinux. Before you can start compiling programs to run on DSLinux you have to have a working toolchain set up. If you can't figure it out, then read it again. Use Google if there's still something you can't figure out.

The cross-compilation shell

OK, have a working toolchain? DSLinux built successfully? Good.

In the top-level directory of the source tree, run

 make xsh

You'll see some environment variables being printed and a new fancy shell prompt. You're now in a special command shell with a cross-compilation environment.

Type the following (from now on $ means a command that you're supposed to type):

 $ echo $CC

It should output "ucfront-gcc arm-linux-elf-gcc".

Keep the shell open, we'll need it for a while still.

Compiling and running a test app

Now that we have everything set up, we'll start by getting something really simple to compile.

 $ mkdir user/helloworld $ cd user/helloworld $ cat <<"EOF" > helloworld.c

After you typed that you'll see "> ". That means the shell is expecting more input. Copy and paste the following into the terminal:

 #include <stdio.h> int main() { printf("Hello DSLinux World!\n"); return 0; }

Then press ENTER, type EOF and press ENTER again. If this didn't make sense you can use a text editor to create helloworld.c instead.

Now we'll compile helloworld manually (to do a proper port we have to set up a Makefile and do various other things):

 $ $CC $CFLAGS $LDFLAGS helloworld.c -o helloworld

If you didn't see any errors you should have a helloworld executable to run in DSLinux. To check you can type:

 $ file helloworld helloworld: BFLT executable - version 4 gotpic

If the file command doesn't output BFLT executable, then something went wrong. If file didgive you the right output, copy helloworld to linux/usr/bin on your CF/SD card and start DSLinux. Now run helloworld:

 $ helloworld Hello DSLinux World!

It worked! Now we'll move on to getting a "real" app running.

Porting an app

Getting the app to build

For this example I've chosen to port units, a useful app for unit conversion (kilometres to miles, etc). Download the units source tarball (units-x.xy.tar.gz) from http://www.gnu.org/software/units/units.html. At the time of writing the newest version was 1.86.

Go up one directory to the user/ directory.

 $ cd ..

Create directory for the program and change into it:

 $ mkdir units $ cd units

Unpack the units tarball, and rename the resulting directory to src

 $ tar zxvf /path/to/units-1.86.tar.gz units-1.86/... ...

(Anyone who is about to complain that /path/to/units-1.86.tar.gz doesn't exist...Please don't.)

 $ mv units-1.86 src $ cd src $ ls ChangeLog getopt.c Makefile.in parse.tab.c texi2man units.h configure getopt.h Makefile.OS2 parse.y units.c units.info configure.ac INSTALL makeobjs.cmd README units.dat units.man COPYING install-sh mkinstalldirs README.OS2 units.doc units.texinfo getopt1.c Makefile.dos NEWS strfunc.c units.dvi

The source code. But what do we do with it?

In this case we see that there is a configure script, which means that the app is using GNU autotools.

In such cases one is usually just supposed to type ./configure && maketo do a normal compile.

 $ ./configure checking for C compiler default output file name... a.out checking whether the C compiler works... configure: error: cannot run C compiled programs. If you meant to cross compile, use `--host'. See `config.log' for more details.

Indeed we did mean to cross compile, let's do what the script suggested. We'll also add --prefix=/usr, because that's the prefix in DSLinux where we'll put the app. In this case it is important because units has to know where to find a datafile. I use --host=${CROSS}because the ${CROSS} variable contains the prefix of the binaries in the toolchain.

 $ ./configure --prefix=/usr --host=${CROSS} ... (lots of output) config.status: creating Makefile

If you didn't see any error messages units is now "configured" for building.

 $ make

If you didn't see any error messages (warnings from the compiler are often OK) units should now be compiled. For good measure, check units, as was done previously with the helloworld program, using the file command. Again, if the file command did not output BFLT Executable, something went wrong. One reason for this may be that variables which say how to build the program have been changed in units' Makefile! Check the Makefile (see below) if it is redefining any variables already set in the DSLinux environment. Generally we do not want CC, CFLAGS or LDFLAGS to be redefined by the Makefile. We will be writing our own Makefile shortly, though, so don't panic. (This is just in case you want to test the program before the next step).

Testing the app

But now what? We can't use make install, so how are we supposed to know where binaries, datafiles, etc should be copied?

Answer: by inspecting the Makefile or installing it first. Preferably both. You shouldn't attempt to port programs you haven't first tested on your linux box.

A package for units should be available for your distro.

And so on. If a package isn't available for your distribution you can compile and install units yourself.

I use debian, so I typed dpkg -L unitsto get a list of files installed by the units package.

The files of interest for installing in DSLinux are units.dat (unit definitions) and units(binary).

Copy them to your CF/SD card and see if units runs.

 $ cp units.dat /path/to/card/linux/usr/share $ cp units /path/to/card/linux/usr/bin $ sudo umount /path/to/card

You can also transfer the files over wifi from DSLinux if you want to (just make sure you don't corrupt the files in the transfer). In DSLinux try to run units.

 $ units 2438 units, 71 prefixes, 32 nonlinear units You have: 1 mile You want: kilometres * 1.609344 / 0.62137119 You have:

Yay, it worked! To exit units press Ctrl-D (EOF). But it's no "port" yet.

We are now going to automate what we did above. To do so, we are going to write a Makefile.

What is a Makefile?

Makefiles describe how to build a program. Makefiles are parsed by a program called 'make'. Using information gathered from Makefiles, the make tool automates dependency handling during the build process. This means that it is not neccessary to compile a whole project all over again just because a change was made to a single file, for example. Instead, make will figure out what parts of the project need to be rebuilt to integrate the change.

The whole DSLinux build process is handled by make.

Makefiles have very simple syntax. There are macros and rules.

Macros

You can can create macros like this:

 TOP_DIR = /top

and expand macros like this:

 $(TOP_DIR)

So wherever you write $(TOP_DIR) now, make will see /topinstead.

Rules

Makefiles define targets that are to be built. Targets are usually files to create during the build process. Each target can have dependencies (other files needed to create a given file, for example). A Makefile rule defines the commands used to create a target. The commands used to create a target are run if the dependencies of the target have a newer timestamp than the target, or if the target does not exist at all. A complete rule looks like this:

 target: dependencies (optional) <tab> command1 <tab> command2 <tab> command3 <tab> command4

The tab character is very important. It is used to indent the commands used to create a target. Note that you cannotuse spaces - use tabs. If your editor inserts several spaces instead of a tab character, change the settings or use another one.

Targets need not necessarily be files. They can also be "phony" targets, that do not produce a file but are used to run a couple of commands. Phony targets may have dependencies, but they do not produce a file that has their name.

An example

This example Makefile was derived from the Makefile for the fwver app bundled into DSLinux. fwver was made by pepsiman. The example Makefile may not be obvious at first glance. It makes use of some make magic we have not yet talked about. For example, make knows how to make a .o file from the .c file. We do not need to tell it how to do that.

 # Makefile for helloworld EXEC = helloworld OBJS = helloworld.o # declare these targets as phony, so make will not try to look # for files named like them: .PHONY: all romfs clean # 'all' is a phony target. It is called when the build process # enters this directory and wants to build the program. all: $(EXEC) # This is the rule that builds the hello world executable. $(EXEC): $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS$(LDLIBS_$@)) # The romfs target is also phony. It is used to copy your app # into the DSLinux filesystem image. romfs: $(ROMFSINST) /bin/$(EXEC) # clean is another phony target that removes every file generated # during the build. clean: rm -f $(EXEC) *.elf *.gdb *.o

Creating a Makefile for the app

We will now create a Makefile that does the exact same thing we did above. Note that the elaborate comments in this example Makefile should be removed before submitting it in a patch. Now, here it is:

 # Makefile for units # 'all' is the default target. It will be called # by the DSLinux build system. # In our case, it depends on the hidden file .compiled, # which is created by one of the targets below. all: .compiled # Before we can compile units, we need to run the # configure script. But we do not want to run configure # more often than needed. The trick is to create # a file just after running configure, and have make # only run the configure script if the file does not # already exist. In our case, we name that file # .configured. It is a hidden file because its name # starts with a dot, so you can only see it in the # output of ls -a, not just ls. Here is the rule that # runs configure and creates the .configured file if # it does not exist. # Note: $@ is a special pre-defined macro that expands to # the target of the rule. Hence, in this rule, $@ expands # to .configured. .configured: cd src && ./configure --prefix=/usr --host=${CROSS} touch $@ # Once the .configured file exists, we know that the # program has been configured sucessfully. Now we can # run make in the source code directory of the program # to compile it. $(MAKE) is a macro that expands to the # name of the currently used make program. We use it instead # of just 'make' in case the make binary has been renamed # to something else for some reason. The '-C src' argument # tells make to change into the src directory before doing anything. # After compiling, we create the .compiled file, which satisfies # the 'all' target above. .compiled: .configured $(MAKE) -C src touch $@ # The clean target gets called when make clean is run. # We should only run make clean in the source tree # of the program if a Makefile has been generated # by the configure script already. This is achieved # by the test -f src/Makefile command. # The minus (-) preceeding the test command tells # make to ignore errors reported by the command, # and continue with the next line nonetheless. # Thus we remove the .configured and .compiled files # in any case, which causes everything to start from # scratch again the next time make is run. clean: -test -f src/Makefile && $(MAKE) -C src clean rm -f .configured .compiled # We have to add a romfs target so units and units.dat gets # copied to the right place automatically. romfs: .compiled $(ROMFSINST) ./src/units /bin/units $(ROMFSINST) ./src/units.dat /usr/share/units.dat # The dependencies of the .PHONY target are those targets # that do not create a file that has their name. # We have to add them to the special .PHONY target otherwise # they wouldn't be built if a file that has their name exists # or gets created or updated somehow. .PHONY: all clean romfs

units.1 is a man page and units.info is a info document, neither of which are used in DSLinux. So we ignore those.

Now save the Makefile.

Test the Makefile

 $ make clean $ make # to make sure it still works $ make # to make sure it does not run configure again this time $ make clean

Makefiles for C++ programs

If you want to build a C++ program, look at /user/rtest. This is a sample & well documented "Hello World". Note that in the config directory, you need to add the requirement for uClibc++.

Integrating the app with the DSLinux build system

Now that we have units properly set up, it's time to tell the DSLinux build system about it.

Type exit, and you'll be back in the dslinux directory.

Open config/config.in in a text editor.

 $ myeditor config/config.in

Search for tripwire, and add the following to the next line (config.in is sorted alphabetically):

 bool 'units' CONFIG_USER_UNITS_UNITS

Open config/Configure.help in a text editor.

 $ myeditor config/Configure.help

Add the following in an appropriate place:

 CONFIG_USER_UNITS_UNITS The Units program converts quantities expressed in various scales to their equivalents in other scales.

Finally open user/Makefile in a text editor.

 $ myeditor user/Makefile

Add the following in an appropriate place:

 dir_$(CONFIG_USER_UNITS_UNITS) += units

Now we have to make sure units gets built.

 $ make menuconfig

Navigate to Kernel/Library/Defaults Selection, Customize Vendor/User Settings and press y. Choose Exit, Exit, Yes. You'll now get another dialog. Navigate to Miscellaneous Applications, units. Press y and choose Exit, Exit, Yes.

Type make to build DSLinux.

 $ make

If you did everything correctly the build should now be done without any fatal errors and units should be in the image.

 $ ls -lh images/linux/usr/bin/units images/linux/usr/share/units.dat -rwxr----- 1 jss jss 45K 2006-11-22 23:16 images/linux/usr/bin/units -rw-r----- 1 jss jss 201K 2006-11-22 23:16 images/linux/usr/share/units.dat

It worked! We're now very close to having a patch to submit (first you should test the app extensively).

Submitting your port

What is needed and why?

To add a new program, we need the unmodified sources of it so we can import them into the repository on a special branch. This way we have access to the base line dslinux-specific patches for the program are based on. Having the original source around also helps when the program is being updated to a new version later.

We also need a patch that make the source compile with dslinux. This patch gets comitted to the main branch (trunk) in the Subversion repository, so if people checkout the trunk (as they usually do) they will get the ported version of the program by default.

The patch should include any modification made to the unmodifed source code of the program, and also changes made to Makefiles and other files in the source tree to tie the program into the build system.

Creating the patch

Please read CreatingPatches before reading on here!

First, make sure to clean out any binary files created during the build process of your program. If there are binary files in your patch the patch won't work!

At the top-level directory of the DSLinux source tree, type the following:

 $ svn diff config/config.in config/Configure.help user/Makefile > /tmp/units.diff

You now have a file called /tmp/units.diff containing a unified diff of our changes to the DSLinux build system.

Now we have to add to that file the changes to the units program itself. But first, clean out the units directory so we don't get any junk in the diff. You should also check for backup files created by your text editor. Unpack the original units tarball, and then run diff on the original source tree and the one you modified:

 $ make -C user/units clean # remove all junk (hopefully) $ tar -C user/units -zxvf /path/to/units-1.86.tar.gz # extract original source code $ diff -urN user/units/units-1.86 user/units/src | less # view diff

Use enter/space/pgup/pgdown to browse through the diff and q to exit less.

Hmm, the diff looks OK but there are two junk files left that were generated by the configure script.

Delete those:

 $ rm user/units/src/config.status user/units/src/config.log

Run diff again.

 $ diff -urN user/units/units-1.86 user/units/src | less

Looks OK!

Now append the diff to /tmp/units.diff:

 $ diff -urN user/units/units-1.86 user/units/src >> /tmp/units.diff

Also, add the Makefile you created to the patch:

 $ diff -u /dev/null user/units/Makefile >> /tmp/units.diff

Now send your patch (/tmp/units.diff) to the dslinux-devel mailing list. Include a URL to the original sources of the application in the body of your mail.

Porting a library

For this tutorial, I show you how to add the library libmad to DSLinux. It is a good idea to read about porting an applicationfirst.

Create a directory

uClinux libraries are created in the lib directory:

 cd lib mkdir libmad

Now put all files for this library into the libmad folder.

Modify the lib Makefile

You have to add the new library "libmad" to lib/Makefile. First add the libmad directory to the makefile:

 dir_$(CONFIG_LIB_MAD) += libmad dir_$(CONFIG_LIB_MAD_FORCE) += libmad

Then add the directory to the LINKLIBS variable in the makefile:

 $(ROOTDIR)/lib/libid3tag/*.a \ $(ROOTDIR)/lib/libmad/*.a

Don't forget to add a "\" to the line before!

Add the include file

Almost every library adds an include file to export its services. This include file must be copied/linked into the uClinux include path. Edit include/Makefile and add "mad.h" to the LINKHDRS variable:

 $(ROOTDIR)/lib/libid3tag/id3tag.h,. \ $(ROOTDIR)/lib/libmad/mad.h,.

Don't forget to add a "\" to the line before!

Modify the config files

Edit config/config.in. Add a configure option for libmad to the Library configuration:

 bool 'Build libmad' CONFIG_LIB_MAD_FORCE

If you are adding a program using this library, you must add a dependency to build libmad if the program is selected:

 bool 'madplay' CONFIG_USER_MADPLAY if [ "$CONFIG_USER_MADPLAY" = "y" ]; then define_bool CONFIG_LIB_ZLIB y define_bool CONFIG_LIB_ID3TAG y define_bool CONFIG_LIB_MAD y fi

In Configure.help, add a help text for libmad:

 CONFIG_LIB_MAD_FORCE Embedded MP3 decoder. Only enable this if you want to force the library to be built. The Config will make sure this library is built if it is needed.

Write a Makefile for the library

Each library needs a Makefile for building. Most libraries have their own build mechanism. If it uses a configure script, put the source code of the library into a subdirectory of the lib/yourlibrary directory, as described above.

You can also create a Makefile that compiles everything manually. This is much more work than the method described aboveand should only be used for libraries and applications that do not come with a configure script.

Here is an example Makefile that compiles the libmad library manually:

 # Makefile for libmad in uClinux # ============================== # Special compiler options CFLAGS += -DHAVE_CONFIG_H -DASO_INTERLEAVE1 -DASO_IMDCT -DFPM_ARM CFLAGS += -Wall -O -fforce-mem -fforce-addr -fthread-jumps CFLAGS += -fcse-follow-jumps -fcse-skip-blocks -fexpensive-optimizations CFLAGS += -fregmove -fschedule-insns2 -fstrength-reduce -fomit-frame-pointer # Header files INCS:= config.h version.h fixed.h bit.h timer.h stream.h frame.h \ synth.h decoder.h global.h layer12.h layer3.h huffman.h \ D.dat imdct_s.dat qc_table.dat rq_table.dat sf_table.dat # Object files OBJS:= version.o fixed.o bit.o timer.o stream.o frame.o synth.o \ decoder.o layer12.o layer3.o huffman.o imdct_l_arm.o .PHONY: all all: libmad.a libmad.a: $(OBJS) $(INCS) $(AR) rv $@ $(OBJS) $(RANLIB) $@ # We only link statically in DSLinux, hence libraries are never installed. # Thus the romfs target does nothing. .PHONY: romfs romfs: .PHONY: clean clean: -rm -f *.[oa] *~

As you see, it's pretty simple. If the library has used autoconf/automake, you must create a file config.h. Remove all files not used for the DSLinux build process.

Add the config option to all config.vendor files

Add a line

 # CONFIG_LIB_MAD_FORCE is not set

to all config.vendor files in vendors/Nintendo/*

Now start with the usual make menuconfig / make cycle.