12

I need to load driver on early stage booting. For development I use LVM (logical volume manager) so I can easily revert test setups to the default image from a snapshot. For this I have to load the lvm driver before accessing the root partition. This is done with an init ramdisk (initrd or initramfs). This is also good to support custom kernel. But unlike Debian, Raspbian does not support initramfs out of the box. How can I use an init ramdisk?

Ingo
  • 40,606
  • 15
  • 76
  • 189

5 Answers5

16

I have found that there is a setup in

rpi ~$ cat /etc/default/raspberrypi-kernel
# Defaults for raspberrypi-kernel

# Uncomment the following line to enable generation of
# /boot/initrd.img-KVER files (requires initramfs-tools)

#INITRD=Yes

# Uncomment the following line to enable generation of
# /boot/initrd(7).img files (requires rpi-initramfs-tools)

#RPI_INITRD=Yes

The comments there are the only documentation I have found. In particular I can't find anything about rpi-initramfs-tools. They are simply not available. So I tested with INITRD=Yes by uncommenting it because initramfs-tools are installed by default. Then to create an initramfs I execute

rpi ~$ sudo update-initramfs -c -k $(uname -r)

and it works like a charm. You must also do it to generate the first initramfs. It produces one for example /boot/initrd.img-4.14.71-v7+. From now on you can simply update with:

rpi ~$ sudo update-initramfs -u
ln: failed to create hard link '/boot/initrd.img-4.14.71-v7+.dpkg-bak' => '/boot/initrd.img-4.14.71-v7+': Operation not permitted
update-initramfs: Generating /boot/initrd.img-4.14.71-v7+

