Friday, December 12, 2025

Why Agile left me disappointed and frustrated

Agile software development

Agile can be defined in various ways; the following is one such definition:

Agile software development is an umbrella term for a set of frameworks and practices based on the values and principles expressed in the Manifesto for Agile Software Development and the 12 Principles behind it.

Some of the claimed benefits of Agile include:

  • Faster delivery of working software through iterative development
  • Improved adaptability, allowing teams to respond quickly to changing requirements
  • Greater customer involvement and continuous feedback
  • Better visibility and transparency throughout the project
  • Enhanced team collaboration and communication
  • Early identification of risks and issues through frequent reviews

I did not choose Agile myself; it was imposed on all of us by the company I worked for at the time. From the very beginning, I was never fond of it, and over the following decade I became increasingly aware of its problems and developed a strong dislike for it.

Overbearing, nanny-like process

I am aware that Agile can be applied in different ways and practised with varying approaches. Some argue that if Agile becomes too overbearing or inflexible, it is being implemented incorrectly. However, it often seems that any issues with Agile are dismissed, with the blame consistently placed on the people following the process rather than the methodology itself.

The company I worked for insisted on using the Agile methodology with a Scrum framework. In retrospect, I believe Scrum was the worst part. Simple, straightforward development tasks were transformed into a bizarre kind of kindergarten role-playing, complete with Scrum Masters and user stories. Every working day, people were forced to gather at 10 a.m., march into a meeting room like mindless robots, and repeat the same phrases: “Yesterday I did this…” and “Today I will do this…”, while others stood around silently, staring at the walls or the floor. This monotonous ritual continued for weeks, months, and years, for as long as anyone had the patience to remain with the company.

The Agile Manifesto

The Agile Manifesto outlines the core principles of Agile. I would like to offer my own perspective on its twelve principles.

1. Our highest priority is to satisfy the customer through the early and continuous delivery of valuable software.

The highest priority should not be customer satisfaction at the expense of everything else. This approach is misguided, because customer satisfaction does not depend solely on early software releases. There are many other crucial factors, such as usability, scalability, reliability, and support. Releasing software too early increases the likelihood of defects and design flaws, which can ultimately undermine the user experience.

The drive for early and continuous software delivery can also have a negative impact on team morale. I have personally experienced this many times when management, guided by this principle, set unrealistic deadlines and expected a constant stream of fully tested features, even when the time and resources were insufficient to deliver them.

2. Welcome changing requirements, even late in development. Agile processes harness change for the customer’s competitive advantage.

No, push back against this if you can. It is not an effective way to develop complex projects. The desire to please and satisfy the customer often comes at the expense of the development team. If requirements are constantly changing, this likely indicates that the requirements specification was never properly completed or that the project is inherently too experimental.

Nobody wants to work overtime or on weekends just to meet a project deadline when the customer decides to change the requirements. While Agile does not explicitly mandate such working practices, in reality, this is how many companies operate.

3. Deliver working software frequently, from a couple of weeks to a couple of months, with a preference to the shorter timescale.

Why rush? Are you competing to see who can type the fastest on a keyboard? It all depends on the project. Some projects can be delivered in stages, while others should not be rushed and may take many months before anything tangible can be presented to the customer. The development team should rely on their own judgement and experience to determine the optimal schedule. Just because somebody expects a feature within a week does not necessarily mean it is feasible given the current resources.

Management can sometimes develop a misguided perception that if you are not delivering working software on a weekly basis, you are somehow lazy or incompetent. The constant pressure to justify your role to the company by demonstrating measurable progress each sprint can be overwhelming. It is far more valuable to have knowledgeable and motivated software developers who are not micromanaged by individuals lacking deep technical expertise. Trust developers to do their jobs and to decide when features are ready for release.

4. Business people and developers must work together daily throughout the project.

“Must” is a strong word here. If specific business requirements need to be implemented in software, it can be helpful for developers to understand some of the underlying business practices. This is usually addressed during requirements gathering and can be a valuable interaction. However during the software development, the phrase "too many cooks spoil the broth" is also appropriate here. Software developers need to concentrate on their development tasks and have the freedom to innovate. Frequent requests and critiques from those without deep technical knowledge can often be unwelcome.

5. Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.

Completely agree with this.

6. The most efficient and effective method of conveying information to and within a development team is face-to-face conversation.

This is not always the case. In fact, I personally spent many long hours in face-to-face discussions with people who refused to read documentation or who simply wanted to appear busy, organising numerous repetitive meetings without accomplishing much.

7. Working software is the primary measure of progress.

Absolutely not! People have different skills and can contribute in many ways: updating documentation, fixing bugs, providing help and training to junior developers, volunteering to administer company web or email servers, assisting with customer escalations, and more.

8. Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely.

Life is more complicated than this. Greater emphasis should be placed on people, as they naturally experience ups and downs. Maintaining a constant pace can be difficult due to various life events such as bereavement, depression, medical issues, or personal conflicts at home. Treat people fairly and give them some leeway when they need it.

Even in the absence of external pressures, when life is otherwise smooth, constant demands from so-called “efficiency experts” to work better, harder, and faster can still lead some people to develop symptoms of anxiety.

