Debian on my old 486

This text was written 2014/09/04.
My english is well... you know... I'm french...

Note also that a lot of information in there is rendered useless by the use of my own tiny bootloader. All the msdos stuff is useless. All gpxe stuff too. I also wrote my own NFS2 server, so unfs3 is also useless.

For some JTAG experiment with a parallel port JTAG interface I needed a parallel port. Only my old 486 has that. I thus let that old beast boot a linux based system (for there is openocd which I know talks JTAG for the mini2440 I have). I chose debian. Here is the needed information, if I ever need to reproduce it again in the future. (And yes, that was a pain.)

Physical setup

recent PC <---- ethernet 10Mbps -----> (old) 486 with 16MiB of RAM                 
on eth0                                with a ne2000 clone (IRQ 5, port 0x300)
runs debian                            msdos 6.22 (to start with)

"ethernet 10Mbps" means a cable is plugged in ethernet ports
of both computers. Direct connection.

On the recent PC, run:
route add eth0

We want the recent PC to host the root filesystem of the 486,
so NFS.
And also the linux kernel. (No room left on the 486.) So the
bootloader has to download stuff from the recent PC with whatever
works (tftp, http, ...).

File transfer to the 486

At some point, the floppy drive of the 486 didn't work anymore with msdos. Why, I don't know... It works very well under linux.

Impossible to download stuff from the internet with the recent PC, put it on a floppy, try it on the 486.

A few years ago, I started to write an IP network stack (for fun) (what?). Fortunately, I had it all on the 486. No need to tranfer it (how? everything fails...). I modified it to be able to send files to the 486 from the recent PC.

getfile.tar.gz (source + in there)

To run it, just type: tcp (strange name, too lazy to change from previous project).

It waits for some data.

On the recent PC, use 486send.c

Run as: 486send [file]

Once it's done, on the 486 is a new file called FILE.BIN that you rename to whatever you want.

486send.c has a delay of 20ms between each data packet sent. There is no ack sent from the 486, you need to verify by yourself that the file is okay (check its size). If it's not, increase the delay. You can change the filename in udp.asm.

Well, just very hackish, but functional. Read the source and good luck. 100% asm, not good quality, a bit (lot?) of dead code, etc.

Technically, 486send sends at most 255 bytes of data per packet on the udp port 1 and a packet on port 2 when it's over. 255 by laziness, the code is easier to write in udp.asm.


The 486 has no room on the harddisk for a linux kernel.

Forget about a filesystem.

It can have a few small utilities though (at most 1 MiB, total).

It has a floppy drive. Two in fact. A 3.5 and a 5.25. The 3.5 can boot a floppy. (I guess the 5.25 can too, by playing with the BIOS.)

The idea

Have a bootloader on the 486 that loads via the ne2000 a kernel from the recent PC. No initrd, the kernel has all it needs to mount its root from nfs.


I tried to put grub on a 3.5 floppy (size 1.44 MiB), it didn't fit. I tried to remove as much as possible from this bloat. It fit. But didn't work. Grub has no driver for the ne2000, so I gave up with that thing.

Guys, please, stop the bloat. It's a BOOTLOADER, okay?


I tried to install that thing on a floppy, no success, no time to dig into the documentation. The idea was to have a small kernel to fit on the floppy with lilo in front of it.

Am I stupid? Is it insane to ask for lilo to put itself on a floppy and load a kernel from there?

loadlin / linld

That worked. I thought it didn't at first because the kernel had some problems and rebooted the 486. I blamed loadlin or linld (linux people should be serious), but no.

Here come local copies of stuff from the internet (things come and go).

To run loadlin, I typed:

loadlin d:kernel

I mounted a NFS drive on d: (see below).

For linld, I don't remember. Since loadlin works, it's no care. (But because of bcc/tasm/tlink/make I host the archive here.)


Before writing my own little bootloader, this was the solution I used. Just put a floppy in the 3.5 drive and switch the beast on. The only issue I have is that the ethernet cable has to be unplugged/replugged after switching the 486 on otherwise gpxe does not download anything.

I have a web server running on the recent PC (apache, port 8001) and gpxe downloads the kernel via http.

gpxe.dsk is a floppy image.

To use it:

dd if=gpxe.dsk of=/dev/fd0 bs=512

(Or wherever is your floppy instead of /dev/fd0. Mine pops up at /dev/sdb because I use an usb floppy drive.)

To build it:

cd gpxe-1.0.1/src
make EMBEDDED_IMAGE=../script.gpxe bin/gpxe.dsk

where script.gpxe contains:

ifopen net0
set net0/ip
set net0/netmask
set net0/gateway
set net0/dns
#kernel root=/dev/nfs ip= nfsroot= init=/bin/sh
#kernel root=/dev/ram0 rw

(Some lines are useless, kept for the record.)

My ultra tiny bootloader

gpxe was long to come from the floppy to the memory.

So I wrote my own bootloader. It is 434 bytes, fits on a FAT12 floppy (this part of the work is left as an exercise to the reader, I only provide the raw bootloader, linked to work at 0x7c00) and just works TM.

NFS for loadlin

XFS is an NFS implementation for DOS. Version 2 (I think, it works with a nfs version 2 server).

On the recent PC (server)


Only one file has to be sent. I stole nfs_prot.x and mount.x from something called nfs-server (2.0) [source] and hacked a bit (a very little bit).

From that I ran, in two different directories:

rpcgen -a -C mount.x
rpcgen -a -C nfs_prot.x

And modified/implemented some functions for XFS to work. (rpcgen is a nice little thing.)

I run:

mount_server (in one terminal)
nfs_port_server [file] (this one as root, I kept port 2049, maybe by
                        changing that in the .x file can it be run as

On the 486 (client)

The 486 boots msdos 6.22.

You need XFS and a "packet driver" (I think that's how it's called) for the ne2000.


Modify xfs.bat to contain:

echo off
rem XFS Version 1.71

loadhigh ne2000 0x60 5 0x300
loadhigh xfskrnl 0x60

xfstool @init

rem done

..\loadlin d:kernel

5 is the interrupt, 0x300 is the IO address. I didn't pass them at first and xfs sent stuff but didn't process received stuff (tcpdump -n -i eth0 on the recent PC to check that something was going on). It used interrupt 9 (if I remember well). So, guess what...

Modify the file 'init' to contain:

# XFS Version 1.8
# Command Script
# see  `Xfstool help <command>'  for more
# `dino' is  NFS-CLIENT
# `speedy' is  NFS-SERVER
# `speedy' is also PCNFSD-SERVER

init sed2 sm= gw=

# or
# init dino csum=off
# or
# init BOOTP csum=off

# authentication

#mount    f: speedy:/usr/share/dos
#mount lpt2: speedy:laser timeo=30

mount d: sed:/

#rdate speedy

# per-drive re-authentication
# dlogin  f:
# dlogin all

You also need to modify the file 'hosts':

# XFS Version 1.71
# hosts
# Note: Please keep this file in LF/CR (DOS) format!
#     gateway   broadcast   netmask     speedy-bb     speedy          nfs-server     dino    sed2     sed

After running xfs.bat, linux should start to boot.

NFS for the debian 486

On the recent PC (server)

(See below for my own NFS2 server.)

I didn't want to install a nfs server via apt-get. So I use unfs3.

Run (as root) as:

./unfsd -d -e /home/sed/jtag/nfs/sbin/exports -u -n 8192 -m 8193

Where /home/sed/jtag/nfs/sbin/exports contains:


(/home/sed/jtag/debian2 is the root directory for debian debootstrap, also used in the kernel .config)

unfs3-0.9.22.tar.gz [source]

Linux kernel

See below, the config file is different for a use with my own NFS2 server.


The config file: config-

For the record: check CONFIG_CMDLINE.
Note that /init doesn't exist, but that's fine, the kernel will use /sbin/init instead. Debian will then more or less boot, complain about udev, but who cares about udev on that machine... and about tmpfs not present or something. No big deal. /proc is there, /sys is there, /dev is there, /dev/pts is there.

I needed the parallel port thing, the ne2000 thing, network stuff, and nfs. Floppy drives too. Just vga for the display (I think). And that's more or less it.

No initrd. Why do people insist in using that thing for a machine you fully know, I don't know... Same for modules. (Okay, this is debatable, but my 486 has no usb, so boom, debate over.)

I could have tried a 3.something kernel, but so many problems with this one and well, I'm getting old and life is short. That works? Yes? Then you're done. Move on. Other peoples' bugs are other peoples' bugs.

At first, the kernel didn't work because CONFIG_PHYSICAL_START and CONFIG_PHYSICAL_ALIGN were set to 0x1000000 (16 * 1024 * 1024) but my 486 only has 16MiB of RAM, so that doesn't fit. I had to set the value to 0x100000 (1024 * 1024).

Believe it or not, I had to debug the early boot process of the linux kernel, using some "tt: jmp tt" in .S files and some "while (1);" in .C files, to see at which point it failed, ie. the 486 reboots. It was on a "popf" thing, which led to a very strong hypothesis that something was wrong with the RAM settings.

I did some experiments in the past with some ARM "embedded" computer (this 486 is slower and has less memory than those so called "embedded" devices) and for those you must play with those values, that's why I took a look and bingo.

Hey, people at linux, when someone chooses 486 as a target, you should provide a more reasonable value for CONFIG_PHYSICAL_START and CONFIG_PHYSICAL_ALIGN. I doubt that many 486 have more than 16MiB of RAM...

Debian debootstrap

debian testing

That does not work.

debootstrap --arch=i386 testing /home/sed/jtag/debian.testing

For the record, list of packages I installed: installed-packages-testing.txt

To get this list:

dpkg --get-selections > /installed-packages-testing.txt

To reinstall on a fresh base system:

dpkg --merge-avail /var/lib/apt/lists/
dpkg --set-selections < /installed-packages-testing.txt
apt-get dselect-upgrade

This process is documented here (except they don't write about 'dpkg --merge-avail' as far as I checked).

Debian testing didn't work. Running "ls" gives "illegal instruction".

And there come several problems.

gdb crashes too. Same reason. Can't debug with it.

strace works.


strace -i /bin/ls

And have the address of the instruction.

Problem: what instruction is at this address? You need to map it back to some binary file (/bin/ls or a library it uses). For that you do "cat /proc/[PID of /bin/ls]/maps" but you must do it at the right time.

Or: use CTRL+Z to stop strace (still at the right time, but it's much easier to reach).

And here comes a second problem: the shell has no controlling terminal, because we are very early in the boot process (or whatever). So CTRL+Z does not work.

So I wrote shell.c to open a /dev/ttyX, become an orphan process to detach from whatever session it might be into, create a new session (setsid) and launch a shell with stdin/stdout/stderr pointing to /dev/ttyX (via dup2).

With that thing, some maths (addition, substraction, hex to decimal, decimal to hex, woo!) and objdump, I found that the illegal instruction was in libpthread, specifically in the function __get_cpu_features, which calls "cpuid". That instruction does not exist on a 486.

libpthread comes from the libc.

The libc of debian/testing at the time of writing this webpage was at version 2.19.

I don't know who is guilty here: glibc? debian?

I should report a bug, but to whom? Ah, forget it, who cares...

I tried to get the source from debian and rebuild it the debian way, changing bits here and there. It didn't work.

Just for the record, here is what to do to rebuild it:

Add (modify url to whatever you want):
  deb-src testing main
to /etc/apt/sources.list
  apt-get update
  apt-get build-dep libc6
  apt-get source libc6
  cd glibc-2.19
  dpkg-buildpackage -b -us -uc -nc
You may want to edit:
  debian/sysdeps/ (to change the march mtune thing)
  debian/rules           (to set RUN_TESTSUITE to no)
To install the produced .deb file:
  dpkg -i file.deb
If you delete build-tree, delete stuff in stamp-dir.

This documentation tells all about building a package from source in debian.

debian stable

debootstrap --arch=i386 stable /home/sed/jtag/debian2

That worked. glibc is 2.13. Debian version 7.

For the record, list of packages I installed: installed-packages-stable.txt

For the french keyboard layout:

  apt-get install console-data keyboard-configuration
  cp /usr/share/keymaps/i386/azerty/fr-pc.kmap.gz /etc/console/boottime.kmap.gz
Maybe that can work too:
  dpkg-reconfigure keyboard-configuration

And that's it, debian is working on my 486. I also added a swap on the nfs server:

On the recent PC (server):
  dd if=/dev/zero of=swapfile bs=1024 count=262144
  mkswap swapfile
On the 486:
  losetup /dev/loop0 /path/to/swapfile
  swapon /dev/loop0

That seems to work. (It doesn't, see below for a better solution with nbd.)

So be it.

My own NFS2 server

Get it here.

To use it, you need the new .config file for the kernel. I changed the IP addresses. for the server and for the 486 and added "network block device" to have a swap over the network (see below).

Swap over the network

The 486 has only 16MiB of RAM. I need a swap. But no space on disk, so needs a network swap.

There is nbd out there ('apt-cache search nbd-client' and 'apt-cache search nbd-server').

We'll use it on port 8012.

To use a swap, on the server do:

  dd if=/dev/zero of=/tmp/swapfile bs=1024 count=262144
  nbd-server 8012 /tmp/swapfile

On the 486, do:

  nbd-client -s 8012 /dev/nbd0
  mkswap /dev/nbd0
  swapon /dev/nbd0


Created: Thu, 04 Sep 2014 17:01:27 +0200
Last update: Wed, 08 Oct 2014 12:29:24 +0200