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