As you see you will get a warning (it's not an error) from ln. The boot partition has a fat filesystem that does not support links but it doesn't matter. The only problem is that it needs an entry in /boot/config.txt like

initramfs initrd.img-4.14.71-v7+ (without equal sign)

Otherwise the boot loader cannot find the ramdisk and boot up fails.

With Raspbian we have also two kernel images, for example /boot/initrd.img-4.14.71 and /boot/initrd.img-4.14.71-v7+ and by default update-initramfs generates an init ramddisk for each kernel but that will not fit onto the limited space of the boot partition. So we have also to ensure that we only generate an initramfs for the running kernel.

Managing initramfs is done with the script /etc/kernel/postinst.d/initramfs-tools. We have to modify this script as follows.

Edit /etc/default/raspberrypi-kernel to comment out INITRD and uncomment RPI_INITRD instead.

Then create a file

rpi ~$ sudo editor /etc/kernel/postinst.d/rpi-initramfs-tools

with this content:

#!/bin/bash -e
# Environment variables are set by the calling script

version="$1"
bootopt=""

command -v update-initramfs >/dev/null 2>&1 || exit 0

# passing the kernel version is required
if [ -z "${version}" ]; then
        echo >&2 "W: initramfs-tools: ${DPKG_MAINTSCRIPT_PACKAGE:-kernel package} did not pass a version number"
        exit 2
fi

# exit if kernel does not need an initramfs
if [ "$RPI_INITRD" = 'No' ]; then
        # delete initramfs entries in /boot/config.txt
        /bin/sed -i '/^initramfs /d' /boot/config.txt
        exit 0
fi

# there are only two kernel types: with and without postfix "-v7+" or "-v8+"
currentversion="$(uname -r)"

# get §currenttype from $currentversion
currenttype="<no currenttype>"
echo $currentversion | grep -Pq '^\d+\.\d+\.\d+\+$' && currenttype="+"
echo $currentversion | grep -Pq '^\d+\.\d+\.\d+-v[78]\+$' && currenttype="${currentversion#*-}"

# get $newtype from $version
newtype="<no newtype>"
echo $version | grep -Pq '^\d+\.\d+\.\d+\+$' && newtype="+"
echo $version | grep -Pq '^\d+\.\d+\.\d+-v[78]\+$' && newtype="${version#*-}"

# we do nothing if the new kernel is not for the same kernel type then the current
if [ "$newtype" != "$currenttype" ]; then
        exit 0
fi

# absolute file name of kernel image may be passed as a second argument;
# create the initrd in the same directory
if [ -n "$2" ]; then
        bootdir=$(dirname "$2")
        bootopt="-b ${bootdir}"
fi

# avoid running multiple times
if [ -n "$DEB_MAINT_PARAMS" ]; then
        eval set -- "$DEB_MAINT_PARAMS"
        if [ -z "$1" ] || [ "$1" != "configure" ]; then
                exit 0
        fi
fi

# we're good - create initramfs.  update runs do_bootloader
INITRAMFS_TOOLS_KERNEL_HOOK=1 update-initramfs -c -t -k "${version}" ${bootopt} >&2

# delete initramfs entries in /boot/config.txt
/bin/sed -i '/^initramfs /d' /boot/config.txt

# insert initramfs entry in /boot/config.txt
INITRD_ENTRY="initramfs initrd.img-${version} followkernel"
echo >&2 $(basename "$0"): insert \'"$INITRD_ENTRY"\' into /boot/config.txt
/bin/sed -i "1i $INITRD_ENTRY" /boot/config.txt

Make the script executable:

rpi ~$ sudo chmod 755 /etc/kernel/postinst.d/rpi-initramfs-tools

The extensions in the script ensures that an initramfs is only created for the current running kernel and that there is managed an entry in /boot/config.txt. If you like to see the changes you can do it with diff --ignore-tab-expansion ~/initramfs-tools /etc/kernel/postinst.d/rpi-initramfs-tools.

For manual updates we create:

rpi ~$ sudo editor /usr/local/sbin/update-rpi-initramfs

with this content:

#!/bin/bash
# This script calls default update-initramfs
# and then insert a 'initramfs' entry into /boot/config.txt if necessary

# should return e.g. "update-initramfs: Generating /boot/initrd.img-4.14.79-v7+"
# or                 "update-initramfs: Deleting /boot/initrd.img-4.14.71-v7+"
MSG=$(/usr/sbin/update-initramfs "$@")
RETCODE=$?
echo $MSG

if [[ $RETCODE -ne 0 ]]; then
        echo >&2 ATTENTION! Check \'initramfs\' entry in /boot/config.txt
        exit "$RETCODE"
fi

CMP="update-initramfs: Deleting *"
if [[ $MSG == $CMP ]]; then
        # delete initramfs entries in /boot/config.txt
        /bin/sed -i '/^initramfs /d' /boot/config.txt
        echo $(basename "$0"): deleted all \'initramfs\' entries from /boot/config.txt
        exit 0
fi

CMP="update-initramfs: Generating *"
if [[ $MSG == $CMP ]]; then
        # delete initramfs entries in /boot/config.txt
        /bin/sed -i '/^initramfs /d' /boot/config.txt

        # exit if kernel does not need an initramfs
        source /etc/default/raspberrypi-kernel
        if [ "${INITRD,,}" != 'yes' ]; then
                echo $(basename "$0"): no entry in /boot/config.txt \(see INITRD in /etc/default/raspberrypi-kernel\)
                exit 0
        fi

        # insert initramfs entry in /boot/config.txt
        VERSION=$(basename "$MSG")
        INITRD_ENTRY="initramfs $VERSION"
        echo $(basename "$0"): insert \'"$INITRD_ENTRY"\' into /boot/config.txt
        /bin/sed -i "1i $INITRD_ENTRY" /boot/config.txt

        exit 0
fi

echo >&2 ATTENTION! Check 'initramfs' entry in /boot/config.txt
exit 1

Set permissions:

rpi ~$ sudo chmod 755 /usr/local/sbin/update-rpi-initramfs

From now on you should only use update-rpi-initramfs instead of update-initramfs, for example:

rpi ~$ sudo update-rpi-initramfs -u

This ensures that you always have the right entry in /boot/config.txt.


References:
[1] Package maintainer scripts and hooks

Oddstr13
  • 103
  • 2
Ingo
  • 40,606
  • 15
  • 76
  • 189
  • A new user had an edit on this: *"Removed the -e Option from bash shenbang otherwise script will exit with errorcode 1 on 'echo $currentversion | grep...'"*. I presume this was your intent and rejected the edit, although I did not actually check the logic. – goldilocks Jun 20 '20 at 16:01
  • @goldilocks You've done it right. The script shall fail immediately when an error occurs. But I'm still a bit unsure if it may be a bug. I will have a look at it. By the way: the answer needs a complete review. – Ingo Jun 20 '20 at 16:23
  • I think this might be missing a step somewhere -- something like `sudo mv -i /etc/kernel/postinst.d/initramfs-tools ~`, based on references to `/etc/kernel/postinst.d/initramfs-tools` and than later diffing against `~/initramfs-tools` (without disabling the former any other way as far as I can see). – n8henrie Mar 23 '21 at 19:20
5

I can offer an alternative solution.

Instead of renaming the initramfs-image and modifying the existing /etc/kernel/postinst.d/* files, I added an initramfs hook that modifies /boot/config.txt so that it will load the initramfs with its current name.

Therefore, there is no need to rename the initramfs any longer after creation, and it can use the standard name chosen by the initramfs generation scripts (i. e. initrd.img-$KERNELRELEASE).

The script is customizable by setting variables for all important path name components at its beginning, so that all customizable settings are in one place.

The script also avoids updating config.txt if it already contains the current name of the actual initramfs image, saving the life endurance of your SD card a little.

Before you install the hook, make sure your /boot/config.txt contains the line which the hook attempts to update.

That line must have a format like the last one in the following section from my config.txt:

# Important: This value will be updated by initramfs generation scripts.
#
# Special syntax: Do not use "=" for assignment here.
initramfs initrd.img-4.19.42-v7+ followkernel

I named the hook script

/etc/initramfs-tools/hooks/update_initrd_ref_qaumv1g34z54324pbel831evd

which contains a UUID at the end of its name, so there is no danger of name collisions with existing or future scripts of the same name. However, the name does not really matter; feel free to rename it as long as you leave it in the same directory.

And here are the contents of that script:

#! /bin/sh -e
# Update reference to $INITRD in $BOOTCFG, making the kernel use the new
# initrd after the next reboot.
BOOTLDR_DIR=/boot
BOOTCFG=$BOOTLDR_DIR/config.txt
INITRD_PFX=initrd.img-
INITRD=$INITRD_PFX$version

case $1 in
    prereqs) echo; exit
esac

FROM="^ *\\(initramfs\\) \\+$INITRD_PFX.\\+ \\+\\(followkernel\\) *\$"
INTO="\\1 $INITRD \\2"

T=`umask 077 && mktemp --tmpdir genramfs_XXXXXXXXXX.tmp`
trap "rm -- \"$T\"" 0

sed "s/$FROM/$INTO/" "$BOOTCFG" > "$T"

# Update file only if necessary.
if ! cmp -s "$BOOTCFG" "$T"
then
    cat "$T" > "$BOOTCFG"
fi

Note that this is a script, so you need to

$ chmod +x /etc/initramfs-tools/hooks/update_initrd_ref_qaumv1g34z54324pbel831evd

after creating it.

This is actually all there is to it - run

$ update-initramfs -u

as usual, and the contents of /boot/config.txt should "automagically" match the name of your /boot/initrd.img-* file.

Guenther Brunthaler
  • 481
  • 1
  • 5
  • 11
  • Thanks for the suggestion. I will test it in detail. Just had only a glance at it. Does this script will be triggered automatically on kernel updates like on debian by default? – Ingo Jul 03 '19 at 19:52
  • @Ingo Yes, it will/does. – Guenther Brunthaler Jul 04 '19 at 11:43
  • As long as you have initramfs-tools installed. – Guenther Brunthaler Jul 04 '19 at 11:51
  • Worked quite well on this [64-bit Raspbian](https://downloads.raspberrypi.org/raspios_arm64/images/) Bullseye install to load the `vc4` driver for [early KMS](https://wiki.archlinux.org/title/Kernel_mode_setting#Early_KMS_start)! – genpfault Jan 28 '22 at 04:24
1

the script called during the kernel installation must be slightly edited because otherwise it will exit with error code 1 if grep doesn't match

#!/bin/sh -e
# Environment variables are set by the calling script

version="$1"
bootopt=""

command -v update-initramfs >/dev/null 2>&1 || exit 0

# passing the kernel version is required
if [ -z "${version}" ]; then
        echo >&2 "W: initramfs-tools: ${DPKG_MAINTSCRIPT_PACKAGE:-kernel package} did not pass a version number"
        exit 2
fi

# exit if kernel does not need an initramfs
if [ "$INITRD" = 'No' ]; then

        # delete initramfs entries in /boot/config.txt
        /bin/sed -i '/^initramfs /d' /boot/config.txt
        exit 0
fi

# there are only two kernel types: with and without postfix "-v7+" or "-v8+"
currentversion="$(uname -r)"

# get §currenttype from $currentversion
currenttype="<no currenttype>"
if [ `echo $currentversion | grep -P '^\d+\.\d+\.\d+\+$'` ]; then
        [ $? -eq 0 ] && currenttype="+"
else [ `echo $currentversion | grep -P '^\d+\.\d+\.\d+-v[78]l?\+$'` ];
       [ $? -eq 0 ] && currenttype="${currentversion#*-}"
fi

# get $newtype from $version
newtype="<no newtype>"

if [ `echo $version | grep -P '^\d+\.\d+\.\d+\+$'` ]; then
        [ $? -eq 0 ] && newtype="+"
else [ `echo $version | grep -P '^\d+\.\d+\.\d+-v[78]l?\+$'` ];
       [ $? -eq 0 ] && newtype="${version#*-}"
fi

# we do nothing if the new kernel is not for the same kernel type then the current
if [ "$newtype" != "$currenttype" ]; then
        exit 0
fi

# absolute file name of kernel image may be passed as a second argument;
# create the initrd in the same directory
if [ -n "$2" ]; then
        bootdir=$(dirname "$2")
        bootopt="-b ${bootdir}"
fi

# avoid running multiple times
if [ -n "$DEB_MAINT_PARAMS" ]; then
        eval set -- "$DEB_MAINT_PARAMS"
        if [ -z "$1" ] || [ "$1" != "configure" ]; then
                exit 0
        fi
fi

# we're good - create initramfs.  update runs do_bootloader
INITRAMFS_TOOLS_KERNEL_HOOK=1 update-initramfs -c -k "${version}" ${bootopt} >&2

# delete initramfs entries in /boot/config.txt
/bin/sed -i '/^initramfs /d' /boot/config.txt

# insert initramfs entry in /boot/config.txt
INITRD_ENTRY="initramfs initrd.img-${version} followkernel"
echo >&2 $(basename "$0"): insert \'"$INITRD_ENTRY"\' into /boot/config.txt
/bin/sed -i "1i $INITRD_ENTRY" /boot/config.txt
1

Not tested this but have used a similar method to add overlayfs modules and a custom scripts to the the initramfs to enable RO operation. If all you want is to add the LVM modules to the initramfs I think all you need to do is:

  1. Add the modules you want to include to /etc/initramfs-tools/modules.
  2. Add any support scripts you need to run during boot to '/etc/initramfs-tools/scripts/...'.
  3. Run mkinitramfs -o /boot/initrd to build the initramfs
  4. Add initramfs initrd followkernel to your /boot/config.txt to enable it.
  5. Reboot.

EDIT: As Ingo pointed out in the comments this method does not automatically recompile the initramfs as a part of any system updates. You'd have to manually re-run mkinitramfs -o /boot/initrd to compile a new image with, for example, a new LVM module to match a newly installed kernel. So, might not be suitable for what you need.

Roger Jones
  • 1,479
  • 5
  • 14
  • 1
    Hi *Roger*, many thanks for your feedback. `I'm using an initramfs for a long time. [..] But it is a little bit dangerous because automatic updating a initramfs isn't supported by Raspbian.` More details about my motivation to make this triggered update you can read at [Custom initramfs](https://raspberrypi.stackexchange.com/a/89914/79866). Adding support scripts in `etc/initramfs-tools/scripts/` doesn't trigger `update-initramfs` on kernel updates. – Ingo Jan 14 '19 at 12:46
  • If the `followkernel` option is that what it looks like it would be a very useful hint to simplify my scripts. I will look at it. Btw. I also use `overlayfs` for [How do I make the OS reset itself every time it boots up?](https://raspberrypi.stackexchange.com/a/85569/79866) but I don't need an `initframfs` for it because `overlayfs` is part of the kernel. – Ingo Jan 14 '19 at 13:21
  • @Ingo That's a fair comment about this method not automatically rebuilding the `initramfs`. Wasn't a consideration for what we were doing as the final disk image was more or less static once burned to the SD card (we'd not expect our users to run `apt-get`). I'll amend my answer to clarify that point. Was not aware that `unionfs` was in the stock kernel but made extensive use of the `initramfs` scripts anyway to delay the boot whilst a UPS charged up and so on. – Roger Jones Jan 14 '19 at 15:23
  • I have tested `initramfs initrd followkernel`. It has no effect. The bootloader does not use an initramfs if initrd does not exactly match the name in `/boot/`, e.g. `initrd.img-4.14.79-v7+`. I have considered to rename the initrd to a generic name but then I have no control over tne version. Don't k now what's better ... – Ingo Jan 14 '19 at 15:26
  • @ingo Sorry, didn't read your answer properly and you're already using `sed` to edit `config.txt`. I believe that `initramfs-tools` exports the version as a variable, can you edit your `sed` command to change the `initramfs` line to contain the new version instead? Something like `sed -i "s/^initramfs\\ initrd.*/initramfs\\ initrd.img-${version}/" /boot/config.txt`? http://manpages.ubuntu.com/manpages/xenial/man8/initramfs-tools.8.html – Roger Jones Jan 14 '19 at 15:55
  • @Ingo I mean't to add a suggestion about running `sed` from the `/etc/initramfs-tools/hooks/` directory to edit `config.txt` so it's a part of the compilation rather than the postinstall. Feels to me that it should be part of the compilation (so that `mkinitramfs` works too) but there's no obvious way to do it safely within the initramfs-tools environment. – Roger Jones Jan 14 '19 at 16:13
0

I've used a different solution, adding a hook in the /etc/kernel/postinst.d directory. It has been working for a while, but I am about to make a miniscule change that I will include here that hasn't been tested because I want to include the ability to run the v8 kernel. So I added this script as /etc/kernel/postinst.d/rebuild

#!/bin/sh -e
# Rebuild initramfs{7,8}.img after kernel upgrade to include new kernel’s modules.
#
# Exit if rebuild cannot be performed or not needed.
[ -x /usr/sbin/mkinitramfs ] || exit 0
# Exit if not building kernel for this Raspberry Pi’s hardware version (assume armv7l or aarch64).
version="$1"
case "${version}" in
  *-v7+) arch=7;;
  *-v8+) arch=8;;
  *) exit 0
esac

[ -f /boot/initramfs$arch.img ] && lsinitramfs /boot/initramfs$arch.img | grep -q "/$version$" && exit 0 # Already in initramfs.
# Rebuild.
mkinitramfs -o /boot/initramfs$arch.img "$version"

making it executable. I don't touch the the /etc/default/raspberrypi-kernel file at all.

I do edit /boot/config.txt to either include initramfs initramfs7.img followkernel or initramfs initramfs8.img followkernel and arm_64bit=1. I don't mind making that small distinction which I think is necessary so often I can undo the arm_64bit flag if I want. It only

akc42
  • 101
  • 2