9. Continuous attention to technical excellence and good design enhances agility.

Yes people should aim for technical excellence and good design.

10. Simplicity–the art of maximizing the amount of work not done–is essential.

That sounds ideal, but in reality, developers often have to maintain legacy codebases full of cruft and complexity. It is important to be pragmatic and accept that writing messy code cannot always be avoided.

11. The best architectures, requirements, and designs emerge from self-organizing teams.

Is this a law of nature? If not, I would take it with a very large pinch of salt.

12. At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly.

It is ultimately up to the team to decide. If they do not find this activity useful, they should not be forced to carry it out at regular intervals.

Final thoughts

Personally, I feel that Agile and its Manifesto are largely overhyped. They have created a huge industry for companies to sell books and training courses. Ultimately, just as I do not need a life coach to guide me through my daily tasks, I also do not need Agile to tell me how to organise my software development tasks.

Monday, December 8, 2025

Installing NetBSD-10 on Raspberry Pi 4

Introduction

Installing NetBSD-10 on the Raspberry Pi 4 is much the same as installing it on the Raspberry Pi 3. Refer to this article for more details: Installing NetBSD-10 on Raspberry Pi 3.

There are however some differences:

  • The Raspberry Pi 4 uses different EEPROM to boot the system and can boot directly from GPT partitions.
  • The Raspberry Pi 4’s UEFI firmware is capable of booting NetBSD-10.

For further information on updating the Raspberry Pi 4’s EEPROM, please refer to this link: Raspberry Pi boot EEPROM.

NetBSD Installation Steps

Several steps are required to install NetBSD. The following sections explain each step in greater detail.

Step 1: Create GPT Partitions

The first step in installing NetBSD is to create required GPT partitions. In this example, a microSD card is used, which is detected as sd0 device:

gpt destroy sd0
gpt create -f sd0
gpt add -a 1m -l "EFI-system"  -t efi  -s 128m sd0
gpt add -a 1m -l "NetBSD-root" -t ffs  -s 8g   sd0
gpt add -a 1m -l "NetBSD-swap" -t swap -s 4g   sd0
gpt add -a 1m -l "NetBSD-var"  -t ffs  -s 4g   sd0
gpt add -a 1m -l "NetBSD-opt"  -t ffs          sd0

Step 2: Create File Systems

Execute dkctl to display the wedges currently mapped to partitions. Note that wedge mappings are dynamic and may differ on your system:

dkctl sd0 listwedges
/dev/rsd0: 5 wedges:
dk1: EFI-system, 262144 blocks at 2048, type: msdos
dk2: NetBSD-root, 16777216 blocks at 264192, type: ffs
dk3: NetBSD-swap, 8388608 blocks at 17041408, type: swap
dk4: NetBSD-var, 8388608 blocks at 25430016, type: ffs
dk5: NetBSD-opt, 90914816 blocks at 33818624, type: ffs

Use the wedge mappings shown above to create the necessary file systems. Note that raw special devices should be used; therefore, the wedges specified in the newfs commands should be /dev/rdkN, where N is the wedge ID:

newfs_msdos -F 32 /dev/rdk1

for i in rdk2 rdk4 rdk5
do
  newfs -B le -O 2ea /dev/${i} || break
done

The EFI partition is formatted as a FAT32 file system, while the remaining partitions (excluding swap) are formatted as NetBSD little-endian (option "-B le") UFS file systems. If you are using the NetBSD big-endian (evbarm-aarch64eb) port, adjust the newfs byte-order option accordingly to create big-endian file systems.

Step 3: Mount File Systems and Extract Files

Mount all file systems:

mkdir /mnt-netbsd-root && mount -o log NAME=NetBSD-root /mnt-netbsd-root &&
mkdir /mnt-netbsd-root/boot && mount -t msdos NAME=EFI-system /mnt-netbsd-root/boot &&
mkdir /mnt-netbsd-root/var && mount -o log NAME=NetBSD-var /mnt-netbsd-root/var &&
mkdir /mnt-netbsd-root/opt && mount -o log NAME=NetBSD-opt /mnt-netbsd-root/opt

Download Raspberry Pi 4 UEFI firmware, NetBSD EFI bootloader, and NetBSD sets. Note that the X11 sets are not included, as the Raspberry Pi 4 will be used in headless/server mode in this setup:

ftp https://github.com/pftf/RPi4/releases/download/v1.50/RPi4_UEFI_Firmware_v1.50.zip
ftp https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/evbarm-aarch64/installation/misc/bootaa64.efi

mkdir netbsd_sets
for i in base comp etc games kern-GENERIC64 man misc modules rescue text
do
  ftp -o netbsd_sets/${i}.tar.xz https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/evbarm-aarch64/binary/sets/${i}.tar.xz || break
done

Install UEFI firmware and NetBSD bootloader into boot partition:

unzip -d /mnt-netbsd-root/boot RPi4_UEFI_Firmware_v1.50.zip
mkdir -p /mnt-netbsd-root/boot/EFI/BOOT && cp bootaa64.efi /mnt-netbsd-root/boot/EFI/BOOT
sync

Extract NetBSD sets:

