1#!/bin/sh
2#
3# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# This script converts a Chrome OS device to a pre-factory-install state:
8#  * Firmware write protect disabled
9#  * H2O BIOS, with RO VPD area copied from the current BIOS
10#  * Original EC firmware
11#  * Blank SSD (no GPT)
12#
13# Minimal usage:
14#   tofactory.sh -b H2OBIOS.bin -e ec_shellball.sh
15
16SCRIPT_BASE="$(dirname "$0")"
17. "$SCRIPT_BASE/common_minimal.sh"
18load_shflags || exit 1
19
20# Constants used by DEFINE_*
21VBOOT_BASE='/usr/share/vboot'
22
23# DEFINE_string name default_value description flag
24DEFINE_string bios "" "Path of system firmware (BIOS) binary to write" "b"
25DEFINE_string ec "" "Path of EC shellball to execute" "e"
26DEFINE_string backup_dir "" "Path of directory in whoch to store backups" "k"
27DEFINE_string asset_tag "unspecified_tag" \
28  "Asset tag of device, used to name backups" "a"
29DEFINE_string ssd "/dev/sda" "Path to SSD / target drive" "s"
30DEFINE_boolean wipe_ssd $FLAGS_TRUE "Wipe SSD after firmware updates" ""
31DEFINE_boolean nothing $FLAGS_FALSE \
32  "Print commands but do not modify device" "n"
33
34# Parse command line
35FLAGS "$@" || exit 1
36eval set -- "$FLAGS_ARGV"
37
38# Globals
39# ----------------------------------------------------------------------------
40set -e
41
42# Flashrom commands with device overrides
43FLASHROM_BIOS="flashrom -p host"
44FLASHROM_EC="flashrom -p ec"
45
46# A log file to keep the output results of executed command
47EXEC_LOG="$(make_temp_file)"
48
49# Temporary Work directory
50WORK_DIR="$(make_temp_dir)"
51OLD_BIOS="$WORK_DIR/old_bios.bin"
52NEW_BIOS="$WORK_DIR/new_bios.bin"
53
54# Functions
55# ----------------------------------------------------------------------------
56
57# Error message for write protect disable failure, with reminder
58wp_error() {
59  local which_rom=$1
60  shift
61  echo "ERROR: Unable to disable $which_rom write protect: $*" 1>&2
62  echo "Is hardware write protect still enabled?" 1>&2
63  exit 1
64}
65
66# Disable write protect for an EEPROM
67disable_wp() {
68  local which_rom=$1  # EC or BIOS
69  shift
70  local flash_rom="$*"  # Flashrom command to use
71
72  debug_msg "Disabling $which_rom write protect"
73  $NOTHING ${flash_rom} --wp-disable || wp_error "$which_rom" "--wp-disable"
74  $NOTHING ${flash_rom} --wp-range 0 0 || wp_error "$which_rom" "--wp-range"
75
76  # WP status bits should report WP: status: 0x00
77  local wp_status="$(${flash_rom} --wp-status | grep "WP: status:")"
78  if [ "$wp_status" != "WP: status: 0x00" ]; then
79    if [ "$FLAGS_nothing" = $FLAGS_FALSE ]; then
80      wp_error "$which_rom" "$wp_status"
81    fi
82  fi
83}
84
85# Back up current firmware and partition table
86make_backups() {
87  debug_msg "Backing up current firmware to $FLAGS_backup_dir"
88  mkdir -p "$FLAGS_backup_dir"
89  cp "$OLD_BIOS" "$FLAGS_backup_dir/$FLAGS_asset_tag.bios.bin"
90  ${FLASHROM_EC} -r "$FLAGS_backup_dir/$FLAGS_asset_tag.ec.bin"
91
92  # Copy the VPD info from RAM, since we can't extract it as text
93  # from the BIOS binary.  Failure of this is only a warning, since
94  # the information is still in the old BIOS.
95  mosys vpd print all > "$FLAGS_backup_dir/$FLAGS_asset_tag.vpd.txt" ||
96    echo "WARNING: unable to save VPD as text."
97
98  # Copy the first part of the drive, so we can recreate the partition
99  # table.
100  local gpt_backup="$FLAGS_backup_dir/$FLAGS_asset_tag.gpt.bin"
101  debug_msg "Backing up current GPT table."
102  dd if="$FLAGS_ssd" of="$gpt_backup" bs=512 count=34
103
104  # Add a script to restore the BIOS and GPT
105  local restore_script="$FLAGS_backup_dir/$FLAGS_asset_tag.restore.sh"
106  cat >"$restore_script" <<EOF
107#!/bin/sh
108echo "Restoring BIOS"
109${FLASHROM_BIOS} -w "$FLAGS_asset_tag.bios.bin"
110echo "Restoring EC"
111${FLASHROM_EC} -w "$FLAGS_asset_tag.ec.bin"
112echo "Restoring GPT"
113dd of="$FLAGS_ssd" if="$FLAGS_asset_tag.gpt.bin" conv=notrunc
114EOF
115  echo "To restore original BIOS and SSD:"
116  echo "  cd $FLAGS_backup_dir && sh $FLAGS_asset_tag.restore.sh"
117}
118
119# Main
120# ----------------------------------------------------------------------------
121
122main() {
123  # Make sure the files we were passed exist
124  [ -n "$FLAGS_bios" ] || err_die "Please specify a BIOS file (-b bios.bin)"
125  [ -n "$FLAGS_ec" ] || err_die "Please specify an EC updater (-e updater.sh)"
126  ensure_files_exist "$FLAGS_bios" "$FLAGS_ec" || exit 1
127
128  # If --nothing was specified, keep flashrom from writing
129  if [ "$FLAGS_nothing" = $FLAGS_TRUE ]; then
130    NOTHING="echo Not executing: "
131  fi
132
133  # Stop update engine before calling flashrom.  Multiple copies of flashrom
134  # interfere with each other.
135  debug_msg "Stopping update engine"
136  initctl stop update-engine
137
138  # Read the current firmware
139  debug_msg "Reading BIOS from EEPROM"
140  ${FLASHROM_BIOS} -r "$OLD_BIOS"
141
142  # Copy current info to the backup dir, if specified
143  if [ -n "$FLAGS_backup_dir" ]; then
144    make_backups
145  fi
146
147  # Find the RO VPD area in the current firmware
148  local t="$(mosys -k eeprom map "$OLD_BIOS" | grep 'RO VPD')"
149  local vpd_offset="$(echo $t | sed 's/.*area_offset="\([^"]*\)" .*/\1/' )"
150  local vpd_size="$(echo $t | sed 's/.*area_size="\([^"]*\)" .*/\1/' )"
151  debug_msg "Found VPD at offset $vpd_offset size $vpd_size"
152  # Convert offset and size to decimal, since dd doesn't grok hex
153  vpd_offset="$(printf "%d" $vpd_offset)"
154  vpd_size="$(printf "%d" $vpd_size)"
155  debug_msg "In decimal, VPD is at offset $vpd_offset size $vpd_size"
156
157  # Copy the RO VPD from the old firmware to the new firmware
158  debug_msg "Copying VPD from old firmware to new firmware"
159  cp "$FLAGS_bios" "$NEW_BIOS"
160  dd bs=1 seek=$vpd_offset skip=$vpd_offset count=$vpd_size conv=notrunc \
161    if="$OLD_BIOS" of="$NEW_BIOS" || err_die "Unable to copy RO VPD"
162
163  # Disable write protect
164  disable_wp "EC" ${FLASHROM_EC}
165  disable_wp "BIOS" ${FLASHROM_BIOS}
166
167  # Write new firmware
168  debug_msg "Writing EC"
169  # TODO: if EC file ends in .bin, use flashrom to write it directly?
170  $NOTHING sh "$FLAGS_ec" --factory || err_die "Unable to write EC"
171  debug_msg "Writing BIOS"
172  $NOTHING ${FLASHROM_BIOS} -w "$NEW_BIOS" || err_die "Unable to write BIOS"
173
174  # Wipe SSD
175  if [ "$FLAGS_wipe_ssd" = $FLAGS_TRUE ]; then
176    debug_msg "Wiping SSD"
177    $NOTHING cgpt create -z "$FLAGS_ssd" || err_die "Unable to wipe SSD"
178  fi
179
180  # Leave the update engine stopped.  We've mucked with the firmware
181  # and SSD contents, so we shouldn't be attempting an autoupdate this
182  # boot anyway.
183}
184
185main
186