ENTRY

[ESC]
9d2400 words2 saves2 replies

Connecting to Cyberspace From a Pentium MMX (Part 1)

So there is this Guild Competition in the Technica Guild, of accessing Cyberspace on the Crappiest hardware. The current record holder, at the time of writing, is a Raspberry Pi 2B. When I saw that I got wondering instantly, could I do better?

I have some Allwinner F1C100s and F1C200s boards around with 32 and 64 MB RAM, and a single ARMv5 core at 533MHz,... but that's just not interesting. It does not have a screen, keyboard, or any networking capability by itself. It would have to be connected to a proper computer to be able to do this task. So instead, I looked up at my shelf and noticed: The Toshiba Satellite 320CDT in it's grey glory. That laptop was manufactured in 1998 (back when my age had just a single digit), it has a Pentium-MMX processor at 233MHz and 98MB of RAM. It might worth give this a try...

I want everything needed for this to be running ON the laptop itself. If possible I would avoid cutting corners by outsourcing tasks to other, more powerful machines and use the laptop as a dumb terminal.

So let's see, roughly what would be needed to meaningfully connect to Cyberspace from this Laptop?

  • Keyboard and Screen
  • Network connection
  • Enough computing power to complete a TLS handshake
  • A working client for Cyberspace (I asked and it does not have to be the official web client)

Keyboard and Screen is a given on this laptop, the other three could be a challenge.

To get useful networking support (and possibly a useful client) I assume a modern OS would help a lot. In it's current state the laptop runs Windows 98 SE, and I want to leave it that way. I don't want to destroy the OS currently on it. But Windows 98 just won't cut it. The logical step now would be to either change the HDD, or go ahead and put in some CF/SD card or even SATA adapter. That would also help by a lot because I could prepare the software on my modern computer and just boot the finished thing from the laptop.

Sadly, I don't have a replacement HDD, nor any adapter at my disposal. I tried asking around, but had no luck. I also don't want to buy something just for this. So I'll have to take a detour first.

Picking up Side-Quests

(That will make sense of my bio, if it hasn't already)

Alright, so we won't be able to use the Laptop's built-in HDD to boot. Let's see what else we have on the laptop that may help us:

  • Floppy
  • CD-ROM
  • 2x PCMCIA
  • RS-232 port
  • Parallel port
  • IrDA
  • oh, and look at that: a single USB port!

A USB drive would give us plenty of space, and it would be easy to just put stuff on it from my modern computer. USB speed, even in the lowest mode, Low-Speed with 1.5Mbit, also okay for a good experience on this laptop. The only drawback is that this laptop is not able to boot from USB.

That's not an issue actually, because it can boot from floppy (or CD-ROM), and I have access to an USB floppy drive and plenty of floppies. So I just have to put something on a floppy that can chain load something else from the USB drive.

I knew that there was such a loader somewhere on the internet already, but I forgot it's name, so I obviously have to invent a new one myself.

I had a similar project before, which I named Memeloader, where "A big Linux kickstarts a small Linux". I'll probably re-use the name... and wait! Linux kickstarting Linux... we might be onto something!

Linux has this thing called kexec, where the Linux kernel can exec into a "new" kernel loaded into the memory. We know that a minimal Linux kernel fits onto a single floppy disk (The FLOPPINUX project has proven it for us). So what if... hear me out... we put a small Linux on the floppy, which... this time... kickstarts a big Linux.... ah yes, this is just what we need!

I also considered using CD-ROM/CD-RW, but that would be tedious and wasteful at best, because this project will involve a lot of trial and error.

The Small Linux Kickstarts the Big Linux

But why...? Oh yeah, I skipped over this part. I was thinking of other solutions as well, like putting only the kernel on the floppy, and the rootfs on the USB stick. Or even build a modular kernel and put the kernel modules on the USB as well, but it just does not fit. I played around with the latest Linux Kernel 7.0.10: If I want a kernel that has both networking and USB storage support, even the most minimal and modular config builds a kernel with the size of 1.5MB which not only does not fit on a floppy, which has 1.44MB of total space, it would leave no room for anything else and we'll need to put a little more additional stuff on the floppy to do anything useful here. Older Linux versions probably builds smaller, but I don't feel like fiddling with old software now.

So It's settled: we need to build a small Linux that fits on a floppy, can mount a USB drive, load the kernel from it, and kexec into that bigger kernel. A "loader" of some sort...

Thankfully the aforementioned FLOPPINUX project did the hard work for us in figuring out how to build the smallest still useful kernel, that boots on very old processors, like the i486 (The Pentium-MMX is i586).

I followed their guide in building the kernel, and setting up SYSLINUX, with great success. I was able to boot a tiny kernel from floppy. At that point, what I had wasn't much different than FLOPPINUX, so it was time for customization: I just need to add stuff to mount an USB stick, and support kexec, and a script that automates all this.

With the Windows 98 device manager, I determined, that the USB controller is OHCI (USB 1.1), connected to the PCI bus. So I'll need support for PCI, OHCI and USB mass storage devices, which basically means SCSI over USB and SCSI storage class support.

I'll also need additional support for a decent filesystem. The FLOPPINUX project uses FAT, but that's old and smelly. It's okay for the floppy (that's what I plan to keep there), but I'd like to have something nicer on the USB, that supports at least some UNIX attributes.

Plan B could be to add partition support and have two partitions on the USB drive, one FAT with the kernel, and a better one with the rest, but eh... that's just ugly in my opinion...

Getting it to Fit