for i in base comp etc games kern-GENERIC64 man misc modules rescue text
do
  tar -C /mnt-netbsd-root -xpf netbsd_sets/${i}.tar.xz || break
done
sync

Step 4: Configure NetBSD

Create the /etc/fstab file:

cat > /mnt-netbsd-root/etc/fstab << 'EOF'
NAME=EFI-system    /boot       msdos     rw                0 0
NAME=NetBSD-root   /           ffs       rw,noatime,log    1 1
NAME=NetBSD-var    /var        ffs       rw,noatime,log    1 1
NAME=NetBSD-opt    /opt        ffs       rw,noatime,log    1 2
NAME=NetBSD-swap   none        swap      sw,dp             0 0
kernfs             /kern       kernfs    rw
procfs             /proc       procfs    rw
ptyfs              /dev/pts    ptyfs     rw
tmpfs              /var/shm    tmpfs     rw,-m1777,-sram%25
EOF

Create the additional mount points and the time zone symlink:

mkdir /mnt-netbsd-root/kern /mnt-netbsd-root/proc /mnt-netbsd-root/home
ln -sf ../usr/share/zoneinfo/Europe/London /mnt-netbsd-root/etc/localtime

Create the /etc/rc.conf file:

cat >> /mnt-netbsd-root/etc/rc.conf << 'EOF'
rc_configured=YES
critical_filesystems_local="${critical_filesystems_local} /opt"
hostname="rp4"
domainname="home.lan"

# Static IP config
net_interfaces="genet0"
ifconfig_genet0="inet 192.168.1.1 netmask 255.255.255.0"
dns_search="home.lan"
dns_nameservers="192.168.1.254"
defaultroute="192.168.1.254"

# Dynamic IP config
#dhcpcd=YES
#dhcpcd_flags="-qM genet0"

ip6addrctl=YES
ip6addrctl_policy="ipv4_prefer"

ntpdate=YES
sshd=YES
devpubd=YES
EOF

Step 5: Clean Up

Finally unmount all file systems and remove temporary mount points:

sync &&
umount /mnt-netbsd-root/boot &&
umount /mnt-netbsd-root/opt &&
umount /mnt-netbsd-root/var &&
umount /mnt-netbsd-root &&
rm -rf /mnt-netbsd-root

Step 6: First Boot

Attach the microSD card or USB disk to the Raspberry Pi 4 and power it on.

To enable automatic boot from a specific device, you may need to modify the UEFI firmware boot order: at the UEFI boot screen, press Esc, then navigate to Boot Maintenance ManagerBoot OptinsChange Boot Order, and move the microSD card and USB drive to the top of the list.

To disable the UEFI 3 GiB memory limit, follow these steps: at the UEFI boot screen, press Esc, then navigate to Device ManagerRaspberry Pi ConfigurationAdvanced Configuration, and disable the 3 GiB memory limit option.

Review afterboot(8), then carry out any additional configuration as required.

Note that the newly installed system will have an empty /etc/openssl/certs directory, which may cause problems when connecting to TLS enabled sites. To resolve this, generate the certificates using the /etc/rc.d/certctl_init script. This only needs to be done once, on the first boot:

/etc/rc.d/certctl_init onestart

Thursday, November 13, 2025

Installing NetBSD-10 on Raspberry Pi 3

Introduction

NetBSD-10 can be installed on the Raspberry Pi 3 either as a 32-bit operating system (evbarm-earmv7hf port) or as a 64-bit operating system (evbarm-aarch64 port). The 64-bit binaries deliver noticeably better performance, particularly for 64-bit integer operations. It is therefore recommended to download and install the 64-bit binaries.

The Raspberry Pi 3 boots an operating system using boot firmware, which is typically stored on a FAT32 partition. Two types of boot firmware are available:

NetBSD-10 does not seem to function correctly with UEFI firmware on the Raspberry Pi 3, as the kernel panics during boot. It is therefore recommended to use the original firmware instead.

The installation process outlined in this article requires access to an existing NetBSD system, either physical or virtual, in order to partition the disk, create file systems, extract the NetBSD sets, and configure the installed system. The interactive NetBSD installer sysinst is not used here, as it lacks the flexibility offered by the manual approach.

Disk Partitions: MBR and GPT

Two different disk partitioning schemes can be used: Master Boot Record (MBR) and GUID Partition Table (GPT). The Raspberry Pi 3 firmware supports booting only from MBR; however, GPT can still be used by employing a hybrid MBR/GPT. The hybrid MBR/GPT method enables the boot disk to be partitioned with GPT while tricking the firmware into recognising it as an MBR boot.

For straightforward disk partitioning requirements, pure MBR may be adequate; however, GPT (hybrid MBR/GPT in this case) offers several advantages, including support for disks larger than 2 TB and a more convenient overall structure.

NetBSD Installation Steps

Several steps are required to install NetBSD. The following sections explain each step in greater detail.

Step 1: Create GPT Partitions

The first step in installing NetBSD is to create required GPT partitions. In this example, a microSD card is used, which is detected as sd0 device:

