blob: 5f691c02706d37c246ae46dbd27260c9afafcdbb [file] [log] [blame]
# Copyright 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# This script first determines if it needs to run at all: if the cr50 firmware
# image is not present in the local directory this must be happening on a
# board without a cr50 device, no need to do anything.
#
# If the firmware image is present, the script checks the number of previous
# runs saved in a state file. The file name is bound to the firmware image, if
# the firmware image changes, the name of the state file will also have to
# change.
#
# In most cases one firmware update run will be enough, but sometimes more
# than one step will be required (when updating from an old cr50 version or
# when rotating RW keys). The entire chromebook needs to be restarted between
# cr50 update runs, up to four update runs on a particular firmware image are
# allowed by this script.
#
# The gsctool utility exit status indicates if more runs are required. Exit
# status of 0 means update has succeeded. Other exit statuses are processed by
# the follow up startup script cr50-result.conf.
#
description "Chromium OS startup file for cr50 firmware updater"
author "chromium-os-dev@chromium.org"
# Starts on boot-services by exception, since it triggers a long chain of
# dependant tpm-related daemons that need to start early. Normally services
# should start on 'starting system-services'.
start on started boot-services
script
logit() {
logger -t ${UPSTART_JOB} "$*"
}
FACTORY_UTILS="/usr/share/cros/factory_utils.sh"
if [ -f "${FACTORY_UTILS}" ]; then
. "${FACTORY_UTILS}"
if is_factory_test_mode; then
logit "Not running in factory mode"
exit 0
fi
fi
. /usr/share/cros/cr50-get-name.sh
CROSSYSTEM="/usr/bin/crossystem"
STATE_DIR="/var/cache"
UPDATER="/usr/sbin/gsctool"
for f in "${CROSSYSTEM}" "${UPDATER}"; do
if [ ! -f "${f}" ]; then
logit "${f} not found, quitting."
exit 4
fi
done
if "${CROSSYSTEM}" "mainfw_act?recovery"; then
logit "Not running in recovery mode"
exit 0
fi
logit "Starting cr50 update"
VERSIONF="${STATE_DIR}/cr50-version"
CR50_IMAGE="$(cr50_get_name "${UPDATER} -s")"
if [ ! -f "${CR50_IMAGE}" ]; then
logit "${CR50_IMAGE} not found"
exit 4
fi
# Write the current CCD state to a log file so that it can be included
# in feedback reports.
"${UPDATER}" -s -I >"${STATE_DIR}/cr50-ccd" 2>&1
# To keep track of the state of the update process across reboots let's keep
# a counter of boot attempts for the current cr50 image
logit "hashing ${CR50_IMAGE}"
# File to keep state of the update process.
STATEF="${STATE_DIR}/cr50.$(cat "${CR50_IMAGE}" | md5sum | cut -c-10).state"
if [ ! -f "${STATEF}" ]; then
# Must be a new image, get rid of the old state files.
rm -f "${STATE_DIR}"/cr50.*.state
logit "creating new state ${STATEF}"
echo "0" > "${STATEF}"
else
state="$(cat "${STATEF}")"
logit "current state ${state} in ${STATEF}"
case "${state}" in
(3)
logit " not running"
exit 0
;;
([012])
: $(( state += 1 ))
echo "${state}" > "${STATEF}"
;;
(*)
logit "unexpected state ${state}"
echo "0" > "${STATEF}"
;;
esac
fi
# Now 'gsctool -f' output is scanned to determine which kind of cr50 device
# is installed on this system. This is not completely straightforward. A
# typical output looks as follows:
#
# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
# target running protocol version 6
# offsets: backup RO at 0x40000, backup RW at 0x4000
# keyids: RO 0x3716ee6b, RW 0xb93d6539
# Current versions:
# RO 0.0.10
# RW 0.0.9
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#
# but older versions of cr50 firmware do not report the key IDs. So, the
# following cases are possible:
#
# - the RW image is older than 0.0.11. In this case each subsequent
# invocation of gsctool needs to be delayed by at least 5 seconds (to
# return the updater state machine on the device into the idle state).
#
# - the /^Keyid: / string is present in the output AND the RO key ID has bit
# D2 set. This indicates a prod signed cr50 device. If the string is not
# present, or the bit is not set - the cr50 is dev signed.
# Let's always go over /dev/tpm0 here, it won't take long.
exit_status=0
output="$("${UPDATER}" -s -f 2>&1)" || exit_status="$?"
if [ "${exit_status}" = "0" ]; then
logit "version retrieved: ${output}"
else
logit "exit status: ${exit_status}"
logit "output: ${output}"
echo "${output}" > "${VERSIONF}.error"
exit 4 # Make sure there is no reboot.
fi
# File to stash full output of gsctool -f for feedback
echo "${output}" > "${VERSIONF}"
sync
# This makes sure that /dev/tpm0 is used for communications with Cr50, and
# that it does not reboot immediately after an update, but waits for the AP
# to reset the system. When processing the next reset the Cr50 reboots to
# pick up the new image.
usb_update_options="-s -u"
logit "Will run ${UPDATER} ${usb_update_options} ${CR50_IMAGE}"
exit_status=0
output=$("${UPDATER}" ${usb_update_options} "${CR50_IMAGE}" 2>&1) || \
exit_status="$?"
if [ "${exit_status}" = "0" ]; then
# Successful completion means cr50 is up to date, no more update attempts
# are required for this firmware image.
logit "cr50 is running updated firmware"
echo "3" > "${STATEF}"
else
logit "exit status: ${exit_status}"
logit "output: ${output}"
: $(( exit_status += 100 )) # Move to a different return code space.
fi
exit "${exit_status}"
end script