| #!/bin/sh |
| # Copyright 2020 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. |
| |
| # Devices with integrated modems do not have on-chip storage, they are fed |
| # their persistent state by the AP. As part of the modem factory flow, a |
| # golden modem file system image (known as the FSG) is created, stored in |
| # the eMMC boot partition, and made read-only. A hash of that image is |
| # permanently blown into fuses. This script runs at boot, checks the FSG |
| # image in the stateful partition against the device fuses, and reloads |
| # the image from the eMMC boot partition upon mismatch. |
| |
| JOB="verify_fsg" |
| RMTFS_DIR=/var/lib/rmtfs/boot |
| FSG_PATH="${RMTFS_DIR}/modem_fsg" |
| FSG_SOURCE="$(echo /dev/mmcblk*boot0)" |
| |
| logit() { |
| logger -t "${JOB}" "$@" |
| } |
| |
| logwarn() { |
| logit -p warn "$*" |
| } |
| |
| logerr() { |
| logit -p error "$*" |
| } |
| |
| # Read the FSG fuse hash, and store the result into |
| # fsg_fuse_hash. |
| read_fuses() { |
| fuse_path="/sys/bus/nvmem/devices/qfprom0/nvmem" |
| fsg_fuse_hash= |
| if [ ! -r "${fuse_path}" ]; then |
| logerr "Fuse driver does not appear to be loaded." |
| return 1 |
| fi |
| |
| fsg_fuse_hash="$(dd if="${fuse_path}" bs=1 skip=$((0x750)) count=32 \ |
| status=none | od -tx1 -vAn | tr -d ' \n')" |
| } |
| |
| # Compare the FSG hash to what's in the fuses. |
| check_fsg_hash() { |
| if [ ! -f "${FSG_PATH}" ]; then |
| return 1 |
| fi |
| |
| read_fuses |
| # If the fuses are unprogrammed, then return failure differently. |
| local z16="0000000000000000" |
| local unprogrammed="${z16}${z16}${z16}${z16}" |
| if [ -z "${fsg_fuse_hash}" ] || \ |
| [ "${fsg_fuse_hash}" = "${unprogrammed}" ]; then |
| return 2 |
| fi |
| |
| # Hash the FSG, and compare against what was burned into the fuses |
| # in the factory. |
| local fsg_hash |
| |
| fsg_hash="$(sha256sum < "${FSG_PATH}" | awk '{print $1}')" |
| if [ "${fsg_hash}" != "${fsg_fuse_hash}" ]; then |
| return 1 |
| fi |
| |
| return 0 |
| } |
| |
| # See if this is an old style eMMC partition, and load the size if so. |
| # Note: This function works by altering the fsg_size variable used by |
| # reload_fsg(). |
| # TODO(evgreen): Delete this after the last P2A build. |
| # There is only expected to be one more P2A build. |
| load_old_fsg_size() { |
| # In the old style format, there's just a size at 0 and the FSG at 512. |
| fsg_size="$(dd if="${FSG_SOURCE}" bs=8 skip=0 count=1 status=none | \ |
| tr -d '\n\0' | grep -a -E '^[0-9]{4,}$')" |
| |
| : "${fsg_size:=0}" |
| } |
| |
| # Read the FSG out of the eMMC boot partition. |
| reload_fsg() { |
| local fsg_header |
| local fsg_size=0 |
| |
| # Get the 3 byte header, which is "FSG". |
| fsg_header="$(dd if="${FSG_SOURCE}" bs=3 skip=0 count=1 status=none)" |
| if [ "${fsg_header}" = "FSG" ]; then |
| fsg_size="$(dd if="${FSG_SOURCE}" bs=1 skip=4 count=8 status=none)" |
| |
| # If the header area is just blank, save an extra read and just bail now. |
| elif [ -z "${fsg_header}" ]; then |
| logit "Blank eMMC boot partition." |
| return 1 |
| else |
| # Ordinarily this would be the place to error out. For this next |
| # build, also check for an "old-style" FSG. |
| load_old_fsg_size |
| fi |
| |
| # Wifi-only SKUs will land here. |
| if [ "${fsg_size}" -eq 0 ]; then |
| logit "No LTE FSG found." |
| return 1 |
| fi |
| |
| logit "Reloading FSG" |
| if [ "${fsg_size}" -gt 4193792 ]; then |
| logwarn "Warning: FSG size invalid. LTE will not work." |
| fi |
| |
| mkdir -p "${RMTFS_DIR}" |
| rm -f "${FSG_PATH}" |
| # Copying a byte at a time is too slow. Copy blocks then bytes. |
| dd if="${FSG_SOURCE}" of="${FSG_PATH}" bs=512 skip=1 \ |
| count="$((fsg_size / 512))" status=none |
| |
| if [ "$((fsg_size % 512))" -ne 0 ]; then |
| local offset="$((fsg_size / 512 * 512))" |
| dd if="${FSG_SOURCE}" of="${FSG_PATH}" bs=1 skip="$((offset + 512))" \ |
| seek="${offset}" count="$((fsg_size % 512))" status=none |
| fi |
| } |
| |
| verify_fsg() { |
| # Check the FSG hash against the fuses. Reload from the eMMC boot |
| # partition if it fails. |
| if check_fsg_hash; then |
| return |
| fi |
| |
| if ! reload_fsg; then |
| # Perhaps this is a wifi-only SKU. |
| return |
| fi |
| |
| check_fsg_hash |
| local retval=$? |
| if [ "${retval}" -eq 0 ]; then |
| # Delete all EFS images in case they got corrupted and that |
| # causes the modem to crash or hang rather than turning to the FSG. |
| # Only do this if the hash now agrees, otherwise developers with |
| # unprogrammed fuses will be constantly fighting this deletion. |
| rm -f "${RMTFS_DIR}"/modem_fs[c12] |
| else |
| # For the first factory run, the fuses are not set. Allow it to |
| # continue with the FSG tarball pre-populated in the boot partition. |
| if [ "${retval}" -eq 2 ]; then |
| logwarn "Fuses are unprogrammed. LTE will need developer assistance." |
| |
| # For other errors, blank out the FSG in case the eMMC boot partition |
| # was compromised. |
| else |
| logerr "FSG hash check failed. LTE will not work" |
| rm -f "${FSG_PATH}" |
| fi |
| fi |
| } |
| |
| main() { |
| if [ "$#" -ne 0 ]; then |
| logerr "$0: Expected no arguments." |
| exit 1 |
| fi |
| |
| verify_fsg |
| } |
| |
| main "$@" |