gpt destroy sd0
gpt create -f sd0
gpt add -a 1m -l "EFI-system"  -t efi  -s 128m sd0
gpt add -a 1m -l "NetBSD-diag" -t ffs  -s 1g   sd0
gpt add -a 1m -l "NetBSD-root" -t ffs  -s 8g   sd0
gpt add -a 1m -l "NetBSD-swap" -t swap -s 1g   sd0
gpt add -a 1m -l "NetBSD-var"  -t ffs  -s 4g   sd0
gpt add -a 1m -l "NetBSD-opt"  -t ffs          sd0

The "gpt destroy" command clears disk of any previous GPT partitions and the "gpt create" command creates initial GPT table.

The "gpt add" commands create a number of partitions. The "-a 1m" flag aligns partitions on 1 MiB boundary. The "-l" flag label each parition with a specific name.

The following partitions are created:

  • EFI-system: FAT32 boot partition which will contain boot firmware and NetBSD kernel. This should be created first before any other partitions that may follow.
  • NetBSD-diag: The secondary diagnostic runtime environment that can be used to repair the primary runtime environment if it fails to boot properly.
  • NetBSD-root: The primary runtime environment that contains the root file system.
  • NetBSD-swap, NetBSD-var, NetBSD-opt: The remaining partitions used for various file systems.

The "gpt create" command also automatically creates a Protective MBR (PMBR) partition. This partition prevents legacy MBR-based disk utilities that are unaware of GPT from altering the disk sectors reserved for GPT.

Created GPT partitions can be viewd with "gpt show" command:

gpt show sd0
      start       size  index  contents
          0          1         PMBR
          1          1         Pri GPT header
          2         32         Pri GPT table
         34       2014         Unused
       2048     262144      1  GPT part - EFI System
     264192    2097152      2  GPT part - NetBSD FFSv1/FFSv2
    2361344   16777216      3  GPT part - NetBSD FFSv1/FFSv2
   19138560    2097152      4  GPT part - NetBSD swap
   21235712    8388608      5  GPT part - NetBSD FFSv1/FFSv2
   29624320   95109120      6  GPT part - NetBSD FFSv1/FFSv2
  124733440       2015         Unused
  124735455         32         Sec GPT table
  124735487          1         Sec GPT header

Sector 0 contains the MBR partition information, sector 1 contains the primary GPT header, and sectors 2–33 contain the primary GPT table. The first partition starts at sector 2048, followed by other NetBSD partitions.

Created MBR partitions can be viewd with "fdisk" command:

fdisk sd0
Partition table:
0: GPT Protective MBR (sysid 238)
    start 1, size 124735487 (60906 MB, Cyls 0-7764/108/24)
        PBR is not bootable: Bad magic number (0x0000)
1: <UNUSED>
2: <UNUSED>
3: <UNUSED>
No active partition.
Drive serial number: 0 (0x00000000)

The PMBR partition (sysid 238) is located at index 0 and spans the entire disk (sectors 1–124735487). This corresponds to the same disk area allocated for GPT.

Step 2: Create Hybrid MBR/GPT Layout

When the Raspberry Pi 3 is powered on, it locates the boot disk and attempts to boot from the first (index 0) MBR partition formatted with the FAT32 file system (sysid 12). To meet this requirement, a hybrid MBR/GPT layout must be created. The NetBSD "fdisk -gu" command can be used to modify the MBR at sector 0 without affecting any of the GPT partitions.

To create a hybrid MBR/GPT layout, the following modifications should be applied:

  • MBR partition 0 should be assigned sysid 12 and correspond to the sectors containing GPT EFI partition. This is the MBR boot partition used by the Raspberry Pi 3 during power-on.
  • MBR partition 1 should be assigned sysid 238 and correspond to the sectors containing GPT metadata. This is a placeholder PMBR partition. The exact sectors are not critical, provided they do not overlap with those of MBR partition 0. Mapping it to the sectors holding the GPT primary header and table is sufficient.

The actual MBR partition modifications are shown below:

fdisk -gu sd0
...
Which partition do you want to change?: [none] 0
The data for partition 0 is:
GPT Protective MBR (sysid 238)
    start 1, size 124735487 (60906 MB, Cyls 0-7764/108/24)
        PBR is not bootable: Bad magic number (0x0000)
sysid: [0..255 default: 238] 12
start: [0..7764cyl default: 1, 0cyl, 0MB] 2048
size: [0..7764cyl default: 124733440, 7764cyl, 60905MB] 262144
bootmenu: [] (space to clear)
The bootselect code is not installed, do you want to install it now? [n]
...
Which partition do you want to change?: [none] 1
The data for partition 1 is:
<UNUSED>
sysid: [0..255 default: 169] 238
start: [0..7764cyl default: 1, 0cyl, 0MB] 1
size: [0..0cyl default: 2047, 0cyl, 1MB] 2047
bootmenu: [] (space to clear)
The bootselect code is not installed, do you want to install it now? [n]
...
Which partition do you want to change?: [none] 
...
Installed bootfile doesn't support required options.
Update the bootcode from /usr/mdec/mbr? [n] 
...
Should we write new partition table? [n] y

The final hybrid MBR/GPT partition layout should look like this:

