Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Testing and measuring the TAMS 3011, Part 6: Booting NetBSD on new hardware, the saga begins

If at first you don't succeed...

Peter Seebach, Freelance author, Plethora.net
Peter Seebach
Peter Seebach has been talking about "porting UNIX" for many years, and was glad to finally get around to it.

Summary:  Porting an operating system to new hardware can be a fairly easy process, or a fairly difficult one, depending on the issues you encounter. Peter Seebach walks you through his experience getting NetBSD running on a new board using existing hardware.

View more content in this series

Date:  19 Sep 2006
Level:  Intermediate
Also available in:   Russian

Activity:  6411 views
Comments:  

Although we'd all like it to be otherwise, the process of porting an operating system to new hardware is hardly an instantaneous one, and it is not always easy. You might run into a number of potential difficulties, especially if you are coming to the problem for the first time.

This article, and likely the next couple in the Testing and measuring the TAMS 3011 series, details my experience porting NetBSD to the TAMS 3011. These articles are not about the finished port, but about the process of developing it. I can only hope you find the hilarious errors as funny as I found them frustrating at the time.

Getting it to compile

The first significant step in getting the kernel working is just getting a kernel with code in it that ought to do something. The existing 405GPr systems are all found in the arch/evbppc directory. (Unless otherwise stated, directory names used in this article are relative to /usr/src/sys/.)

This article continues to use the cross-compile toolset described in the previous article, "Porting NetBSD to the TAMS 3011," hosted on a NetBSD system. In principle, you can do this in other hosting environments, such as Cygwin; it was just more convenient for me to use the system I already had.

Configuration files and file specifications are in arch/evbppc/conf/, include files are in arch/evbppc/include/, and actual code for each platform or family is in its own subdirectory, such as arch/evbppc/obs405/ (for the openblocks 200 and 266 systems), or arch/evbppc/walnut/ (for the IBM "walnut" evaluation board).

Of the various systems, the openblocks266 and walnut look to be the most similar to the TAMS3011. I went with the openblocks266. So, the first thing I had to do was to make a new kernel config file in that directory; I named it TAMS3011. The kernel config file includes (by reference, using an "include" directive) several other files; for instance, the OPENBLOCKS266 config file includes the file std.obs405, which in turn includes the file files.obs405. The difference in usage between these files is a little subtle. The std.system file includes options which are expected to be useful and standard on all of the related systems, such as the EXEC_ELF option, while the files.system file includes a list of specific source modules that that particular port uses. Finally, there's a file named Makefile.system.inc, which is automatically included in the Makefile for building the kernel. So, I copied these files along with the main config file, creating the new files, TAMS3011, files.tams3011, std.tams3011, and Makefile.tams3011.inc.

For a first pass, I simply commented out all of the openbios_* calls, which have to do with extracting configuration data passed in by the OpenBios bootstrap environment. I needed a header file to define things such as the UART frequency, so I created a tams3011.h. With these all in place, I was ready to start compiling the kernel and looking for errors.

Obviously, I encountered a number of errors. The first was simply that the default C compiler on my x86 system couldn't compile for PowerPC®. My admittedly ugly solution was to add a number of lines at the top of the TAMS3011 config file (they could go anywhere, but conventionally options like this go near the top):

makeoptions CC="powerpc--netbsd-gcc"
makeoptions AS="powerpc--netbsd-as"
makeoptions LD="powerpc--netbsd-ld"
makeoptions STRIP="powerpc--netbsd-strip"
makeoptions DBSYM="powerpc--netbsd-dbsym"
makeoptions COPTS="-O2"

This overrides the default native tools with tools from the cross-compile toolchain. This isn't the correct way to do it, but it is a first step, and it works well enough to get the compiler started. It would have worked as well to set these as environment variables, and perhaps been a cleaner solution, but I kept forgetting to reset them.


A sea of red flags

The NetBSD kernel is built, by default, with a broad array of warnings enabled. More significantly, it is built with the -Werror flag, which promotes warnings to errors; if you have not eliminated all warnings from your code, it won't compile. When you are doing the initial pass of hacking out some code and replacing it, this is a lifesaver; having the build fail with a warning that a variable is possibly being used uninitialized saves a round trip trying to boot the kernel on a remote system. The warning flags are as follows:


Listing 1: An ounce of prevention is worth a pound of cure

	-Wreturn-type
	-Werror
	-Wall
	-Wpointer-arith
	-Wmissing-prototypes
	-Wstrict-prototypes
	-Wreturn-type
	-Wswitch
	-Wshadow
	-Wcast-qual
	-Wwrite-strings

Some options are disabled: -Wno-sign-compare, -Wno-format-zero-length, and -Wno-main. The latter suppresses the warning generated because the kernel's main() routine does not have a signature normally allowed for a main function in a hosted environment.


A question of timing

