Patches, scripts and notes for running Debian sid ppc64 from the internal drive of a PS3 under OtherOS++, on a 6.4 kernel, alongside a working GameOS install. Existing guides for modern kernels put the root filesystem on external USB, which sidesteps two bugs in the PS3 storage path. Both are fixed here, so Debian boots from the OtherOS region, every region the hypervisor exposes is visible, and the GameOS regions are read-only.
One script does the host side: The easy way. The numbered steps after it are reference.
Upgrading from v1: your root device is renamed and this will stop the machine booting. The OtherOS region moves from
/dev/ps3dato/dev/ps3dd, which is what petitboot has always called it. Edityaboot.confandfstabbefore rebooting into the new kernel — see docs/migration.md.
| Console | A PS3 running OtherOS++ capable CFW. Verified on a Slim CECH-2503B with Evilnat 4.93 Cobra 8.5 CEX |
| Bootloader | Petitboot in VFLASH, and an OtherOS region already created with glevand's create_hdd_region.sh |
| Drive | Any internal drive. Verified on a Kingston SA400S37960G, 960 GB |
| Kernel source | Geoff Levand's ps3-linux tree at 6.4 |
| Host machine | Linux with gcc-powerpc64-linux-gnu, debootstrap, qemu-user-static, binfmt-support, e2fsprogs |
| Transfer | A USB stick, to carry the image and scripts to petitboot |
One script on the host, two at the console, then reboot.
On the host machine, from the root of this repository:
sudo ./scripts/make-debian-installer.sh
It works out what is already done, shows you a checklist, and offers to run everything outstanding, the rebuild loop only, or a single step. It pauses before each step to say what it is about to do and roughly how long that takes, keeps the command output on screen and logs it to a file, and picks the USB stick from the removable devices it can find. Non-removable disks are never offered.
Then, at the petitboot shell on the console:
sh /tmp/petitboot/mnt/sda1/partition-region.sh # first time only
sh /tmp/petitboot/mnt/sda1/write-image.sh
Reboot and choose debian, not debian-failsafe.
write-image.sh needs no arguments — the tool writes a manifest.txt to the
stick with the image name, hash, size and kernel release, and the script reads
it.
How long. First run is around 90 minutes: about 20 for debootstrap, 15 or more for the kernel depending on cores, the rest in image build, transfer and write. The rebuild loop afterwards is under 15 minutes.
--plain turns off the cursor addressing, colour and progress bars and behaves
exactly as the individual scripts do. That happens automatically when output is
not a terminal, when TERM is dumb, or when NO_COLOR is set.
To rebuild later, run the same command again and pick the rebuild loop. The tool detects that the kernel is installed and the region is partitioned, and skips straight to build, transfer, write.
Everything from here on is reference: what that tool is doing, how to do it by hand when something goes wrong in the middle, and why the two kernel patches exist.
make-debian-installer.sh runs exactly these, and they are the fallback when a
step fails and you want to pick up from the middle. It calls the same scripts
these steps call rather than reimplementing them.
Every command below runs from the root of this repository, so script paths are
relative to it. ~/ps3-linux is the kernel tree from step 1 — adjust it
throughout if you clone somewhere else.
Where each step runs. Steps 0 to 6 run on the host machine. Steps 7, 8 and 9 happen at the console — 7 and 8 at the petitboot shell, 9 is the reboot itself.
Steps 0 to 4 and 7 are done once. Steps 5, 6, 8 and 9 are the rebuild loop. Once the kernel is installed and the region is partitioned, iterating means build the image, copy it to the stick, write it, reboot.
Which loop depends on what you changed. A userland change is 5, 6, 8, 9. A
kernel change is 3, 4, 5, 6, 8, 9 — steps 3 and 4 first, or step 5 packages a
tree that still contains the old vmlinux.
Steps 0 and 1–3 are independent tracks. One builds a Debian userland, the other builds a kernel. They share nothing and can run in either order, or at the same time. They meet at step 4, which puts the kernel into the tree:
userland 0 ────────────────┐
├──> 4 ─> 5 ─> 6 ─> 7 ─> 8 ─> 9
kernel 1 ─> 2 ─> 3 ──────┘
If the kernel is already built, skip 1 to 3 entirely. If the tree already exists, skip 0.
0. Build the Debian root filesystem. (once)
sudo ./scripts/build-rootfs.sh /srv/ps3root
This debootstraps Debian sid ppc64 from Debian ports, installs the packages
needed to boot and administer the machine, writes fstab and yaboot.conf by
label, and installs the udev rule described below. Everything after this
assumes the tree exists at /srv/ps3root.
It produces the userland only. No kernel, no modules, no initrd — those are step 4, the one place in this sequence that installs a kernel. It depends on nothing above it, so it can run before, after or alongside steps 1 to 3.
Takes about 20 minutes under emulation on an 8-core host, most of it debootstrap running package scripts for a big-endian PowerPC target. The resulting tree is around 721 MB.
You set your own passwords. The script prompts twice during the run, once for root and once for the user it creates. This repository ships no credentials of any kind — no passwords, keys, accounts or SSH host keys.
SSH is enabled and the machine comes up reachable. ssh.service is on and
systemd-networkd takes a DHCP address as eth0, so you can log in over the
network after first boot.
That also means the box is exposed on your LAN from first boot, and Debian's
sshd permits password authentication for normal users. Once you are in,
install a key and turn passwords off:
ssh-copy-id <user>@<address>
# then in /etc/ssh/sshd_config
PasswordAuthentication no
1. Get the kernel source. (once)
git clone https://git.kernel.org/pub/scm/linux/kernel/git/geoff/ps3-linux.git ~/ps3-linux
2. Apply both patches. (once)
./scripts/kernel-patch.sh ~/ps3-linux
The script prints the patched code back so you can see it landed. It also fails
if drivers/ps3/ps3stor_lib.c still carries the withdrawn v1 hack.
On a newer kernel, 0001 may do nothing. René Rebe's bounce buffer fix was
posted upstream in November 2025. It is absent at v6.17, so anything from
roughly 6.18 onward already carries it. Check before applying it:
grep -n 'offset += bvec.bv_len' drivers/block/ps3disk.c
Nothing back means the tree needs 0001. kernel-patch.sh runs the same test
and skips the patch when the line is already there, reporting
0001 bounce buffer offset: already applied. 0002 is not upstream and is
always needed.
3. Configure and build the kernel. (once)
make -C ~/ps3-linux ARCH=powerpc ps3_defconfig
./scripts/kernel-config.sh ~/ps3-linux
make -C ~/ps3-linux ARCH=powerpc CROSS_COMPILE=powerpc64-linux-gnu- -j$(nproc)
A bare make, not make vmlinux. The bare target builds vmlinux and the
modules together; vmlinux alone skips modules entirely and step 4 then has
nothing to install.
Run kernel-config.sh once, after ps3_defconfig — re-running the defconfig
discards it. It adds devtmpfs, cgroups, namespaces and built-in USB HID, none
of which ps3_defconfig has and all of which a systemd userland needs. See
docs/kernel-config.md.
It also turns CONFIG_LOCALVERSION_AUTO off, which matters: a patched tree
otherwise builds as -dirty, /lib/modules stops matching uname -r, and no
module loads. .scmversion does not fix this — kbuild dropped it in 5.19. A
trailing + can still appear when HEAD is not on an exact tag; that is
harmless as long as modules_install runs after the final build.
4. Install the kernel and modules into the tree. (once)
powerpc64-linux-gnu-strip -s -o /tmp/vmlinux-stripped ~/ps3-linux/vmlinux
sudo cp /tmp/vmlinux-stripped /srv/ps3root/boot/vmlinux
sudo make -C ~/ps3-linux ARCH=powerpc CROSS_COMPILE=powerpc64-linux-gnu- \
INSTALL_MOD_PATH=/srv/ps3root modules_install
sudo cp ~/ps3-linux/.config /srv/ps3root/boot/config-<kernel release>
sudo chroot /srv/ps3root mkinitramfs -o /boot/initrd.img <kernel release>
The strip is not optional. Unstripped vmlinux is about 143 MB; stripped it is
about 19 MB.
This is the only step that puts a kernel into the tree. Re-run it whenever you rebuild the kernel, before step 5, or the image will carry the previous one.
<kernel release> is what make -s kernelrelease prints in the kernel tree.
kernel-config.sh prints it too, at the end of step 3. It has to match the
directory under /lib/modules exactly, which is what turning
CONFIG_LOCALVERSION_AUTO off in step 3 is protecting: a mismatch means
mkinitramfs builds an initrd with no modules in it.
Copy it verbatim, including any trailing + — that character is part of the
directory name, and 6.4.0+ and 6.4.0 are different directories.
Do not copy a release string out of this document. What you get depends on the
tree: kernel-config.sh turns CONFIG_LOCALVERSION_AUTO off and sets no
suffix of its own, so a fresh clone gives a bare 6.4.0+, while a tree built
before under different settings may give something longer. Read it from the
tree:
make -s -C ~/ps3-linux ARCH=powerpc CROSS_COMPILE=powerpc64-linux-gnu- kernelrelease
ls /srv/ps3root/lib/modules # after modules_install, must match
make-debian-installer.sh reads it the same way and never asks you to type it.
The .config copy is not strictly required — modules_install does not place
it, and without it mkinitramfs warns that it cannot check for CONFIG_RD_ZSTD
and assumes it is available. One line here and the warning goes away.
The initrd has to be regenerated on every kernel rebuild, alongside the
vmlinux copy. Miss it and step 5 packages a new kernel with an initrd built
for the old one.
The initrd is also why the emergency shell has a keyboard. mkinitramfs did
not pull USB HID or the host controllers in, so kernel-config.sh builds them
into the kernel instead.
Watch the total size. About 19 MB of vmlinux plus about 15 MB of initrd is
34 MB that petitboot has to kexec into 256 MB of RAM, alongside itself.
5. Build the image. (rebuild loop)
sudo mkdir -p /mnt/img
sudo mke2fs -t ext4 -b 4096 -O ^metadata_csum,^64bit -L ps3root \
-F /tmp/ps3root4g.img 1048576
sudo mount -o loop /tmp/ps3root4g.img /mnt/img
sudo cp -a /srv/ps3root/. /mnt/img/
sudo umount /mnt/img
md5sum /tmp/ps3root4g.img
Keep that md5; step 8 needs it.
Populate with cp -a, never mke2fs -d. A -d built image passed e2fsck -fn
cleanly and contained an almost empty /usr — one directory in it. It fails at
run-init rather than at build time.
Build on the host, not on the console: petitboot's mke2fs is from 2010 and
writes group descriptors the 6.4 ext4 driver rejects outright.
1048576 blocks of 4 KiB is 4 GiB. The region holds 18 GiB, but 4 is enough —
the tree is around 721 MB and the finished filesystem uses 875 MB — and it
quarters both the write and the verify. Grow it later from inside Debian with
resize2fs.
scripts/build-image.sh is the same sequence scripted, with an e2fsck pass
at the end.
6. Copy it to the USB stick. (rebuild loop)
sudo mkdir -p /mnt/stick
sudo mount -t drvfs D: /mnt/stick
sudo sh -c 'gzip -1 -c /tmp/ps3root4g.img > /mnt/stick/ps3root4g.img.gz'
sudo cp scripts/partition-region.sh scripts/write-image.sh /mnt/stick/
sudo umount /mnt/stick
The two petitboot scripts go on the stick alongside the image — steps 7 and 8 run them from there.
Put the stick in the rightmost USB port on the front of the console. That
gives sda1 consistently on a CECH-2503B regardless of what else is attached,
which is what makes the paths in steps 7 and 8 predictable. Enumeration order
is not guaranteed in general, so both scripts print the path they resolved to
as their first output.
gzip -1 deliberately. The image is mostly zeroes, so it comes out around
525 MB at any level, and -1 is much faster. drvfs is the WSL driver for
mounting a Windows drive letter; on a native Linux host, mount the stick
however you normally would.
7. Partition the OtherOS region. (once) At the petitboot shell:
sh /tmp/petitboot/mnt/sda1/partition-region.sh
Only needed once, or again if the region is recreated. It is not part of the rebuild loop — writing an image does not disturb the partition table.
Sizes are in raw sectors throughout, because petitboot's parted mishandles
GiB and % arguments — asking for 18GiB produced a 6 GiB partition.
8. Write and verify the image. (rebuild loop) Still at petitboot, with the md5 from step 5:
sh /tmp/petitboot/mnt/sda1/write-image.sh <md5>
The image name defaults to ps3root4g.img.gz, which is what step 5 produces,
so it only needs passing when the file is called something else — a second
argument, before the optional size in MiB:
sh /tmp/petitboot/mnt/sda1/write-image.sh <md5> ps3root18g.img.gz 18432
Pass the size too when the image is not 4 GiB, or the hash check only verifies the first 4096 MiB of it.
It writes with a plain redirect rather than piping into dd, because busybox
dd has no iflag=fullblock and silently truncates a gzip stream, then reads
the region back and compares the hash before checking the contents. Both
petitboot scripts log to the USB stick.
9. Reboot and select Debian. (rebuild loop) Check dmesg | grep ps3disk
on first boot; the line to look for is OtherOS region is region 3. Expected
output is in
docs/region-handling.md.
| Parameter | Default | Meaning |
|---|---|---|
ps3disk.regions=<mask> |
0 |
Which regions to expose. 0 is all accessible. Bit n is region n. |
ps3disk.writable=<mask> |
0 |
Regions to force read-write, in addition to the OtherOS region. |
ps3disk.otheros_rw=<0|1> |
Y |
Whether the detected OtherOS region is writable. |
ps3disk.regions=8 # only the OtherOS region; still named ps3dd
ps3disk.otheros_rw=0 # everything read-only, for a rescue boot
Region selection never renumbers anything, so a region keeps its name whichever others are exposed.
scripts/61-ps3-persistent-storage.rules is not part of the kernel patches and
is needed on any PS3 running a modern systemd, patched kernel or not.
systemd's 60-persistent-storage.rules matches sd*, sr*, vd*, mmcblk*,
cciss* and pmem*. PS3 block devices are ps3d* and match none of them, so
the blkid builtin never runs, no ID_FS_* properties are set, and /dev/disk
is never created at all. Anything resolved by label or UUID through udev then
fails.
Root still boots, which is what makes it confusing to diagnose — the initramfs
resolves root=LABEL= itself, without udev. But the swap unit systemd
generates from fstab waits on /dev/disk/by-label/ps3swap, nothing creates
it, and it times out after 90 seconds. Swap never activates.
build-rootfs.sh installs the rule to /etc/udev/rules.d/. With it in place,
/dev/disk/by-label/ps3root and ps3swap both appear and blkid returns full
properties for every region.
The proper fix is upstream: systemd's 60-persistent-storage.rules should
match ps3d* alongside the other block device patterns. Until it does, every
PS3 running systemd needs a local rule. This one is deliberately numbered 61 so
it runs after the file it supplements, and is written to be droppable once
upstream catches up.
What has actually run on hardware:
patches/0001, patches/0002 |
Booted. Region detection, write protection, naming, concurrent reads across three regions — all confirmed, see docs/region-handling.md |
61-ps3-persistent-storage.rules |
Confirmed: /dev/disk/by-label populated, blkid working |
kernel-patch.sh, kernel-config.sh |
Run. Produced the kernel that boots |
build-image.sh, partition-region.sh |
Run. Produced the image and partition table now on the console |
write-image.sh |
Run. Wrote and verified the image now on the console |
build-rootfs.sh |
Run. Built the tree that boots |
make-debian-installer.sh |
Not run end to end. Written after the dry run. Its state detection, progress polling, removable-device scan and menus are tested; a full run through it is not. It calls the scripts above rather than reimplementing them, so the steps it drives are verified |
Every script in this repository has run on hardware, in the order above. A
full dry run went from a clean machine to booting Debian: a fresh tree at
/srv/ps3root-test, kernel and modules installed into it, image built,
partitioned and written at petitboot, booted.
Numbers from that run:
| debootstrap and packages | about 20 minutes, 8-core host, under emulation |
| resulting tree | 721 MB |
| image | 875 MB used of a 4 GiB filesystem |
| initrd | 13.9 MB |
| gzipped image on the stick | 337 MB |
| kernel release | 6.4.0-g98ec4e7cee0f+ — that tree's value, not a target |
The kernel release above is what that particular tree produced; it carried a
.scmversion from an early build. A clean clone gives 6.4.0+. Read yours
with make -s kernelrelease rather than expecting either.
The tree is 721 MB rather than the 1.4 GB quoted in earlier notes because
build-rootfs.sh now runs apt-get clean. The earlier tree carried every
.deb it had downloaded.
ps3disk_scatter_gather() copies between the driver's 64 KiB bounce buffer and
the request's bio vectors. Commit 6e0a48552b8c ("ps3disk: use
memcpy_{from,to}_bvec", 2021) converted it to the newer bvec accessors and
dropped the offset += size line in the process. Every vector in a
multi-segment request is therefore copied to or from offset zero, corrupting
all but the first.
Single-segment requests are fine, so the superblock reads correctly and
anything larger returns repeated data. It presents as ext4 group descriptor
errors, binaries that exist but will not execute, and a different errno on each
boot, since request segmentation varies between boots. patches/0001 restores
the line.
Petitboot is unaffected: 2.6.30 predates the refactor. Upstream ps3disk.c at
v6.17 still has this bug — the fix postdates that release, so check for
offset += bvec.bv_len in whatever tree you move to.
Mainline ps3disk logs N accessible regions found. Only the first one will be used and takes region 0, which under OtherOS++ is the whole physical drive
rather than the OtherOS region. Under Sony's original OtherOS the hypervisor
exposed one region and there was nothing to choose between; CFW exposes them
all.
patches/0002 gives each accessible region its own block device instead of
choosing between them, matching petitboot:
| Device | Region | Contents | Default |
|---|---|---|---|
ps3da |
0 | whole physical drive | read-only |
ps3db |
1 | GameOS, UFS2 | read-only |
ps3dc |
2 | GameOS cache, FAT32 | read-only |
ps3dd |
3 | OtherOS | read-write |
Everything except the OtherOS region is read-only by default. Region 1 is roughly 870 GB of games and the filesystem GameOS needs in order to boot at all, and a partially written GameOS region means a full reinstall. The OtherOS region is identified as the highest-numbered accessible region that is neither region 0 nor the size of the whole drive.
The FAT32 cache region is a useful file transfer path between the two systems:
mount -o ro /dev/ps3dc /mnt/gameos-cache
ps3db is not mountable — CONFIG_UFS_FS is off deliberately, since Linux UFS
support is read-only and patchy and there is nothing useful to read there.
The patch is confined to drivers/block/ps3disk.c. It needs no change to
ps3stor_lib.c, asm/ps3stor.h, ps3rom or ps3flash, because ps3disk
computes its region id locally and never calls ps3stor_read_write_sectors().
It replaces the v1 __fls hack, which is withdrawn rather than superseded.
Not upstream, and specific to OtherOS++ layouts. The design rationale, the serialisation argument, the write-protection failure modes and the hardware verification are in docs/region-handling.md.
patches/ kernel patches, apply in order
scripts/ build and install helpers
docs/ design notes, kernel config, troubleshooting log
scripts/ splits in two. Those run on the host build the kernel and image;
those run at the petitboot shell write to the console and log their output to
the USB stick. make-debian-installer.sh is the host-side driver that calls
the rest in order.
| Document | |
|---|---|
| docs/migration.md | v1 to v2: ps3da becomes ps3dd. Read before rebuilding. |
| docs/region-handling.md | Why patches/0002 is built the way it is, and what was verified. |
| docs/kernel-config.md | Config options beyond ps3_defconfig, and why each is needed. |
| docs/troubleshooting.md | Symptoms and causes, in the order they appear during a build. |
Written by setsid. patches/0002, the documentation and the scripts are mine;
the work below is other people's.
- René Rebe for the
ps3diskoffset fix, and Christoph Hellwig for reviewing it - T2 SDE for
architecture/powerpc64/package/linux/0010-ps3stor-multiple-regions.patch, which established that the answer is one block device per accessible region rather than a better choice of single region.patches/0002takes that idea and differs in the details — seedocs/region-handling.md - Geoff Levand for the PS3 kernel tree
- glevand for
create_hdd_region.shand the OtherOS tools - Paul Sajna and olin000 for the modern-kernel USB guides this builds on
- kostirez1 for the region layout work on psx-place
Two licences, split so a scanner can identify them:
patches/ |
GPL-2.0, matching the kernel files they modify — LICENSE.GPL-2.0 |
scripts/, docs/, this README |
MIT — LICENSE |
Every file carries an SPDX-License-Identifier saying which applies. LICENSE
is the MIT text verbatim with nothing above it, because prose in front of it is
what made GitHub report the repository as NOASSERTION.