fdisk sd0
Partition table:
0: Primary DOS with 32 bit FAT - LBA (sysid 12)
    start 2048, size 262144 (128 MB, Cyls 0/32/33-16/113/33)
        PBR is not bootable: All bytes are identical (0x00)
1: GPT Protective MBR (sysid 238)
    start 1, size 2047 (1 MB, Cyls 0-0/32/32)
        PBR is not bootable: Bad magic number (0x0000)
2: <UNUSED>
3: <UNUSED>
No active partition.
Drive serial number: 0 (0x00000000)

Step 3: Create File Systems

Execute dkctl to display the wedges currently mapped to partitions. Note that wedge mappings are dynamic and may differ on your system:

dkctl sd0 listwedges
/dev/rsd0: 6 wedges:
dk1: EFI-system, 262144 blocks at 2048, type: msdos
dk2: NetBSD-diag, 2097152 blocks at 264192, type: ffs
dk3: NetBSD-root, 16777216 blocks at 2361344, type: ffs
dk4: NetBSD-swap, 2097152 blocks at 19138560, type: swap
dk5: NetBSD-var, 8388608 blocks at 21235712, type: ffs
dk6: NetBSD-opt, 95109120 blocks at 29624320, type: ffs

Use the wedge mappings shown above to create the necessary file systems. Note that raw special devices should be used; therefore, the wedges specified in the newfs commands should be /dev/rdkN, where N is the wedge ID:

newfs_msdos -F 32 /dev/rdk1

for i in rdk2 rdk3 rdk5 rdk6
do
  newfs -B le -O 2ea /dev/${i} || break
done

The EFI partition is formatted as a FAT32 file system, while the remaining partitions (excluding swap) are formatted as NetBSD little-endian (option "-B le") UFS file systems. If you are using the NetBSD big-endian (evbarm-aarch64eb) port, adjust the newfs byte-order option accordingly to create big-endian file systems.

Step 4: Mount File Systems and Extract Files

Mount all file systems associated with the primary and secondary runtime environments:

mkdir /mnt-netbsd-root && mount -o log NAME=NetBSD-root /mnt-netbsd-root &&
mkdir /mnt-netbsd-root/boot && mount -t msdos NAME=EFI-system /mnt-netbsd-root/boot &&
mkdir /mnt-netbsd-root/var && mount -o log NAME=NetBSD-var /mnt-netbsd-root/var &&
mkdir /mnt-netbsd-root/opt && mount -o log NAME=NetBSD-opt /mnt-netbsd-root/opt &&
mkdir /mnt-netbsd-diag && mount -o log NAME=NetBSD-diag /mnt-netbsd-diag

Download Raspberry Pi 3 boot firmware and NetBSD sets. Note that the X11 sets are not included, as the Raspberry Pi 3 will be used in headless/server mode in this setup:

ftp https://github.com/raspberrypi/firmware/releases/download/1.20240902/raspi-firmware_1.20240902.orig.tar.xz

mkdir netbsd_sets
for i in base comp dtb etc games kern-GENERIC64 man misc modules rescue text
do
  ftp -o netbsd_sets/${i}.tar.xz https://cdn.netbsd.org/pub/NetBSD/NetBSD-10.1/evbarm-aarch64/binary/sets/${i}.tar.xz || break
done

Extract boot firmware and NetBSD kernel into boot partition:

tar -C /mnt-netbsd-root/boot --strip-components=2 -xf raspi-firmware_1.20240902.orig.tar.xz '*/boot/*' &&
tar -C /mnt-netbsd-root/boot -xf netbsd_sets/kern-GENERIC64.tar.xz netbsd.img  &&
sync

ls -l /mnt-netbsd-root/boot
total 76342
-rwxr-xr-x  1 root  wheel      1594 Sep  2  2024 LICENCE.broadcom
-rwxr-xr-x  1 root  wheel     52476 Sep  2  2024 bootcode.bin
-rwxr-xr-x  1 root  wheel      7327 Sep  2  2024 fixup.dat
-rwxr-xr-x  1 root  wheel      5456 Sep  2  2024 fixup4.dat
-rwxr-xr-x  1 root  wheel      3232 Sep  2  2024 fixup4cd.dat
-rwxr-xr-x  1 root  wheel      8450 Sep  2  2024 fixup4db.dat
-rwxr-xr-x  1 root  wheel      8450 Sep  2  2024 fixup4x.dat
-rwxr-xr-x  1 root  wheel      3232 Sep  2  2024 fixup_cd.dat
-rwxr-xr-x  1 root  wheel     10295 Sep  2  2024 fixup_db.dat
-rwxr-xr-x  1 root  wheel     10295 Sep  2  2024 fixup_x.dat
-rwxr-xr-x  1 root  wheel  16775144 Dec 16  2024 netbsd.img
-rwxr-xr-x  1 root  wheel   2983488 Sep  2  2024 start.elf
-rwxr-xr-x  1 root  wheel   2259296 Sep  2  2024 start4.elf
-rwxr-xr-x  1 root  wheel    811804 Sep  2  2024 start4cd.elf
-rwxr-xr-x  1 root  wheel   3756808 Sep  2  2024 start4db.elf
-rwxr-xr-x  1 root  wheel   3006920 Sep  2  2024 start4x.elf
-rwxr-xr-x  1 root  wheel    811804 Sep  2  2024 start_cd.elf
-rwxr-xr-x  1 root  wheel   4828616 Sep  2  2024 start_db.elf
-rwxr-xr-x  1 root  wheel   3730536 Sep  2  2024 start_x.elf