Sorting out the warnings caught a number of things my first haphazard pass had overlooked, for instance, not having specified the UART frequency. A little background is in order. Serial port hardware is driven by a clock at some fixed frequency, say, 25MHz, or 7MHz, or some other likely number. Some systems have a dedicated I/O clock; others use some exotic divisor of the most convenient clock available to reduce the number of parts needed. A serial port runs at a given baud rate by figuring out which divisor of that frequency will most closely approximate that baud rate. So, to use a UART-based serial port, you need to know at what speed the clock is.

My first attempt was to just copy in a value from another port. With this, when I loaded my kernel, I got a string of garbage; it looked like a baud rate mismatch. My second attempt was to copy the value from the Linux® kernel source for this platform. I'll reproduce the code rather than trying to explain it:


Listing 2: IOCCC entry?

	#define BASE_BAUD (((400*1000000)/((0x3C>>1)+1))/16)

I can explain the "400*1000000" part: The 3011's CPU runs at 400MHz. So, I tried this. No luck. In a fit of desperation, I turned to the manual, which said that the UART frequency was, by default, a processor speed over 31. (A footnote to this: If you have to write 31, without actually putting a 3 next to a 1, you could write it as ((0x3C>>1)+1).) So I tried 400 million divided by 31... And it worked. Well, worked is a bit strong. I got a panic within about five lines, but the console was working.

As a side note, when putting magic numbers in embedded systems, be aware of data types. It is not unheard of for a value like this to end up being shoved into a narrow register, and if the value you generate doesn't fit, it will be very surprising indeed. Since you might be starting with a large value (in this case, 400 million), you have a lot of room for typos to create problems. This is easy to forget, especially when you have several levels of nested macros generating such a constant.

If you get unexpected results, you might find it useful to check what the macros are generating. If they're all constants, you can do this with the preprocessor. If there are live calculations, you might put in a small chunk of code that assigns the constant to an object so you can print it from a debugger:


Listing 3: Finding out a calculated value

{
uint32_t dummyval;
dummyval = BASE_BAUD;
}


The board_info feature, round 1

