From 379714eed72e7bd14d352018798b958d90545dd3 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Wed, 12 Jun 2019 00:05:01 +0300 Subject: initial --- .gitignore | 1 + README.md | 202 ++++++++++++++++ config.inc | 35 +++ config/macbookair4_2 | 3 + config/macbookair5_2 | 3 + config/macbookpro10_1 | 3 + config/macbookpro8_1 | 3 + grub.cfg | 166 +++++++++++++ layout-stage1.txt | 4 + layout-stage2.txt | 3 + mmga | 577 ++++++++++++++++++++++++++++++++++++++++++++++ seabios-macbook-fix.patch | 13 ++ squash-commits.sh | 9 + 13 files changed, 1022 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.inc create mode 100644 config/macbookair4_2 create mode 100644 config/macbookair5_2 create mode 100644 config/macbookpro10_1 create mode 100644 config/macbookpro8_1 create mode 100644 grub.cfg create mode 100644 layout-stage1.txt create mode 100644 layout-stage2.txt create mode 100755 mmga create mode 100644 seabios-macbook-fix.patch create mode 100755 squash-commits.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d931c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +work/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd7b635 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# mmga: Make MacBook Great Again +**mmga** is a script to help flashing coreboot on some MacBook Air and Pro models without using external SPI programmer. + +#### Supported models +As of time of writing, following models are supported in coreboot. Other models might be supported in future. +* MacBook Air 4,2 (13'' Mid 2011) (`macbookair4_2`) +* MacBook Air 5,2 (13'' Mid 2012) (`macbookair5_2`) +* MacBook Pro 8,1 (13'' Early 2011) (`macbookpro8_1`) +* MacBook Pro 10,1 (15'' Mid 2012 Retina) (`macbookpro10_1`) + +#### System requirements +* Recent Linux booted with `iomem=relaxed` kernel parameter (needed for internal flashing to work); +* Build dependencies. Here's a list for Ubuntu 16.04: + ``` + # apt install bison build-essential curl flex git gnat libncurses5-dev m4 zlib1g-dev make libpci-dev libusb-1.0-0-dev + ``` + 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 source tree and build it: +``` +$ git clone https://review.coreboot.org/flashrom.git && cd flashrom +$ make +``` +Optionally you can install it to `/usr/local/sbin`: +``` +$ sudo make install +``` + +## How it works +The firmware is stored on SPI chip. On Intel platforms it consists of various regions: `fd` (Flash Descriptor), `me` (Intel ME), `bios` (the BIOS, or, in case of MacBooks, EFI), and some other (such as `gbe` for Gigabit Ethernet). The most important region in context of this story is FD, the Intel Flash Descriptor. + +The **Intel Flash Descriptor** is a data structure stored on the flash chip; it contains information such as space allocated for each region of the flash image (also called a layout), read-write permissions for each region and many more. The Flash Descriptor is located at the **first 4K** of the SPI chip (`0x0000-0x0fff`). + +Here's flash chip layout used in MacBook Air 5,2 (which has 8M flash chip), extracted from original firmware dump with ifdtool: +``` +00000000:00000fff fd +00190000:007fffff bios +00001000:0018ffff me +``` +As you can see, +- the first region (`0x0000-0x0fff`) is used for a Flash Descriptor (as it always is); +- right next to it (`0x1000-0x18ffff`) Intel ME firmware is stored; +- and the last and largest region (`0x190000-0x7fffff`) is the BIOS, or Apple's EFI in our case. + +Normally, the FD should be read-only, but this is not the case with MacBooks. Apparently, Apple's "Think Different" (TM) thing applies to firmware security as well. + +You can check access permissions on your MacBook by running `flashrom -p internal` (if it doesn't work, make sure you have booted with `iomem=relaxed` and/or use `-p internal:laptop=force_I_want_a_brick` instead). This is what it shows on MacBook Air 5,2 with latest Mojave firmware updates: +``` +# flashrom -p internal +flashrom v1.1-rc1-3-g4ca575d on Linux 4.9.0-9-amd64 (x86_64) +flashrom is free software, get the source code at https://flashrom.org + +Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns). +No DMI table found. +Found chipset "Intel QS77". +Enabling flash write... SPI Configuration is locked down. +PR0: Warning: 0x00190000-0x0066ffff is read-only. +PR1: Warning: 0x00692000-0x01ffffff is read-only. +At least some flash regions are write protected. For write operations, +you should use a flash layout and include only writable regions. See +manpage for more details. +OK. +Found Micron/Numonyx/ST flash chip "N25Q064..3E" (8192 kB, SPI) mapped at physical address 0x00000000ff800000. +No operations were specified. +``` +As you can see, only the `bios` region is read-only, and not even whole `bios` region, because `0x670000-0x681fff` is writable for some reason: +``` +PR0: Warning: 0x00190000-0x0066ffff is read-only. +PR1: Warning: 0x00692000-0x01ffffff is read-only. +``` +It's interesting that this behavior is reproducible only after cold boot. If you suspend to S3, resume and run flashrom again, `fd` will be read-only: +``` +PR0: Warning: 0x00000000-0x00000fff is read-only. +PR1: Warning: 0x00190000-0x0066ffff is read-only. +PR2: Warning: 0x00692000-0x01ffffff is read-only. +``` +Looks like a bug in Apple's firmware. Obviously it should always be read-only. + +Anyway, that means that after cold boot **`fd` and `me` regions are writable**, and that gives us around 1.5M of writable space. Since we can rewrite FD, we can write a new FD with custom layout. So **the idea is to repartition the flash chip** and flash new bios to a writable space. + +Let's write a new layout (I decided to use `0x00000-0xfffff` region for convenience): +``` +00000000:00000fff fd +00001000:00020fff me +00021000:000fffff bios +00100000:007fffff pd +``` + + +In this layout, we allocate 128K for `me` and 892K for `bios`. To fit the original 1.5M ME image into the 128K 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 ~92K. We also have to allocate the remaining `0x100000-0x7fffff` region for something to be able to address and flash it in future, otherwise flashrom will give us a "Transaction error". So we just mark it as `pd`, which stands for "Platform Data". + +After the new layout is ready, we build small coreboot ROM that fits into the allocated 892K bios region. We do that, then 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 old firmware, which still resides in `0x190000-0x7fffff`, will be ignored. This is **stage1**. + +After we boot into just-flashed coreboot, we're able to flash the whole 8M chip, because it's not write-protected anymore. 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 shut down 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 dirty work. + +##### Usage: +``` +./mmga 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 +``` + +#### 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 as it's required for graphics initialization to work (libgfxinit is written in Ada): +``` +$ make crossgcc-i386 CPUS=$(nproc) +$ make iasl +``` +Dump the flash chip contents: +``` +./mmga dump +``` +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. Please be aware that `build-stage1` applies SeaBIOS patch and you will have to apply it manually.) + +Flash it: +``` +./mmga flash-stage1 +``` +If it's done and you didn't see any errors, you have to **shutdown** the laptop. It's important: DO NOT REBOOT, shut it down. If you reboot, old FD will still be used and you will boot the Apple's firmware, because it was left untouched in the `0x190000-0x7fffff` region. But since you partly replaced the old `me` region, it may lead to undefined behaviour. So again, do not reboot, shut it down. + +#### stage2 +Create patched FD 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 +``` +If it's done and you again didn't see any errors, you have to **shutdown** the laptop again. DO NOT REBOOT, shut it down. It's even more important now: if you reboot, old FD will be used, the one that describes `bios` region as `0x21000-0xfffff`. And since you just flashed `bios` to `0x21000-0x7fffff`, this `0x21000-0xfffff` will most likely just contain `FF`s, so the laptop won't boot and will look like a brick. In that case you will need to press and hold power button for ~10 seconds to hard reset. To avoid all that, just do not reboot, shut it down. + +## Misc + +The script was tested on MacBook Air 5,2 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). + diff --git a/config.inc b/config.inc new file mode 100644 index 0000000..47091c6 --- /dev/null +++ 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 new file mode 100644 index 0000000..00409b8 --- /dev/null +++ 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 new file mode 100644 index 0000000..6f0da3f --- /dev/null +++ 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 new file mode 100644 index 0000000..37c6778 --- /dev/null +++ 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 new file mode 100644 index 0000000..796da82 --- /dev/null +++ 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 new file mode 100644 index 0000000..ca45ec2 --- /dev/null +++ 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 new file mode 100644 index 0000000..2726ecf --- /dev/null +++ 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 new file mode 100644 index 0000000..7876777 --- /dev/null +++ b/layout-stage2.txt @@ -0,0 +1,3 @@ +00000000:00000fff fd +00001000:00020fff me +00021000:007fffff bios diff --git a/mmga b/mmga new file mode 100755 index 0000000..86ee939 --- /dev/null +++ b/mmga @@ -0,0 +1,577 @@ +#!/bin/bash +# +# mmga: Make MacBook Great Again +# +# Copyright (C) 2019 Evgeny Zinoviev +# +# 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.1" +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/22" + [macbookpro8_1]="refs/changes/51/33151/3" + [macbookpro10_1]="refs/changes/73/32673/22" +) + +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" + echo "CONFIG_SEABIOS_REVISION=y" >> "$file" + echo "CONFIG_SEABIOS_REVISION_ID=\"macbook-fix\"" >> "$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 +} + +patch_seabios() { + if [[ "$PAYLOAD" != "seabios" ]]; then + return + fi + pushd "$COREBOOT_PATH/payloads/external/SeaBIOS" >/dev/null + echotitle "Patching SeaBIOS..." + make seabios + cd seabios + if [ $(git branch --list macbook-fix) ]; then + echotitle "SeaBIOS patch already in place, skipping..." + popd >/dev/null + return + fi + git checkout -b macbook-fix + patch -p1 < "$DIR/seabios-macbook-fix.patch" + git commit -am "Fix MacBook USB keyboard" --no-verify + 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} 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 + + patch_seabios + + 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 + + patch_seabios + + 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 diff --git a/seabios-macbook-fix.patch b/seabios-macbook-fix.patch new file mode 100644 index 0000000..d537501 --- /dev/null +++ b/seabios-macbook-fix.patch @@ -0,0 +1,13 @@ +diff --git a/src/hw/usb-hid.c b/src/hw/usb-hid.c +index fa4d9a2..7d3561c 100644 +--- a/src/hw/usb-hid.c ++++ b/src/hw/usb-hid.c +@@ -59,7 +59,7 @@ usb_kbd_setup(struct usbdevice_s *usbdev + // XXX - this enables the first found keyboard (could be random) + return -1; + +- if (epdesc->wMaxPacketSize != 8) ++ if (epdesc->wMaxPacketSize != 8 && epdesc->wMaxPacketSize != 10) + return -1; + + // Enable "boot" protocol. diff --git a/squash-commits.sh b/squash-commits.sh new file mode 100755 index 0000000..a231ee6 --- /dev/null +++ b/squash-commits.sh @@ -0,0 +1,9 @@ +#!/bin/bash +git checkout --orphan new-master +git add . +git commit -m "$1" +git branch -m master old-master +git branch -m new-master master +git push --force --set-upstream origin master +git branch -D old-master +git push -- cgit v1.2.3