After a LOT of bit-shaving, messing around with different configs, counting kilobytes and considering what to add and what to leave, I was able to build everything I needed into a kernel image with the size of 1080K.

That leaves us a bit of space for an initramfs. Technically, we don't need an initramfs, but it's a bit nicer to work with: We can compress it to save more space, even with the decompression code added to the kernel. More importantly, since the initramfs is loaded into the RAM by the bootloader (which is SYSLINUX in our case), we really don't have to touch the floppy anymore after the kernel is started, allowing us to leave out the FAT filesystem code from the kernel.

Fun fact: I still had to add the block device driver for floppies, a.k.a.: "Normal floppy disk support" to the kernel, because otherwise the floppy drive keeps spinning forever after SYSLINUX started the kernel. I assume SYSLINUX expects the kernel to re-initialize the floppy drive after it boots so it does not bother stopping it from spinning. Thankfully this fit on the floppy as well.

I mentioned that I would like to use a better filesystem on the USB drive, we already let go of FAT, but what can we use to replace it? After some more tinkering around and counting more bytes, I ended up with ext2. This is the single filesystem support I added to this kernel, nothing else, and this will be the filesystem used on the USB drive. I wanted to go for ext4 but I couldn't find a combination that I liked and did not overshoot the size. Other filesystems like BTRFS or XFS didn't make much sense for this project either... I hope at least the lack of journaling has a little performance benefit on this old system.

So at the end, three things go on the floppy: SYSLINUX with it's config, a kernel image, and the initramfs. Except for SYSLINUX itself (which lives in the MBR), these are just regular files on the FAT filesystem.

What's in the BusyBox?

The contents of the initramfs is very simple: just some essential tools and a shell all provided by BusyBox, and a script to get it all runnning. I can't thank FLOPPINUX enough, as they also did the heavy lifting here: they provided instructions on how to compile BusyBox statically using the i486-linux-musl toolchain. This toolchain allows us to build binaries that would even run on an i486! The musl libc it ships with is also very tiny, so static linking does not add much weight to the binaries. This not only allows us to spare space on the floppy, but also in the RAM... I guess...? I'm not sure how binaries are loaded, to be honest... So anyway, this toolchain is perfect for our purposes!

The script, which is started as init, basically just waits for /dev/sda to appear (it must be the USB drive, because we don't have drivers for anything else that appears as sd*), mounts it, and continues the boot process from there...

... Okay, but how does it continue it, you might ask? Great question! So I already mentioned I would like to use kexec to kickstart a "big" Linux kernel, that has networking and everything. The thing is.. to interact with the necessary kernel facilities for kexec, you need the userspace kexec-tools. Sadly, BusyBox does not have a kexec applet that we could use. We must use the upstream kexec-tools. To get it running in this environment, that also means compiling it.

I'm not an expert at C compiling, so based on some educated guesses, I threw random stuff in random envvars like CC, CFLAGS, LD, etc. in a similar way to how the FLOPPINUX instructions were for BusyBox. It almost worked...

An assembly source file, purgatory/arch/i386/compat_x86_64.S did not want to assemble. It was throwing the error: unknown pseudo-op: .code64'. Purgatory is actually a thing with kexec. It's a binary object, running between the two kernels, setting up the stage for the next kernel. I have zero knowledge of assembly, but .code64sounds like something that has to do withx86_64, the name of the file compat_x86_64` suggests this as well. I assume this is needed when someone attempts to kexec from a 32bit OS to a 64bit one on x86_64, but the toolchain has no idea how to handle that. With little hope, I removed this file from the corresponding Makefile, so it does not attempted to assembled (or however you call this process). Much to my surprise, kexec built fine after that, and it seemed like it even works properly on the target hardware! What a relief!

I have made a miscalculation here, though. Probably because my experimenting earlier, I could not fit the kexec binary on the floppy... But I wasn't that easy to fool: Since when we would need kexec, we already have the USB drive mounted. So I just put kexec on the USB drive instead and made my script copy and use it from there: problem solved.

With the final configuration of everything it seems like kexec would fit on the floppy now, but the solution was already built.

I also thought that I'll be super smart and spare even more bytes on the floppy, so I cut my loader script in half, and put the other half on the USB stick as well. This was pretty pointless, but at least part of the boot process is now easier to modify I think... Oh and also I thought I'll be a good guy and help the "big" kernel re-initializing the USB controller (for reasons I'll detail later). So just before executing the new kernel the script also shuts down the USB controller. That is the reason why kexec and the script is copied "into RAM" first before using them.

With all that, the stage is set. Now I just need something to boot from that USB stick... but oh boy, that will be a whole other story for a whole other post.

Keeping It All Tidy

Building all this from source is tedious at best, by giving out all commands by hand, navigating menuconfig, editing files etc., and it's also very fragile. So it was clear that I needed to automate the process... at first I started with a single bash script, but it quickly became a mess, so I needed something better.

Previously I worked on another project, which involved building some Linux system. The "build system" I made up there was inspired by Buildroot (Which I could have used for this), but instead of solving everything with makefiles, it uses separate scripts for each logical unit, and make is only handling the "orchestration" of those scripts (as intended). I borrowed that system from there, and used it to automate building all parts of this project.

Everything described here, and in the following posts is available on Codeberg, and can be reproduced by a single make -j command, if you have the right tools installed of course.


With that, thank You, for reading through all that mess I came up with! I'm planning on posting 2 more parts of this. It will take a while because I have a lot of on my plate right now, but I hope I can get it done before I forget all the fun details. I'll also post a more elaborate and polished version of this on my blog.

2 replies

Log in to read the replies and join the conversation