Extract NetBSD sets into the primary and secondary runtime environments:

for i in base comp dtb etc games man misc modules rescue text
do
  tar -C /mnt-netbsd-root -xpf netbsd_sets/${i}.tar.xz || break
done

for i in base dtb etc games man misc modules rescue text
do
  tar -C /mnt-netbsd-diag -xpf netbsd_sets/${i}.tar.xz || break
done &&
rm -rf /mnt-netbsd-diag/boot/*

sync

We remove all files under /mnt-netbsd-diag/boot/ because they have already been extracted into the boot partition, which is shared by both runtime environments.

Step 5: Configure NetBSD Primary Runtime Environment

Create the cmdline.txt and config.txt files in the boot partition:

cat > /mnt-netbsd-root/boot/cmdline.txt << 'EOF'
root=NAME=NetBSD-root console=fb
#root=NAME=NetBSD-diag console=fb
EOF

cat > /mnt-netbsd-root/boot/config.txt << 'EOF'
# For more info see:
# https://www.raspberrypi.com/documentation/computers/config_txt.html
# https://www.raspberrypi.com/documentation/computers/legacy_config_txt.html

arm_64bit=1
upstream_kernel=1
os_prefix=dtb/broadcom/
cmdline=/cmdline.txt
kernel=/netbsd.img
kernel_address=0x200000
enable_uart=1
force_turbo=0
EOF

Create the /etc/fstab file:

cat > /mnt-netbsd-root/etc/fstab << 'EOF'
NAME=EFI-system    /boot       msdos     rw                0 0
NAME=NetBSD-root   /           ffs       rw,noatime,log    1 1
NAME=NetBSD-var    /var        ffs       rw,noatime,log    1 1
NAME=NetBSD-opt    /opt        ffs       rw,noatime,log    1 2
NAME=NetBSD-swap   none        swap      sw,dp             0 0
kernfs             /kern       kernfs    rw
procfs             /proc       procfs    rw
ptyfs              /dev/pts    ptyfs     rw
tmpfs              /var/shm    tmpfs     rw,-m1777,-sram%25
EOF

Create the additional mount points and the time zone symlink:

mkdir /mnt-netbsd-root/kern /mnt-netbsd-root/proc /mnt-netbsd-root/home
ln -sf ../usr/share/zoneinfo/Europe/London /mnt-netbsd-root/etc/localtime

Create the /etc/rc.conf file:

cat >> /mnt-netbsd-root/etc/rc.conf << 'EOF'
rc_configured=YES
critical_filesystems_local="${critical_filesystems_local} /opt"
hostname="rp3"
domainname="home.lan"

# Static IP config
net_interfaces="mue0"
ifconfig_mue0="inet 192.168.1.1 netmask 255.255.255.0 tso4 tso6 ip4csum tcp4csum tcp6csum udp4csum udp6csum"
dns_search="home.lan"
dns_nameservers="192.168.1.254"
defaultroute="192.168.1.254"

# Dynamic IP config
#dhcpcd=YES
#dhcpcd_flags="-qM mue0"

ip6addrctl=YES
ip6addrctl_policy="ipv4_prefer"

ntpdate=YES
sshd=YES
devpubd=YES
EOF

Step 6: Configure NetBSD Secondary Diagnostic Runtime Environment

Create the /etc/fstab file:

cat > /mnt-netbsd-diag/etc/fstab << 'EOF'
NAME=EFI-system    /boot       msdos     rw                0 0
NAME=NetBSD-diag   /           ffs       rw,noatime,log    1 1
NAME=NetBSD-swap   none        swap      sw,dp             0 0
kernfs             /kern       kernfs    rw
procfs             /proc       procfs    rw
ptyfs              /dev/pts    ptyfs     rw
tmpfs              /var/shm    tmpfs     rw,-m1777,-sram%25
EOF

Create the additional mount points and the time zone symlink:

mkdir /mnt-netbsd-diag/kern /mnt-netbsd-diag/proc /mnt-netbsd-diag/home
ln -sf ../usr/share/zoneinfo/Europe/London /mnt-netbsd-diag/etc/localtime

Create the /etc/rc.conf file:

cat >> /mnt-netbsd-diag/etc/rc.conf << 'EOF'
rc_configured=YES
hostname="rp3-diag"
domainname="home.lan"

# Static IP config
net_interfaces="mue0"
ifconfig_mue0="inet 192.168.1.1 netmask 255.255.255.0 tso4 tso6 ip4csum tcp4csum tcp6csum udp4csum udp6csum"
dns_search="home.lan"
dns_nameservers="192.168.1.254"
defaultroute="192.168.1.254"

# Dynamic IP config
#dhcpcd=YES
#dhcpcd_flags="-qM mue0"

ip6addrctl=YES
ip6addrctl_policy="ipv4_prefer"

ntpdate=YES
sshd=YES
devpubd=YES
EOF

Step 7: Clean Up

Finally unmount all file systems and remove temporary mount points:

sync &&
umount /mnt-netbsd-root/boot &&
umount /mnt-netbsd-root/opt &&
umount /mnt-netbsd-root/var &&
umount /mnt-netbsd-root &&
umount /mnt-netbsd-diag &&
rm -rf /mnt-netbsd-root /mnt-netbsd-diag

Step 8: First Boot

Attach the microSD card or USB disk to the Raspberry Pi 3 and power it on. NetBSD can now be booted into either the primary or secondary runtime environment, selected by modifying the cmdline.txt file in the boot partition. Review afterboot(8), then carry out any additional configuration as required.

Note that the newly installed system will have an empty /etc/openssl/certs directory, which may cause problems when connecting to TLS enabled sites. To resolve this, generate the certificates using the /etc/rc.d/certctl_init script. This only needs to be done once, on the first boot:

/etc/rc.d/certctl_init onestart

Tuesday, October 28, 2025

SPARC V9 Boot Process – Part 1: bootblk

Introduction

SPARC V9 is a 64-bit Reduced Instruction Set Computer (RISC) architecture introduced in the mid-1990s that was widely used in servers and workstations manufactured by Sun Microsystems. As its popularity gradually declined, the hardware lost the performance advantage it once held in its early years and is now considered a legacy architecture.

Despite its age and competitive obsolescence, SPARC V9 is an interesting architecture to study. It has several unique characteristics, and some of us who lived through the dot-com boom still enjoy using Sun hardware for nostalgic reasons.

This article takes a closer look at the overlooked elements of the SPARC V9 boot process. It explores the OpenBoot firmware and demonstrates how to implement a first-stage bootloader from scratch.

OpenBoot

OpenBoot represents Sun Microsystems’ implementation of the IEEE 1275 Open Firmware standard, providing facilities for hardware configuration, system diagnostics, and operating system initialisation on SPARC V9 architectures. After completing the Power-On Self-Test (POST), OpenBoot firmware displays the ok prompt (provided the autoboot property is set to false), allowing the boot process to be started from a disk or over a network.

The OpenBoot PROM includes an FCode interpreter, which enables the execution of code written in a machine-independent interpreted language known as FCode. Firmware for pluggable storage adapters or even the first-stage bootloader can be written in FCode, allowing it to remain portable across different CPU architectures that implement a compatible FCode interpreter.

During initialisation, OpenBoot constructs the device tree — a hierarchical data structure that represents the system’s hardware and configuration parameters, and may also include firmware device drivers. The device tree consists of device nodes, where each node may have properties, methods, data, and other child nodes.

There are several standard system nodes; however, the /chosen node includes a number of useful properties that can be accessed by the first-stage bootloader. This node contains parameters chosen or specified at runtime:

Property Name Encoding Value
name string "chosen"
stdin integer Ihandle for the console input.
stdout integer Ihandle for the console output.
bootpath string The device path for the last boot device.
bootargs string The arguments to the last boot command.
memory integer Ihandle for the package that describes physical memory.
mmu integer Ihandle for the package that describes memory management unit.

Below is an example of what it looks like on a Sun T5220:

{0} ok dev /chosen
{0} ok .properties
bootargs                 
bootpath                 
mmu                      fff74080 
memory                   fff74290 
stdout                   feb51d88 
stdin                    feb51f90 
stdout-#lines            ffffffff 
name                     chosen

For example, the first-stage bootloader can use the hexadecimal value of the stdin ihandle to read text from the console, and the hexadecimal value of the stdout ihandle to write text to the console.

OpenBoot provides two primary interfaces: the user interface and the client interface. The user interface presents the ok prompt and offers a command-line environment for user interaction. The client interface supplies a set of services that can be invoked by a client program. A client program may be the first-stage bootloader, known as bootblk, and can comprise either FCode bytecodes or 64-bit SPARC V9 instructions packaged within an ELF64 binary.

In the following sections, I describe how a 64-bit ELF64 binary can call the OpenBoot client interface handler and invoke a range of services.

OpenBoot Client Interface Services

The OpenBoot firmware provides a number of services that can be invoked by the first-stage bootblk bootloader. The services are accessed via the client interface handler (a function pointer), with arguments and return values passed through an array of fixed size cells. A cell is a unit of storage in OpenBoot and on SPARC V9 its size is 8 bytes (64 bits).

The array passed to the OpenBoot client interface handler comprises the following cells:

Cell Name Description
service Pointer specifying the address of a '\0' terminated string of the client interface service
Num args Integer specifying the number of input arguments to the client interface service
Num rets Integer specifing the number of return values from the client interface service
Arg1 ...
Arg2 ...
ArgN Input arguments to the client interface service
Ret1 ...
Ret2 ...
RetN Return values from the client interface service

In the C programming language, a call to the write service can be implemented as shown in the following example:

/* 64-bit Open Firmware uses 64-bit cells */
typedef uint64_t ofw_cell_cdt;

