| # 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 |