Imbrium Logo

Building Apps with uClinux-dist and m68k-elf


Troughout this documentation, my root uClinux directory is /home/uclinux/ and below that, I have the directories that apply to uClinux, such as:

	# cd /home/uclinux
	# ls -l
	drwxr-xr-x    2 uwe      uwe          4096 Sep 16 08:44 bin
	drwxr-xr-x    4 uwe      uwe          4096 Sep 16 09:55 httpd
	drwxr-xr-x    6 uwe      uwe          4096 Sep 16 08:51 src
	drwxr-xr-x    8 uwe      uwe          4096 Sep 10 19:03 toolchain
	drwxr-xr-x   15 uwe      uwe          4096 Sep 11 16:22 uClinux-dist
	[...]
	#

Also, my target board is a uCdimm from Arcturus Networks based on a 33MHz 68VZ628 controller from Motorola, with 2MB Flash, 4MB RAM and a CS8900 Ethernet controller.


Prerequisites for compiling

Entering a user application into the configuration scheme so it will be recognized by the the top-level configuration script involves a few steps.

Of course, you need to have a uClinux-distribution. Then, you will need the binutils toolchain and the gcc compiled for the target platform. The easiest here is to get the pre-compiled m68k-elf-tools. I would definitely recommend this binary.tgz since the by-hand compilation of the gcc, the elf2flt, etc. turned out to be somewhat difficult to me.

You have to make absolutely sure that you $PATH points to the cross-compiler binutils, the gcc being used and the correct elf2flt and genromfs binaries. In case you are using the pre-compiled binaries, this is /usr/local/bin

For some, this is one of the biggest problems and quite actually, it is very convenient to either use a stand-alone development system for this or create a user on your machine solely for developing uClinux apps for your target.


Preparing your program source directory

I have used Larry Doolittle's ntpclient program as an example. Larry, I hope you don't mind :)

First, I put the program sources into /home/uclinux/uClinux-dist/user/ntpclient and wrote a Makefile. Ok, I just took an existing one and modified it to make it look like this:

	# Makefile for ntpclient
	# /home/uclinux/uClinux-dist/user/ntpclient/Makefile
	
	EXEC = ntpclient
	OBJS = ntpclient.o phaselock.o
	CFLAGS += -D__USE_BSD
	all: $(EXEC)
	$(EXEC): $(OBJS)
		$(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
	romfs:
		$(ROMFSINST) /bin/$(EXEC)
	clean:
		rm -f $(EXEC) *.gdb *.elf *.o
	test: $(EXEC)
		./$(EXEC) -d -r < test.dat

The next thing is done in the file /home/uclinux/uClinux-dist/config/config.in. I have simply added an extra entry in the file for my project, which is called ntpclient:

	#############################################################################

	mainmenu_option next_comment
	comment 'NTP Client'

	bool 'ntpclient'                        CONFIG_USER_NTPCLIENT

	endmenu

	#############################################################################

Now edit the /home/uclinux/uClinux-dist/user/Makefile to include my ntpclient program in the make process. Add a line at the end of all the dir_% statements to include your program whose sources should of course reside in user/ntpclient (OK, a link is good enough, too).In this case:

	# Uwe
	#
	dir_$(CONFIG_USER_NTPCLIENT) += ntpclient
	#

Running a top-level make menu/x/config will now try to configure the kernel. First, I look at the output that is issued on the console and then I can see:

	*
	* NTP Client
	*
	ntpclient (CONFIG_USER_NTPCLIENT) [N/y/?] (NEW)

The make process recognized the program. So I simply enter Y at the prompt and then you should see:

	make dep
	make clean
	make	
	[...Lots of output...]
	m68k-elf-gcc -m68000 -Os -g -fomit-frame-pointer
	-DCONFIG_LINEO -Dlinux -D__linux__ -Dunix -D__uClinux__ -DEMBED
	-I/opt/dragonix/uClinux/lib/uClibc/include
	-I/opt/dragonix/uClinux/lib/libm
	-I/opt/dragonix/uClinux
	-fno-builtin -m68000 -msep-data
	-I/opt/dragonix/uClinux/linux-2.4.x/include
	-D__USE_BSD
	-c -o ntpclient.o ntpclient.c

Hints for make

In case you need to rebuild just all the user applications, do a top-level

	# make user_only

If you make changes to your user application and do not want to go through the build process for all the user apps, add the following to user/ntpclient/Makefile at the beginning:

	UCLINUX_BUILD_USER = 1
	include $(ROOTDIR)/config.arch

and then run

	# make user/ntpclient_only

Here is a list of those time-saving top-level make commands:

	make dep				# Dependencies
	make user_only			# makes all the user apps
	make user_clean			# cleans the user directories
	make user/appdir_clean	# cleans one appdir directory (see above for Makefile)
	make linux				# just the linux kernel image
	make lib_only			# Um.. libs only? :)
	make romfs				# takes the user stuff and creates the romfs
	make image				# uses genromfs to create the image.bin

Here now you can see the compiler options that are being used. I put some line breaks in the output to see things better. This is a lot and I'm just getting into understanding what they all mean.

As you can see from the line that says -I/home/uClinux/uClinux-dist/lib/uClibc/include, the uClibc is used.


Notes: Problems when compiling ntpclient

I grabbed this from Larry Doolittle's mail in the uclinux-dev mailing list:

On Pentium and higher, the system clock uses the processor cycle clock,
both because it is higher resolution and takes less time to read
than the RTC chip. This does add complexity, because the jiffy
interrupt comes from the RTC still. This is not what I was referring to.

The code of interest is in arch/*/kernel/time.c. I will refer to
the i386 branch here, because that is what I'm familiar with
(and because the 2.0.36 tree I have unpacked in front of me doesn't
have an arch/or1k directory :-p).

The do_gettimeoffset() routines compute how many microseconds have
elapsed since the beginning of the current jiffy.
The function do_fast_gettimeoffset() is the Pentium-specific version.
Ignore that and look at do_slow_gettimeoffset(). This reads the
RTC value and scales it to microseconds.

Now look at timer_interrupt(). The "11 minute" code is there, ignore
that and go to do_timer(), in the non-arch-specific kernel/sched.c.
Eventually that thread of control passes to update_wall_time_one_tick().
_That_ is what I was referring to. Depending on the settings configured
with the adjtimex() system call, we get to control the kernel's perception
of the length of a jiffy to much better than a microsecond.

If the DragonBall's crystal is 32.000 kHz, and you divide by 320 to get
the interrupt, time_adjust_step is 10000, and time_adj is close to
zero. You are expected to make small adjustments to time_adj if you
find your system time runs fast or slow, this is what NTP is supposed
to do automatically).

If the DragonBall's crystal is 32.768 kHz, and you divide by 328 to get
the interrupt, time_adjust_step is 10010, and time_adj is close to
(1e6/(32768/328)-10010)*2^22, or -983040 (I think I got that right).
Again, small corrections to time_adj are possible and recommended.

The user-space access point to time_adjust_step and time_adj is adjtimex(2).

There is a command line shell for this by Dick and Van Zandt, its man
page is in section 8 on my Red Hat machine. xntpd is the normal tool
for using this feature to phase lock the system clock to UTC, that's
a very heavyweight beast. I have about 3/4 of the work done in my
ntpclient program, which works (as far as it goes) on uCLinux. I have
mailed it to a few people for evaluation -- if someone wants to add the
"close the feedback loop" code before I get around to it, contact me.