Sunday, February 19, 2023

Dusting off Dreamcast Linux

Yes, here at Old VCR we live in the past, when RISC Unix workstations still ruled the earth like large boxy tentaculous Cthulhus. Oh, sure, if you wanted a modern equivalent you could just buy a Raptor POWER9 like the one I'm typing on now. But around here even PowerPC is too pedestrian of an architecture. We need something unique.
That's more like it! A keyboard, mouse, a NIC, VGA output, 16MB of RAM and a whole gig (you wish) of read-only optical drive space with a 200MHz Hitachi SuperH SH-4 CPU faulting its paltry 8K of I-cache and 16K of D-cache non-stop. Now freshly refurbished, its cooling fan runs louder than my Power Mac Quad G5 at idle and the drive makes more disk seeking noise than when I can't find a lost floppy. And since the buzzword with Linux distros today is immutability, what could be more immutable than an ephemeral, desperately undersized RAM disk overlaid on a live CD?

Need portability? Well, just load the disc into our handy dandy greymarket clone Treamcast with its built-in LCD display and don't tell Sega. You can take your Unix on the go with the car power adapter: if you can read the screen, now you can live the dream.

But jokes aside, Dreamcast Linux has something to teach later Johnny-come-latelies with a distro surprisingly well-adapted to its target platform, support for many peripherals, and an all-in-one batteries-included philosophy. Plus, it was one of the earliest Un*xy things for game consoles circa 2001, predating PlayStation 2 Linux by about a year or so, though PS2 Linux was at least Sony-official. (While at least one Linux purports to run on an O.G. PlayStation, this was a slightly later development.)

So I think there's enough noteworthy about it to merit dusting DC Linux off for a new generation to experience. We'll add a couple quality of life pieces to smooth out a few rough edges, but we won't remove anything and largely eschew in-place upgrades such that the flavour of the original experience remains. After all, no one's running this as a home server (I think). If you like your vintage less aged there are newer attempts to put Linux on the Dreamcast, and of course it runs NetBSD, but if you feel like running a refreshingly geriatric Linux kernel on a classic console then this is your blog post. We'll take a tour of the operating system and the way Linux used to be, talk about what's unique about the DC port and what's been added (and what's missing), and then step through how you can burn a disc to run in yours.

One of the best things about O.G. Dreamcast Linux is that it has very minimal system requirements: you just need a Dreamcast and keyboard. It supports much more than that, most notably the controllers, the Dreamcast mouse and the Dreamcast Broadband Adapter (and if you have the BBA, you might be able to get away without the keyboard by Telnetting in remotely), but pretty much anyone could run it. The first hit is always free.

This and the initial set of screenshots are taken with my INOGENI VGA2USB3 capture box connected to a clone DC VGA adapter. Normally I would crop from 16:9 to 4:3 but I'm leaving the full frame on to demonstrate any display irregularities are due to the system, not the capture device or an inartful trim.
Dreamcast Linux — or indeed any DC homebrew at all — might not have been possible were it not for Sega's ill-fated attempt at multimedia music CDs, called MIL-CD (Music Interactive Live CD). Recall that the Dreamcast's normal and preferred medium is the "Gigabyte Disc" GD-ROM, Yamaha's 1GB CD variant with closer pit spacing but using the same 780nm near-infrared laser. To broaden the third-party catalogue, Sega devised MIL-CD so that enhanced music CDs could feature menus, videos and Internet linkages, something like an Enhanced CD Bluebook format specific to the Dreamcast. Unfortunately for Sega, not only was MIL-CD very unsuccessful and only eight actual titles ultimately produced (let alone anything else that could play them), but it also allowed the Dreamcast to be booted from ordinary CD and CD-Rs instead of requiring all titles to be the more expensive GD-ROMs.

