mmga

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

mmga (15497B)


      1 #!/bin/bash
      2 #
      3 # mmga: Make MacBook Great Again
      4 #
      5 # Copyright (C) 2019, 2021 Evgeny Zinoviev <me@ch1p.io>
      6 #
      7 # This program is free software; you can redistribute it and/or
      8 # modify it under the terms of the GNU General Public License as
      9 # published by the Free Software Foundation; version 2 of
     10 # the License.
     11 #
     12 # This program is distributed in the hope that it will be useful,
     13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 # GNU General Public License for more details.
     16 
     17 set -e
     18 trap 'onerror ${LINENO}' ERR
     19 
     20 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
     21 . $DIR/config.inc
     22 
     23 VERSION="0.2"
     24 WORK_DIR="$DIR/work"
     25 
     26 CBOL=$(tput bold)
     27 CRST=$(tput sgr0)
     28 CRED=$(tput setaf 1)
     29 CGRN=$(tput setaf 2)
     30 CYAN=$(tput setaf 6)
     31 
     32 declare -A BOARD_REFS=(
     33     [macbookair5_2]="refs/changes/04/32604/33"
     34     [macbookpro8_1]="refs/changes/51/33151/3"
     35     [macbookpro10_1]="refs/changes/73/32673/30"
     36 )
     37 
     38 onerror() {
     39     local parent_lineno="$1"
     40     local message="$2"
     41     local code="${3:-1}"
     42 
     43     echo "${CRED}${CBOL}caught error on line $parent_lineno${CRST}"
     44     if [ ! -z "$message" ]; then
     45         echo "${CRED}$message${CRST}"
     46     fi
     47     echo "exiting with code $code"
     48     exit $code
     49 }
     50 
     51 echoerr() {
     52     echo "${CRED}${CBOL}ERROR: $@${CRST}" 1>&2
     53 }
     54 
     55 echoinf() {
     56     echo "${CGRN}$@${CRST}"
     57 }
     58 
     59 echotitle() {
     60     echo "${CGRN}>> ${CBOL}$@${CRST}"
     61 }
     62 
     63 strrepeat() {
     64     local str="$1"
     65     local n="$2"
     66     for i in {1..80}; do
     67         echo -n "$str"
     68     done
     69 }
     70 
     71 validate_model() {
     72     if [ -z "$MODEL" ]; then
     73         echoerr "MODEL is not set."
     74         exit 1
     75     elif [ ! -f "$DIR/config/$MODEL" ]; then
     76         echoerr "Model \"$MODEL\" is not supported"
     77         exit 1
     78     fi
     79 }
     80 
     81 validate_payload() {
     82     case "$PAYLOAD" in
     83         grub)
     84             if [ ! -z "$GRUB_CFG_PATH" ] && [ ! -f "$GRUB_CFG_PATH" ]; then
     85                 echoerr "GRUB_CFG_PATH: $GRUB_CFG_PATH not found"
     86                 exit 1
     87             fi
     88             ;;
     89         seabios)
     90             ;;
     91         *)
     92             echoerr "Unknown payload \"$PAYLOAD\""
     93             exit 1
     94             ;;
     95     esac
     96 }
     97 
     98 # $1: config variable name
     99 # $2: executable path
    100 validate_executable() {
    101     local var="$1"
    102     local path="$2"
    103     if [ ! -x "$path" ]; then
    104         echoerr "$var: \"$path\" not found"
    105         exit 1
    106     fi
    107 }
    108 
    109 validate_coreboot_path() {
    110     if [ -z "$COREBOOT_PATH" ]; then
    111         echoerr "COREBOOT_PATH is not set."
    112         exit 1
    113     elif [ ! -d "$COREBOOT_PATH" ]; then
    114         echoerr "COREBOOT_PATH: \"$COREBOOT_PATH\" not found"
    115         exit 1
    116     fi
    117 }
    118 
    119 # $1: working directory
    120 # $2: layout file
    121 # $3: label
    122 patch_ifd() {
    123     local dir="$1"
    124     local layout="$2"
    125     local label="$3"
    126 
    127     if [ ! -f "$WORK_DIR/oem/dump.bin" ]; then
    128         echoerr "Original dump \"$WORK_DIR/oem/dump.bin\" doesn't exists"
    129         exit 1
    130     fi
    131 
    132     mkdir_new "$dir"
    133 
    134     echotitle "Updating FD layout for $label..."
    135     pushd "$WORK_DIR/oem" >/dev/null
    136     if [ -f dump.bin.new ]; then
    137         rm dump.bin.new
    138     fi
    139     $IFDTOOL -n "$layout" dump.bin
    140     if [ ! -f dump.bin.new ]; then
    141         echoerr "dump.bin.new doesn't exists, something's wrong"
    142         exit 1
    143     fi
    144     popd >/dev/null
    145 
    146     echotitle "Extracting FD modules for $label..."
    147     pushd "$dir" >/dev/null
    148     mv "$WORK_DIR/oem/dump.bin.new" .
    149     $IFDTOOL -x dump.bin.new
    150     rm flashregion_1_bios.bin
    151     popd >/dev/null
    152 }
    153 
    154 prepare_config_stage1() {
    155     local file="$WORK_DIR/config"
    156     cp "$DIR/config/$MODEL" "$file"
    157     config_write_common "$file"
    158     echo "CONFIG_IFD_BIN_PATH=\"$WORK_DIR/stage1/flashregion_0_flashdescriptor.bin\"" >> "$file"
    159     echo "CONFIG_ME_BIN_PATH=\"$WORK_DIR/stage1/flashregion_2_intel_me.bin\"" >> "$file"
    160     echo "CONFIG_COREBOOT_ROMSIZE_KB_1024=y" >> "$file"
    161     echo "CONFIG_CBFS_SIZE=0xd0000" >> "$file"
    162     config_write_payload "$file"
    163 }
    164 
    165 prepare_config_stage2() {
    166     local file="$WORK_DIR/config"
    167     cp "$DIR/config/$MODEL" "$file"
    168     config_write_common "$file"
    169     if [[ "$STAGE2_USE_FULL_ME" == "1" ]]; then
    170         # use OEM FD and ME
    171         echo "CONFIG_IFD_BIN_PATH=\"$WORK_DIR/oem/flashregion_0_flashdescriptor.bin\"" >> "$file"
    172         echo "CONFIG_ME_BIN_PATH=\"$WORK_DIR/oem/flashregion_2_intel_me.bin\"" >> "$file"
    173     else
    174         echo "CONFIG_IFD_BIN_PATH=\"$WORK_DIR/stage2/flashregion_0_flashdescriptor.bin\"" >> "$file"
    175         # use neutered ME from stage1
    176         echo "CONFIG_ME_BIN_PATH=\"$WORK_DIR/stage1/flashregion_2_intel_me.bin\"" >> "$file"
    177     fi
    178     echo "CONFIG_CBFS_SIZE=0x500000" >> "$file"
    179     config_write_payload "$file"
    180 }
    181 
    182 config_write_common() {
    183     local file="$1"
    184     echo "CONFIG_HAVE_IFD_BIN=y" >> "$file"
    185     echo "CONFIG_HAVE_ME_BIN=y" >> "$file"
    186 }
    187 
    188 config_write_payload() {
    189     local file="$1"
    190     if [ -f "$WORK_DIR/$PAYLOAD.elf" ]; then
    191         echo "CONFIG_PAYLOAD_ELF=y" >> "$file"
    192         echo "CONFIG_PAYLOAD_FILE=\"$WORK_DIR/$PAYLOAD.elf\"" >> "$file"
    193     else
    194         if [ "$PAYLOAD" == "grub" ]; then
    195             echo "CONFIG_PAYLOAD_GRUB2=y" >> "$file"
    196         elif [ "$PAYLOAD" == "seabios" ]; then
    197             echo "CONFIG_PAYLOAD_SEABIOS=y" >> "$file"
    198         fi
    199     fi
    200 }
    201 
    202 postbuild_cbfs_add() {
    203     if [[ "$PAYLOAD" == "grub" ]]; then
    204         echotitle "Adding grub.cfg..."
    205         $CBFSTOOL "$COREBOOT_PATH/build/coreboot.rom" add \
    206             -t raw -n etc/grub.cfg -f "$(get_grub_cfg)"
    207     fi
    208 }
    209 
    210 flash() {
    211     local rom="$1"
    212     local layout="$2"
    213 
    214     fd_modules=(fd me bios)
    215     for m in "${fd_modules[@]}"; do
    216         echotitle "Flashing $m..."
    217         sudo $FLASHROM $FLASHROM_ARGS \
    218             -p internal:laptop=force_I_want_a_brick \
    219             -w "$rom" -l "$layout" -i $m -N
    220     done
    221 }
    222 
    223 coreboot_check_board() {
    224     pushd "$COREBOOT_PATH" >/dev/null
    225     if [ ! -d src/mainboard/apple/$MODEL ]; then
    226         echoerr "Tree for $MODEL not found. Forgot to run fetch?"
    227         exit 1
    228     fi
    229     popd >/dev/null
    230 }
    231 
    232 get_grub_cfg() {
    233     if [ -z "$GRUB_CFG_PATH" ]; then
    234         echo "$DIR/grub.cfg"
    235     else
    236         echo "$GRUB_CFG_PATH"
    237     fi
    238 }
    239 
    240 get_stage2_layout() {
    241     if [[ "$STAGE2_USE_FULL_ME" == "1" ]]; then
    242         echo "$WORK_DIR/oem/layout.txt"
    243     else
    244         echo "$DIR/layout-stage2.txt"
    245     fi
    246 }
    247 
    248 # $1: layout file
    249 # $2: region name
    250 get_layout_region() {
    251     local layout="$1"
    252     local reg="$2"
    253     if [ ! -f "$layout" ]; then
    254         echoerr "get_layout_region: file $layout doesn't exists"
    255         1
    256     fi
    257     echo 0x$(cat "$layout" | grep $reg | cut -f 1 -d " " | sed 's/:/-0x/')
    258 }
    259 
    260 mkdir_in() {
    261     # mkdir if needed
    262     if [ ! -d "$1" ]; then
    263         mkdir "$1"
    264     fi
    265 }
    266 
    267 mkdir_new() {
    268     # rm -rf if needed, then mkdir
    269     if [ -d "$1" ]; then
    270         rm -rf "$1"
    271     fi
    272     mkdir "$1"
    273 }
    274 
    275 show_help() {
    276     echo "${CBOL}mmga $VERSION: Make MacBook Great Again!${CRST}
    277 
    278 This is a tool to help automate coreboot flashing process on
    279 Apple MacBooks without using external SPI programmer.
    280 
    281 This is a free software provided as is WITHOUT ANY WARRANTY. The
    282 developer(s) are not responsible for bricked laptops, lost data or
    283 anything else. You take the risk and you are responsible for what
    284 you do with your MacBook.
    285 
    286 Before you start, please read the README twice to understand what
    287 this tool does.
    288 
    289 ${CBOL}Usage:${CRST}
    290     ${0} <options> ACTION
    291 
    292 ${CBOL}Options:${CRST}
    293     -h, --help: show this help
    294 
    295 ${CBOL}stage1 actions:${CRST}
    296               dump: dump flash content
    297              fetch: fetch board tree from Gerrit if needed
    298     prepare-stage1: patch IFD, neutralize and truncate ME
    299      config-stage1: make coreboot config (for manual use)
    300       build-stage1: make config and build ROM (for auto use)
    301       flash-stage1: flash ROM (\$COREBOOT_PATH/build/coreboot.rom)
    302 
    303 ${CBOL}stage2 actions:${CRST}
    304     prepare-stage2: patch IFD if needed
    305      config-stage2: make coreboot config (for manual use)
    306       build-stage2: make config and build ROM (for auto use)
    307       flash-stage2: flash ROM (\$COREBOOT_PATH/build/coreboot.rom)
    308 
    309 ${CBOL}other actions:${CRST}
    310          flash-oem: flash OEM firmware back
    311 "
    312 }
    313 
    314 if [ "$#" -lt 1 ]; then
    315     show_help
    316     exit
    317 fi
    318 
    319 mkdir_in "$WORK_DIR"
    320 
    321 while test $# -gt 0; do
    322     case "$1" in
    323         -h|--help)
    324             show_help
    325             exit
    326             ;;
    327         -*)
    328             echoerr "Unrecognized option $1"
    329             shift
    330             ;;
    331         *)
    332             ACTION="$1"
    333             shift
    334             ;;
    335     esac
    336 done
    337 
    338 validate_payload
    339 validate_model
    340 validate_coreboot_path
    341 
    342 VALIDATE=(FLASHROM IFDTOOL CBFSTOOL ME_CLEANER)
    343 for var in ${VALIDATE[@]}; do
    344     validate_executable $var "${!var}"
    345 done
    346 
    347 case "$ACTION" in
    348     dump)
    349         mkdir_new "$WORK_DIR/oem"
    350 
    351         echotitle "Dumping flash chip..."
    352         sudo $FLASHROM $FLASHROM_ARGS \
    353             -p internal:laptop=force_I_want_a_brick \
    354             -r "$WORK_DIR/oem/dump.bin"
    355         echoinf "Successfully saved to ${CBOL}$WORK_DIR/oem/dump.bin${CRST}"
    356 
    357         echotitle "Dumping layout..."
    358         pushd "$WORK_DIR/oem" >/dev/null
    359         $IFDTOOL -f "$WORK_DIR/oem/layout.txt" "$WORK_DIR/oem/dump.bin"
    360         cat "$WORK_DIR/oem/layout.txt"
    361 
    362         echotitle "Extracting OEM modules..."
    363         $IFDTOOL -x dump.bin
    364         popd >/dev/null
    365         ;;
    366 
    367     fetch)
    368         if [ ! -d "$COREBOOT_PATH/src/mainboard/apple/$MODEL" ]; then
    369             pushd "$COREBOOT_PATH" >/dev/null
    370             branch=$(git rev-parse --abbrev-ref HEAD)
    371             if [ -z "$BOARD_REFS[$MODEL]" ]; then
    372                 echoerr "refs for $MODEL not found"
    373                 exit 1
    374             fi
    375             if [[ "$branch" != "master" ]]; then
    376                 echotitle "Switching to master..."
    377                 git checkout master
    378             fi
    379             if [ $(git branch --list $MODEL) ]; then
    380                 echotitle "Branch $MODEL already exists, trying to delete it..."
    381                 git branch -d $MODEL
    382             fi
    383             echotitle "Fetching $MODEL..."
    384             git fetch "https://review.coreboot.org/coreboot" ${BOARD_REFS[$MODEL]} && git checkout FETCH_HEAD
    385             echotitle "Creating branch $MODEL..."
    386             git checkout -b $MODEL
    387             if [ ! -d "$COREBOOT_PATH/src/mainboard/apple/$MODEL" ]; then
    388                 echoerr "Tree for $MODEL still doesn't exists, something's wrong"
    389                 exit 1
    390             fi
    391             popd >/dev/null
    392             echotitle "Done"
    393         else
    394             echo "Nothing to fetch, you already have tree for $MODEL."
    395         fi
    396         ;;
    397 
    398     prepare-stage1)
    399         patch_ifd "$WORK_DIR/stage1" "$DIR/layout-stage1.txt" stage1
    400 
    401         echotitle "Neutralizing and truncating ME..."
    402         $ME_CLEANER -t -r \
    403             -O "$WORK_DIR/stage1/flashregion_2_intel_me.bin" \
    404             "$WORK_DIR/oem/flashregion_2_intel_me.bin"
    405 
    406         size=$(stat --printf="%s" "$WORK_DIR/stage1/flashregion_2_intel_me.bin")
    407         if [ "$size" -gt 131072 ]; then
    408             echoerr "Truncated ME is still larger than 128K ($size bytes). This is not OK, do not continue."
    409             exit 1
    410         fi
    411         ;;
    412 
    413     config-stage1)
    414         prepare_config_stage1
    415         echoinf "Config saved to ${CBOL}$WORK_DIR/config${CRST}"
    416         ;;
    417 
    418     build-stage1)
    419         prepare_config_stage1
    420         coreboot_check_board
    421 
    422         pushd "$COREBOOT_PATH" >/dev/null
    423         make distclean
    424 
    425         echotitle "Building coreboot..."
    426         cp "$WORK_DIR/config" "$COREBOOT_PATH/.config"
    427         make olddefconfig
    428         make
    429         postbuild_cbfs_add
    430 
    431         echotitle "Extracting $PAYLOAD.elf to reuse it in future..."
    432         $CBFSTOOL build/coreboot.rom extract -m x86 -n fallback/payload \
    433             -f "$WORK_DIR/$PAYLOAD.elf"
    434 
    435         popd >/dev/null
    436         ;;
    437 
    438     flash-stage1)
    439         if [ -f "$WORK_DIR/stage1.rom" ]; then
    440             rm "$WORK_DIR/stage1.rom"
    441         fi
    442         cp "$COREBOOT_PATH/build/coreboot.rom" "$WORK_DIR/stage1.rom.tmp"
    443         dd if=/dev/zero of="$WORK_DIR/7mb.bin" bs=1024 count=7168 2>/dev/null
    444         cat "$WORK_DIR/stage1.rom.tmp" "$WORK_DIR/7mb.bin" > "$WORK_DIR/stage1.rom"
    445         rm "$WORK_DIR/stage1.rom.tmp"
    446 
    447         fd_region=$(get_layout_region "$WORK_DIR/oem/layout.txt" fd)
    448         me_region=$(get_layout_region "$WORK_DIR/oem/layout.txt" me)
    449 
    450         echo
    451         echo "${CBOL}IMPORTANT!${CRST}"
    452         echo "Let's check read-only regions again. I will now launch ${CBOL}flashrom -p internal${CRST}:"
    453         echo
    454         echo "${CYAN}$(strrepeat "-" 80)${CRST}"
    455         sudo $FLASHROM $FLASHROM_ARGS -p internal:laptop=force_I_want_a_brick
    456         echo "${CYAN}$(strrepeat "-" 80)${CRST}"
    457         echo
    458         echo "1: FD. If you see that ${CBOL}$fd_region${CRST} region is read-only, DO NOT CONTINUE!"
    459         echo
    460         echo "    a) If you resumed from S3, reboot and try again."
    461         echo "    b) If the $fd_region region is read-only after cold boot, inform"
    462         echo "       the developer and DO NOT CONTINUE."
    463         echo ""
    464         echo "2: ME. If you see that ${CBOL}$me_region${CRST} region is read-only, DO NOT CONTINUE!"
    465         echo
    466         echo -n "If none of the above is the case, type uppercase yes to flash coreboot: "
    467 
    468         read ans
    469         if [[ "$ans" != "YES" ]]; then
    470             echo "Exiting."
    471             exit
    472         fi
    473 
    474         flash "$WORK_DIR/stage1.rom" "$DIR/layout-stage1.txt"
    475         echotitle "Done."
    476 
    477         echo
    478         echo "If you see three ${CBOL}Verifying flash... VERIFIED.${CRST} lines above, then you're lucky."
    479         echo "Now, shutdown the laptop (DO NOT REBOOT, shut it down), wait a few seconds"
    480         echo "and turn it on. Then, continue to stage2."
    481         echo
    482         echo "If you see any errors, DO NOT SHUTDOWN your laptop and DO NOT REBOOT!"
    483         echo "Instead, contact the developer(s) and let them help you recover."
    484         ;;
    485 
    486     prepare-stage2)
    487         if [[ "$STAGE2_USE_FULL_ME" == "0" ]]; then
    488             patch_ifd "$WORK_DIR/stage2" "$DIR/layout-stage2.txt" stage2
    489         else
    490             echo "Nothing to prepare. Continue to config-stage2 or build-stage2."
    491         fi
    492         ;;
    493 
    494     config-stage2)
    495         prepare_config_stage2
    496         echoinf "Config saved to ${CBOL}$WORK_DIR/config${CRST}"
    497         ;;
    498 
    499     build-stage2)
    500         prepare_config_stage2
    501         coreboot_check_board
    502 
    503         pushd "$COREBOOT_PATH" >/dev/null
    504         make distclean
    505 
    506         echotitle "Building coreboot..."
    507         cp "$WORK_DIR/config" "$COREBOOT_PATH/.config"
    508         make olddefconfig
    509         make
    510         postbuild_cbfs_add
    511 
    512         popd >/dev/null
    513         ;;
    514 
    515     flash-stage2)
    516         flash "$COREBOOT_PATH/build/coreboot.rom" $(get_stage2_layout)
    517         echotitle "Done."
    518 
    519         echo
    520         echo "Congratulations!"
    521         echo
    522         echo "Now, shutdown the laptop again (again, DO NOT REBOOT, shut down), wait a few seconds,"
    523         echo "power it up and enjoy coreboot."
    524         ;;
    525 
    526     flash-oem)
    527         if [ ! -f "$WORK_DIR/oem/dump.bin" ]; then
    528             echoerr "OEM dump $WORK_DIR/oem/dump.bin not found"
    529             exit 1
    530         fi
    531 
    532         echo -n "Type uppercase yes to flash OEM firmware: "
    533 
    534         read ans
    535         if [[ "$ans" != "YES" ]]; then
    536             echo "Exiting."
    537             exit
    538         fi
    539 
    540         flash "$WORK_DIR/oem/dump.bin" "$WORK_DIR/oem/layout.txt"
    541         echotitle "Done."
    542 
    543         echo
    544         echo "Now, shutdown the laptop (DO NOT REBOOT, shut down), wait a few seconds,"
    545         echo "then power it up again."
    546         ;;
    547 
    548     *)
    549         echoerr "Unrecognized action $ACTION"
    550         exit 1
    551         ;;
    552 esac