The board_info feature is a small property database (using the NetBSD kernel's built-in property database support) that is used on a number of 405GPr ports to store data about the system. The first data required are the amount of memory and the processor frequency. I cheated, in the first case, by simply hard-coding the value into the bootstrap functions.

On the OpenBios systems, a hunk of useful bootstrap data is passed into the kernel through a pointer stored in r31. The bootstrap code saves, then copies, this data structure. When the system is far enough up to allow memory allocation, a property database is filled in from this, using the openbios_board_info_set() function. (This is in arch/powerpc/ibm4xx/openbios/openbios.c.) Contrary to my original assumption that this whole chunk of code could be easily omitted, it's used in a few places to obtain configuration information that is needed elsewhere in the kernel. The first two pieces of information used are memory size and processor frequency.

Setting these two items directly is simple enough:


Listing 4: Setting the processor speed


	unsigned int processor_speed = 400 * 1000 * 1000;
	board_info_init();
        board_info_set("processor-frequency",
		&processor_speed, sizeof(processor _speed), PROP_CONST, 0);

This code was essentially copied from the openbios_board_info_set routine, which filled in several other values in roughly the same way. It does, however, have a bug. The bug manifests in the system, after announcing that it's running at 400MHz, by calculating the number of cycles per kernel tick (which are set at 100Hz by default) as zero. Why? Because the clock code thinks the kernel is at 26Hz, and if the kernel is running at 26Hz, and you want 100 interrupts per second, you have less than one cycle per interrupt.

The original OpenBios code used entries in a static kernel structure to store the data that was being stored with board_info_set. The board_info_set macro is a wrapper around the NetBSD propdb interface, and it turns out that PROP_CONST indicates, not that the value can't be changed, but that the propdb code should just store a pointer to the given argument rather than allocating space for a copy. Since the given argument is a local variable, the entry in the database is "whatever that chunk of stack space holds now." Declaring the variable static was the simplest path to fixing this.

With processor-frequency (and later, mem-size) configured, the kernel was surprisingly functional. While a number of devices were working, though, the ethernet controllers were unfortunately not among them.


Getting the MAC address (board_info, round 2)

The setting information passed in from OpenBios, on an OpenBios system, includes at least one MAC address, for the emac0 controller found on the 405GPr. This controller does not have a built-in MAC address; the operating system must program it, using information stored in the boot EEPROM. Since RedBoot has already done this, I thought I might be able to skip this step. My first workaround was to replace the error return in the event that no MAC address was available with an attempt to read it off the chip. This came up with 00:00:00:00:00:00, which led me to the discovery that resetting the chip had cleared its stored MAC address. My second workaround was to modify the reset routine to read the MAC address, reset the chip, and restore the address. This was... ugly. But it worked well enough to get the controller to probe, so it was a working step along the road to a correct implementation. (The mere fact that every single line of code touched during this process was to be removed later doesn't mean it wasn't progress.)

The code for writing a MAC address (stored in a six-byte array) to the chip looks like this:


Listing 5: Writing an ethernet address

	EMAC_WRITE(sc, EMAC_IAHR, enaddr[0] << 8 | enaddr[1]);
	EMAC_WRITE(sc, EMAC_IALR,
	    enaddr[2] << 24 | enaddr[3] << 16 | enaddr[4] << 8 | enaddr[5]);

The existing code just did this once. My atrocious workaround was to do the analogous read operation to obtain the address at the beginning of a reset, then write the data back; then, back up at the top of the driver, do the read again to see what I wrote. This was not, in developer parlance, a "clean" solution.

The DS83816 ethernet chip on this system also has no local MAC address storage. In fact, most 83815/83816 chips have a local EEPROM (as do most other ethernet MAC chips); this one doesn't. I spent a few minutes trying to debug this, but just then I got e-mail from a TAMS engineer who helpfully mentioned in passing that there's no EEPROM on the 83816 used in this system. What to do? Use the existing configuration. Since my workaround was doing so well on the emac0 controller, I adapted similar code in the sip0 driver. I still couldn't get it to work, and after a bit of fussing, I decided to come back to it later; this turned out to be the absolute best decision I made in the whole networking process.


Network root file system and the real-time clock

In fact, with the emac0 interface working, it was quite simple to get network booting running well enough to serve a root file system to the 3011. There were some quirks, most obviously that the real-time clock wasn't working, but I could open a shell and run simple commands. Having learned from my experience with the UART, my first stop was in the documentation. I had, originally, somehow convinced myself that the RTC was part of the CPU; in fact, it isn't, and I had put in a config line for the wrong clock. I put the right clock ("dsrtc0", for the DS1307 real-time clock) in my config file, and that started working immediately. The only configuration needed was the I2C address, which I found in the board documentation for the 3011.

At this point, having conveniently verified that the I2C bus was working, I thought it might be interesting to look at the other device on the I2C bus, the 4KB EEPROM that holds the boot configuration data. This turned out to be surprisingly simple, since there's already a driver for the AT24Cxx chips. The only real problem I ran into was misunderstanding the "addr N" notation used for devices on the I2C bus, which led to an hour or so of trying to debug a chip apparently containing 4k of 0x80 bytes. So now I could read the data. But...


Down the rabbit hole

After a bit of poking about, I discovered that the EEPROM was being detected later than the emac0 network controller, so reading the configuration data from it would be a little late to do me any good. The simple solution is to use the config_defer() function in emac0, which registers a callback function to be run later in configuration. I moved the bulk of the emac0 setup into a second function, to be called back later, and had the EEPROM driver grab the configuration information.

This worked, and even eliminated my previous hack of saving and restoring configuration in the reset routine (which was, I think, obviously a bad idea), but it felt pretty ugly. While I was looking into the question of possibly reordering probes, I got the idea to look at how the Linux source does it.

Looking in the Linux source, I found that this information was extracted from a structure, of a type declared in a nearby header file. In fact, oddly enough, the structure was declared in the walnut.h header, which led me to believe that it was the same structure used on the Walnut -- the OpenBios configuration structure that NetBSD already had code for!

I spent a few hours trying to find and extract this data, experimenting with various things. It turns out the whole line of inquiry was mistaken; there are actually multiple data types involved. Most of the IBM4xx boards supported by Linux typedef some structure to type bd_t. However, some of them are using a type defined elsewhere in the kernel, and some define their own, with different layouts. In fact, the tams_moab code was using the RedBoot version of the structure, not the OpenBios structure.

But what really got me confused was the initialization. Here's the initialization for the bd_t object, as found in the Linux source for a few different IBM PowerPC 4xx-series platforms:


Listing 6: Variable initializations gone wild

	bd_t *bip = (bd_t *) __res;
	bd_t *bd = (bd_t *)&__res;
	bd_t __res;
	unsigned char __res[sizeof(bd_t)];
	bd_t *bip = &__res;
	extern bd_t __res;

Now, these are all from different files, but the basic impression one gets is that there's a reasonably standard type, bd_t, and that there's one of them stored in an external object named __res. What was lacking was a theory of where this __res object was getting initialized, but I eventually found that it was being initialized from a value passed into the kernel in r31. Very, very similar to the OpenBios stuff.

Unfortunately, the whole thing turns out to be a red herring. RedBoot has special code to load information in a particular structure (not the same as the OpenBios one) into memory and pass the address to the kernel when executing a Linux kernel. The idea that I could just grab the OpenBios information and use it was, it turns out, wrong.

Rather than depend on this, I decided to do things the clean way and read the configuration data from the EEPROM early in the boot. This turned out to have its own quirks and excitements. In the next article, I'll get into the details of how it eventually worked, and then move on to interrupt mapping, why bus_space_unmap is your friend, and other things you never needed to know before.


Resources

Learn

Get products and technologies

Discuss

About the author

Peter Seebach

Peter Seebach has been talking about "porting UNIX" for many years, and was glad to finally get around to it.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Multicore acceleration, Linux
ArticleID=160751
ArticleTitle=Testing and measuring the TAMS 3011, Part 6: Booting NetBSD on new hardware, the saga begins
publish-date=09192006
author1-email=developerworks@seebs.plethora.net
author1-email-cc=dwpower@us.ibm.com

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Try IBM PureSystems. No charge.

Special offers