The key is the Dreamcast boot process. When the console detects a factory-pressed GD-ROM (GD-Rs officially require a boot disc), most likely from the presence of the special security ring between the low-density CD-readable inner ring and the high-density main section, it reads an boot sector executable conventionally named IP.BIN from the first 16 sectors and runs it. Part of IP.BIN's tasks are to enforce region coding and to also display the Sega copyright message shown here, which on the Genesis became relevant in Sega v. Accolade. It also provides a filename to the Dreamcast ROM (usually 1ST_READ.BIN) that the DC loads and runs as the main program.

This process is exactly the same for booting MIL-CDs — with two crucial differences. When a MIL-CD starts up, the Dreamcast loads IP.BIN as usual, but the Dreamcast ROM will load the filename it provides into memory scrambled by a proprietary algorithm and disable the GD-ROM drive in software. If the second-stage executable were not pre-scrambled in a matching fashion, the result will be garbage, and the console will halt. Even if it could run, however, all it can do is play audio: the executable shouldn't have any further access to data from the drive.

Sega's tactical error was making the entire system's security dependent on these two lockouts despite the overwhelming weight of history. Independent developers noticed the different pathway and used it with their own software developed with the official Sega Katana SDK to provide the obfuscation; probably the first was the Datel GameShark CDX, first developed in early 2000, though Bleemcast! got to market faster using the same method until Sony sued them into grenade shrapnel. Both packages exploited an undocumented reset call to regain control of the GD-ROM drive, allowing them to start games from disc after they themselves had loaded. The notorious Utopia boot disc got around the obfuscation problem with a pirated Katana SDK and used the same reset technique, letting pirated software load by simply booting Utopia and switching discs when instructed. The Utopia crew flamed out quickly as they were stupid enough to put their photographs on the disc, leading to a knock on the door from German police, but the crack circulated widely and the damage was done. When the obfuscation algorithm was eventually cracked too, pirated games could simply boot directly, as could anything else.

Arguably the rampant piracy that resulted was one of the causes of the Dreamcast's decline, and Sega seemed to confirm this theory by introducing late model Dreamcasts that had MIL-CD support removed, but in too tardy a fashion to make any difference. These units won't boot Dreamcast Linux either, but they aren't extremely common and obviously less desirable.

In our case, the second-stage executable is a pre-scrambled RedBoot bootloader from eCos, a free open source real-time operating system originally developed at Cygnus Solutions in 1997. Red Hat bought Cygnus in 1999 and eventually terminated eCos as a product in 2002, making this 2001 build some of the last official Red Hat builds in use. Dreamcast Linux uniquely uses RedBoot to load the RAM disk image and kernel and start the operating system.

RedBoot listens on both the rear serial port (at 115200bps) and on port 9000 for commands. The IP settings are internal to the Dreamcast and are set by programs such as the Planet Web browser or Quake III Arena. If you connect to port 9000 while it's waiting, you get this prompt:

% telnet sadie 9000
Connected to sadie.
Escape character is '^]'.
== Executing boot script in 8.-1612046094 seconds - enter ^C to abort
RedBoot> help
Manage machine caches
   cache [ON | OFF]
Change directory
   cd <directory>
Compute a 32bit checksum [POSIX algorithm] for a range of memory
   cksum -b <location> -l <length>
Print directory
   dir [<directory>]
Display (hex dump) a range of memory
   dump -b <location> [-l <length>]
Execute an image
   exec [-b <parameter block addr>]
        [-m <mount rdonly flags>]
        [-f <ramdisk flags>]
        [-r <root device>]
        [-l <loader type>]
        [-i <initrd start addr>]
        [-j <initrd size>]
        [-c "kernel command line"]
        [<entry point>]
Execute code at a location
   go [-w <timeout>] [entry]
Help about help?
   help [<topic>]
Load a file
   load [-r] [-v] [-h <host>] [-m {FILE | TFTP | xyzMODEM}]
        [-b <base_address>] <file_name>
Mount filesystem
Network connectivity test
   ping [-v] [-n <count>] [-l <length>] [-t <timeout>] [-r <rate>]
        [-i <IP_addr>] -h <IP_addr>
Print current working directory
Reset the system
Unmount filesystem
Display RedBoot version information
RedBoot> version

