Archive for the '68 Katy' Category
Banging my Head Against the Wall
Still working on the uClinux bring-up for my 68000 protoboard machine. Still not there. Why is this so hard?
Yesterday the boot process was working up to the point where it tried to mount the root filesystem image. I finally got that sorted out, as well as the mistake I’d made in the available ROM size. I created a hybrid boot config that placed the filesystem image in ROM, and expected everything else to be preloaded in RAM by my monitor program. Finally I was ready to boot up all the way to a shell prompt! Alas, it wasn’t meant to be. Here’s what’s happening now – with a lot of debug prints added:
uClinux/MC68000
Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff Dionne
KERNEL -> TEXT=0x080000-0x0b95d4 DATA=0x0bef18-0x0c5b50 BSS=0x0c5b50-0x0d3f53
KERNEL -> ROMFS=0x0c5b50-0x0c5b50 MEM=0x0d3f60-0x0fbe00 STACK=0x0fbe00-0x0ffe00
No Command line passed
Done setup_arch
start_mem is 0xd3f60
virtual_end is 0xfbe00
before free_area_init
free_area_init -> start_mem is 0xd6f60
virtual_end is 0xfbe00
MC68000 68 Katy support by Big Mess o' Wires, Steve Chamberlin
Calibrating delay loop.. ok - 0.81 BogoMIPS
Mem_init: start=db000, end=fbe00
Memory available: 128k/243k RAM, 0k/0k ROM (741k kernel code, 876k data)
Swansea University Computer Society NET3.035 for Linux 2.0
NET3: Unix domain sockets 0.13 for Linux NET3.035.
uClinux version 2.0.39.uc2 (ubuntu@ubuntu-VirtualBox) (gcc version 2.95.3 20010315 (release)(ColdFire patches - 20010318 from http://fiddes.net/coldfire/)(uClinux XIP and shared lib patches from http://www.snapgear.com/)) 48 Fri Nov 14 00:42:45 CET 2014
systctl_init
kernel_thread
idling...
init
setup
sys_setup
FTDI FT245 driver version 1.00 by Steve Chamberlin
ttyS0 at 0x00060000 (irq = 2) is a FTDI FT245
Ramdisk driver initialized : 16 ramdisks of 4096K size
Blkmem copyright 1998,1999 D. Jeff Dionne
Blkmem copyright 1998 Kenneth Albanowski
Blkmem 1 disk images:
0: 3000-25FFF (RO)
VFS: Mounted root (romfs filesystem) readonly.
sys_setup_done
still running.
open console
trying /etc/init
trying /bin/init
do_mmap:
Process blocks 1: 000dd2ec: 00000000 -> 000e60d8: 000e60f8 (20456 @000ee018 #1).
bdflush() activated...sleeping again.
Exit_mmap:
Process blocks 3: 000dd36c: 00000000.
do_mmap:
Process blocks 3: 000dd36c: 00000000 -> 000e6118: 000e6138 (12264 @000f5018 #1).
Jump to 0
zBug(ROM) for 68Katy (press ? for help)
084000>
It successfully mounts the filesystem, it starts the init process, there are a couple of memory allocations, and then suddenly I’m back at the monitor prompt. What the hell?!
There is no intentional mechanism by which uClinux can return control to the ROM monitor, but that’s what appears to have happened. But how? After doing some sleuthing, it looks like uClinux is jumping to address zero. Normally that would cause a crash, since the value at address zero is supposed to be the initialization value of the supervisor stack after a CPU reset, and not executable code. But my monitor program has a “feature” where it stores a branch instruction at address zero, and initializes the stack elsewhere. By making that branch instruction point to a different spot than the actual CPU reset vector, I was able to confirm that a jump to zero is happening, rather than an actual CPU reset or the kernel’s hard_reset_now() function.
The easiest way to accidentally jump to address zero is to overwrite part of the stack with zeroes while in a subroutine or interrupt handler. When the return address is popped off the stack, boom! The program goes to zero city. That can happen in a buggy program, or after a stack overflow. But those shouldn’t be concerns when running standard pieces of the Linux boot process, should they?
My fear is that this isn’t a software bug at all, but glitchy hardware behavior due to noise or failed timing constraints or poor electrical decoupling. I saw something very similar a couple of days ago, where the monitor program would reliably jump back to the command prompt whenever I tried to load a binary file, but before I actually started even loading the file. The problem disappeared when I removed a recently-added bypass capacitor from the protoboard. Hmmmm, not good.
But assuming for a moment that this is a software bug, let’s take a closer look at what’s happening right before it dies. Everything up to the line trying /bin/init is the kernel startup code talking. When it’s done initializing things, it launches the user-mode program init, which is the first real process. Init’s job is to start all the other user-mode processes, like the login shell and background services. Launching it is a big deal: the kernel needs to locate /bin/init in the filesystem, allocate memory, copy the program binary into the allocated memory, perform load-time patch-up of the program’s addresses given the physical address at which it was loaded (no virtual memory here), and finally start the program running as a new process to be managed by the scheduler. So it’s possible something’s going wrong there, but… this is the Linux kernel, it’s supposed to work. I thought that once I’d written my hardware-specific code, my porting job would be done. I didn’t expect to have to debug execve().
I’m not completely sure how to interpret those last mmap log entries, but I think it’s saying that process id 1 (that should be init) has a single allocated block of 20456 bytes at address 0ee018 with a refcount of 1. Then later there are a couple of lines from process 3, which seems to imply init successfully started some child processes. Or did it?
I’ll try adding some debug print statements to init to see what’s happening, as well as waving a dead chicken over the board to scare off any hardware gremlins lurking there. This is proving to be a tougher task than I’d planned for!
Read 16 comments and join the conversationInching Forward
So close to a successful boot, yet so far away! I’m continuing to work on porting uClinux to my 68000 protoboard computer. Since my last post, I’ve added hardware to support timer and serial interrupts, and shrunk the linux boot image down to a petite 417K. The image is supposed to be ROM-resident, but because it takes so long to reprogram a Flash ROM, I’m using my monitor program to load the image into RAM and execute it from there. This requires editing the kernel linker script to assemble everything to RAM addresses, but it seems to work OK, and the result is shown above. Almost there!
Something goes wrong when it tries to mount the filesystem: either it’s using the wrong address (C53D0-C53CF looks suspiciously wrong), or there’s not actually enough RAM to hold the boot image plus .bss segment plus dynamic RAM allocations (why 16 ramdisks?). Probably it’s just out of RAM. Encouraged that it got so far in an all-RAM boot, I changed the linker script back to assemble code to ROM addresses, rebuilt the kernel, and burned a combined monitor+linux image to the Flash ROM. But when I try to boot uClinux from ROM, it does nothing. No output, no signs of life. I can use the monitor program to disassemble the code to make sure the addresses look right, and trace through the first few instructions, but that’s all. The monitor doesn’t support breakpoints, and even if it did, there’s no way to set breakpoints in ROM for the 68000. So I’m reduced to just staring at the code, trying to guess what went wrong. Discouraging.
If staring at the code doesn’t reveal the problem, the only other approach I can think of is a tedious one. I can add instructions to set the debug LEDs at various points in the linux boot code, then burn a new ROM and watch what the LEDs do when I try to start the kernel. That should work, but every time I need to add more debug instructions to narrow down the problem, it’ll be a 15 minute cycle of rebuilding the kernel image, pulling the ROM chip from the protoboard, burning the new image, and replacing the chip. Ugh.
Read 5 comments and join the conversationBaby Steps with uClinux
What you see above is my first attempt to boot a uClinux 2.0 kernel on the 68 Katy prototype hardware, the result of several days of work. It doesn’t look like much, but the fact that it even begins to boot up and print kernel messages is great! In the space of a few weeks, I’ve gone from knowing virtually nothing about the Linux kernel or Linux porting, to having a general idea how it should be done, to now having an actual build candidate that runs on the hardware. There’s still more work and more debugging to do, but I’m beginning to see the light at the end of the tunnel.
The boot process stalls at “calibrating delay loop..” because it’s trying to make a bogomips measurement, but there’s no timer interrupt. The CPU’s interrupt pins aren’t connected to any timer sources yet! One step at a time, and hopefully it’ll all still fit on the breadboard.
To reach this point, I had to get the uClinux 2.0 kernel source building with an existing board config (detailed in my previous post), then use that config as a template to make a new one for my hardware. It seems like that should have been fairly simple, since it only required changing the serial driver, memory layout, and a few config settings, but I found it challenging. Creating a serial driver was… interesting, and I probably did it all wrong. Then I used the Weiss SM2010 board as a template, which in hindsight may have been a poor choice. It’s the only board defined for the 68000 platform under kernel 2.0, so the line between 68000 platform generic code and SM2010 specific code is sometimes fuzzy. The result is something that’s more hackish than I’d like, but since I’m never going to merge these changes back, that’s probably OK.
I never found a detailed guide to porting, so I just had to search through hundreds of files to see which ones had been modified for SM2010, and then understand what they were doing and whether I should modify the code for my own board. If I were a nice guy, I’d write up a detailed how-to in order to save work for the next person who does this, but since it’s based on 10+ year old Linux source, it’s unlikely there ever will be a next person. Let’s just say there were dozens of little mysteries that had to be solved one-by-one, that I’m too tired to describe just now. 🙂 If anyone’s really curious, I’ll send you my notes.
Operating System, Meet Hardware
The final uClinux image is 829K, which is a problem given that the prototype machine only has 512K of ROM. So I did what any worthy hacker would do, and just truncated the file! That guarantees it will never boot correctly, but because the truncated portion was part of the rom-based filesystem image and not actual kernel code, it should at least boot to the point where it mounts the filesystem and tries to access it. When it gets that far, I have some ideas about how to shrink it down, hopefully to under 512K. I’m not sure how much RAM will be needed for a successful boot, or whether the 512K of RAM in the prototype will be enough. It looks like there’s about 35K of initialized data and 85K of uninitialized data (.bss segment). With the code itself stored in ROM, that still leaves 392K of RAM for dynamic allocations.
Did I say the code was stored in ROM? It will be, but for ease of testing I’m putting everything in RAM right now. I’ve got a simple monitor/bootloader in ROM, which in the screenshot above you can see performing the uClinux image transfer and initial jump. That makes it quick and easy to test new uClinux images, as compared to reprogramming the ROM chip for each test. But now that I think about it, there will never be enough RAM if I have to fit all the kernel code and data in there together. I’m going to have to burn a new ROM for each test. I might be able to improve things a little by giving the monitor program the ability to do in-system reprogramming of the ROM, instead of physically pulling the ROM chip from the board each time. Of course the monitor would have to avoid overwriting itself in the process.
The last major hardware task is interrupts, and there are a couple of challenges I’ll need to face. On the 68000, it’s not enough to just pull an IRQ pin low to start interrupt processing. When the pin is pulled low, the 68000 performs an interrupt acknowledge cycle, during which the interrupting peripheral is supposed to drive the vector number of its interrupt handler onto the bus. I have no circuitry to do that. The alternative is something called autovectoring, in which the vector number is determined automatically from the interrupt number. But I would still need some circuitry to assert the necessary control signals to request autovectoring during the acknowledge cycle.
The second interrupt challenge is clearing the interrupt. With the FT245 serial chip, this will happen automatically – the chip’s RXF signal will be deasserted after the CPU reads a byte from the FIFO. But for the timer, I’ll need to build a mechanism to explicitly clear the timer interrupt flag after the interrupt is processed. That normally wouldn’t be difficult, but I’m so low on board space and glue logic resources that I’m not sure how I’ll make it happen. It’s going to take some creativity to squeeze it all in there!
Read 10 comments and join the conversation
Building uClinux for a 68000 Target
Compiling the Linux kernel is fun! uClinux is a version of regular desktop Linux that’s designed for low-end embedded hardware, especially CPU’s that lack a memory management unit and can’t support virtual memory. It looks like a good fit for my 68000 single board computer project, so I’ve begun getting familiar with the uClinux source code and how to build it. My plan is to first build a uClinux image for some similar 68000-based system that’s already supported in the kernel tree, and then modify the source code as necessary to support my custom board.
In theory the process should go like this: download the uClinux source code, run a config program to choose the hardware target and kernel options that you’d like, then compile the code to produce a finished kernel image. And that’s true, but it skips over a huge number of details and gotchas, as I discovered along the way. The real challenge was finding an operating system, version of the uClinux source, version of the compiler toolchain, and version of the standard C library that all worked together. That proved to be much more challenging than I would have ever imagined!
TL;DNR
Here’s what finally worked for me for two existing 68K-based boards.
Arcturus uCsimm, 3.x kernel – Ubuntu 14.04 64-bit with libncurses5-dev package installed, 20140504 full source distribution of uClinux, 20130217 m68k-uclinux precompiled toolchain, uClibc selected as the C standard library.
Weiss SM2010, 2.0.x kernel – Ubuntu 12.04 32-bit with libncurses5-dev package installed, 20040218 full source distribution of uClinux, 20030314 m68k-elf precompiled toolchain, uC-libc selected as the C standard library.
If you don’t care much about build details, but are interested in how the compiled kernel boots on 68K hardware, skip the sections below and jump to the end.
Host OS
Step 1 is setting up a build computer with a Linux operating system. I chose to use Ubuntu, a popular and well-supported Linux distribution. But what version of Ubuntu? As I later discovered, the Ubuntu version number wasn’t very important, but the choice of 32-bit or 64-bit Ubuntu was critical. If you anticipate using pre-compiled 64-bit toolchain binaries (described further below), you must use 64-bit Ubuntu. If you anticipate using pre-compiled 32-bit toolchain binaries, you should use 32-bit Ubuntu. All but one of the precompiled toolchains I found were 32-bit binaries, so I recommend using 32-bit Ubuntu. This is sometimes also called x86, and 64-bit is sometimes called x86_64 or amd64.
Supposedly it’s possible to run 32-bit binaries on 64-bit Ubuntu, but I was never able to get it to work. The way it fails is almost comical, too. Say you’re running 64-bit Ubuntu, and you’ve got a program binary called tool in your current directory. Unknown to you, the program was compiled as a 32-bit binary. When you attempt to run the program at the command prompt, you’ll see this:
user@ubuntu$ ./tool
bash: ./tool: No such file or directory
What do you mean, no such file or directory? It’s right there!
Attempting to run a 64-bit binary on 32-bit Ubuntu generates a slightly more useful error message “cannot execute binary file”. When in doubt, the file command (type file <filename>) will tell you if a program binary is 32-bit or 64-bit.
If you don’t have a spare PC ready to dedicate to an Ubuntu install, VirtualBox is a convenient way to run Ubuntu as a virtual machine within a Windows environment. virtualboxes.org has many pre-built OS images for Virtual Box, including many for Ubuntu, which will save you the trouble of going through the Ubuntu install process in the VM.
uClinux Source Code
The source code for uClinux can be downloaded here. Should you just grab the newest version? Maybe not, if you’re targeting a specific board or a custom board with very limited resources. The latest version of the source (currently 20140504) has dropped support for building 2.0.x kernels, and only 2.4.x or 3.x kernels are supported. The configs for some useful older boards (useful to 68K hackers anyway) like the Weiss SM2010 were never updated after kernel 2.0.x, so kernels for those boards can’t be built from the latest uClinux source.
Even if the board you’re interested in is supported in newer kernels, you may wish to build an older kernel to reduce ROM and RAM requirements. Using the latest uClinux source, an image for the Arcturus uCsimm board using the 3.x kernel was 3 MB, but an image for the same board using the 2.4.x kernel was only 1.7 MB. That’s a huge savings. And using a 10 year old version of the uClinux source, an image for the Weiss SM2010 using the 2.0.x kernel was only 870K! Hello, bloat.
There’s a hidden gotcha in the uClinux source distributions: the included user applications (telnet, busybox, etc) and standard C libraries continue to evolve, while the actual kernel source code and board configs don’t. Over time this can break things without anyone noticing. For example, I tried to build the Weiss SM2010 2.0.x kernel image, using the 20070717 source distribution of uClinux. This failed during compilation of busybox, with a “dereferencing pointer to incomplete type” error in ping.c. This was probably due to me using an older toolchain from 2003, which was necessary to compile the 2001-vintage kernel 2.0.x source. When I switched to the 20040218 source distribution of uClinux, the ping.c error disappeared.
For best results, the source distribution of uClinux and the precompiled toolchain should be from the same year as the kernel source you’re building, and the board config files. If you want to build 2.0.x kernels, then use old uClinux source distributions from back when 2.0.x was semi-current – 2001 to 2004 or thereabouts. Failure to do this can lead to all sorts of crazy compile errors, as I discovered.
The uClinux source distributions come as .gz or .bz2 files. To uncompress them, copy them to your Ubuntu home directory, and then type
tar xvf uClinux-dist-YYYYMMDD.tar.bz2
or
tar xzvf uClinux-dist-YYYYMMDD.tar.gz
Toolchain
The toolchain contains required tools like the 68K cross-compiler, and elf2flt conversion tool. You might think these would be included in source form with the uClinux source distribution, but that’s not how it works: you need to provide your own toolchain. This page has links to many precompiled toolchain versions for uClinux with 68K targets. If possible, use a toolchain from the same year as the source code you’re compiling.
Somewhere along the way, the uClinux 68K tools appear to have all been renamed from m68k-elf-* to m68k-uclinux-*. For example, the compiler in newer toolchains is m68k-uclinux-gcc, and in older ones it’s m68k-elf-gcc. If you installed the toolchain, but your kernel compile fails because it can’t even find the compiler, this is probably the reason. Install a newer or older version of the toolchain to get a compiler with the right name, and avoid a pile of confusing compilation errors.
As mentioned in the introduction, you also need to make sure the precompiled binaries in the toolchain match the requirements of your OS. 32-bit Ubuntu needs 32-bit toolchain binaries. It should be possible to compile the toolchain itself from its source code, but I found very little documentation about this and didn’t try it.
Precompiled toolchains are typically distributed as shell files with a compressed payload appended. To install them, you copy them to the Ubuntu / (root) directory, and then type:
sudo sh ./m68k-uclinux-tools-YYYYMMDD.sh
This installs the tools under /usr/local. Most toolchains were easy to install, but I ran into a problem with the 20030314 version of the toolchain, and had to do a little script hacking. The script uses tail to pipe its compressed payload to gunzip, but it appears to have been written for an older/different version of tail than what’s included in Ubuntu 12.04. It’s missing the -n flag needed to tell tail how many lines to skip, so it interprets the line count as the filename and complains:
tail: cannot open '+43' for reading: No such file or directory
I tried to edit the script, but the editor screwed up the binary data in the payload. Finally I just unpacked the data myself without running the script at all:
user@ubuntu$ tail -n +43 m68k-elf-tools-20030314.sh | gunzip > tools.tar
user@ubuntu$ tar xvf tools.tar
Standard C Library
The standard C library is a small bit of code that implements functions like strcpy(). Under uClinux, there are three choices for the standard C library available in the config menu: uClibc, uC-libc, and glibc. Yes, there are two entirely different libraries whose names differ only by a hyphen. As I understand it, uC-libc was the original standard C library for uClinux, but has now been mostly replaced by uClibc.
If building a modern kernel, you most likely want to select uClibc, but for older kernels it’s unclear. I tried building the Weiss SM2010 2.0.x kernel with uClibc, but it eventually failed during compilation of __assert.c:
cc1: error: unrecognized command line option "-mlittle-endian"
All the references I could find to this error on the web were related to ARM compilers. After some digging, I discovered that the Weiss SM2010 configs were just totally broken with respect to uClibc: vendors/Weiss/SM2010/config.uClibc was set up for an ARM target, not 68K! The original authors clearly never used uClibc then, and that file was probably just copied from some other board config. I switched to using uC-libc, and everything built without errors.
Building It All
Once you’ve got the uClinux source and the toolchain installed, it’s time to configure the build options. Cd into the directory containing uClinux source and type make menuconfig. If you’re lucky, after a few moments you’ll see a terminal-based menu program. If you’re not lucky, make menuconfig will fail with a cryptic-looking error.
The most likely failure is an error about curses.h not being found. This is because you’re missing the curses library, needed for fancy terminal graphics and cursor control. To install it, type sudo apt-get install libncurses5-dev. If you’re building xconfig instead of menuconfig, you may also be missing gtk+ (sudo apt get install gtk+2.0-dev) and libglade (sudo apt-get install libglade2-dev).
If you’re using the latest version the uClinux source, and running on 32-bit Ubuntu, you’ll also fail with a linker error about 64-bit incompatible linkage types:
/usr/bin/ld: i386:x86-64 architecture of input file 'zconf.tab.o' is incompatible with i386 output
This is because the person who prepared the uClinux source distribution accidentally left in some 64-bit .o files in the config directory. Delete the offending .o files in config/kconfig, type make menuconfig again, and this time it should work.
The config menu is used to select the vendor and board to target, the C standard library to use, the user space programs to compile, and other options. Make your choices here, then exit. This will save a config file to be used during kernel compilation.
If you’re targeting a pre-3.x kernel, now type make dep. This isn’t necessary for 3.x kernels.
Finally, type make to start the build process. This will run for a long time, filling your screen with thousands of lines of compiler messages. If you’re successful, an image file named something like image.bin will be produced in the images/ subdirectory – this is the actual file that should be downloaded/bootloaded/flashed to the target board. If you’re unsuccessful, compilation will fail somewhere, and you’ll spend hours troubleshooting your source distribution, toolchain, and build settings.
The first target I tried was the Arcturus uCsimm board with a 3.x kernel. The build always failed with a linker error while linking the telnet daemon, telnetd:
/usr/local/m68k-uclinux/bin/ld.real: cannot find -lutil
I think this has something to do with the standard C library, but I never figured it out. I disabled compilation of telnetd using menuconfig. I also got linker errors related to something called libmpc:
/usr/local/libexec/gcc/m68k-uclinux/4.5.1/cc1: error while loading shared libraries: libmpc.so.2:
I thought this was due to a missing package. However, there is no libmpc2 package available for Ubuntu 64-bit. I was finally successful by installing libmpc3, and creating a symlink from libmpc.so.2 to libmpc.so.3. That feels evil.
The uCsimm build always concludes with the error:
cp: cannot create regular file '/tftpboot'
Looking at the Makefile, this error can be safely ignored.
The second target I tried was the Weiss SM2010 with a 2.0.x kernel. This one took a while to find the right combination of OS, uClinux source, toolchain, and C standard library as described above. But once those were in place, it built cleanly with no errors.
Deconstructing image.bin
So now that you’ve got an image.bin file (or sm2010.rom in the case of the Weiss board), what do you do with it? What is this file anyway? That’s what I wondered, so I opened it with a hex editor and started looking around. It didn’t take long to discover that it’s just a regular 68000 program, with a lot of extra stuff attached, including a filesystem image.
The uCsimm image begins with a very recognizable 256-entry 68000 vector table, which is supposed to appear at address 0 in the 68000’s memory space. The table specifies a reset vector of 0x10C10400, so presumably ROM is mapped to address 0x10C10000 on that board, but is also temporarily mapped to 0x00000000 during startup – a common 68000 trick also employed by the Macintosh. 0x400 is the size of the vector table that must be skipped to get to the code entry point, so 0x10C10000 ROM base plus 0x400 vector table size equals the 0x10C10400 reset vector.
I was later able to confirm my guess about the uCsimm’s ROM memory map: vendor/Arcturus/uCsimm/config.linux defines CONFIG_ROMBASE as 0x10C10000 and CONFIG_ROMSTART as 0x10C10400.
The SM2010 image doesn’t contain a vector table, and the code entry point looks to be the first byte in the file. Presumably its vector table is hard-wired, or is updated through some other means.
OK, so what code is at the entry point? What does it do? By copying the bytes from the image file into this awesome online disassembler, I found the answer. The boards have similar init code, though they’re not identical. Here are the first instructions for the SM2010 board:
4e71 nop 46fc2700 movew #0x2700,%sr 2e7c000ffffc moveal #0x000ffffc,%sp 207c00e62bb8 moveal #0x00e62bb8,%a0 227c00000400 moveal #0x00000400,%a1 247c00008f80 moveal #0x00008f80,%a2 2018 movel %a0@+,%d0 22c0 movel %d0,%a1@+ b5c9 cmpal %a1,%a2 6200fff8 bhiw 0x0000001e 207c00008f80 moveal #0x00008f80,%a0 227c000239f0 moveal #0x000239f0,%a1 20fc00000000 movel #0,%a0@+ b3c8 cmpal %a0,%a1 6200fff6 bhiw 0x00000034 48780000 pea 0x00000000 487900000400 pea 0x00000400 486f0004 pea %sp@(4) 48780000 pea 0x00000000 4eb900e00570 jsr 0x00e00570
So let’s see. Moving 0x2700 to the status register, that will disable interrupts on the 68000. Then it’s initializing the stack pointer to 0x000ffffc, which is presumably the top of RAM. After that there’s a copy loop, and about 35K bytes of data are copied from 0x00e62bb8 to 0x00000400. This must mean the former address is part of ROM, and the latter is RAM. Next it fills memory with zeroes from 0x00008f80 to 0x000239f0. It concludes by pushing some hard-coded values on the stack, and jumping to 0x00e00570, another ROM address.
After some extended sleuthing, I finally found that this code comes from /linux-2.0.x/arch/m68knommu/platform/68000/SM2010/crt0_rom.S. Here’s the commented source version:
nop movew #0x2700, %sr /* disable interrupts: */ moveal #_boot_stack, %sp /* set up stack at the end of RAM: */ /* Copy data segment from ROM to RAM */ moveal #__data_rom_start, %a0 moveal #__data_start, %a1 moveal #__data_end, %a2 /* Copy %a0 to %a1 until %a1 == %a2 */ LD1: movel %a0@+, %d0 movel %d0, %a1@+ cmpal %a1, %a2 bhi LD1 moveal #__bss_start, %a0 moveal #end, %a1 /* Copy 0 to %a0 until %a0 == %a1 */ L1: movel #0, %a0@+ cmpal %a0, %a1 bhi L1 pea 0 pea env pea %sp@(4) pea 0 jsr start_kernel
Aha! So this code looks like it’s designed to run directly from ROM, and it begins by setting up the RAM-based .data and .bss segments.
I found the code for start_kernel, and that’s where things began to look complicated. From what I can tell, the kernel isn’t actually meant to be launched directly, but rather it expects to be passed command line arguments from a bootloader. I’m guessing those pea instructions are the SM2010’s way of passing an empty argument list to the kernel.
What’s Next
That’s as far as I’ve gotten with deconstructing uClinux. I’m still a long way from having a Linux image I can try programming to my custom 68000 board, either the current breadboard version or the planned PCB version with extra memory and peripherals. But I feel like I’ve taken some important steps in understanding how uClinux is put together, how to build it, and how it boots. Now I’ll focus on creating a new uClinux target for my custom board, with the right memory map and set of peripherals. I don’t know what that involves, but I’ll find out!
Read 4 comments and join the conversationBreadboarding the 68K
My 68K breadboard computer is alive! It’s always a thrill when a pile of random chips does something recognizably computer-ish for the first time. Blinking some LEDs in sequence is great; running BASIC is super extra great. I’m excited.
This simple breadboard machine is a prototype of the 68000 single board computer I plan to build next. By testing the key design ideas in a breadboard prototype, I hope to uncover any lurking design problems while they’re still easy to find and fix. Once the design is committed to a PCB with lots of tiny surface-mount components, it will be much more difficult to make changes. Even probing specific signals to observe what’s happening may be difficult. The breadboard is a much more forgiving place to experiment and learn.
Compared to my plans for the final 68000 machine, this breadboard version has less memory, a lower clock speed, a narrower data bus, fewer peripherals, no interrupts, and glue logic built from discrete chips instead of a CPLD or FPGA. Except for the serial interface, the whole thing was built entirely from parts I had on hand. The specs are:
- 68008 CPU running at 2 MHz
- 256K Flash ROM
- 512K SRAM
- USB to parallel serial port
- 8 addressable LEDs for debugging
- Miscellaneous 74LS and 74HCT chips for address decoding and glue logic
The computer doesn’t use interrupts – it always runs at the highest interrupt level, and polls the serial port when it needs input. It also doesn’t use any of the CPU’s DMA or legacy 6800 peripheral support. There’s no handshaking for memory accesses either. The /DTACK signal that devices are supposed to use to indicate a successful memory transfer is just hard-wired to ground – the so-called DTACK Grounded approach to system design.
The Build
Getting the machine up and running went surprisingly smoothly, and only took six days of occasional work. The biggest challenges were the hidden 250 mA PTC fuse in my power supply, and noise in the /RESET signal. Other than that, it was just a matter of placing all the chips on the breadboard and stringing a few hundred wires to connect them.
It was tedious work to perform all the address decoding with discrete logic instead of a CPLD. It’s so nice to type a Verilog equation like /CS = A19 * A18 * /AS * (/F0 + /F1 + /F2), and so annoying to wire up the equivalent equation from a bunch of individual AND, OR, and NAND gates. I quickly ran out of gates, and wasn’t able to decode the address space as fully as I’d hoped, or cover all the reset-related edge cases. It gave me new respect for how useful even a tiny CPLD can be for consolidating glue logic.
The first test programs were written using the Easy 68K editor and assembler. It was a huge help to run the programs in the Easy 68K simulator, to make sure they worked as intended before trying them on my hardware. There’s nothing worse than staring at a non-functional DIY computer with no way to tell if you’ve got a software bug or a hardware one.
Easy 68K assembles programs into Motorola S-record files, which I opened with my EPROM burner software, and programmed into my Flash ROM. That was all I needed to get some basic LED blinker programs going. Physically moving the Flash ROM chip back and forth between the breadboard and the EPROM burner wasn’t much fun, though, so I wrote a quick and dirty bootloader. The bootloader is stored in ROM, and runs when the computer is first turned on, waiting for data to appear at the serial port. The first four incoming bytes define the size of the transfer, then the remaining data bytes are stored sequentially starting at the RAM base address. After the last byte is transferred, the bootloader jumps to the RAM base address to execute the program that was loaded. It’s not fancy – there’s no acknowledgement, or checksums, or facility for alternate load or start addresses. But it’s enough to experiment with new programs without having to unplug the ROM chip each time.
Once I had a working bootloader, I started searching for 68000 software that could be easily ported to this hardware. Lee Davison’s Enhanced Basic for 68000 is well known for being excellent in this respect. All that’s needed is to change a few assembler definitions for RAM base address and size, and to provide implementations for the routines to get or print a character. The rest is just vanilla 68000 assembly code with no platform dependencies. Unfortunately it appears that Lee Davison passed away recently, and his web site is gone. I wasn’t able to find an original version of Enhanced Basic anywhere. In the end, I used an Easy 68K specific version of Enhanced Basic, then ripped out all the Easy 68K specific parts. It took me a while to discover some Easy 68K specific modifications that had been made to the “platform independent” part of the code (naughty, naughty), but it still only took a few hours to get Enhanced Basic up and running on the breadboard hardware.
Next Steps
With 512K of RAM, it might be possible to bootload and run a super-minimal version of ucLinux. Even if it’s not possible, I expect I can learn a lot from the attempt. So I’ll be downloading the ucLinux source and getting familiar with how to build it and modify it. That should keep me busy for a while. 🙂
On the hardware front, I found one potential problem that I’m unsure how to handle. During the first half clock of every bus cycle, the 68K CPU puts its address and data lines into a hi-Z state. And during reset, the CPU puts virtually all of its outputs into a hi-Z state, including the address strobe and other control signals. This is a big problem for my address decoder and other glue logic, because they basically blow up if their inputs aren’t well-defined 0’s and 1’s. At best their outputs will be wrong or at invalid voltage levels. At worst they’ll consume a ton of current or enter some crazy oscillation, injecting noise everywhere in the circuit. I’m not sure why my breadboard prototype doesn’t exhibit problems like this already, but I suspect I’m just lucky. The full-scale 68000 system on a PCB may not be so lucky.
What’s the best way to fix this problem? Other homebrew 68K designs I’ve looked at don’t mention it. I could put a weak pull-up resistor on every CPU output, to gently pull it to a valid voltage level if the CPU isn’t driving it. But that would be a lot of resistors, and it might increase the signal rise/fall times during normal CPU operation, reducing the maximum clock speed that can be reached. Hmmm.
What’s In A Name?
This 68K effort needs a name. My other projects all have vaguely humorous names or name puns: Big Mess o’ Wires, Nibbler (the 4-bit CPU), Plus Too, Floppy Emu, etc. For this project I’m thinking of 68 Kangaroo, keeping with Floppy Emu’s Australian wildlife theme and extending 68K with an -angaroo. Other ideas along the same line of thinking are 68 Katy (my wife might be jealous), or 68 Kale (try it sautéed with garlic). Single Bored Computer also has a nice sound to it, and would be great for online dating. Ah, the possibilities…
Read 18 comments and join the conversationReset Debouncing Gives Me Ulcers
Who knew that getting a decent /RESET signal could be so difficult? I’ve been working on a scaled-down breadboard version of my 68000 system design. It mostly works so far, running a simple program that echoes serial port bytes. (More details soon, I promise.) But I noticed that every time I hit the reset button, it would vomit some garbage characters out the serial port, so I decided to investigate. Poking about with the scope, I discovered that my /RESET signal looked bad, really bad. Convinced that it was something to do with the 68000, I duplicated the reset debounce circuit on a separate breadboard. It’s just a switch connected to ground in parallel with a 10 uF capacitor, and a 10K ohm pull-up resistor to 5V.
The screenshot above shows what happens when I press the reset button. Oh ye gods! That is just horrible. What the heck is going on? First we’ve got the signal jumping up (??) to nearly 8 volts. How on earth does that happen? In other tests, I’ve seen it jump even higher, as high as 10V. Then at the end of all this bouncing switch noise (which the cap was supposed to smooth out), there’s about 12 microseconds of quiet where the signal sits near 0 volts. But then it jumps up to 2.5V near instantly. That should be impossible, as the RC time constant in this circuit is 10^4 * 10^-5, which is 10^-1 seconds or 100 ms (probably much bigger than I need). It shouldn’t be possible for the capacitor to charge that quickly. Something strange and unexpected is happening here.
I know there are better debounce circuits than this – I could use a double-throw switch, or add in a buffer with a Schmitt trigger input, or use a reset IC. But before I first understand what’s going wrong here, I don’t think I’d trust an alternative solution.
Read 12 comments and join the conversation