blob: e4b6ad8aeb5b5d73ec6f61b25013fd3e7c294d13 [file] [log] [blame]
#!/bin/sh
# Copyright 2017 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 is run in the factory process, which sets the board id and
# flags properly for cr50.
PLATFORM_INDEX=false
UPDATER="/usr/sbin/gsctool"
BOARD_ID_NVRAM_READER="/usr/share/cros/cr50-read-board-id.sh"
TPM_WRITESPACE="/usr/share/cros/tpm2-write-space.sh"
TPM_LOCKSPACE="/usr/share/cros/tpm2-lock-space.sh"
# The return codes for different failure reasons.
ERR_GENERAL=1
ERR_ALREADY_SET=2
ERR_ALREADY_SET_DIFFERENT=3
ERR_DEVICE_STATE=4
die_as() {
local exit_value="$1"
shift
echo "ERROR: $*"
exit "${exit_value}"
}
die() {
die_as "${ERR_GENERAL}" "$*"
}
char_to_hex() {
printf '%s' "$1" | od -A n -t x1 | sed 's/ //g'
}
hex_eq() {
[ $(printf '%d' "$1") = $(printf '%d' "$2") ]
}
cr50_check_board_id_and_flag() {
local new_board_id="$(char_to_hex $1)"
local new_flag="$2"
# Note that it is supposed to output the same data layout as `gsctool -a -i`.
local output
output="$("${BOARD_ID_NVRAM_READER}")"
local status="$?"
if [ "${status}" != 0 ]; then
die "Failed to read board id."
fi
# Parse the output. E.g., 5a5a4146:a5a5beb9:0000ff00
output="${output##* }"
if [ "${output%:*}" = "ffffffff:ffffffff" ]; then
# Board ID is type cleared, it's ok to go ahead and set it.
return 0
fi
# Check if the board ID has been set differently.
# The first field is the board ID in hex. E.g., 5a5a4146
local board_id="${output%%:*}"
if [ "${board_id}" != "${new_board_id}" ]; then
die_as "${ERR_ALREADY_SET_DIFFERENT}" "Board ID has been set differently."
fi
# Check if the flag has been set differently
# The last field is the flag in hex. E.g., 0000ff00
local flag=0x"${output##*:}"
local desc=""
# The 0x4000 bit is the difference between MP and whitelabel flags. Factory
# scripts can ignore this mismatch if it's the only difference between the set
# board id and the new board id.
if hex_eq "$((flag ^ new_flag))" "0x4000"; then
desc="Whitelabel mismatch."
elif ! hex_eq "${flag}" "${new_flag}"; then
die_as "${ERR_ALREADY_SET_DIFFERENT}" "Flag has been set differently."
fi
die_as "${ERR_ALREADY_SET}" "Board ID and flag have already been set. ${desc}"
}
cr50_set_board_id_and_flag() {
local board_id="$1"
local flag="$2"
local updater_arg="${board_id}:${flag}"
"${UPDATER}" -a -i "${updater_arg}" 2>&1
if [ $? != 0 ]; then
die "Failed to update with ${updater_arg}"
fi
}
reverse_endian() {
local v=$1
echo "${v}" | tac -rs .. | tr -d '\n'
}
generic_tpm2_set_board_id() {
local flag="$2"
local p1
p1="$(char_to_hex "$1")"
# the second 4 bytes are bitwise inverse of the first part.
local p2="0x${p1}"
p2="$(printf '%X' "$(( ~ p2 & 0xFFFFFFFF ))" )"
flag="$(printf '%X' "$(( flag ))" )"
p1="$(reverse_endian "${p1}")"
p2="$(reverse_endian "${p2}")"
flag="$(reverse_endian "${flag}")"
flag="${flag}0000"
local board_id="${p1}${p2}${flag}"
"${TPM_WRITESPACE}" 013FFF00 "${board_id}" || die "Failed to write board id space."
"${TPM_LOCKSPACE}" 013FFF00 || die "Failed to lock board id space."
}
# Check if a string version has a valid format.
# Convert string version representation into ordinal number.
# String version representation is of the form
#
# <epoch>.<major>.<minor>
#
# Where each field is a number
check_version_valid() {
local version="$1"
if ! echo "${version}" | grep -qE "^([0-9]+\.){2}[0-9]+" ; then
die "Wrong version string format: ${version}"
fi
}
# Convert string version representation into ordinal number.
# This function verifies the version format and prints a single number which is
# calculated as
#
# (epoch * 256 + major) * 256 + minor
version_to_ord() {
local version="$1"
local epoch
local major
local minor
local scale=256
check_version_valid "${version}"
epoch="$(echo "${version}" | cut -d '.' -f 1)"
major="$(echo "${version}" | cut -d '.' -f 2)"
minor="$(echo "${version}" | cut -d '.' -f 3)"
echo "$(( (epoch * scale + major) * scale + minor ))"
}
# Check if a string version is a prod image.
# This function verifies the version format and checks if the major version is
# an odd number.
check_version_prod() {
local version="$1"
local major
check_version_valid "${version}"
major="$(echo "${version}" | cut -d '.' -f 2)"
return $(( (major & 1) ^ 1 ))
}
# Exit if cr50 is running an image with a version less than the given prod or
# prepvt version. The arguments are the lowest prod version the DUT should be
# running, the lowest prepvt version the DUT should be running, and a
# description of the feature.
check_cr50_support() {
local target_prod="$1"
local target_prepvt="$2"
local desc="$3"
local output
local exit_status=0
local rw_version
local target
output="$("${UPDATER}" -a -f -M 2>&1)" || exit_status="$?"
if [ "${exit_status}" != "0" ]; then
die "Failed to get the version."
fi
rw_version="$(echo "${output}" | grep RW_FW_VER | cut -d = -f 2)"
if check_version_prod "${rw_version}"; then
target="${target_prod}"
else
target="${target_prepvt}"
fi
if [ "$(version_to_ord "${rw_version}")" -lt \
"$(version_to_ord "${target}")" ]; then
die "Running cr50 ${rw_version}. ${desc} support was added in .${target}."
fi
}
# Only check and set Board ID in normal mode without debug features turned on
# and only if the device has been finalized, as evidenced by the software
# write protect status. In some states scripts should also skip the reboot
# after update. If the SW WP is disabled or the state can not be gotten, skip
# reboot. Use ERR_GENERAL when the board id shouldn't be set. Use the
# ERR_DEVICE_STATE exit status when the reboot and setting the board id should
# be skipped
check_device() {
local exit_status=0
local flash_status=""
flash_status=$(flashrom -p host --wp-status 2>&1) || exit_status="$?"
if [ "${exit_status}" != "0" ]; then
echo "${flash_status}"
exit_status="${ERR_DEVICE_STATE}"
elif ! crossystem 'mainfw_type?normal' 'cros_debug?0'; then
echo "Not running normal image."
exit_status="${ERR_GENERAL}"
elif echo "${flash_status}" | grep -q 'write protect is disabled'; then
echo "write protection is disabled"
exit_status="${ERR_DEVICE_STATE}"
fi
exit "${exit_status}"
}
main() {
local phase=""
local rlz=""
case "$#" in
1)
phase="$1"
;;
2)
phase="$1"
rlz="$2"
;;
*)
die "Usage: $0 phase [board_id]"
esac
local flag=""
case "${phase}" in
"check_device")
# The check_device function will not return
check_device
;;
"whitelabel_pvt_flags")
# Whitelabel flags are set by using 0xffffffff as the rlz and the
# whitelabel flags. Cr50 images that support partial board id will ignore
# the board id type if it's 0xffffffff and only set the flags.
# Partial board id support was added in 0.3.24 and 0.4.24. Before that
# images won't ever ignore the type field. They always set
# board_id_type_inv to ~board_id_type. Trying the whitelabel_flags command
# on these old images would blow the board id type in addition to the
# flags, and prevent setting the RLZ later. Exit here if the image doesn't
# support partial board id.
check_cr50_support "0.3.24" "0.4.24" "partial board id"
rlz="0xffffffff"
flag="0x3f80"
;;
"whitelabel_dev_flags")
# See "whitelabel_pvt_flags" for more details.
check_cr50_support "0.3.24" "0.4.24" "partial board id"
rlz="0xffffffff"
# Per discussion in b/179626571
flag="0x3f7f"
;;
"whitelabel_pvt")
flag="0x3f80"
;;
"whitelabel_dev")
# Per discussion in b/179626571
flag="0x3f7f"
;;
"unknown")
flag="0xff00"
;;
"dev" | "proto"* | "evt"* | "dvt"*)
# Per discussion related in b/67009607 and
# go/cr50-board-id-in-factory#heading=h.7woiaqrgyoe1, 0x8000 is reserved.
flag="0x7f7f"
;;
"mp"* | "pvt"*)
flag="0x7f80"
;;
*)
die "Unknown phase (${phase})"
;;
esac
# To provision board ID, we use RLZ brand code which is a four letter code
# (see full list on go/crosrlz) from cros_config.
if [ -z "${rlz}" ] ; then
rlz="$(cros_config / brand-code)"
if [ $? != 0 ]; then
die "cros_config returned non-zero."
fi
fi
case "${#rlz}" in
0)
die "No RLZ brand code assigned yet."
;;
4)
# Valid RLZ are 4 letters
;;
10)
if ! hex_eq "${rlz}" "0xffffffff"; then
die "Only support erased hex RLZ not ${rlz}."
fi
;;
*)
die "Invalid RLZ brand code (${rlz})."
;;
esac
cr50_check_board_id_and_flag "${rlz}" "${flag}"
if [ "${PLATFORM_INDEX}" = false ]; then
cr50_set_board_id_and_flag "${rlz}" "${flag}"
else
generic_tpm2_set_board_id "${rlz}" "${flag}"
fi
echo "Successfully updated board ID to '${rlz}' with phase '${phase}'."
}
main "$@"