mmga

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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:
A.gitignore | 1+
AREADME.md | 453+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.inc | 35+++++++++++++++++++++++++++++++++++
Aconfig/macbookair4_2 | 3+++
Aconfig/macbookair5_2 | 3+++
Aconfig/macbookpro10_1 | 3+++
Aconfig/macbookpro8_1 | 3+++
Agrub.cfg | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alayout-stage1.txt | 4++++
Alayout-stage2.txt | 3+++
Ammga | 552+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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