RedBoot(tm) bootstrap and debug environment - built 05:27:24, May 29 2001

Copyright (C) 2000, 2001, Red Hat, Inc.

RAM: 0x8c000000-0x8d000000, 0x8c034950-0x8d000000 available

Control-C from a connected keyboard won't do it; while the Maple bus is USB-like, RedBoot doesn't speak it. If we wanted to boot with custom Linux command line arguments, this is how:

RedBoot> mount
RedBoot> load -v /boot/vmlinux
   Type     Offset   VirtAddr PhysAddr FileSiz  MemSiz   Flags    Align
00 00000001 00010000 8c210000 8c210000 000bdfd8 000ca420 00000007 00010000
Entry point is 0x8c210000
RedBoot> load -v -r -b 0x8c400000 /boot/initrd.gz
Raw file loaded 0x8c400000-0x8c440725

This mounts the Rock Ridge image and loads the kernel and compressed initrd. Then, to boot the kernel, we would enter these command line arguments by default, or as you like.

RedBoot> exec -c "mem=16M init=/busybox /etc/profile"
Now booting linux kernel (entry 0x8c210000)
MOUNT_RDONLY  : 0x00000000
RAMDISK_FLAGS : 0x00000000
ORIG_ROOT_DEV : 0x00000100
LOADER_TYPE   : 0x00000001
INITRD_START  : 0x00400000
INITRD_SIZE   : 0x00400000
COMMAND LINE  : mem=16M init=/busybox /etc/profile

The connection halts at this point since RedBoot's network connections are then terminated by the new kernel and the console remains on the Dreamcast locally (though a serial port boot should keep the console on the serial port).

The Dreamcast version of RedBoot is one of the components we lack source for. Although the eCos CVS server is still active, there were apparently pieces that were maintained out of tree, and it's not clear if the residual SuperH support still supports the Dreamcast BBA. Later versions of LinuxSH used SH-Boot, but remember that part of our brief here at Old VCR is historical preservation, so I've retained eCos RedBoot in this build.

The earliest kernel messages don't seem to go to the screen, though you can get them from dmesg, of course.

Linux version 2.4.5 (yaegashi@aragorn) (gcc version 3.1 20010501 (experimental)) #27 Thu May 31 07:06:51 JST 2001
SEGA Dreamcast support.
On node 0 totalpages: 4096
zone(0): 4096 pages.
zone(1): 0 pages.
zone(2): 0 pages.
Kernel command line: mem=16M init=/busybox /etc/profile
CPU clock: 200.00MHz
Bus clock: 100.00MHz
Module clock: 50.00MHz
Interval = 125000
Console: colour dummy device 80x25
Calibrating delay loop... 199.47 BogoMIPS
Memory: 10332k/16384k available (1173k kernel code, 6052k reserved, 132k data, 188k init)
Dentry-cache hash table entries: 2048 (order: 2, 16384 bytes)
Inode-cache hash table entries: 1024 (order: 1, 8192 bytes)
Buffer-cache hash table entries: 1024 (order: 0, 4096 bytes)
Page-cache hash table entries: 4096 (order: 2, 16384 bytes)
CPU: SH7750/SH7751
POSIX conformance testing by UNIFIX
PCI: MMIO fixup to Sega Corporation Broadband Adapter
Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039
Starting kswapd v1.8
devfs: v0.102 (20000622) Richard Gooch (
devfs: boot_options: 0x0

But the first visible messages (to feeble human eyes) are these:

Notice the console framebuffer, the SCI(F) serial port support, Maple bus for input devices, the Realtek 8139-based Broadband Adaptor (connected over a PCI connection) and the GD-ROM. The framebuffer detects whether we're connected over VGA or NTSC. On my old school CRT TV set, I see fb0: Mode 640x480-32 pitch = 2560 cable: COMPOSITE video output: NTSC instead of cable: VGA video output: VGA (sorry, PAL users, you'll have to use VGA). The console's scroll window has been altered so that the Tux SuperH logo is not obliterated by incoming new messages. Although we booted with Busybox, /busybox does not provide the userland; it only serves as init.