/* The ihandle always seems to be 32-bit integer */
typedef uint32_t ihandle_cdt;

/* Open Firmware Client Interface Function handler */
static int (*ofw_cif_handler)(void *);

/* Open Firmware ihandles for stdin and stdout */
static ihandle_cdt ihandle_stdin  = (ihandle_cdt)-1;
static ihandle_cdt ihandle_stdout = (ihandle_cdt)-1;

/* Write string in strbuf of length strbuf_len to stdout */
int32_t ofw_write_stdout(const char *strbuf, uint32_t strbuf_len)
{
    ofw_cell_cdt ci_arg[7];

    ci_arg[0] = (ofw_cell_cdt)"write";        /* Service */
    ci_arg[1] = (ofw_cell_cdt)3;              /* Number of arguments */
    ci_arg[2] = (ofw_cell_cdt)1;              /* Number of return values */
    ci_arg[3] = (ofw_cell_cdt)ihandle_stdout; /* Arg1: stdout ihandle */
    ci_arg[4] = (ofw_cell_cdt)strbuf;         /* Arg2: strbuf address */
    ci_arg[5] = (ofw_cell_cdt)strbuf_len;     /* Arg3: strbuf length */
    ci_arg[6] = (ofw_cell_cdt)-1;             /* Ret1: return value */

    /* Call into cif handler */
    ASSERT(ofw_cif_handler(ci_arg) == 0);
    return (int32_t)ci_arg[6];
}

