Linux on the PS4 is not new. Community kernels have run on it for years, and you can find ready-made distros for it. But installing one has always meant a PC, a USB stick or a partitioning step, and a fixed distro picked by whoever built the image.

I wanted something simpler: copy any distro's rootfs archive to the console, start Linux, and pick it from a menu on the TV with the controller. Install, boot, add another one, all from the couch.

That became PS4 Multiboot. This post is about the problems I had to solve to get there.

The boot menu with two installed distros and one ready to install

How Linux starts on a PS4

On a jailbroken PS4, a payload (the PS4 Linux Loader) starts a Linux kernel and initramfs from the console's own drive. From that point everything is a normal Linux boot, with one difference: the initramfs is all you have. There is no installer, no desktop and no distro yet.

So the whole project lives in the initramfs: a BusyBox shell script for the logic (init, about 900 lines) and a few small C programs for the parts a shell can't do.

Problem 1: installing without repartitioning

The PS4's internal drive is encrypted, and its user partition is formatted with UFS2, the FreeBSD file system. The loader passes the drive key to Linux, so the partition can be unlocked with cryptsetup:

cryptsetup -d /key/eap_hdd_key.bin --cipher aes-xts-plain64 -s 256 \ --offset 0 --skip "$skip" create ps4hdd /dev/sdX27 mount -t ufs -o ro,ufstype=ufs2 /dev/mapper/ps4hdd /ps4hdd 

Repartitioning a game console's drive is not an option, and Linux's UFS2 write support is marked experimental. So the drive stays mounted read-only almost all the time.

Each distro lives in one big file on the PS4 partition, for example /user/home/debian-ps4.img. Creating it is the only write that goes through UFS2: a short read-write remount, a file full of zeros, then back to read-only.

After that, the file is never written through the file system again. A small C tool, blockmap, asks the kernel where each block of the file sits on the disk (the FIBMAP ioctl) and prints a device-mapper table:

0 2097152 linear /dev/mapper/ps4hdd 8392704 2097152 1048576 linear /dev/mapper/ps4hdd 12587008 ... 

dmsetup create turns that table into a block device, /dev/mapper/distimg, made of exactly the file's own blocks. It gets formatted as ext4 and mounted as the distro's root. Linux writes to the image directly, UFS2 never sees a write, and the rest of the PS4 data is untouched.

The same trick works for reading: the rootfs archive is also mapped with blockmap, so the PS4 partition can be unmounted completely while the install runs.

Problem 2: a boot menu with no desktop

In the initramfs there is no X, no Wayland and no GUI toolkit. The menu is fbui, about 1,800 lines of C that draw straight into the framebuffer (/dev/fb0): rounded cards, a glow behind the selected one, progress bars, a bitmap font.

Input comes from /dev/input/event*. A DualShock 4 on a USB cable reports Cross and Circle with different key codes depending on the driver, so the mapping accepts both:

case BTN_SOUTH: case KEY_ENTER: ... return A_OK; case BTN_EAST: case KEY_ESC: ... return A_BACK; case BTN_THUMB: return A_OK; /* Cross on some drivers */ case BTN_THUMB2: return A_BACK; /* Circle */ 

The D-pad arrives either as buttons or as ABS_HAT0X, so both are handled too. A USB keyboard works the same way.

The shell script and fbui talk through exit codes: the menu exits with the index of the chosen card, and the script decides what to do with it. Each screen (menu, welcome, progress, error) is just another fbui mode.

Problem 3: one more thing the PS4 needed

On the PS4 Slim and Pro, the HDMI bridge chip turns its output off a few seconds after boot. Without a fix, the menu is drawn to a screen nobody can see.

ps4hdmi is a small C daemon that talks to the bridge through the PS4's ICC device and turns the output back on every two seconds. It runs in the initramfs, and the installer also adds it to each installed distro as a systemd service.

Problem 4: the name and logo, before anything is unpacked

I wanted every distro to show up with its own name and logo, with no config file per distro. The user just drops in debian-ps4-rootfs.tar.xz and the card should say Debian and show the Debian logo.

The information is inside the archive: /etc/os-release gives the ID and sometimes a LOGO icon name, and the icon itself is somewhere under /usr/share/pixmaps or /usr/share/icons. The problem is that it's inside a compressed tar of several hundred MB, and BusyBox tar can't extract by wildcard.

So fbui has a scan mode: a small tar stream parser (ustar, GNU long names and pax headers) that reads the archive from a pipe and keeps only the files that match a few patterns:

xz-mt -dc -T0 < archive.tar.xz | fbui scan /run/meta/debian \ etc/os-release usr/lib/os-release 'usr/share/pixmaps/*' 'usr/share/icons/*logo*' 

It runs in the background while the menu is already on the screen. When it finds the logo, the card picks it up live. fbui logo then decodes the PNG or JPEG (with the single-header stb_image), scales it to the card, and even tints the card's glow with the logo's main color.

If a distro has no logo at all, the card shows the first letter of its name. After install, the name and logo are saved in the image, so the next boot shows them instantly.

Problem 5: making any rootfs bootable on a PS4

A generic rootfs from linuxcontainers.org or a distro's own archive knows nothing about the PS4. After unpacking it, the installer adds:

  • the kernel modules and extra firmware for the PS4 kernel, shipped inside the initramfs,
  • the HDMI keep-alive service,
  • X settings for the PS4 GPU,
  • a hostname, a /etc/hosts line and the root entry in /etc/fstab,
  • a login when the archive has none: user and password named after the distro, so debian / debian.

The script can't assume the archive's layout either. Some rootfs archives have everything inside a top-level folder, so the installer flattens it, then checks that an init really exists before it calls the install a success.

What I learned

  • A shell script plus small C tools goes a long way. The logic stays readable, and C is used only where the shell can't do it: the framebuffer, the tar parser, FIBMAP, the HDMI chip.
  • Testing on a console is slow, so test somewhere else first. I tested most changes by running the real initramfs in a chroot inside a Docker container, with a fake framebuffer that saves each screen as a PNG. Only the last step went to the PS4.
  • Don't trust what an archive looks like. Different compressions, a folder at the top or not, a logo or not, a user or not: every distro does it differently.

Try it

You need a jailbroken PS4 with GoldHEN, an FTP client and a distro's rootfs archive (.tar.xz, .tar.gz or .tar.bz2). The release files and full instructions are on GitHub:

github.com/blue0x1/ps4-multiboot

It's GPL-2.0, and issues and pull requests are welcome. If you get a distro running that I haven't tried, I'd like to hear which one.