With the kernel now booted, we can switch to pixel-precise grabs from the framebuffer for the remaining screenshots. I provide a pre-built copy of fbcat in /usr/bin which will read the Linux framebuffer and convert it to a Netpbm pixmap.

Log in as root, no password. The machine listens on Telnet and FTP, and I added a small Gopher server demo. Never put a console running DC Linux outside of a firewall: it is an intentionally insecure system. Any bot scanning your network will get root immediately.

You'll notice in the messages that NTP started up using chrony. I'll talk about what we've added to O.G. 2K1 Dreamcast Linux as we go along, but first, let's discuss the framebuffer since we're sitting in front of it.

Logged into our VGA demonstration system, fbset reports a bog-standard 640x480 32bpp console display. That isn't totally true, though: a popular trick is to copy to and from the framebuffer device (here /dev/fb/0 or /dev/fb0) using cp and cat. If you copy /dev/fb0 to a file and copy it back, though, you don't exactly get on-screen what you started with:
This puzzled me to no end, because the framebuffer clearly works. I queried the ioctls for the framebuffer and found a perfectly ordinary looking 32 bit per pixel layout, but writing zeroes or indeed any other kind of character data to the framebuffer device generated this sort of artifacting.

After a lot of pulled hair, I decided to look at how fbcat was accessing the framebuffer since it could clearly make sensible grabs. While it was using the same kinds of ioctls, it was doing the actual reading by mmap()ing the buffer instead of file reads. I wrote a simple program to mmap() the screen like it did and added a memset() to zero out the screen — and that worked! I could even memcpy() stuff around, since it was all just bytes. But treating the screen as an array of unsigned char generated the same kind of frustrating artifacts when I did any sort of writing.

If you know anything about how memset() and friends are implemented, you've already guessed the answer: the access has to be by whole 32-bit word, which is what those functions do when they're dealing with quantities of sufficient size. When I changed the code to write entire words for pixels, the screen updated correctly. Since I rather like the Tux SuperH logo, I reconstructed the bitmap data from the framebuffer and created a little demonstration program that clears the Dreamcast framebuffer, paints Tux, and sets the Linux console back to scrolling under it. tuxclear is in /usr/bin and the source (along with a gzipped header file containing the Tux bitmap data) is in /usr/src.

On our NTSC CRT TV set connected to the Dreamcast with a composite video cable, however, we have another little problem.

A old school CRT TV or composite display will not be able to display the full 640x480 display. The cutoff on the sides is tolerable but only about 80-85% of the vertical resolution will be visible depending on the exact TV or monitor in use. By default we can't even see the login prompt!
To deal with this (after blindly logging in), I added a little shell script /usr/bin/tvclear that sets a different scroll window, and then looked at fbset again. This time we see two new fields, bcast true and laced true, indicating that the frame buffer is generating broadcast video timings (in this case NTSC, directly supported by the Dreamcast-specific kernel) and interlacing the display. You can use this as a hint to determine if the display you're connected to is progressive-scan VGA or not. Incidentally, this is also how fbset rats out the little Treamcast's LCD panel as connected via composite video, not VGA. However — and like modern TVs — there is no overscan region on the Treamcast's flat panel and the entire 640x480 display is visible. Unfortunately tvclear's screen window settings are ignored by many programs, so we'll have to think of a better solution for that which won't regress modern displays.

If the reduced scroll window bothers you and you just want all 80x30 on the console, I wrote a replacement /usr/bin/clear shell script to reset the scroll window from the console (everywhere else it just stubs into /usr/bin/cls, which is what I renamed the old /usr/bin/clear binary to). This is probably also a good time to mention that the screen will conveniently turn off after a sufficient period of inactivity.

How did we manage to compile /usr/bin/tuxclear, by the way?

The kernel and everything else were apparently compiled with a cross-compiler on i386, which doesn't help me much since I don't run any Intel system normally (let alone a 32-bit one). However, we do have gcc and g++ 3.1 as hosted compilers on the system itself (intriguingly marked experimental), along with period-correct Perl 5.6.0, awk, and no Python. GNU make 3.79 serves as your build system, but unfortunately compiling anything more than trivial small tools will not be possible with the limited space we have in the RAM disk.

The solution is NFS. On my NetBSD NFS server we have this line in /etc/exports:

/netfs -alldirs -mapall=censored:censored -noresvport -noresvmnt

There's only one UID and GID that matter in DC Linux, i.e., root's, so it's fine (even necessary) to squash everything to a single unprivileged UID on the server side. Back on the Dreamcast, I created a new /netfs mount point in its perilously overfull /boot/initrd.gz ext2 image and then added two new entries to /etc/fstab:

nfs:/netfs/dreamcast/linux /netfs nfs rw,hard,intr,rsize=4096,wsize=4096 0 0
nfs:/netfs/dreamcast/linux/tmp /tmp nfs rw,hard,intr,rsize=4096,wsize=4096 0 0

The upshot is that this revised spin of Dreamcast Linux will automatically try to mount these filesystems to /netfs and /tmp (because the GNU compiler will put its assembler and parse files here) if a host named nfs can be resolved and will answer. The beauty is, on a system without a BBA or a network where there is no NFS server that answers to that name, nothing will be mounted and those mount points will remain as provided by the initrd, and the system will still run standalone as before.

The other problem we need to solve is swap. Linux, or at least not this Linux, won't let you use a swapfile hosted over NFS; swapon will give you an illegal argument error and refuse to enable it. The workaround is to attach the swapfile to a loop device and have swapon use the loop device as swap. A minor complication here is that Linux 2.4 has a hard limit of eight loop devices but I don't think that'll be a major drawback compared to every other limitation we're currently dealing with!

For swap-over-NFS, create a file of suitable size (I chose 256MB) named swapfile on the NFS server in the mount providing /netfs and on the Dreamcast then do mkswap /netfs/swapfile. Then, whenever you want to start or stop swap-over-NFS, execute one of these:

/etc/init.d/ start
/etc/init.d/ stop

This script handles the dance with the loop device and enabling the swapfile. If /netfs/swapfile isn't present, it won't do anything.

You'll notice the script I wrote lives in /etc/init.d (DC Linux predates systemd, which shall be considered a feature), and I was initially planning on making it facultatively autostart like NFS. However, let me be the first to point out the obvious that NFS can be slow, and swap-over-NFS can be really slow. The read and write sizes in /etc/fstab were selected after messing around with various values and seeing what performed best, but some configure scripts could take over an hour to execute (!), and some compilation jobs I just left to run overnight. This was a great stress test of the hardware and the buildchain, by the way. I had the Dreamcast running non-stop for days.

But what outright killed the idea of making swap-over-NFS default if available was that some really large packages still couldn't build. I would go to lunch with the Dreamcast chugging away, come back and find the console had seized up. Invariably on the console would be a whole string of these dreaded messages:

eth0: Too much work at interrupt, IntrStatus=0x0010.

This message occurs because Linux 2.4.x's driver for the Realtek 8139 (in the BBA) looked like this:

static void rtl8139_interrupt (int irq, void *dev_instance,
                               struct pt_regs *regs)
        struct net_device *dev = (struct net_device *) dev_instance;
        struct rtl8139_private *tp = dev->priv;
        int boguscnt = max_interrupt_work;
        void *ioaddr = tp->mmio_addr;
        int ackstat, status;
        int link_changed = 0;


        do {
                status = readw (ioaddr + (IntrStatus));
                if (status == 0xFFFF)
                if (status & RxUnderrun)
                        link_changed = readw (ioaddr + (CSCR)) & CSCR_LinkChangeBit;
                ackstat = status & ~RxAckBits;
                writew ((ackstat), ioaddr + (IntrStatus));
                if ((status &
                     (PCIErr | PCSTimeout | RxUnderrun | RxOverflow |
                      RxFIFOOver | TxErr | TxOK | RxErr | RxOK)) == 0)
                if (netif_running (dev) && (status & RxAckBits))
                        rtl8139_rx_interrupt (dev, tp, ioaddr);
                if (status & (PCIErr | PCSTimeout | RxUnderrun | RxOverflow |
                              RxFIFOOver | TxErr | RxErr))
                        rtl8139_weird_interrupt (dev, tp, ioaddr,
                                                 status, link_changed);
                if (netif_running (dev) && (status & (TxOK | TxErr)))
                        rtl8139_tx_interrupt (dev, tp, ioaddr);
        } while (boguscnt > 0);
        if (boguscnt <= 0) {
                printk (KERN_WARNING "%s: Too much work at interrupt, "
                        "IntrStatus=0x%4.4x.\n", dev->name, status);
                writew ((0xffff), ioaddr + (IntrStatus));

By default the value of max_interrupt_work is 20, which means this interrupt routine will only permit itself to loop 20 times with back-to-back "work." If there's a lot of NFS traffic (like, oh, I dunno, a lot of swapping), then there'll be a lot of interrupts, and the driver will start dropping some. That means mangled writes, or if the victim operation happens to be a critical section of swap, the console will poop its pants. I tuned the NFS read and write sizes to reduce this as much as possible but I couldn't eliminate it completely. While I'm told this value can be increased as an option, I'm not sure what the ideal value is, and it would be better to get it to a later kernel where the driver is better written anyway (at least 2.5.something, preferably 2.6).

All this I/O activity also makes the Dreamcast's clock run slow, and the solution for that is once again chrony and NTP. I'll talk about that at the end of our tour.

The other fun thing about Dreamcast Linux is that it recognizes it's running on a game console, for goodness' sake, so there have to be games. So there are some. The high scores don't save, of course, and most of these are text-based, but among others you've got Hack, Mille Bourne, Canfield, Adventure, Boggle, Tetris, Wumpus, Star Trek and ... PrBoom?
Conveniently, this version of PrBoom (2.2.0) uses SDL to render directly to the Dreamcast console framebuffer. The original documentation for Dreamcast Linux says you should run it after fbset -depth 16 (the default is 32). Frankly, it didn't seem to make any difference if the depth was 16, 32 or 8192: PrBoom started when it wanted to and bombed out when it didn't. Swap-over-NFS did seem to help, so maybe it was just a little short on memory, or maybe it was just placebo effect.
The artifact here is because the screen was moving while I was capturing, not because the game was defective. That said, performance isn't great because it's totally rendered in software (rendering at 640x480 doesn't help) and there's no support for the Dreamcast sound hardware either. It gets a little better with transparency off and at a smaller viewport. The Doom shareware WAD is helpfully included on disc.

The other included game engine is an older version of xmame. Try as I might, though, I couldn't get the SDL version to run on the local framebuffer, so that makes a good transition to talking about X support.

Yes, you can run X in Dreamcast Linux (Wayland? ha ha ha). The included version is XFree86 3.3.6, interestingly reporting itself as running on i686, probably as a cross-compilation artifact. You'll definitely need the Dreamcast mouse for this and you really want swap-over-NFS running as well or the GD-ROM will really thrash (it works without it, but alarmingly slowly).
You get one resolution, though, and that resolution is 640x480.
That stinks because the default session that opens (/root/.xsession) has windows that are clearly too big for the screen (but that's how it came, so I'm leaving it that way for now), and even worse on the TV set. Fortunately left clicking on the left window button will quickly minimize them. Don't close the login window, by the way, as that will end your X session.
On the other hand, the nice big letters are definitely readable on a TV set or the little Treamcast LCD. The window manager is twm, a good choice for being low-impact.
With login minimized and the xterm window shrunk to a more manageable size, we start xmame.x11. It's an old MAME, of course (0.37b15), and the default configuration in /usr/lib/xmame/xmamerc expects its ROMs in /var/lib/xmame which is naturally read-only. I solved this problem by making another NFS mount over that directly and loading ROMs there. This version of MAME plays Pac-Man by default, so I dug up a totally legal copy of the ROMs and renamed the components to the names it was expecting (you'll get some CRC warnings too, most likely).

With all that in place, there's no sound, but everything else seemed to work (legally). A really nice touch is that the Dreamcast controller D-pad is enabled as a joystick. Press Y for 1P credit and Start for 1P start.

There are no included graphical web browsers, and the build of X has some curious omissions like no Motif (possibly due to its licensing at the time) and no Xpm which would make doing so difficult, though there are Athena widgets and other basics (something like Chimera could potentially work in such an environment). However, both Lynx and w3m are included, and they seem to work fine for Gopher and Web access except of course for sites requiring TLS. In fact, there are no cryptography libraries on the system at all, and only a very old zlib (1.1.3).

So that brings us to what else I ended up adding. The first thing was to update zlib; the version on board is obviously way too old and also lacks header files, so it couldn't be compiled against for new software anyway. However, any update should also be done in such a way as to not upset programs linked against the older library since they would doubtlessly not be ABI-compatible. An exhaustive search of the entire filesystem found only three programs linked against the shipped /usr/lib/, namely CVS, Lynx and SDL MAME (but oddly not the X11 version). These were preserved by editing them in a hex editor to link them to /usr/lib/ (note change in version number) and then symlinking that to the 1.1.3 shared library. With those programs preserved, I then built the current zlib 1.2.13 and added its header files and libraries.

The natural next step would be to build something like OpenSSL, but modern versions require later build tools (the build attempt turned into a big mess), and older versions don't support enough to make them worth the effort. Instead, I went the self-contained application route. We need HTTPS support with TLS, and fortunately yours truly maintains a self-contained TLS library for ancient hardware: Crypto Ancienne. The length of the one-file build really strained the Dreamcast building it, but I finally got it to stick with -O2, and carl works great on the Dreamcast as a substitute for curl for downloading over HTTP and HTTPS. For SSH I selected Dropbear ssh, a "relatively small" SSH client and server package that's also completely self-contained and intended for embedded systems. It supports public keys (guess you'll be storing those on NFS too) and X11 forwarding. It connected to all my local SSH servers just fine.

And since there's always a need for useful network tools, I also threw in builds of socat (minus the crypto support), micro_inetd and period-correct netcat. Patches for these, mostly because this version of gcc doesn't like directives inside of macros, are all in /usr/src. With micro_inetd in particular you can run carl in proxy mode (something like micro_inetd 8765 /usr/bin/carl -pt, which listens on port 8765), or anything else you don't want to/can't hack /etc/inetd.conf for.

Last but not least: the time. As I mentioned earlier, the clock tends to lose time with lots of I/O, and DC Linux doesn't support the Dreamcast's hardware clock (the one that's always asking you for the time when the battery runs down), so the best solution is NTP. Although I fashioned a tiny clone ntpdate out of ntpclient (source for my changes in /usr/src), this wasn't enough to avoid make complaining about clock skew all the time after long jobs, and the timezone data included with this version is of course 22 years out of date with respect to changes in timezones and daylight savings.

Updating the timezone data was simply a matter of copying files from this Fedora 37 workstation, but building David Mills' venerable reference NTP implementation was one of the big packages that made the Dreamcast consistently flip out. Fortunately, chrony compiled with minimal changes. Because the initrd was dangerously full, however, my initial attempt to put the configuration file in /etc/chrony.conf caused attempt to access beyond end of device errors when I restarted. This was solved by changing the configuration to use /usr/etc/chrony.conf instead, which has no such limitation. The configuration is very simple:

server ntp iburst
pool iburst
driftfile /var/lib/chrony/drift
makestep 1 3

This causes chronyd to try consulting either a (presumably) local timesource named ntp or the North American NTP pool, and steps the clock for the first couple updates, slewing thereafter. Only one needs to be accessible (in fact, on my home test network which is not externally routable, only my local stratum-2 NTP timesource is available). The driftfile directive is obviously useless on a read-only filesystem, but it's there for future consideration. chronyd will automatically start with the system, and you can query its status with chronyc. Linux does not read or update the Dreamcast hardware clock.

Finally, how to set the local timezone? By default this is JST, the home timezone of DC Linux's original project leader, which I've preserved as an homage but certainly isn't my local timezone. Irritatingly, relinking /etc/localtime will run the risk of incurring the same problems with the initrd, but we have a simpler, more flexible way: I added hooks to /etc/profile, /etc/csh.cshrc, /etc/csh.login and /etc/csh.logout that, if they exist and are executable, will run /netfs/profile, /netfs/csh.cshrc, /netfs/csh.login and /netfs/csh.logout respectively. In the appropriate file just set the TZ environment variable to your timezone, which for me is PST8PDT. You can use this mechanism as well for things like changing your shell (I have exec /usr/bin/tcsh in /netfs/profile because I'm one of those people). There's also a hook during boot in init.d to run /netfs/ if it exists and is executable.

Total number of useless discs made during the creation of Dusted-off Dreamcast Linux: eight. Several Bothan CD-Rs died to bring you this update.

What's next? The obvious one is to try to update the kernel, since that should yield more features without (too much) churn to the userland experience, and should fix that obnoxious Realtek 8139 driver problem with dropped interrupts. The LinuxSH project has kernels through at least 2.6, though the latest kernels are all marked as testing, and the source in CVS shows that the Dreamcast hardware was already marked as "attic" even then. We also don't know if any changes were made to build the userland binaries it comes with. Until I come up with a cross-compiling solution, though, we'd be largely limited to pre-built kernels and wouldn't be easily able to fix bugs in them.

On the userland side itself a few more games would be nice, as would expanding the available X libraries so that more substantial apps can be run, though the generally slow graphics and low resolution would be a practical limit on many things. And of course trying to set up the included Lynx forwarding through a local self-hosted Crypto Ancienne HTTPS-over-HTTP proxy will neatly solve the TLS problem, assuming this is one of the versions that can still be coerced into doing so (alternatively, building Chimera and its needed image libraries might also do). I should see how much w3m can be tricked into doing something similar.

In the meantime, get your Dreamcast out of the closet and feel free to play around. I've christened it Dusted-off Dreamcast Linux, or DODCL, which you pronounce "DOH-deckle" (a la Intellivision "decle"). The components and burning instructions are on Github.


  1. For further context: The PS1 effort - the Russian "Runix" - dates back to July 2001 (if you check the original files' timestamps), which still makes the Dreamcast port predate it, but not by much.

  2. First of all, nice reading, very deply on the subjet, kudos for writing it! I wonder if the linux kernel would be able to see the extra ram from this mod: saddly don't have a dreamcast to try it myself. Iirc it should be possible to increase the vram and sound ram too, but requires soldering skills.

    Other thing that I always would like to see is if someone try the PS2 Linux on a PSX DESR as if I'm not mistaken it has 72mb of ram compared to the 32mb on the PS2 retail models.

    1. That's an interesting mod (a little involved for me with my relatively rudimentary soldering skills, but fascinating). However, I suspect it probably wouldn't immediately access the additional 16MB without modification, although the kernel shouldn't need much tweaking.

  3. Next stop:
    xpilot multiplayer: the dreamcast vs a ps3 vs your hp-ux box.

    BTW, my C8000 is the best performing machine in xpilot (out of what I own). It's weirdly bad on my Octane. Runs better on a G3.


  4. When I first heard about Linux on the DC years ago, my first thought was "Without a hard drive there are going to be issues." With the PS2 Linux kit you could always set swap to some ridiculous amount so you could compile most anything. Not that everything would run well, mind you. The 32MB of RAM was the primary limitation. My original PS2 LInux HDD finally failed in 2009 IIRC. (Though I've still got a sealed unused PS2 HDD somewhere) Some of my current Fedora on X86_64 configuration files have their origins on that PS2 Linux kit via YDL on the PS3.


Comments are subject to moderation. Be nice.