Skip to content

Instantly share code, notes, and snippets.

@runlevel5
Created July 2, 2026 08:49
Show Gist options
  • Select an option

  • Save runlevel5/cc67282d94598444fbbf07a1e2698262 to your computer and use it in GitHub Desktop.

Select an option

Save runlevel5/cc67282d94598444fbbf07a1e2698262 to your computer and use it in GitHub Desktop.
Installing Arch POWER (ppc64 (big-endian) or ppc64le (little-endian)) on a QEMU/libvirt VM

Installing Arch POWER (ppc64 (big-endian) or ppc64le (little-endian)) on a QEMU/libvirt VM

This documents a from-scratch install of Arch POWER Linux distribution on a pseries QEMU/KVM VM running on a POWER9 host, with a PReP boot partition and GRUB (powerpc-ieee1275 target). It also captures every pitfall hit along the way — each one cost real debugging time, so they're called out explicitly rather than silently avoided.

Endianness note: this was run and verified end-to-end on powerpc64 (big-endian). Arch POWER also publishes a powerpc64le (little-endian) repo, and everywhere the two variants diverge is called out inline below — but only the big-endian path has actually been executed. The Open Firmware/PReP/GRUB mechanics (sections 0, 2's boot-order handling, 3, and most of 4) are endianness-agnostic and should carry over unchanged; the places that differ are the install ISO and the kernel package name/files, both flagged where they come up.

0. Why this is harder than an x86 install

PowerPC installs are unusually fragile compared to x86/UEFI mainly because there's no BIOS/UEFI boot menu convenience. The firmware is Open Firmware (SLOF under QEMU pseries), which boots from a raw PReP partition containing GRUB's core.img directly — no ESP, no bootloader search heuristics like UEFI has. grub-install targeting and boot-order handling are correspondingly stricter and less forgiving of mistakes (see the pitfalls below).

1. Preparation

1.1 Pick your target: big-endian vs little-endian

Everything else in this guide is identical between the two; only the items in this table differ. Refer back to it wherever a step says "(see 1.1)".

Big-endian powerpc64 (verified) Little-endian powerpc64le (unverified)
Package repo https://repo.archlinuxpower.org/base/powerpc64/ https://repo.archlinuxpower.org/base/powerpc64le/
Install ISO archpower-current-powerpc64.iso not checked — find the matching ISO on the project's download page
Kernel package linux-ppc64 (64KB pages, the only page size offered on the BE repo) linux (64KB pages — matches BE, and the better choice unless you have a specific reason not to) or linux-4k (4KB pages — an alternative build for compatibility with software/guests that assume 4KB pages; pick only if you know you need it)
Kernel image /boot/vmlinuz-linux-ppc64 /boot/vmlinuz-linux
Initramfs image /boot/initramfs-linux-ppc64.img /boot/initramfs-linux.img
  • Disk: 50GB is comfortable and is what this guide uses. Layout:
    • vda1 — 9 MiB, PReP boot partition (no filesystem)
    • vda2 — ~43 GiB, ext4, root
    • vda3 — ~7 GiB, swap
  • VM specs used:
    • libvirt/QEMU, machine='pseries-10.2', arch='ppc64le' (this is just QEMU's emulation target binary — it runs big-endian or little-endian ppc64 guest kernels equally; guest endianness is a kernel property, not a hypervisor one)
    • cpu mode='custom' model='POWER9'
    • 8 vCPUs, 8GiB RAM
    • Disk: virtio-blk (bus='virtio'), backed by a qcow2 image
    • Install media: attached as a scsi cdrom (virtio-scsi controller)
    • NIC: virtio model (<interface type='network'><model type='virtio'/></interface>, attached to libvirt's default NAT network/virbr0). Use virtio here rather than an emulated NIC (e.g. e1000/rtl8139) — it's what QEMU's pseries machine expects for good throughput, and it's what shows up guest-side as an enp*-style interface, which is why the systemd-networkd match in 4.7 uses Name=en*.
    • A serial console is essential: <serial type='pty'><target type='spapr-vio-serial'/></serial>. This is your only way to interact with the SLOF firmware and the GRUB shell — there's no graphical framebuffer to fall back on. Access it with virsh console <domain> (needs a real pty — allocate one with ssh -tt if you're driving this over SSH, or run it in a local terminal).

2. Getting the install ISO and booting it

  1. Download the Arch Linux POWER installer ISO matching your target endianness (see 1.1) from the Arch Linux POWER project. This guide uses archpower-current-powerpc64.iso (big-endian); if you're doing a powerpc64le install, get the matching little-endian ISO instead.
  2. Attach the ISO to the VM as a cdrom device with <boot order='1'/>, ahead of the disk (<boot order='2'/> on the virtio disk).
  3. Start the VM and attach to the console:
    virsh start <domain>
    virsh console <domain>
    
    SLOF will boot the ISO (order 1 wins), landing you in the Arch Linux POWER live/installer environment.

Pitfall #1 — the VM never boots your install, only the ISO. If the cdrom keeps <boot order='1'/> after you've finished installing, every reboot will land you back in the live installer, forever, regardless of whether your disk install is correct. This is easy to not notice, because the live environment looks similar enough to a real login prompt that you can spend a long time debugging GRUB when the actual problem is "the VM never even tried the disk." Fix: once install is done, either detach the cdrom device entirely, or set its boot order below the disk's.

3. Partitioning

From inside the live environment, partition the target disk (assume /dev/vda):

fdisk /dev/vda
  • g — create a new empty GPT partition table
  • n — new partition 1, size +9M (a PReP partition needs to be small; a handful of MB is plenty for GRUB's core.img)
    • t — change its type; search for "PowerPC PReP boot" and select it
  • n — new partition 2, most of the remaining space, default type (Linux filesystem)
  • n — new partition 3, remaining space for swap
    • t — set its type to Linux swap
  • w — write the table

Format:

mkfs.ext4 /dev/vda2
mkswap /dev/vda3
swapon /dev/vda3

Pitfall #2 (avoided, but worth stating) — do not put a filesystem on the PReP partition. It's not mounted; GRUB writes its raw core.img ELF blob directly onto the block device during grub-install. mkfs'ing it will just get overwritten and confuses nothing — but don't bother, and don't try to mount it.

Mount the root partition and pacstrap into it:

mount /dev/vda2 /mnt

Sanity-check the result with lsblk before moving on — the PARTTYPE column is worth including since it's the actual GUID GRUB/SLOF look for to identify the PReP partition (9e1a2d38-c612-4316-aa26-8b49521e5a8b), not just its human-readable name:

lsblk -o NAME,SIZE,TYPE,FSTYPE,PARTTYPE,PARTLABEL,MOUNTPOINT /dev/vda

Expected output:

NAME     SIZE TYPE FSTYPE   PARTTYPE                             PARTLABEL MOUNTPOINT
vda       50G disk
├─vda1     9M part          9e1a2d38-c612-4316-aa26-8b49521e5a8b
├─vda2  42.8G part ext4     0fc63daf-8483-4772-8e79-3d69d8477de4           /mnt
└─vda3   7.1G part swap     0657fd6d-a4ab-43c4-84e5-0933c84b4f4f

Confirm before continuing: vda1 has no FSTYPE (correct — it's meant to stay raw), vda2 shows ext4 and is mounted at /mnt, and vda3 shows swap (it doesn't need a MOUNTPOINT — swap is activated, not mounted).

4. Bootstrap and configuration

4.1 Base install — include the kernel explicitly

Use the kernel package name for your target from 1.1:

# big-endian (powerpc64):
pacstrap -K /mnt base base-devel linux-ppc64 linux-firmware grub openssh vim sudo

# little-endian (powerpc64le):
pacstrap -K /mnt base base-devel linux linux-firmware grub openssh vim sudo

Pitfall #3 — base does not include a kernel. This has been Arch policy since ~2012 on every architecture: base deliberately excludes the kernel so you choose one explicitly (linux, linux-lts, linux-zen, etc). On the big-endian PowerPC port, the equivalent package is linux-ppc64; on powerpc64le it's plain linux — neither is linux on the BE side, and neither is pulled in automatically. Forgetting it produces a system that installs and pacstraps fine, boots to a grub> prompt or nothing at all, and has an empty /boot except for GRUB's own files. There is no warning for this at install time — it only surfaces when you try to boot.

4.2 fstab and chroot

genfstab -U /mnt >> /mnt/etc/fstab
arch-chroot /mnt

Pitfall #4 — use arch-chroot, not plain chroot. Plain chroot does not bind-mount /dev, /proc, /sys into the target. Things that look unrelated break as a result — e.g. sshd failing immediately with Could not open /dev/null because /dev inside the chroot is empty. arch-chroot (from arch-install-scripts, present on the live ISO) handles all of this for you, plus copies resolv.conf so DNS works inside the chroot.

4.3 Hostname, users, sudo

echo archpower > /etc/hostname
passwd                                   # set root password

useradd -m -G wheel -s /bin/bash tle
passwd tle

echo '%wheel ALL=(ALL:ALL) ALL' > /etc/sudoers.d/wheel

Authorize your SSH key for the new user:

mkdir -p /home/tle/.ssh
echo '<your public key>' >> /home/tle/.ssh/authorized_keys
chown -R tle:tle /home/tle/.ssh
chmod 700 /home/tle/.ssh
chmod 600 /home/tle/.ssh/authorized_keys

4.4 Console keymap (needed before building the initramfs)

echo 'KEYMAP=us' > /etc/vconsole.conf

Pitfall #5 — missing /etc/vconsole.conf breaks mkinitcpio. The sd-vconsole build hook errors out (file not found: '/etc/vconsole.conf') if this file doesn't exist yet when mkinitcpio runs (which happens automatically as a post-install hook the moment you install the kernel package). The initramfs is still generated, but the build reports failure, which is alarming and easy to mistake for something more serious. Create the file before installing the kernel, or just rerun mkinitcpio -P afterward once it exists.

If you installed the kernel before creating this file, just rebuild:

mkinitcpio -P

Confirm the kernel and initramfs actually exist:

ls -la /boot
# BE (linux-ppc64): grub/  initramfs-linux-ppc64.img  vmlinuz-linux-ppc64
# LE (linux):        grub/  initramfs-linux.img        vmlinuz-linux

4.5 GRUB terminal output — avoid the gfxterm crash

Edit /etc/default/grub and uncomment:

GRUB_TERMINAL_OUTPUT=console

Pitfall #6 — GRUB defaults to gfxterm, which has no video driver on this firmware. The default /etc/default/grub leaves GRUB_TERMINAL_OUTPUT commented out, which means GRUB tries a graphical menu (gfxterm, GRUB_GFXMODE=auto). SLOF's serial console (spapr-vty) has no framebuffer GRUB can drive, so grub.cfg's attempt to set a video mode fails with:

error: video/video.c:grub_video_set_mode:782: no suitable video mode found

This aborts script execution before the boot menu ever displays, dropping you into an interactive grub> shell instead — which looks like a totally broken install, but is just this one setting. Forcing console output avoids it entirely.

4.6 Generate config and install GRUB

grub-mkconfig -o /boot/grub/grub.cfg
grub-install --target=powerpc-ieee1275 --boot-directory=/boot /dev/vda1

Pitfall #7 — target the PReP partition, not the whole disk. grub-install --target=powerpc-ieee1275 --boot-directory=/boot /dev/vda (the whole disk) fails with:

grub-install: error: the chosen partition is not a PReP partition.

Unlike a BIOS/MBR install (where you point grub-install at the whole disk and it writes to the MBR gap), a PReP install writes GRUB's core.img directly into the PReP partition itself. Point grub-install at the partition device (/dev/vda1), not the disk (/dev/vda).

You should see:

Installing for powerpc-ieee1275 platform.
Installation finished. No error reported.

4.7 Networking (systemd-networkd)

base/base-devel doesn't include a DHCP client (dhcpcd isn't pulled in by default). Since systemd is already there as your init system, use systemd-networkd instead of installing anything extra:

mkdir -p /etc/systemd/network
cat > /etc/systemd/network/20-wired.network <<'EOF'
[Match]
Name=en* eth*

[Network]
DHCP=yes
EOF

systemctl enable systemd-networkd
systemctl enable systemd-resolved
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

(Check ip link for your actual interface name and adjust the Name= glob if it doesn't match en*/eth*.)

Note — benign warning: systemd-networkd may log Could not set hostname: Access denied on startup. This is expected: the shipped systemd-networkd.service unit sandboxes with ProtectHostname=yes by default, which blocks it from applying a hostname received via DHCP option 12. It does not affect actual address/route assignment — confirm with networkctl status / ip a instead of worrying about that log line.

4.8 SSH

ssh-keygen -A
systemctl enable sshd

If you need a non-default port, add Port <N> to /etc/ssh/sshd_config before enabling the service.

5. Finish and reboot

Exit the chroot, unmount, detach the install ISO (or demote its boot order below the disk — see Pitfall #1), and restart the VM:

exit
umount -R /mnt

Then, from the host (not the VM):

virsh detach-disk <domain> <cdrom-target> --config --live
virsh destroy <domain>
virsh start <domain>

Watch the console — you should see: SLOF → PReP partition → GRUB (a real text menu, no crash) → kernel boot → login prompt, all without manual intervention.

6. Debugging the GRUB shell, if you land in one anyway

If you ever do end up at an interactive grub> prompt, these are the commands that got a manual boot working while diagnosing the above:

grub> insmod part_gpt
grub> insmod ext2
grub> ls

GRUB names disks by Open Firmware device path, not hd0-style BIOS names — expect something like (ieee1275/disk) (ieee1275/disk,gpt1) (ieee1275/disk,gpt2) (ieee1275/disk,gpt3). Find your root and boot manually:

grub> ls (ieee1275/disk,gpt2)/boot/
grub> linux (ieee1275/disk,gpt2)/boot/vmlinuz-linux-ppc64 root=UUID=<your-root-uuid> rw
grub> initrd (ieee1275/disk,gpt2)/boot/initramfs-linux-ppc64.img
grub> boot

(Substitute vmlinuz-linux/initramfs-linux.img per 1.1 if you're on powerpc64le.)

Or, to test whether grub.cfg itself is reachable/valid without going through the full normal auto-boot flow:

grub> set root=(ieee1275/disk,gpt2)
grub> configfile (ieee1275/disk,gpt2)/boot/grub/grub.cfg

7. Inspecting a VM's disk without stopping it

Useful if you need to check a running (or hung) VM's filesystem contents without powering it off — e.g. to confirm whether a kernel is actually present. qemu-nbd can attach a disk image read-only even while the VM's own QEMU process still holds it open, by disabling image locking explicitly (the image is opened read-only, so this is safe):

modprobe nbd max_part=8
qemu-nbd --read-only -c /dev/nbd0 \
  --image-opts driver=qcow2,file.driver=file,file.filename=/path/to/disk.qcow2,file.locking=off
partprobe /dev/nbd0
mount -o ro,noload /dev/nbd0p2 /mnt/check   # noload skips journal replay on a dirty fs
# ... inspect ...
umount /mnt/check
qemu-nbd -d /dev/nbd0

(-o ro,noload matters if the filesystem has a dirty/pending journal from a live chroot session — a plain -o ro mount can be refused outright.)

Summary: root causes at a glance

Symptom Root cause Fix
Every reboot re-launches the installer Cdrom has boot order ahead of disk in libvirt XML Detach ISO or demote its boot order
/boot empty except grub/, nothing boots base doesn't include a kernel package pacman -S linux-ppc64 (BE) / linux (LE), then mkinitcpio -P
mkinitcpio reports failure /etc/vconsole.conf missing Create it before building the initramfs
grub-install: chosen partition is not a PReP partition Targeted the whole disk instead of the PReP partition grub-install ... /dev/vda1, not /dev/vda
GRUB drops to grub> shell, no menu gfxterm fails (no suitable video mode found) — no framebuffer on serial-only firmware GRUB_TERMINAL_OUTPUT=console in /etc/default/grub, regenerate config
dhcpcd: command not found Not part of a minimal base install Use systemd-networkd (already present) instead
sshd: Could not open /dev/null Used plain chroot instead of arch-chroot Re-enter with arch-chroot, which bind-mounts /dev, /proc, /sys
SSH Host key verification failed after a fresh boot New host keys generated by ssh-keygen -A each session ssh-keygen -R <host> to clear the stale cached key
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment