Boot Asahi Kernel with u-root on Macbook
Goal:
Boot an Asahi Linux kernel and an u-root initramfs on a water-damaged Apple-Silicon Macbook
At some point I encountered myself with a Macbook Air booting Asahi’s m1n1 in proxy mode, without being able to boot MacOS or doing anything else with it, since the power button was fatally damaged.
Why did I buy a Macbook and how I accidentally destroyed it
Even if I definitely am a Linux-kind-of-person, I got myself an entry-level MacBook Air M1 which should fullfill the following purposes:
- Act as a temporary work device until getting my work laptop from the company - since it was also going to be a Macbook
- Because it’s light and handy, be the laptop of choice to take with me in vacation
- The most important reason: to have an excuse to play around with Asahi Linux
Asahi Linux is a project and community with the goal of porting linux to apple silicon macs.
I acquired this laptop at the end of March and middle April I had already got my company laptop: at least its main mission was fullfilled. It was middle May when I managed to empty some black tee all over the keyboard. Quick prognose: the keyboard and the touchpad had suffered an inminent death.
Lessons learnt
- When you have water or any drink spilled over your laptop keyboard: shut it down immediately (even if you think oh it’s just a couple of drops )
- If you want to incline the laptop to move the water away from the critical parts, do it so that the liquid goes to the back, since there is a slot there where it can come out
And then what?
The estimated cost of reparing was same as buying a new device, so not worth of it. My repair attempt consisting in disassembling and cleaning the burnt spots with ethanol, did not work, like at all. The good news: I had some fun ahead of me: I had installed the m1n1 proxy on the device before the fatal accident.
Steps:
1. (BEFORE) On the mac:
- Partition the SSD and install m1n1
2. On the linux laptop:
- Build m1n1
- Build the Asahi Kernel
- Build u-root
3. (AFTER) On the linux laptop, conneced to the Mac via serial/USB:
- Boot the Asahi Kernel and u-root
As mentioned my Mac was mainly intented to be used to play around with Asahi Linux. Since Asahi is meant to be ran alongside MacOS and because macbooks are almost impossible to brick, there was no risk in installing m1n1 even if I still needed to use MacOS for work.
m1n1 is the bootloader developed by the Asahi Linux project to bridge the Apple (XNU) boot ecosystem to the Linux boot ecosystem.
List disks and partitions:
eramon@cider dev % diskutil list
/dev/disk0 (internal):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme 251.0 GB disk0
1: Apple_APFS_ISC 524.3 MB disk0s1
2: Apple_APFS Container disk3 245.1 GB disk0s2
3: Apple_APFS_Recovery 5.4 GB disk0s3
/dev/disk3 (synthesized):
#: TYPE NAME SIZE IDENTIFIER
0: APFS Container Scheme - +245.1 GB disk3
Physical Store disk0s2
1: APFS Volume Macintosh HD 15.3 GB disk3s1
2: APFS Snapshot com.apple.os.update-... 15.3 GB disk3s1s1
3: APFS Volume Preboot 186.5 MB disk3s2
4: APFS Volume Recovery 1.0 GB disk3s3
5: APFS Volume Data 13.5 GB disk3s5
6: APFS Volume VM 20.5 KB disk3s6
Following the documentation, I resize the APFS container:
eramon@cider ~ % diskutil apfs resizeContainer disk0s2 150GB
Run diskutil again, and see if it worked:
eramon@cider ~ % diskutil list
/dev/disk0 (internal):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme 251.0 GB disk0
1: Apple_APFS_ISC 524.3 MB disk0s1
2: Apple_APFS Container disk3 150.0 GB disk0s2
(free space) 95.1 GB -
3: Apple_APFS_Recovery 5.4 GB disk0s3
/dev/disk3 (synthesized):
#: TYPE NAME SIZE IDENTIFIER
0: APFS Container Scheme - +150.0 GB disk3
Physical Store disk0s2
1: APFS Volume Macintosh HD 15.3 GB disk3s1
2: APFS Snapshot com.apple.os.update-... 15.3 GB disk3s1s1
3: APFS Volume Preboot 184.6 MB disk3s2
4: APFS Volume Recovery 1.0 GB disk3s3
5: APFS Volume Data 16.3 GB disk3s5
6: APFS Volume VM 20.5 KB disk3s6
There was now some free space now after the disk0s2. At this point I tried to restart, the MacBook was rebooting without any problem, so at least we didn’t break anything with the resizing.
Run the asahi installer:
eramon@cider asahi % curl -L https://mrcn.st/alxsh | sh
At some point, the user is prompted to say if the advanced options should be displayed:
By default, this installer will hide certain advanced options that
are only useful for developers. You can enable expert mode to show them.
» Enable expert mode? (y/N):
Choose YES. A little further below, information about the partitions is displayed, and the installer asks what to do:
Partitions in system disk (disk0):
1: APFS [Macintosh HD] (150.00 GB, 6 volumes)
OS: [B*] [Macintosh HD] macOS v12.3 [disk3s1, 46277907-5BF2-4627-9192-9C780B38A30A]
2: (free space: 95.11 GB)
3: APFS (System Recovery) (5.37 GB, 2 volumes)
OS: [ ] recoveryOS v12.3 [Primary recoveryOS]
[B ] = Booted OS, [R ] = Booted recovery, [? ] = Unknown
[ *] = Default boot volume
Using OS 'Macintosh HD' (disk3s1) for machine authentication.
Choose what to do:
f: Install an OS into free space
r: Resize an existing partition to make space for a new OS
q: Quit without doing anything
» Action (f): f
Choose “f”: Install an OS into free space
When asked about which OS to install, I selected the third option, to install just the m1n1 in proxy: no u-boot, no asahi linux. Yet. I did this since I wanted to play around with the m1n1 binary booting a kernel manually. I wasn’t counting with a damaged system, which is what happened next.
After the incident, the boot menu did not appear anymore when hold-pressing the power button, apparently it was also damaged. When booting the system the Asahi logo would greet me, followed for the m1n1 in proxy mode. I couldn’t get anywhere else but there.
At this point, what was I intending to achieve? Even if it wasn’t that clear to me how, the idea I got in mind was the following:
- As I understood, booting Linux in a Silicon Mac works as follows: m1n1 -> bootloader -> kernel -> rootfs
- Bootloader: the Asahi project uses u-boot as the bootloader, here is where I intended to do things a little differently: to follow Linuxboot approach using a (reduced) Asahi kernel with u-root as initramfs
- Kernel: Asahi (relying in the .config of the ARCH distribution)
- Rootfs: Debian
This was going to be a long journey, this post only covers my first tries with the booloader part.
m1n1 is not just the bootloader installed on the Mac, is also a binary used on a host computer to send commands to the Mac when it’s in Asahi Proxy Mode.
The m1n1 proxy mode provides a USB device interface for booting a linux kernel directly from the host computer.
Clone and build m1n1:
eramon@caipirinha:~/dev$ git clone --recursive https://github.com/AsahiLinux/m1n1.git
eramon@caipirinha:~/dev$ cd m1n1
eramon@caipirinha:~/dev/m1n1$ make
Install the cross-compiler:
eramon@caipirinha:~/dev/asahi/m1n1$ sudo apt-get install make gcc-aarch64-linux-gnu
Clone the asahi linux kernel:
eramon@caipirinha:~/dev$ git clone https://github.com/AsahiLinux/linux.git
I relied in the files of the ARCH Asahi Linux distribution:
- To know which version of the asahi linux kernel to build
- To have a working kernel config
NOTE: I asked for a recommendation regarding which .config to use in the Asahi IRC channel
NOTE: even if this config is the recommended one for the Asahi Linux distribution, it might be not the ideal choice to use it in the bootloader stage. The necessary modules should be included directly in the kernel.
To know which source code tag to build, I took a look inside the file https://github.com/AsahiLinux/PKGBUILDs/blob/main/linux-asahi/PKGBUILD:
_rcver=5.19
_asahirel=1
Checkout the source corresponding to 5.19-1:
eramon@caipirinha:~/dev$ cd linux/
eramon@caipirinha:~/dev/linux$ git fetch --tags --all
eramon@caipirinha:~/dev/linux$ git checkout asahi-5.19-1
Get the config file from the ARCH distribution:
eramon@caipirinha:~/dev/linux$ wget https://raw.githubusercontent.com/AsahiLinux/PKGBUILDs/main/linux-asahi/config
eramon@caipirinha:~/dev/linux$ cp config .config
Configure and build the kernel:
eramon@caipirinha:~/dev/linux$ ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make olddefconfig
eramon@caipirinha:~/dev/linux$ ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make Image.gz dtbs
After built, following files have been generated:
- The compressed kernel image: Image.gz
- The MacBook Air device tree, knowing that the product name is J313AP and the SoC is T8103 the product name is J313AP and the SoC is T8103
Copy the above mentioned files, which we’re going to need later, close to the m1n1 binary:
eramon@caipirinha:~/dev/linux$ mkdir ../m1n1/files
eramon@caipirinha:~/dev/linux$ cp arch/arm64/boot/dts/apple/t8103-j313.dtb ../m1n1/files/
eramon@caipirinha:~/dev/linux$ cp arch/arm64/boot/Image.gz ../m1n1/files/
u-root is one of the deliverables of the Linuxboot project.
Linuxboot replaces specific firmware functionality with a Linux kernel and runtime.
One of the outcomes of linuxboot is u-root: a minimal initrams written in Go.
We are using u-root to build an initramfs to be started after the asahi kernel boots.
Clone the u-root source code:
eramon@caipirinha:~/dev$ git clone https://github.com/u-root/u-root
eramon@caipirinha:~/dev$ cd u-root
Install go:
eramon@caipirinha:~/dev/u-root$ wget https://go.dev/dl/go1.19.linux-amd64.tar.gz
eramon@caipirinha:~/dev/u-root$ sudo rm -rf /usr/local/go
eramon@caipirinha:~/dev/u-root$ sudo tar -C /usr/local -xzf go1.19.linux-amd64.tar.gz
Build and install u-root:
eramon@caipirinha:~/dev/u-root$ export PATH=$PATH:/usr/local/go/bin
eramon@caipirinha:~/dev/u-root$ export GOPATH=/home/eramon/dev/go
eramon@caipirinha:~/dev/u-root$ go install github.com/u-root/u-root
The u-root binary will be installed under $GOPATH:
eramon@caipirinha:~/dev/u-root$ export PATH=$PATH:/home/eramon/dev/go/bin
Run u-root with the cross-architecture prefix:
eramon@caipirinha:~/dev/u-root$ GOARCH=arm64 u-root
An initramfs for the arch64 architecture was generated:
09:59:10 Successfully built "/tmp/initramfs.linux_arm64.cpio" (size 11877784)
Same as before, copy the file close to the m1n1 binary:
eramon@caipirinha:~/dev/u-root$ cp /tmp/initramfs.linux_arm64.cpio ../m1n1/files/
Pre-condition: you need to have Python3 installed on the system
In addition to python3, modules serial and construct are necessary:
eramon@caipirinha:~/dev/m1n1$ pip3 install construct
eramon@caipirinha:~/dev/m1n1$ pip3 install pyserial
Opening the lid of the macbook air is all the preparation we need, since:
- Macbooks boot automatically when opening the lid
- The Options menu does not work anymore, the laptop boots straight to the asahi proxy
Connect the Macbook with an USB-C cable to the linux laptop, where m1n1, the linux kernel and u-root were built.
Go to the directory where m1n1 was built:
eramon@caipirinha:~$ cd dev/m1n1/
There are two different ways to boot the kernel and the initramfs with m1n1:
- Directly: booting a linux kernel directly, passing a gzipped kernel, the device tree and optionally an initramfs
- Virtualized: under the m1n1 hypervisor. M1n1 will first load as a guest inside m1n1 - what the asahi developers call inception - and then load the embedded kernel and initramfs
The first way do not - at the time of this writting - work properly. The reason is that the console is not initialised properly, so even if it’s indeed working, we do not see anything else than the asahi logo frozen on the screen.
For the sake of having everything documented, that’s how to boot using the first way:
eramon@caipirinha:~/dev/asahi/m1n1$ export M1N1DEVICE=/dev/ttyACM0
eramon@caipirinha:~/dev/asahi/m1n1$ python3 proxyclient/tools/linux.py -b 'earlycon debug rootwait' files/Image.gz files/t8103-j313.dtb files/initramfs.linux_arm64.cpio.gz
m1n1 communicates with the host via serial, which in the host is /dev/ttyACM1. I used picocom to see the output there, but it always crashed at some point.
The second way allows to do the same while having a serial console, so we have the output and keyboard input through the host.
First, gzip the initramfs, since it must be gzip compressed:
eramon@caipirinha:~/dev/m1n1$ gzip -9 files/initramfs.linux_arm64.cpio
Build the binary concatenating the m1n1 binary - the older format .macho - with the DTB, the initramfs and the kernel image:
eramon@caipirinha:~/dev/m1n1$ cat build/m1n1.macho <(echo 'chosen.bootargs=earlycon debug rw') files/t8103-j313.dtb files/initramfs.linux_arm64.cpio.gz files/Image.gz > files/m1n1-linux-uroot.macho
In another terminal, prepare the serial console, opening the second port with a serial terminal:
eramon@caipirinha:~/dev/m1n1$ sudo apt-get install picocom
eramon@caipirinha:~/dev/m1n1$ picocom --omap crlf --imap lfcrlf -b 500000 /dev/ttyACM1
You should see the following:
Type [C-a] [C-h] to see available commands
Terminal ready
Go back to the first terminal and run the binary as guest to the m1n1 hypervisor:
eramon@caipirinha:~/dev/m1n1$ python3 proxyclient/tools/run_guest.py files/m1n1-linux-uroot.macho
If everything goes well, we should be greeted by the u-root logo in the serial console:
1970/01/01 00:00:00 Welcome to u-root!
_
_ _ _ __ ___ ___ | |_
| | | |____| '__/ _ \ / _ \| __|
| |_| |____| | | (_) | (_) | |_
\__,_| |_| \___/ \___/ \__|
/#
With u-root we get a shell with a minimalistic set of commands.
What next? From there, build and boot a linux kernel and build and mount a Debian rootfs. But that’s a whole story on its own, enough material for an upcoming blog post :)
The first outcome was that the disk devices were not visible from the u-root environment, probably because the nvme controller was compiled as a module in the kernel configuration I had chosen. So that would be the starting point for an eventual next chapter.
https://github.com/AsahiLinux/m1n1
https://github.com/AsahiLinux/docs/wiki/m1n1%3AUser-Guide
https://github.com/AsahiLinux/PKGBUILDs/tree/main/linux-asahi
https://github.com/AsahiLinux/linux
https://github.com/AsahiLinux/docs/wiki/Devices