-
Notifications
You must be signed in to change notification settings - Fork 55
m1n1:User Guide
m1n1 is the bootloader developed by the Asahi Linux project to bridge the Apple (XNU) boot ecosystem to the Linux boot ecosystem.
GitHub: AsahiLinux/m1n1
- Initializes hardware
- Puts up a pretty logo
- Loads embedded (appended) payloads, which can be:
- Device Trees (FDTs), with automatic selection based on the platform
- Initramfs images (compressed CPIO archives)
- Kernel images in Linux ARM64 boot format (optionally compressed)
- Configuration statements
- Chainloads another version of itself from a FAT32 partition (if configured to do so)
Proxy mode enables a huge toolset of developer features, from reducing your Linux kernel test cycle to 7 seconds, to live hardware probing and experimentation, to a hypervisor capable of running macOS or Linux and tracing hardware accesses in real time while provding a virtual UART over USB. See m1n1:Developer Guide for that. This guide only describes trivial proxy use cases.
You need an aarch64-linux-gnu-gcc
cross-compiler toolchain (or a native one, if running on ARM64).
You also need convert
(from ImageMagick) for the boot logos.
$ git clone --recursive https://github.com/AsahiLinux/m1n1.git
$ cd m1n1
$ make
The output will be in build/m1n1.{bin,macho}.
To build on a native arm64 machine, use make ARCH=
.
Building on ARM64 macOS is supported with clang and LLVM; you need to use Homebrew to install the required dependencies:
$ brew install llvm imagemagick
After that, just type make
.
If you have a container runtime installed, like Podman or Docker, you can make use of the compose setup, which contains all build dependencies.
$ git clone --recursive https://github.com/AsahiLinux/m1n1.git
$ cd m1n1
$ podman-compose run m1n1 make
$ # or
$ docker-compose run m1n1 make
-
make RELEASE=1
enables m1n1 release behavior, which hides the console by default and provides an escape hatch to activate an early proxy mode (see Proxy mode). -
make CHAINLOADING=1
enables chainloading support. This requires a Rust nightly toolchain with aarch64 support, which you can get with:rustup toolchain install nightly && rustup target install aarch64-unknown-none-softfloat
.
m1n1 stage 1 release builds packaged with the Asahi Linux Installer have both of those options set. m1n1 stage 2 release builds packaged by distros should just have RELEASE=1
(since they do not need to chainload further) and thus do not need Rust to build.
m1n1 (with your choice of payloads) can be installed from 1TR (macOS 12.1 OS/stub and later) as follows:
kmutil configure-boot -c m1n1-stage1.bin --raw --entry-point 2048 --lowest-virtual-address 0 -v <path to your OS volume>
On older versions (not recommended), you need the macho
instead:
kmutil configure-boot -c m1n1-stage1.macho -v <path to your OS volume>
The Asahi Linux installer will normally do this for you, and most users will never have to do it again manually.
Stage 2 m1n1 is normally stored in the EFI system partition, typically with U-Boot as a payload. Assuming your ESP is mounted at /boot/efi
, you'd normally do:
cat build/m1n1.bin /path/to/dtbs/*.dtb /path/to/uboot/u-boot-nodtb.bin > /boot/efi/m1n1/boot.bin
m1n1 supports the following payloads:
- Device Trees (FDTs), optionally gzip/xz compressed
- Initramfs images, which must be gzip/xz compressed
- arm64 Linux style kernel images, which should be gzip/xz compressed
- Configuration variables in the form "var=value\n"
Payloads are simply concatenated after the initial m1n1 binary.
m1n1 stage 1 is configured by the Asahi Linux Installer by appending variables like this:
cp build/m1n1.bin m1n1-stage1.bin
echo 'chosen.efi-partition=EFI-PARTITION-PARTUUID' >> m1n1-stage1.bin
echo 'chainload=EFI-PARTITION-PARTUUID;m1n1/boot.bin' >> m1n1-stage1.bin
m1n1 stage 2 will normally boot payloads directly, plus receive the chosen.efi-partition
config from stage 1 automatically. Using device trees shipped with U-Boot:
cat build/m1n1.bin \
../uboot/arch/arm/dts/"t[86]*.dtb \
<(gzip -c ../uboot/u-boot-nodtb.bin) \
>m1n1-uboot.bin
Note that U-Boot is compressed before appending. Uncompressed kernels may cause issues with variables getting lost, since their size cannot be accurately determined.
m1n1 can boot a Linux kernel and initramfs directly, either as stage 1 or 2 (but you probably don't want to do this for stage 1). You can specify the boot arguments directly. Using device trees shipped with the kernel:
cat build/m1n1.bin \
<(echo 'chosen.bootargs=earlycon debug root=/dev/nvme0n1p6 rootwait rw') \
<kernel>/arch/arm64/boot/dts/apple/*.dtb \
<initramfs>/initramfs.cpio.gz \
<kernel>/arch/arm64/boot/Image.gz \
>m1n1-linux.bin
Again note the use of a compressed kernel image. Also, if you want to concatenate multiple initramfs images, you should uncompress them first, then concatenate them and compress them again (bug).
If given no payloads, or if booting the payloads fails, m1n1 will fall back to proxy mode.
Proxy mode provides a USB device interface (available on all Thunderbolt ports) for debugging. To use it, connect your target device to your host device with a USB cable (e.g. a USB-C to USB-A cable, with the C side on the m1n1 target). See m1n1:Developer Guide for all the crazy details. These are just some simple examples of what you can do.
When in proxy mode, the host will see two USB TTY ACM devices. The first one is the proper proxy interface, while the second one is reserved for use by the hypervisor's virtual UART feature. You should set the M1N1DEVICE
environment variable to the path to the right device.
export M1N1DEVICE=/dev/ttyACM0
proxyclient/tools/linux.py <path/to/Image.gz> <path/to/foo.dtb> <path/to/initramfs.cpio.gz> -b "boot arguments here"
export M1N1DEVICE=/dev/ttyACM0
proxyclient/tools/chainload.py -r build/m1n1.bin
First, open the secondary port (e.g. /dev/ttyACM1
) with a serial terminal:
picocom --omap crlf --imap lfcrlf -b 500000 /dev/ttyACM1
Then boot your kernel:
proxyclient/tools/chainload.py -r build/m1n1.bin
cat build/m1n1.macho \
<(echo 'chosen.bootargs=earlycon debug rw') \
../linux/arch/arm64/boot/dts/apple/*.dtb \
<initramfs path>/initramfs-fw.cpio.gz \
../linux/arch/arm64/boot/Image.gz \
>m1n1-linux.macho
python tools/run_guest.py /tmp/m1n1-linux.macho
Note the use of the macho
version for run_guest.py (no bin support yet). Also note that we chainload m1n1 first; this is mandatory as the hypervisor ABI is extremely unstable.
If you have a standard release build of m1n1 installed as fuOS (i.e. what you get when you run the Asahi Linux installer), you can enable verbose messages and a backdoor proxy mode by going into 1TR and doing this from the macOS Terminal:
bputil -a
nvram boot-args=-v
You'll have to select the correct boot volume and authenticate yourself for bputil
.
Once initially enabled, the feature can be toggled off with:
nvram boot-args=
And back on with:
nvram boot-args=-v
By doing this, m1n1 will turn on verbose logging, and wait 5 seconds before booting its payloads. If it receives a USB proxy connection in that time, it will go into proxy mode. This is extremely useful when you want to have a working, auto-booting Linux install, but retain the ability to boot kernels via proxy mode if something goes wrong, or just for fast development.
To break into proxy mode, the host needs to open the USB ACM device (either of the 2 will do). This can be done by e.g. running a serial terminal in a loop on the secondary interface (which you might want for the hypervisor anyway):
while true; do
while [ ! -e /dev/ttyACM1 ]; do sleep 1; done
picocom --omap crlf --imap lfcrlf -b 500000 /dev/ttyACM1
sleep 1
done
Once picocom connnects, you can then invoke proxy scripts with M1N1DEVICE=/dev/ttyACM0
.
Note that this state is less secure, as any installed OS can alter the boot-args
property. Reset your boot policy with bputil -nc
and then use kmutil
to install m1n1 again to go back to the original state of requiring authentication to enable this feature.
Wiki for the Asahi Linux project: https://asahilinux.org/