commit f5f3c26cf336d462afc620bcd6d46f08fd8d9538
Author: Kris Yotam <75515498+krisyotam@users.noreply.github.com>
Date: Wed, 25 Sep 2024 23:03:09 -0500
make mac great again
Diffstat:
11 files changed, 1226 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+work/
diff --git a/README.md b/README.md
@@ -0,0 +1,453 @@
+# mmga: Make MacBook Great Again
+
+> **WARNING!** Please DO NOT use mmga! If you want coreboot on your macbook
+> and it's listed as supported, read [this blog post](https://ch1p.io/coreboot-macbook-support/)
+> first or contact me by email.
+
+---
+
+**mmga** is a script to help flashing coreboot on some MacBook Air and Pro
+models without using external SPI programmer. See
+[this blog post](https://ch1p.io/coreboot-macbook-internal-flashing/) on how to
+do the same manually.
+
+### Supported devices
+
+As of time of writing, following devices are supported in coreboot. Other models
+might be supported in future.
+
+* MacBook Pro 8,1 (13'' Early 2011) (`macbookpro8_1`)<br>
+* MacBook Pro 10,1 (15'' Mid 2012 Retina) (`macbookpro10_1`)
+
+ **Attention!** Not all memory configurations are supported, see
+ [here](#ram-configurations).<br>
+
+* MacBook Air 5,2 (13'' Mid 2012) (`macbookair5_2`)
+
+ **Attention!** Not all memory configurations are supported, see
+ [here](#ram-configurations).<br>
+
+* MacBook Air 4,2 (13'' Mid 2011) (`macbookair4_2`).
+
+ **Attention!** Not all memory configurations are supported, see
+ [here](#ram-configurations).
+
+iMac 13,1 is a candidate for support too, but [coreboot port](https://review.coreboot.org/c/coreboot/+/38883)
+for this device is not actively maintained at the moment and it may fail to build.
+I'll add iMac 13,1 support later when it's fixed.
+
+### RAM configurations
+
+Models with soldered RAM are sold with different memory modules, manufactured by
+different manufacturers. Not all of them are supported currently.
+
+To determine which memory you have in your MacBook, you can use `inteltool`
+and [this script](https://github.com/gch1p/get_macbook_ramcfg). You need to run
+them on the target machine.
+
+First, [download coreboot](#stage1) and build `inteltool`:
+```console
+$ cd util/inteltool
+$ make -j4
+```
+
+Download the script and make it executable. Then run:
+```console
+$ sudo ./inteltool -g | /path/to/get_macbook_ramcfg -m MODEL
+```
+
+Replace `MODEL` with your MacBook model: `mbp101` for MacBook Pro 10,1, `mba52`
+for MacBook Air 5,2 and `mba42` for MacBook Air 4,2.
+
+Then check the tables below.
+
+#### MacBook Pro 10,1
+
+| RAM configuration | Supported |
+| ------------------|-----------|
+| 4g_hynix_1600s | 🚫 No |
+| 1g_samsung_1600 | 🚫 No |
+| 4g_samsung_1600s | 🚫 No |
+| 1g_hynix_1600 | 🚫 No |
+| 4g_elpida_1600s | 🚫 No |
+| 2g_samsung_1600 | 🚫 No |
+| 2g_samsung_1333 | 🚫 No |
+| 2g_hynix_1600 | ✅ Yes |
+| 4g_samsung_1600 | 🚫 No |
+| 4g_hynix_1600 | ✅ Yes |
+| 2g_elpida_1600s | 🚫 No |
+| 2g_elpida_1600 | 🚫 No |
+| 4g_elpida_1600 | 🚫 No |
+| 2g_samsung_1600s | 🚫 No |
+| 2g_hynix_1600s | 🚫 No |
+
+#### MacBook Air 5,2
+
+| RAM configuration | Supported |
+|-------------------|-----------|
+| 4g_hynix | ✅ Yes |
+| 8g_hynix | 🚫 No |
+| 4g_samsung | ✅ Yes |
+| 8g_samsung | 🚫 No |
+| 4g_elpida | 🚫 No |
+| 8g_elpida | 🚫 No |
+
+#### MacBook Air 4,2
+
+| RAM configuration | Supported |
+|-------------------|-----------|
+| 2g_hynix | 🚫 No |
+| 4g_hynix | 🚫 No |
+| 2g_samsung | 🚫 No |
+| 4g_samsung | ✅ Yes |
+| 2g_micron | 🚫 No |
+| 4g_elpida | 🚫 No |
+
+---
+
+If your found out that your MacBook's memory is not supported, you can help
+supporting it. Run `sudo inteltool -m`, save output to a text file and create a
+new issue specifying your MacBook model, memory configuration name with the text
+file attached.
+
+
+### System requirements
+
+* Recent Linux distribution booted with `iomem=relaxed` kernel parameter
+ (required for internal programmer to work);
+* Build dependencies. Here's a list for Debian-based distros:
+ ```
+ # apt install bison build-essential curl flex git gnat libncurses5-dev m4 zlib1g-dev make libpci-dev libusb-1.0-0-dev
+ ```
+
+ If you plan to use GRUB2 as a payload:
+ ```
+ # apt install libfreetype-dev unifont autoconf
+ ```
+
+ On other distros package names might differ. Be sure to install **gnat**
+ prior to building coreboot toolchain.
+
+### Building flashrom
+
+First of all, grab recent flashrom sources and build it:
+```
+$ git clone https://review.coreboot.org/flashrom.git && cd flashrom
+$ make
+```
+
+Optionally, install it to `/usr/local/sbin`:
+```
+$ sudo make install
+```
+
+## How it works
+
+The firmware of the devices covered by this project is stored on SPI chip. It
+consists of various regions: `fd` (Flash Descriptor), `me` (Intel ME) and `bios`
+(BIOS, or Apple EFI). Sometimes there are more regions, for example there may be
+`gbe` region for Gigabit Ethernet or `ec` region with EC firmware, but for now,
+let's focus on our MacBooks.
+
+The most important region in context of this story is `fd`, the Intel Flash
+Descriptor.
+
+The Intel Flash Descriptor is a data structure of fixed size (4KB) stored on
+the flash chip (resides in `0x0000-0x0fff`), that contains various information
+such as space allocated for each region on the flash, access permissions, some
+chipset configuration and more. In particular, it contains access permissions
+for `fd` and `me` regions.
+
+This is the flash chip layout used in MacBook Air 5,2 (which has 8 MiB flash chip).
+It can be extracted from stock ROM image with `ifdtool`:
+```
+00000000:00000fff fd
+00190000:007fffff bios
+00001000:0018ffff me
+```
+
+Normally, the `fd` and `me` regions should be read-only in production, but this
+is not the case with MacBooks. Apparently, Apple's "Think Different" thing
+applies to firmware security as well.
+
+Instead, they decided to use SPI Protected Range Registers (PR0-PR4) to set
+protection over `fd`, but here they failed again. Due to a bug (I hope),
+`0x0000-0x0fff` is not write-protected after cold boot and becomes read-only
+only after resuming from S3.
+
+You can dump PRx protections on your device by running `flashrom -p internal`.
+If it doesn't work, make sure to boot with `iomem=relaxed` or try
+`-p internal:laptop=force_I_want_a_brick`.
+
+This is what you should see after a cold boot (and, if so, mmga should work on
+your device):
+```
+PR0: Warning: 0x00190000-0x0066ffff is read-only.
+PR1: Warning: 0x00692000-0x01ffffff is read-only.
+```
+
+And this is after resuming from S3:
+```
+PR0: Warning: 0x00000000-0x00000fff is read-only.
+PR1: Warning: 0x00190000-0x0066ffff is read-only.
+PR2: Warning: 0x00692000-0x01ffffff is read-only.
+```
+
+So, after cold boot flash descriptor is protected neither by PRx registers nor
+by access permission bits on the flash descriptor itself. Under certain
+circumstances, **writable flash descriptor allows flashing whole SPI flash** by
+using a couple of neat tricks, and that is what mmga script does.
+
+Writable `me` region gives us around 1.5 MiB of writable space. The idea is that
+we can shrink ME firmware image with me_cleaner to about ~128 KiB and use the
+freed space for a small temporary coreboot image. Writable `fd` gives us ability
+to change flash layout and move reset vector. We combine all this, flash modified
+regions, then power off (new flash descriptor becomes active on cold boot, so
+reboot won't work). Then boot our small temporary coreboot and flash the whole
+SPI chip, as there will be no more PRx protections set. So this is a two-stage
+process.
+
+Let's write a new layout:
+```
+00000000:00000fff fd
+00001000:00020fff me
+00021000:000fffff bios
+00100000:007fffff pd
+```
+
+In this layout, we allocate 128 KiB for `me` and 892 KiB for `bios`. To fit the
+original 1.5 MiB ME image into the 128 KiB region, it has to be truncated with
+[me_cleaner](https://github.com/corna/me_cleaner) with `-t` and `-r` arguments,
+the size of resulting image is ~92 KiB. We also have to allocate the remaining
+`0x100000-0x7fffff` region for *something*, to be able to address and flash it
+in future. So we just mark it as `pd`, which is commonly used for "Platform Data".
+
+After the new layout is ready, we build small coreboot ROM that fits into the
+allocated 892 KiB bios region. Then we flash **`fd`** (`0x0000-0x0fff`),
+**`me`** (`0x1000-0x20fff`) and **`bios`** (`0x21000-0xfffff`) according to the
+new layout. On the next cold boot, coreboot will be loaded from the
+`0x21000-0xfffff` region, and the old firmware, which still resides in
+`0x190000-0x7fffff`, will be ignored. This is **stage1**.
+
+After we boot with small temporary coreboot ROM, we're able to flash the whole
+8 MiB chip, because there are no more PRx protections set. We repartition the
+chip again, and the new layout looks like this:
+```
+00000000:00000fff fd
+00001000:00020fff me
+00021000:007fffff bios
+```
+
+It's almost the same, except that `bios` fills all the remaining space. Then we
+build coreboot again, flash `fd`, `me` and `bios` and power off again. On the
+next cold boot we will have completely corebooted MacBook. This is **stage2**.
+
+## Usage instructions
+
+The **mmga** script automates steps described above and does all the dirty work.
+
+### Usage:
+
+```
+./mmga <options> ACTION
+```
+
+##### Options:
+
+```
+-h, --help: show help
+```
+
+##### stage1 actions:
+
+```
+ dump: dump flash contents
+ fetch: fetch board tree from Gerrit (if needed)
+prepare-stage1: patch IFD, neutralize and truncate ME
+ config-stage1: make coreboot config (for manual use)
+ build-stage1: make config and build ROM (for auto use)
+ flash-stage1: flash ROM ($COREBOOT_PATH/build/coreboot.rom)
+```
+
+##### stage2 actions:
+
+```
+prepare-stage2: patch IFD (if needed)
+ config-stage2: make coreboot config (for manual use)
+ build-stage2: make config and build ROM (for auto use)
+ flash-stage2: flash ROM ($COREBOOT_PATH/build/coreboot.rom)
+```
+
+##### other actions:
+
+```
+ flash-oem: flash OEM firmware back
+```
+
+### Warning
+
+You **should** have external means of flashing for a backup, **just in case**.
+The procedure described above is quite delicate and error-prone and any mistake
+may lead to a brick. In that case, you should have a copy of your original ROM
+**on external drive**. Please make a backup of `work/oem/dump.bin` after
+running `mmga dump`, or just copy the whole mmga directory.
+
+These posts may be a little helpful if you ever need to flash externally:
+
+- [MacBook Air 5,2](https://ch1p.io/coreboot-mba52-flashing/)
+- [MacBook Pro 10,1](https://ch1p.io/coreboot-mbp101-flashing/)
+
+### Choosing the payload
+
+Currently, SeaBIOS and GRUB are supported by mmga. SeaBIOS supports legacy boot,
+it will most probably boot from an old school MBR partition. On the other hand,
+it may not boot from GPT (I'm not sure about it, correct me if you know more)
+and certainly it will not boot an EFI installation.
+
+Sometimes GRUB is a better choice, but it all depends on your system. If you are
+not sure, do some research before flashing or seek for help.
+
+It may be a good idea to prepare a USB drive with some live system. I can imagine
+a situation where you have chosen the wrong payload and cannot boot into your
+system after power off/on cycle to complete the second stage, because, for instance,
+SeaBIOS doesn't recognize your partition. In that case, you could try to boot
+from live USB to fix your system (if possible) or to complete second stage and
+change the payload.
+
+Of course, in order to do that, you should backup the whole mmga directory after
+the completion of the first stage but **before** the reboot.
+
+**Attention!** Recent SeaBIOS versions break internal keyboard and touchpad on
+MacBooks, for now it's recommended to use GRUB until it's fixed.
+
+### Configuration
+
+Before you start, you have to update variables the `config.inc` file:
+- **`PAYLOAD`**: which payload to use, supported values are `grub` and `seabios`
+- **`MODEL`**: put your macbook model here, example: `macbookair5_2`
+- **`GRUB_CFG_PATH`**: only if you use `grub` payload; if empty, default
+ `grub.cfg` will be used
+- **`COREBOOT_PATH`**: path to cloned coreboot repository (see below)
+- **`FLASHROM`**: path to flashrom binary
+- **`FLASHROM_ARGS`**: in case if flashrom detects multiple flash chips, put
+ `"-c CHIP_MODEL"` here
+- **`STAGE2_USE_FULL_ME`**: if you want to use original Intel ME image in the
+ final ROM for some reason, set to `1`
+
+#### stage1
+
+Get coreboot:
+```
+$ git clone --recurse-submodules https://review.coreboot.org/coreboot.git && cd coreboot
+```
+
+Build coreboot toolchain. You must have gnat compiler installed, it is required
+for graphics initialization (libgfxinit is written in Ada):
+```
+$ make crossgcc-i386 CPUS=$(nproc)
+$ make iasl
+```
+
+Dump the flash chip contents:
+```
+# ./mmga dump
+```
+
+**Create a backup of the stock ROM dump on external drive!**
+
+Create patched FD and neutralize ME:
+```
+$ ./mmga prepare-stage1
+```
+
+If your board's port hasn't been merged to coreboot master yet or you don't know,
+run:
+```
+$ ./mmga fetch
+```
+
+Create coreboot config and build the ROM:
+```
+$ ./mmga build-stage1
+```
+
+(If you're experienced coreboot user or developer, you may want to configure and
+build coreboot yourself. In that case, run `config-stage1` instead of
+`build-stage1`. It will create config that you can then copy to `$COREBOOT_PATH`,
+make your changes and build.)
+
+Flash it:
+```
+# ./mmga flash-stage1
+```
+
+If it's done and there were no errors, you have to **shutdown** the laptop.
+Do not reboot, the new flash descriptor is only active after cold boot, so it
+won't work and may lead to weird stuff as you've just messed with firmware. Wait
+a few seconds and power it back on.
+
+#### stage2
+
+Make patched flash descriptor for the next flash:
+```
+$ ./mmga prepare-stage2
+```
+
+Create new coreboot config and build the ROM (for experienced users,
+`config-stage2` is also available):
+```
+$ ./mmga build-stage2
+```
+
+Flash it:
+```
+# ./mmga flash-stage2
+```
+
+This may take a while, don't interrupt it and let it finish.
+
+Again, if there were no errors, power off the machine again, wait a few seconds,
+and power on.
+
+## FAQ
+
+**My device is not listed, will it work?**
+
+No, but it might be possible to support it.
+
+**Will macOS continue to work with coreboot?**
+
+No. It's theoretically possible to turn a corebooted MacBook into a hackintosh
+by using TianoCore and Clover or OpenCore. Basically, if you want to use macOS,
+don't install coreboot.
+
+**Switching between iGPU and dGPU on MacBook Pro 10,1**
+
+Last time I checked, [hacks](https://wiki.archlinux.org/index.php/MacBookPro10,x#Graphics_2)
+were needed to use Linux with integrated GPU on this model. It seems that Apple EFI
+forces dGPU when OS is not macOS. I've integrated hybrid graphics driver into
+coreboot, it automatically switches to the configured GPU and you don't need to
+care about it anymore. By default, integrated GPU is used. The setting is stored
+in CMOS and you can change it with `nvramtool`.
+
+Note that to use discrete GPU you need to extract VGA ROM from the stock firmware
+dump and add it to CBFS, and configure coreboot to run VGA Option ROMs.
+
+## TODO
+
+- Support custom FMAP for larger first-stage `bios` partition
+- Support multiple payloads at once
+- Support TianoCore as a payload
+
+## Misc
+
+The script was tested on MacBook Air 5,2, MacBook Pro 8,1 and MacBook Pro 10,1.
+If you have successfully corebooted your macbook with it and it worked, please
+let me know.
+
+If you have any problems, contact me via GitHub issues or by email (see the
+copyright header in the script).
+
+## License
+
+GPLv2
diff --git a/config.inc b/config.inc
@@ -0,0 +1,35 @@
+# Which payload to use. Supported payloads: grub, seabios
+PAYLOAD=grub
+
+# Example: macbookair5_2
+MODEL=
+
+# If empty, default grub.cfg (from this repo) will be used. Skip if you use SeaBIOS.
+GRUB_CFG_PATH=
+
+# Path to cloned coreboot repo.
+COREBOOT_PATH=
+
+# Path to flashrom binary. Minimal required flashrom version is 1.0.
+# Do not use flashrom from Debian repos. Instead, get the latest version from git and build:
+#
+# git clone https://review.coreboot.org/flashrom.git
+# cd flashrom
+# make -j$(nproc)
+#
+FLASHROM=/usr/local/sbin/flashrom
+
+# In case if flashrom detects multiple chips, put "-c CHIP_MODEL" here.
+FLASHROM_ARGS=
+
+# If you want to use original Intel ME image in the final (stage2) ROM, set to 1
+STAGE2_USE_FULL_ME=0
+
+# Other required utilities. Note, that they need to be built. To do that,
+# `cd` to each util's directory and run `make`.
+# If COREBOOT_PATH is set correctly, you don't need to edit these values.
+IFDTOOL=${COREBOOT_PATH}/util/ifdtool/ifdtool
+CBFSTOOL=${COREBOOT_PATH}/util/cbfstool/cbfstool
+
+# This should just work once COREBOOT_PATH is set correctly.
+ME_CLEANER=${COREBOOT_PATH}/util/me_cleaner/me_cleaner.py
diff --git a/config/macbookair4_2 b/config/macbookair4_2
@@ -0,0 +1,3 @@
+CONFIG_USE_OPTION_TABLE=y
+CONFIG_VENDOR_APPLE=y
+CONFIG_BOARD_APPLE_MACBOOKAIR4_2=y
diff --git a/config/macbookair5_2 b/config/macbookair5_2
@@ -0,0 +1,3 @@
+CONFIG_USE_OPTION_TABLE=y
+CONFIG_VENDOR_APPLE=y
+CONFIG_BOARD_APPLE_MACBOOKAIR5_2=y
diff --git a/config/macbookpro10_1 b/config/macbookpro10_1
@@ -0,0 +1,3 @@
+CONFIG_USE_OPTION_TABLE=y
+CONFIG_VENDOR_APPLE=y
+CONFIG_BOARD_APPLE_MACBOOKPRO10_1=y
diff --git a/config/macbookpro8_1 b/config/macbookpro8_1
@@ -0,0 +1,3 @@
+CONFIG_USE_OPTION_TABLE=y
+CONFIG_VENDOR_APPLE=y
+CONFIG_BOARD_APPLE_MACBOOKPRO8_1=y
diff --git a/grub.cfg b/grub.cfg
@@ -0,0 +1,166 @@
+set prefix=(memdisk)/boot/grub
+
+insmod nativedisk
+insmod ehci
+insmod ohci
+insmod uhci
+insmod usb
+insmod usbms
+insmod usbserial_pl2303
+insmod usbserial_ftdi
+insmod usbserial_usbdebug
+
+# Serial and keyboard configuration, very important.
+serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1
+terminal_input --append serial
+terminal_output --append serial
+terminal_input --append at_keyboard
+terminal_output --append cbmemc
+
+gfxpayload=keep
+terminal_output --append gfxterm
+
+# Default to first option, automatically boot after 1 second
+set default="0"
+set timeout=5
+
+# This is useful when using 'cat' on long files on GRUB terminal
+set pager=1
+insmod jpeg
+
+background_image (cbfsdisk)/background.jpg
+loadfont (memdisk)/dejavusansmono.pf2
+
+keymap usqwerty
+function try_user_config {
+ set root="${1}"
+ for dir in boot grub grub2 boot/grub boot/grub2; do
+ for name in '' autoboot_ libreboot_ coreboot_; do
+ if [ -f /"${dir}"/"${name}"grub.cfg ]; then
+ configfile /"${dir}"/"${name}"grub.cfg
+ fi
+ done
+ done
+}
+function search_grub {
+ for i in 0 1 2 3; do
+ # raw devices
+ try_user_config "(${1}${i})"
+ for part in 1 2 3 4 5; do
+ # MBR/GPT partitions
+ try_user_config "(${1}${i},${part})"
+ done
+ done
+}
+function try_isolinux_config {
+ set root="${1}"
+ for dir in '' /boot; do
+ if [ -f "${dir}"/isolinux/isolinux.cfg ]; then
+ syslinux_configfile -i "${dir}"/isolinux/isolinux.cfg
+ elif [ -f "${dir}"/syslinux/syslinux.cfg ]; then
+ syslinux_configfile -s "${dir}"/syslinux/syslinux.cfg
+ fi
+ done
+}
+function search_isolinux {
+ for i in 0 1; do
+ # raw devices
+ try_isolinux_config "(${1}${i})"
+ for part in 1 2 3 4 5; do
+ # MBR/GPT partitions
+ try_isolinux_config "(${1}${i},${part})"
+ done
+ done
+}
+menuentry 'Load Operating System (incl. fully encrypted disks) [o]' --hotkey='o' {
+# GRUB2 handles (almost) every possible disk setup, but only the location of
+# /boot is actually important since GRUB2 only loads the user's config.
+
+# LVM, RAID, filesystems and encryption on both raw devices and partitions in
+# all various combinations need to be supported. Since full disk encryption is
+# possible with GRUB2 as payload and probably even used by most users, this
+# configuration tries to load the operating system in the following way:
+
+# 1. Look for user configuration on unencrypted devices first to avoid
+# unnecessary decryption routines in the following order:
+
+# 1) raw devices and MBR/GPT partitions
+ search_grub ahci
+ search_grub ata
+# 2) LVM and RAID which might be used accross multiple devices
+ lvm="lvm/matrix-rootvol lvm/matrix-boot"
+ raid="md/0 md/1 md/2 md/3 md/4 md/5 md/6 md/7 md/8 md/9"
+ for vol in ${lvm} ${raid}; do
+ try_user_config "(${vol})"
+ done
+# 2. In case no configuration could be found, try decrypting devices. Look
+# on raw crypto devices as well as inside LVM volumes this time.
+
+# The user will be prompted for a passphrase if a LUKS header was found.
+ for dev in ahci0 ata0 ${lvm}; do
+ cryptomount "(${dev})"
+ done
+# 3) encrypted devices/partitions
+ for i in 0 1; do
+ for part in 1 2 3 4 5; do
+ for type in ahci ata; do
+ cryptomount "(${type}${i},${part})"
+ done
+ done
+ done
+
+# 3) encrypted devices/partitions
+ search_grub crypto
+# 4) LVM inside LUKS containers
+ for vol in ${lvm}; do
+ try_user_config "(${vol})"
+ done
+
+ # Last resort, if all else fails
+ set root=ahci0,1
+ for p in / /boot/; do
+ if [ -f "${p}vmlinuz" ]; then
+ linux ${p}vmlinuz root=/dev/sda1 rw
+ if [ -f "${p}initrd.img" ]; then
+ initrd ${p}initrd.img
+ fi
+ fi
+ done
+
+ # Last resort (for GA-G41-ES2L which uses IDE emulation mode for SATA)
+ set root=ata0,1
+ for p in / /boot/; do
+ if [ -f "${p}vmlinuz" ]; then
+ linux ${p}vmlinuz root=/dev/sda1 rw
+ if [ -f "${p}initrd.img" ]; then
+ initrd ${p}initrd.img
+ fi
+ fi
+ done
+}
+menuentry 'Search ISOLINUX menu (AHCI) [a]' --hotkey='a' {
+ search_isolinux ahci
+}
+menuentry 'Search ISOLINUX menu (USB) [u]' --hotkey='u' {
+ search_isolinux usb
+}
+menuentry 'Search ISOLINUX menu (CD/DVD) [d]' --hotkey='d' {
+ insmod ata
+ for dev in ata0 ata1 ata2 ata3 ahci1; do
+ try_isolinux_config "(${dev})"
+ done
+}
+menuentry 'Load test configuration (grubtest.cfg) inside of CBFS [t]' --hotkey='t' {
+ set root='(cbfsdisk)'
+ configfile /grubtest.cfg
+}
+menuentry 'Search for GRUB2 configuration on external media [s]' --hotkey='s' {
+ search_grub usb
+}
+menuentry 'Poweroff [p]' --hotkey='p' {
+ halt
+}
+menuentry 'Reboot [r]' --hotkey='r' {
+ reboot
+}
+
diff --git a/layout-stage1.txt b/layout-stage1.txt
@@ -0,0 +1,4 @@
+00000000:00000fff fd
+00001000:00020fff me
+00021000:000fffff bios
+00100000:007fffff pd
diff --git a/layout-stage2.txt b/layout-stage2.txt
@@ -0,0 +1,3 @@
+00000000:00000fff fd
+00001000:00020fff me
+00021000:007fffff bios
diff --git a/mmga b/mmga
@@ -0,0 +1,552 @@
+#!/bin/bash
+#
+# mmga: Make MacBook Great Again
+#
+# Copyright (C) 2019, 2021 Evgeny Zinoviev <me@ch1p.io>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 of
+# the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+set -e
+trap 'onerror ${LINENO}' ERR
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+. $DIR/config.inc
+
+VERSION="0.2"
+WORK_DIR="$DIR/work"
+
+CBOL=$(tput bold)
+CRST=$(tput sgr0)
+CRED=$(tput setaf 1)
+CGRN=$(tput setaf 2)
+CYAN=$(tput setaf 6)
+
+declare -A BOARD_REFS=(
+ [macbookair5_2]="refs/changes/04/32604/33"
+ [macbookpro8_1]="refs/changes/51/33151/3"
+ [macbookpro10_1]="refs/changes/73/32673/30"
+)
+
+onerror() {
+ local parent_lineno="$1"
+ local message="$2"
+ local code="${3:-1}"
+
+ echo "${CRED}${CBOL}caught error on line $parent_lineno${CRST}"
+ if [ ! -z "$message" ]; then
+ echo "${CRED}$message${CRST}"
+ fi
+ echo "exiting with code $code"
+ exit $code
+}
+
+echoerr() {
+ echo "${CRED}${CBOL}ERROR: $@${CRST}" 1>&2
+}
+
+echoinf() {
+ echo "${CGRN}$@${CRST}"
+}
+
+echotitle() {
+ echo "${CGRN}>> ${CBOL}$@${CRST}"
+}
+
+strrepeat() {
+ local str="$1"
+ local n="$2"
+ for i in {1..80}; do
+ echo -n "$str"
+ done
+}
+
+validate_model() {
+ if [ -z "$MODEL" ]; then
+ echoerr "MODEL is not set."
+ exit 1
+ elif [ ! -f "$DIR/config/$MODEL" ]; then
+ echoerr "Model \"$MODEL\" is not supported"
+ exit 1
+ fi
+}
+
+validate_payload() {
+ case "$PAYLOAD" in
+ grub)
+ if [ ! -z "$GRUB_CFG_PATH" ] && [ ! -f "$GRUB_CFG_PATH" ]; then
+ echoerr "GRUB_CFG_PATH: $GRUB_CFG_PATH not found"
+ exit 1
+ fi
+ ;;
+ seabios)
+ ;;
+ *)
+ echoerr "Unknown payload \"$PAYLOAD\""
+ exit 1
+ ;;
+ esac
+}
+
+# $1: config variable name
+# $2: executable path
+validate_executable() {
+ local var="$1"
+ local path="$2"
+ if [ ! -x "$path" ]; then
+ echoerr "$var: \"$path\" not found"
+ exit 1
+ fi
+}
+
+validate_coreboot_path() {
+ if [ -z "$COREBOOT_PATH" ]; then
+ echoerr "COREBOOT_PATH is not set."
+ exit 1
+ elif [ ! -d "$COREBOOT_PATH" ]; then
+ echoerr "COREBOOT_PATH: \"$COREBOOT_PATH\" not found"
+ exit 1
+ fi
+}
+
+# $1: working directory
+# $2: layout file
+# $3: label
+patch_ifd() {
+ local dir="$1"
+ local layout="$2"
+ local label="$3"
+
+ if [ ! -f "$WORK_DIR/oem/dump.bin" ]; then
+ echoerr "Original dump \"$WORK_DIR/oem/dump.bin\" doesn't exists"
+ exit 1
+ fi
+
+ mkdir_new "$dir"
+
+ echotitle "Updating FD layout for $label..."
+ pushd "$WORK_DIR/oem" >/dev/null
+ if [ -f dump.bin.new ]; then
+ rm dump.bin.new
+ fi
+ $IFDTOOL -n "$layout" dump.bin
+ if [ ! -f dump.bin.new ]; then
+ echoerr "dump.bin.new doesn't exists, something's wrong"
+ exit 1
+ fi
+ popd >/dev/null
+
+ echotitle "Extracting FD modules for $label..."
+ pushd "$dir" >/dev/null
+ mv "$WORK_DIR/oem/dump.bin.new" .
+ $IFDTOOL -x dump.bin.new
+ rm flashregion_1_bios.bin
+ popd >/dev/null
+}
+
+prepare_config_stage1() {
+ local file="$WORK_DIR/config"
+ cp "$DIR/config/$MODEL" "$file"
+ config_write_common "$file"
+ echo "CONFIG_IFD_BIN_PATH=\"$WORK_DIR/stage1/flashregion_0_flashdescriptor.bin\"" >> "$file"
+ echo "CONFIG_ME_BIN_PATH=\"$WORK_DIR/stage1/flashregion_2_intel_me.bin\"" >> "$file"
+ echo "CONFIG_COREBOOT_ROMSIZE_KB_1024=y" >> "$file"
+ echo "CONFIG_CBFS_SIZE=0xd0000" >> "$file"
+ config_write_payload "$file"
+}
+
+prepare_config_stage2() {
+ local file="$WORK_DIR/config"
+ cp "$DIR/config/$MODEL" "$file"
+ config_write_common "$file"
+ if [[ "$STAGE2_USE_FULL_ME" == "1" ]]; then
+ # use OEM FD and ME
+ echo "CONFIG_IFD_BIN_PATH=\"$WORK_DIR/oem/flashregion_0_flashdescriptor.bin\"" >> "$file"
+ echo "CONFIG_ME_BIN_PATH=\"$WORK_DIR/oem/flashregion_2_intel_me.bin\"" >> "$file"
+ else
+ echo "CONFIG_IFD_BIN_PATH=\"$WORK_DIR/stage2/flashregion_0_flashdescriptor.bin\"" >> "$file"
+ # use neutered ME from stage1
+ echo "CONFIG_ME_BIN_PATH=\"$WORK_DIR/stage1/flashregion_2_intel_me.bin\"" >> "$file"
+ fi
+ echo "CONFIG_CBFS_SIZE=0x500000" >> "$file"
+ config_write_payload "$file"
+}
+
+config_write_common() {
+ local file="$1"
+ echo "CONFIG_HAVE_IFD_BIN=y" >> "$file"
+ echo "CONFIG_HAVE_ME_BIN=y" >> "$file"
+}
+
+config_write_payload() {
+ local file="$1"
+ if [ -f "$WORK_DIR/$PAYLOAD.elf" ]; then
+ echo "CONFIG_PAYLOAD_ELF=y" >> "$file"
+ echo "CONFIG_PAYLOAD_FILE=\"$WORK_DIR/$PAYLOAD.elf\"" >> "$file"
+ else
+ if [ "$PAYLOAD" == "grub" ]; then
+ echo "CONFIG_PAYLOAD_GRUB2=y" >> "$file"
+ elif [ "$PAYLOAD" == "seabios" ]; then
+ echo "CONFIG_PAYLOAD_SEABIOS=y" >> "$file"
+ fi
+ fi
+}
+
+postbuild_cbfs_add() {
+ if [[ "$PAYLOAD" == "grub" ]]; then
+ echotitle "Adding grub.cfg..."
+ $CBFSTOOL "$COREBOOT_PATH/build/coreboot.rom" add \
+ -t raw -n etc/grub.cfg -f "$(get_grub_cfg)"
+ fi
+}
+
+flash() {
+ local rom="$1"
+ local layout="$2"
+
+ fd_modules=(fd me bios)
+ for m in "${fd_modules[@]}"; do
+ echotitle "Flashing $m..."
+ sudo $FLASHROM $FLASHROM_ARGS \
+ -p internal:laptop=force_I_want_a_brick \
+ -w "$rom" -l "$layout" -i $m -N
+ done
+}
+
+coreboot_check_board() {
+ pushd "$COREBOOT_PATH" >/dev/null
+ if [ ! -d src/mainboard/apple/$MODEL ]; then
+ echoerr "Tree for $MODEL not found. Forgot to run fetch?"
+ exit 1
+ fi
+ popd >/dev/null
+}
+
+get_grub_cfg() {
+ if [ -z "$GRUB_CFG_PATH" ]; then
+ echo "$DIR/grub.cfg"
+ else
+ echo "$GRUB_CFG_PATH"
+ fi
+}
+
+get_stage2_layout() {
+ if [[ "$STAGE2_USE_FULL_ME" == "1" ]]; then
+ echo "$WORK_DIR/oem/layout.txt"
+ else
+ echo "$DIR/layout-stage2.txt"
+ fi
+}
+
+# $1: layout file
+# $2: region name
+get_layout_region() {
+ local layout="$1"
+ local reg="$2"
+ if [ ! -f "$layout" ]; then
+ echoerr "get_layout_region: file $layout doesn't exists"
+ 1
+ fi
+ echo 0x$(cat "$layout" | grep $reg | cut -f 1 -d " " | sed 's/:/-0x/')
+}
+
+mkdir_in() {
+ # mkdir if needed
+ if [ ! -d "$1" ]; then
+ mkdir "$1"
+ fi
+}
+
+mkdir_new() {
+ # rm -rf if needed, then mkdir
+ if [ -d "$1" ]; then
+ rm -rf "$1"
+ fi
+ mkdir "$1"
+}
+
+show_help() {
+ echo "${CBOL}mmga $VERSION: Make MacBook Great Again!${CRST}
+
+This is a tool to help automate coreboot flashing process on
+Apple MacBooks without using external SPI programmer.
+
+This is a free software provided as is WITHOUT ANY WARRANTY. The
+developer(s) are not responsible for bricked laptops, lost data or
+anything else. You take the risk and you are responsible for what
+you do with your MacBook.
+
+Before you start, please read the README twice to understand what
+this tool does.
+
+${CBOL}Usage:${CRST}
+ ${0} <options> ACTION
+
+${CBOL}Options:${CRST}
+ -h, --help: show this help
+
+${CBOL}stage1 actions:${CRST}
+ dump: dump flash content
+ fetch: fetch board tree from Gerrit if needed
+ prepare-stage1: patch IFD, neutralize and truncate ME
+ config-stage1: make coreboot config (for manual use)
+ build-stage1: make config and build ROM (for auto use)
+ flash-stage1: flash ROM (\$COREBOOT_PATH/build/coreboot.rom)
+
+${CBOL}stage2 actions:${CRST}
+ prepare-stage2: patch IFD if needed
+ config-stage2: make coreboot config (for manual use)
+ build-stage2: make config and build ROM (for auto use)
+ flash-stage2: flash ROM (\$COREBOOT_PATH/build/coreboot.rom)
+
+${CBOL}other actions:${CRST}
+ flash-oem: flash OEM firmware back
+"
+}
+
+if [ "$#" -lt 1 ]; then
+ show_help
+ exit
+fi
+
+mkdir_in "$WORK_DIR"
+
+while test $# -gt 0; do
+ case "$1" in
+ -h|--help)
+ show_help
+ exit
+ ;;
+ -*)
+ echoerr "Unrecognized option $1"
+ shift
+ ;;
+ *)
+ ACTION="$1"
+ shift
+ ;;
+ esac
+done
+
+validate_payload
+validate_model
+validate_coreboot_path
+
+VALIDATE=(FLASHROM IFDTOOL CBFSTOOL ME_CLEANER)
+for var in ${VALIDATE[@]}; do
+ validate_executable $var "${!var}"
+done
+
+case "$ACTION" in
+ dump)
+ mkdir_new "$WORK_DIR/oem"
+
+ echotitle "Dumping flash chip..."
+ sudo $FLASHROM $FLASHROM_ARGS \
+ -p internal:laptop=force_I_want_a_brick \
+ -r "$WORK_DIR/oem/dump.bin"
+ echoinf "Successfully saved to ${CBOL}$WORK_DIR/oem/dump.bin${CRST}"
+
+ echotitle "Dumping layout..."
+ pushd "$WORK_DIR/oem" >/dev/null
+ $IFDTOOL -f "$WORK_DIR/oem/layout.txt" "$WORK_DIR/oem/dump.bin"
+ cat "$WORK_DIR/oem/layout.txt"
+
+ echotitle "Extracting OEM modules..."
+ $IFDTOOL -x dump.bin
+ popd >/dev/null
+ ;;
+
+ fetch)
+ if [ ! -d "$COREBOOT_PATH/src/mainboard/apple/$MODEL" ]; then
+ pushd "$COREBOOT_PATH" >/dev/null
+ branch=$(git rev-parse --abbrev-ref HEAD)
+ if [ -z "$BOARD_REFS[$MODEL]" ]; then
+ echoerr "refs for $MODEL not found"
+ exit 1
+ fi
+ if [[ "$branch" != "master" ]]; then
+ echotitle "Switching to master..."
+ git checkout master
+ fi
+ if [ $(git branch --list $MODEL) ]; then
+ echotitle "Branch $MODEL already exists, trying to delete it..."
+ git branch -d $MODEL
+ fi
+ echotitle "Fetching $MODEL..."
+ git fetch "https://review.coreboot.org/coreboot" ${BOARD_REFS[$MODEL]} && git checkout FETCH_HEAD
+ echotitle "Creating branch $MODEL..."
+ git checkout -b $MODEL
+ if [ ! -d "$COREBOOT_PATH/src/mainboard/apple/$MODEL" ]; then
+ echoerr "Tree for $MODEL still doesn't exists, something's wrong"
+ exit 1
+ fi
+ popd >/dev/null
+ echotitle "Done"
+ else
+ echo "Nothing to fetch, you already have tree for $MODEL."
+ fi
+ ;;
+
+ prepare-stage1)
+ patch_ifd "$WORK_DIR/stage1" "$DIR/layout-stage1.txt" stage1
+
+ echotitle "Neutralizing and truncating ME..."
+ $ME_CLEANER -t -r \
+ -O "$WORK_DIR/stage1/flashregion_2_intel_me.bin" \
+ "$WORK_DIR/oem/flashregion_2_intel_me.bin"
+
+ size=$(stat --printf="%s" "$WORK_DIR/stage1/flashregion_2_intel_me.bin")
+ if [ "$size" -gt 131072 ]; then
+ echoerr "Truncated ME is still larger than 128K ($size bytes). This is not OK, do not continue."
+ exit 1
+ fi
+ ;;
+
+ config-stage1)
+ prepare_config_stage1
+ echoinf "Config saved to ${CBOL}$WORK_DIR/config${CRST}"
+ ;;
+
+ build-stage1)
+ prepare_config_stage1
+ coreboot_check_board
+
+ pushd "$COREBOOT_PATH" >/dev/null
+ make distclean
+
+ echotitle "Building coreboot..."
+ cp "$WORK_DIR/config" "$COREBOOT_PATH/.config"
+ make olddefconfig
+ make
+ postbuild_cbfs_add
+
+ echotitle "Extracting $PAYLOAD.elf to reuse it in future..."
+ $CBFSTOOL build/coreboot.rom extract -m x86 -n fallback/payload \
+ -f "$WORK_DIR/$PAYLOAD.elf"
+
+ popd >/dev/null
+ ;;
+
+ flash-stage1)
+ if [ -f "$WORK_DIR/stage1.rom" ]; then
+ rm "$WORK_DIR/stage1.rom"
+ fi
+ cp "$COREBOOT_PATH/build/coreboot.rom" "$WORK_DIR/stage1.rom.tmp"
+ dd if=/dev/zero of="$WORK_DIR/7mb.bin" bs=1024 count=7168 2>/dev/null
+ cat "$WORK_DIR/stage1.rom.tmp" "$WORK_DIR/7mb.bin" > "$WORK_DIR/stage1.rom"
+ rm "$WORK_DIR/stage1.rom.tmp"
+
+ fd_region=$(get_layout_region "$WORK_DIR/oem/layout.txt" fd)
+ me_region=$(get_layout_region "$WORK_DIR/oem/layout.txt" me)
+
+ echo
+ echo "${CBOL}IMPORTANT!${CRST}"
+ echo "Let's check read-only regions again. I will now launch ${CBOL}flashrom -p internal${CRST}:"
+ echo
+ echo "${CYAN}$(strrepeat "-" 80)${CRST}"
+ sudo $FLASHROM $FLASHROM_ARGS -p internal:laptop=force_I_want_a_brick
+ echo "${CYAN}$(strrepeat "-" 80)${CRST}"
+ echo
+ echo "1: FD. If you see that ${CBOL}$fd_region${CRST} region is read-only, DO NOT CONTINUE!"
+ echo
+ echo " a) If you resumed from S3, reboot and try again."
+ echo " b) If the $fd_region region is read-only after cold boot, inform"
+ echo " the developer and DO NOT CONTINUE."
+ echo ""
+ echo "2: ME. If you see that ${CBOL}$me_region${CRST} region is read-only, DO NOT CONTINUE!"
+ echo
+ echo -n "If none of the above is the case, type uppercase yes to flash coreboot: "
+
+ read ans
+ if [[ "$ans" != "YES" ]]; then
+ echo "Exiting."
+ exit
+ fi
+
+ flash "$WORK_DIR/stage1.rom" "$DIR/layout-stage1.txt"
+ echotitle "Done."
+
+ echo
+ echo "If you see three ${CBOL}Verifying flash... VERIFIED.${CRST} lines above, then you're lucky."
+ echo "Now, shutdown the laptop (DO NOT REBOOT, shut it down), wait a few seconds"
+ echo "and turn it on. Then, continue to stage2."
+ echo
+ echo "If you see any errors, DO NOT SHUTDOWN your laptop and DO NOT REBOOT!"
+ echo "Instead, contact the developer(s) and let them help you recover."
+ ;;
+
+ prepare-stage2)
+ if [[ "$STAGE2_USE_FULL_ME" == "0" ]]; then
+ patch_ifd "$WORK_DIR/stage2" "$DIR/layout-stage2.txt" stage2
+ else
+ echo "Nothing to prepare. Continue to config-stage2 or build-stage2."
+ fi
+ ;;
+
+ config-stage2)
+ prepare_config_stage2
+ echoinf "Config saved to ${CBOL}$WORK_DIR/config${CRST}"
+ ;;
+
+ build-stage2)
+ prepare_config_stage2
+ coreboot_check_board
+
+ pushd "$COREBOOT_PATH" >/dev/null
+ make distclean
+
+ echotitle "Building coreboot..."
+ cp "$WORK_DIR/config" "$COREBOOT_PATH/.config"
+ make olddefconfig
+ make
+ postbuild_cbfs_add
+
+ popd >/dev/null
+ ;;
+
+ flash-stage2)
+ flash "$COREBOOT_PATH/build/coreboot.rom" $(get_stage2_layout)
+ echotitle "Done."
+
+ echo
+ echo "Congratulations!"
+ echo
+ echo "Now, shutdown the laptop again (again, DO NOT REBOOT, shut down), wait a few seconds,"
+ echo "power it up and enjoy coreboot."
+ ;;
+
+ flash-oem)
+ if [ ! -f "$WORK_DIR/oem/dump.bin" ]; then
+ echoerr "OEM dump $WORK_DIR/oem/dump.bin not found"
+ exit 1
+ fi
+
+ echo -n "Type uppercase yes to flash OEM firmware: "
+
+ read ans
+ if [[ "$ans" != "YES" ]]; then
+ echo "Exiting."
+ exit
+ fi
+
+ flash "$WORK_DIR/oem/dump.bin" "$WORK_DIR/oem/layout.txt"
+ echotitle "Done."
+
+ echo
+ echo "Now, shutdown the laptop (DO NOT REBOOT, shut down), wait a few seconds,"
+ echo "then power it up again."
+ ;;
+
+ *)
+ echoerr "Unrecognized action $ACTION"
+ exit 1
+ ;;
+esac