For a specification of the client interface services, consult the IEEE 1275-1994 standard.

Sun Disk Label and Bootblk Requirements

When OpenBoot attempts to boot from a disk drive, it checks for a valid Sun disk label in the first 512-byte sector of the boot device. If a valid disk label is not found, OpenBoot displays an error message and aborts the boot process.

Once the Sun disk label has been checked, OpenBoot reads the next 16 512-byte sectors and loads bootblk first-stage bootloader. Since the number of sectors read during this stage is hard-coded into the firmware, the size of bootblk is limited to approximately 8 KiB and should fit within the 16 sectors immediately following the disk label. In my testing on a Sun T5220, I was able to load a bootblk slightly over 10 KiB in size; however, larger bootblk binaries resulted in the firmware error: "ERROR: Last Trap: Fast Data Access MMU Miss".

I developed a utility called sun_disk_label, which can write a fake Sun disk label to a file or disk. The integer values within the disk label must be stored in big-endian byte order; therefore, the utility employs byte-order conversion functions to translate between host and network byte order, allowing it to generate correct disk labels even on little-endian architectures.

The bootblk is primarily written in the C programming language, with a small section containing the _start function implemented in SPARC V9 assembly. The boot stages are as follows:

  1. Upon power-on, firmware begins to execute, probing devices, performing initialisation and running diagnostic routines.
  2. Firmware enables the Memory Management Unit (MMU) and sets up a basic SPARC V9 trap table.
  3. The boot command executes, specifying the boot device and boot arguments.
  4. The first 512-byte sector is read from the boot device and the Sun disk label is checked for validity.
  5. The bootblk binary is read from the next 16 512-byte sectors after the Sun disk label.
  6. The address of the OpenBoot client interface handler is copied to register %o4.
  7. The bootblk binary format is checked (FCode, ELF64, etc) and then executed.
  8. Control is transferred to the bootblk binary (i.e. first-stage bootloader), which can then load a larger second-stage bootloader or an operating system kernel.

Source Code and Demostration

Source code can be downloaded here: sparc_boot_process.tar.gz. Below follows a demonstration of a simple bootblk first-stage bootloader.

Initially, I compile the sun_disk_label utility and write a fake disk label. Then, I compile the bootblk.bin binary and use the dd utility to write it immediately after the disk label:

# gcc -O2 -std=c11 -Wall -pedantic -o sun_disk_label sun_disk_label.c

# ./sun_disk_label write /dev/sd1c

# gcc -Os -std=c11 -Wall -pedantic -ffreestanding -nostdlib -nostartfiles \
      -o bootblk.bin start.s bootblk.c

# file bootblk.bin
bootblk.bin: ELF 64-bit MSB executable, SPARC V9, relaxed memory ordering, version 1 (SYSV), statically linked, not stripped

# dd if=bootblk.bin of=/dev/sd1c bs=512 seek=1 && sync
10+1 records in
10+1 records out
5320 bytes transferred in 0.014 secs (380000 bytes/sec)

Finally, I insert the USB flash drive into the Sun T5220 and boot from the drive containing my bootblk binary, which demonstrates basic input and output functionality on the system console:

{0} ok boot /pci@0/pci@0/pci@1/pci@0/pci@1/pci@0/usb@0,2/storage@3/disk@0
Boot device: /pci@0/pci@0/pci@1/pci@0/pci@1/pci@0/usb@0,2/storage@3/disk@0  File and args: 

Running SPARC-V9 bootblk

ihandle_stdin  0xFEB51F90
ihandle_stdout 0xFEB51D88

Type your name: root
Hello root. Press any key to exit: 
Program terminated
{0} ok 

In the second article, I will examine how the first-stage bootloader can load and execute larger binaries and overcome the 8 KiB size limitation imposed by the firmware.

References

IEEE 1275-1994 Standard for Boot Firmware
IEEE Draft Std P1275.1/D14a Standard for Boot Firmware