blob: 6bd04e2b2a69e19d796d1ffda60d52a34dd12b37 [file] [log] [blame]
#!/bin/sh -ex
# Copyright (c) 2012 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.
. "$(dirname "$0")/chromeos-common.sh"
. "/opt/google/memento_updater/memento_updater_logging.sh"
. "/opt/google/memento_updater/find_omaha.sh"
# Definition of ChromeOS partition layout
DST_FACTORY_KERNEL_PART=2
DST_FACTORY_PART=3
DST_RELEASE_KERNEL_PART=4
DST_RELEASE_PART=5
DST_OEM_PART=8
DST_EFI_PART=12
DST_STATE_PART=1
# Override this if we need to perform additional commands
COMPLETE_SCRIPT=""
# Override this if we want to install with a board different from installer
BOARD=""
# Override this if we want to install with a different omaha server.
OMAHA="$(findLSBValue CHROMEOS_AUSERVER)"
# Override this with console device for I/O
TTY="/dev/tty1"
# Global variables
DST_DRIVE=""
EC_PRESENT=0
DEVSW_PRESENT=1
# Starting
# Change color in tty1 by ANSI escape sequence code
colorize() {
local code="$1"
case "$code" in
"red" )
code="1;31"
;;
"green" )
code="1;32"
;;
"yellow" )
code="1;33"
;;
esac
printf "\033[%sm" "$code" >/dev/tty1
}
# Error message for any unexpected error.
on_die() {
set +x # prevent extra log in console
colorize "red"
log "
ERROR: Factory installation has been stopped.
Press Ctrl-Alt-F3 (Refresh) to get the detail information."
}
exit_success() {
trap - EXIT
exit 0
}
trap on_die EXIT
die() {
set +x # prevent extra log in console
colorize "red"
log "ERROR: $*"
exit 1
}
clear_fwwp() {
log "Firmware Write Protect disabled, clearing status registers."
if [ $EC_PRESENT -eq 1 ]; then
flashrom -p internal:bus=lpc --wp-disable
fi
flashrom -p internal:bus=spi --wp-disable
log "WP registers should be cleared now"
}
ensure_fwwp_consistency() {
local ec_wp main_wp
if [ $EC_PRESENT -eq 0 ]; then
return
fi
ec_wp="$(flashrom -p internal:bus=lpc --wp-status 2>/dev/null)" || return
main_wp="$(flashrom -p internal:bus=spi --wp-status 2>/dev/null)"
ec_wp="$(echo "$ec_wp" | sed -nr 's/WP.*(disabled|enabled).$/\1/pg')"
main_wp="$(echo "$main_wp" | sed -nr 's/WP.*(disabled|enabled).$/\1/pg')"
if [ "$ec_wp" != "$main_wp" ]; then
die "Inconsist firmware write protection status: main=$main_wp, ec=$ec_wp."\
"Please disable Hardware Write Protection and restart again."
fi
}
check_fwwp() {
flashrom -p internal:bus=spi --wp-status 2>/dev/null |
grep -q "write protect is enabled"
}
clear_tpm() {
log "Clearing TPM"
# Reset TPM. tcsd needs to have not been run because it locks the TPM.
tpmc ppon
tpmc clear
tpmc enable
tpmc activate
local firmware_index="0x1007"
local firmware_struct_version="1"
local firmware_flags="0"
local firmware_fw_versions="1 0 1 0"
local firmware_reserved="0 0 0 0"
tpmc write $firmware_index $firmware_struct_version $firmware_flags \
$firmware_fw_versions $firmware_reserved
local kernel_index="0x1008"
local kernel_struct_version="1"
local kernel_uid="4c 57 52 47"
local kernel_kernel_versions="1 0 1 0"
local kernel_reserved="0 0 0 0"
tpmc write $kernel_index $kernel_struct_version $kernel_uid \
$kernel_kernel_versions $kernel_reserved
log "Done clearing TPM"
}
set_time() {
log "Setting time from:"
# Extract only the server and port.
local time_server_url="$(echo "$OMAHA" | sed "s|/update||; s|http://||")"
log " Server $time_server_url."
local result="$(htpdate -s -t "$time_server_url" 2>&1)"
if ! echo "$result" | grep -Eq "(failed|unavailable)"; then
log "Success, time set to $(date)"
hwclock -w 2>/dev/null
return 0
fi
log "Failed to set time: $(echo "$result" | grep -E "(failed|unavailable)")"
return 1
}
list_ethernet_interface() {
local candidate
local candidates="$(ifconfig | grep 'Link encap:Ethernet' | cut -d ' ' -f 1)"
for candidate in $candidates; do
# output if it is not a wifi interface
if ! iw $candidate info >/dev/null 2>&1; then
echo "$candidate"
fi
done
}
check_ethernet_status() {
local ethernet_interface
for ethernet_interface in $(list_ethernet_interface); do
if ifconfig $ethernet_interface | grep -q "inet addr"; then
log "$(ifconfig $ethernet_interface | grep 'inet addr')"
return 0
fi
done
return 1
}
reset_chromeos_device() {
log "Clearing NVData."
if ! mosys nvram clear; then
# Not every platforms really need this - OK if nvram is not cleared.
log "Warning: NVData not cleared."
fi
if crossystem "mainfw_type?nonchrome"; then
# Non-ChromeOS firmware devices can stop now.
log "Device running Non-ChromeOS firmware."
return
fi
if crossystem "mainfw_type?netboot"; then
log "Device network booted."
return
fi
log "Checking for Firmware Write Protect"
# Check for physical firmware write protect. We'll only
# clear this stuff if the case is open.
if [ "$(crossystem wpsw_cur)" = "0" ]; then
# Clear software firmware write protect.
clear_fwwp
fi
log "Checking if TPM should be cleared"
# To clear TPM, we need both software firmware write protect to be off, and
# boot type as "recovery". Booting with USB in developer mode (Ctrl-U) does
# not work.
local tpm_is_cleared=""
if ! crossystem "mainfw_type?recovery"; then
mainfw_type="$(crossystem mainfw_type)"
log " - System was not booted in recovery mode (current: $mainfw_type)."
elif check_fwwp; then
log " - Firmware write protection is not disabled."
else
if ! clear_tpm; then
die "Failed to clear TPM. Installation is stopped."
else
tpm_is_cleared=True
fi
fi
if [ -z "$tpm_is_cleared" ]; then
colorize "yellow"
log "
WARNING: TPM won't be cleared. To force clearing TPM, ensure firmware write
protection is disabled, hold recovery button and reboot the system again.
"
# Alert for a while
sleep 3
fi
ensure_fwwp_consistency
}
get_dst_drive() {
case "$(crossystem arch)" in
x86 )
DST_DRIVE=/dev/sda
;;
arm )
DST_DRIVE=/dev/mmcblk0
;;
* )
die "Failed to auto detect architecture."
;;
esac
# Prevent writing to removable devices. (rootdev does not work here)
local removable="/sys/block/$(basename $DST_DRIVE)/removable"
if [ -f "$removable" ] && [ "$(cat $removable)" = 1 ]; then
die "Cannot install to a removable device ($DST_DRIVE)."
fi
}
lightup_screen() {
# Light up screen in case you can't see our splash image.
local script="/usr/sbin/lightup_screen"
if [ -x "$script" ]; then
$script
else
log "$script does not exist or not executable"
fi
}
prepare_disk() {
log "Factory Install: Setting partition table"
local gpt_layout="/root/.gpt_layout"
local pmbr_code="/root/.pmbr_code"
local inst_flags="--dst $DST_DRIVE --skip_rootfs --yes"
# In current design, we need both gpt layout and "pmbr code" always generated.
[ -s $gpt_layout ] || die "Missing $gpt_layout; please rebuild image."
[ -r $pmbr_code ] || die "Missing $pmbr_code; please rebuild image."
inst_flags="$inst_flags --gpt_layout $gpt_layout --pmbr_code $pmbr_code"
/usr/sbin/chromeos-install $inst_flags >>"$MEMENTO_AU_LOG" 2>&1
# Informs OS of partition table changes. The autoupdater has trouble with loop
# devices.
log "Reloading partition table changes..."
sync
sleep 3 # Wait for sync to take place.
partprobe "$DST_DRIVE"
log "Done preparing disk"
}
select_board() {
# Prompt the user if USER_SELECT is true.
local user_select="$(findLSBValue USER_SELECT | tr '[:upper:]' '[:lower:]')"
if [ "$user_select" = "true" ]; then
echo -n "Enter the board you want to install (ex: x86-mario): " >"$TTY"
read BOARD <"$TTY"
fi
}
find_var() {
# Check kernel commandline for a specific key value pair.
# Usage: omaha=$(find_var omahaserver)
# Assume values are space separated, keys are unique within the commandline,
# and that keys and values do not contain spaces.
local key="$1"
for item in $(cat /proc/cmdline); do
if echo "$item" | grep -q "$key"; then
echo "$item" | cut -d'=' -f2
return 0
fi
done
return 1
}
override_from_firmware() {
# Check for Omaha URL or Board type from kernel commandline.
# OMAHA and BOARD are override env variables used when calling
# memento_updater.
local omaha=""
if omaha="$(find_var omahaserver)"; then
log " Kernel cmdline OMAHA override to $omaha"
OMAHA="$omaha"
fi
local board=""
if board="$(find_var cros_board)"; then
log " Kernel cmdline BOARD override to $board"
BOARD="$board"
fi
}
override_from_board() {
# Call into any board specific configuration settings we may need.
# These will be provided by chromeos-bsp-[board] build in the private overlay.
local lastboard="$BOARD"
if [ -f "/usr/sbin/board_customize_install.sh" ]; then
. /usr/sbin/board_customize_install.sh
fi
# Let's notice if BOARD has changed and print a message.
if [ "$lastboard" != "$BOARD" ]; then
colorize "red"
log " Private overlay customization BOARD override to $BOARD"
sleep 1
fi
}
overrides() {
override_from_firmware
override_from_board
}
disable_release_partition() {
# Release image is not allowed to boot unless the factory test is passed
# otherwise the wipe and final verification can be skipped.
# TODO(hungte) do this in memento_updater or postinst may be better
if ! cgpt add -i "$DST_RELEASE_KERNEL_PART" -P 0 -T 0 -S 0 "$DST_DRIVE"
then
# Destroy kernels otherwise the system is still bootable.
dst="$(make_partition_dev $DST_DRIVE $DST_RELEASE_KERNEL_PART)"
dd if=/dev/zero of=$dst bs=1M count=1
dst="$(make_partition_dev $DST_DRIVE $DST_FACTORY_KERNEL_PART)"
dd if=/dev/zero of=$dst bs=1M count=1
die "Failed to lock release image. Destroy all kernels."
fi
}
run_postinst() {
local install_dev="$1"
local mount_point="$(mktemp -d)"
local result=0
mount -t ext2 -o ro "$install_dev" "$mount_point"
IS_FACTORY_INSTALL=1 "$mount_point"/postinst \
"$install_dev" >>"$MEMENTO_AU_LOG" 2>&1 || result="$?"
umount "$install_dev" || true
rmdir "$mount_point" || true
return $result
}
run_firmware_update() {
local install_drive="$1"
local install_dev=""
local mount_point="$(mktemp -d)"
local result=0
local updater="$(findLSBValue FACTORY_INSTALL_FIRMWARE)"
local stateful_updater="${updater#/mnt/stateful_partition/}"
local mount_opt=""
# If there's nothing assigned, we should load firmware from release rootfs;
# otherwise follow the assigned location (currently only stateful partition is
# supported).
if [ -z "$updater" ]; then
updater="$mount_point/usr/sbin/chromeos-firmwareupdate"
install_dev="$(make_partition_dev "$install_drive" "$DST_RELEASE_PART")"
mount_opt="-t ext2 -o ro"
elif [ "$updater" != "$stateful_updater" ]; then
updater="$mount_point/$stateful_updater"
install_dev="$(make_partition_dev "$install_drive" "$DST_STATE_PART")"
# Stateful partition may be ext4 and turned off ext2 compatibility mode.
mount_opt="-o ro"
else
die "Unknown firmware updater location: $updater"
fi
log "Running firmware updater from $install_dev ($updater)..."
mount $mount_opt "$install_dev" "$mount_point"
# If write protection is disabled, perform factory (RO+RW) firmware setup;
# otherwise run updater in recovery (RW only) mode.
if ! check_fwwp; then
"$updater" --force --mode=factory_install >>"$MEMENTO_AU_LOG" 2>&1 ||
result="$?"
else
# We need to recover the firmware and then enable developer firmware.
"$updater" --force --mode=recovery >>"$MEMENTO_AU_LOG" 2>&1 || result="$?"
# For two-stop firmware, todev is a simple crossystem call; but for other
# old firmware (alex/zgb), todev will perform flashrom and then reboot.
# So this must be the very end command.
"$updater" --force --mode=todev >>"$MEMENTO_AU_LOG" 2>&1 || result="$?"
fi
umount "$mount_point" || true
rmdir "$mount_point" || true
return $result
}
save_omaha_url() {
log "Save active Omaha server URL to stateful partition"
local stateful_dev="$1"
local mount_point="$(mktemp -d)"
local output_file="$mount_point/dev_image/etc/lsb-factory"
mount "$stateful_dev" "$mount_point"
mkdir -p "$(dirname "$output_file")"
echo "FACTORY_OMAHA_URL=$OMAHA" >> "$output_file"
umount "$mount_point" || true
rmdir "$mount_point" || true
}
factory_on_complete() {
if [ ! -s "$COMPLETE_SCRIPT" ]; then
return 0
fi
log "Executing completion script... ($COMPLETE_SCRIPT)"
if ! sh "$COMPLETE_SCRIPT" "$DST_DRIVE" >"$MEMENTO_AU_LOG" 2>&1; then
die "Failed running completion script $COMPLETE_SCRIPT."
fi
log "Completion script executed successfully."
}
factory_reset() {
# Turn off developer mode on devices without physical developer switch.
if [ $DEVSW_PRESENT -eq 0 ]; then
crossystem disable_dev_request=1
fi
log "Performing factory reset"
if ! /usr/sbin/factory_reset.sh; then
die "Factory reset failed."
fi
log "Done."
# TODO(hungte) shutdown or reboot once we decide the default behavior.
exit_success
}
factory_install_usb() {
local i=""
local src_offset="$(findLSBValue FACTORY_INSTALL_USB_OFFSET)"
local src_drive="$(findLSBValue REAL_USB_DEV)"
# REAL_USB_DEV is optional on systems without initramfs (ex, ARM).
[ -n "$src_drive" ] || src_drive="$(rootdev -s 2>/dev/null)"
[ -n "$src_drive" ] || die "Unknown media source. Please define REAL_USB_DEV."
# Finds the real drive from sd[a-z][0-9]* or mmcblk[0-9]*p[0-9]*
src_drive="${src_drive%[0-9]*}"
src_drive="$(echo $src_drive | sed 's/\(mmcblk[0-9]*\)p/\1/')"
colorize "yellow"
for i in EFI OEM STATE FACTORY FACTORY_KERNEL RELEASE RELEASE_KERNEL; do
# The source media must have exactly the same layout.
local part="$(eval 'echo $DST_'${i}'_PART')"
local src="$(make_partition_dev $src_drive $part)"
local dst="$(make_partition_dev $DST_DRIVE $part)"
# Factory/Release may be shifted on source media.
if [ -n "$src_offset" ]; then
case "$i" in
FACTORY* | RELEASE* )
src="$(make_partition_dev $src_drive $((part + src_offset)) )"
true
;;
esac
fi
# Detect file system size
local dd_param="bs=1M"
local count="$(dumpe2fs -h "$src" 2>/dev/null |
grep "^Block count" |
sed 's/.*: *//')"
if [ -n "$count" ]; then
local bs="$(dumpe2fs -h "$src" 2>/dev/null |
grep "^Block size" |
sed 's/.*: *//')"
# Optimize copy speed, with restriction: bs<10M
while [ "$(( (count > 0) &&
(count % 2 == 0) &&
(bs / 1048576 < 10) ))" = "1" ]; do
count="$((count / 2))"
bs="$((bs * 2))"
done
dd_param="bs=$bs count=$count"
fi
log "Copying: [$i] $src -> $dst ($dd_param)"
# TODO(hungte) Detect copy failure
pv -B 1M "$src" 2>"$TTY" |
dd $dd_param of="$dst" iflag=fullblock oflag=dsync
done
colorize "green"
# Disable release partition and activate factory partition
disable_release_partition
run_postinst "$(make_partition_dev "$DST_DRIVE" "$DST_FACTORY_PART")"
run_firmware_update "$DST_DRIVE"
}
factory_install_omaha() {
local i=""
local result=""
local return_code=""
local dst=""
local dst_arg=""
# Channels defined by memento updater
FACTORY_CHANNEL_ARG='--force_track=factory-channel'
RELEASE_CHANNEL_ARG='--force_track=release-channel'
OEM_CHANNEL_ARG='--force_track=oempartitionimg-channel'
EFI_CHANNEL_ARG='--force_track=efipartitionimg-channel'
STATE_CHANNEL_ARG='--force_track=stateimg-channel'
FIRMWARE_CHANNEL_ARG='--force_track=firmware-channel'
HWID_CHANNEL_ARG='--force_track=hwid-channel'
COMPLETE_CHANNEL_ARG='--force_track=complete-channel'
# Special channels for execution
DST_FIRMWARE_PART="$(mktemp --tmpdir "fw_XXXXXXXX")"
DST_HWID_PART="$(mktemp --tmpdir "hwid_XXXXXXXX")"
DST_COMPLETE_PART="$(mktemp --tmpdir "complete_XXXXXXXX")"
# Install the partitions
for i in EFI OEM STATE RELEASE FACTORY FIRMWARE HWID COMPLETE; do
# DST_*_PART can be a numeric partition number or plain file.
local part="$(eval 'echo $DST_'${i}'_PART')"
if [ -z "$part" ]; then
die "INVALID CHANNEL: $i."
elif echo "$part" | grep -qs "^[0-9]*$"; then
dst="$(make_partition_dev $DST_DRIVE $part)"
dst_arg=""
else
dst="$part"
dst_arg="--nocheck_block_device"
fi
log "Factory Install: Installing $i image to $dst"
local channel_arg="$(eval 'echo $'${i}'_CHANNEL_ARG')"
local kpart="none"
if [ "$i" = "FACTORY" -o "$i" = "RELEASE" ]; then
# Set up kernel partition
kpart=""
fi
local extra_arg="--skip_postinst"
if [ "$i" = "FACTORY" ]; then
# Do postinst after update
extra_arg=""
fi
if [ -n "$BOARD" ]; then
extra_arg="$extra_arg --board=$BOARD"
fi
if [ -n "$OMAHA" ]; then
extra_arg="$extra_arg --omaha_url=$OMAHA"
fi
return_code=0
result="$(IS_FACTORY_INSTALL=1 \
/opt/google/memento_updater/memento_updater.sh \
--dst_partition "$dst" --kernel_partition "$kpart" \
--allow_removable_boot $channel_arg $dst_arg $extra_arg)" ||
return_code="$?"
if [ "$i" = "RELEASE" ]; then
disable_release_partition
fi
# Check the result
if [ "$return_code" != "0" ]; then
# memento update has encountered a fatal error.
die "Factory install of target $dst has failed with error $return_code."
elif [ "$result" != "UPDATED" -a "$i" = "FACTORY" ]; then
# Only updating the primary root/kernel partition is strictly required.
# If the omahaserver is configured to not update others that's fine.
die "AU failed."
fi
done
# Post-processing channels in self-executable file.
if [ -s "$DST_FIRMWARE_PART" ]; then
log "Execute firmware-install script"
dst="$DST_FIRMWARE_PART"
dst_arg="--force --mode=factory_install"
if ! sh "$dst" $dst_arg >"$MEMENTO_AU_LOG" 2>&1; then
die "Firmware updating failed."
fi
fi
if [ -s "$DST_HWID_PART" ]; then
log "Execute HWID component list updater script"
dst="$DST_HWID_PART"
dst_arg="$(make_partition_dev $DST_DRIVE $DST_STATE_PART)"
if ! sh "$dst" "$dst_arg" >"$MEMENTO_AU_LOG" 2>&1; then
die "HWID component list updating failed."
fi
fi
# Update omaha information into stateful partition.
save_omaha_url "$(make_partition_dev $DST_DRIVE $DST_STATE_PART)" \
>"$MEMENTO_AU_LOG" 2>&1 || die "Failed to save OMAHA server information."
if [ -s "$DST_COMPLETE_PART" ]; then
log "Found completion script."
COMPLETE_SCRIPT="$DST_COMPLETE_PART"
fi
}
test_ec_flash_presence() {
# If "flashrom -p internal:bus=lpc --get-size" command succeeds (returns 0),
# then EC flash chip is present in system. Otherwise, assume EC flash is not
# present or supported.
if flashrom -p internal:bus=lpc --get-size 1>&2; then
EC_PRESENT=1
log "EC flash is present"
else
EC_PRESENT=0
log "EC flash is not present or supported"
fi
}
test_devsw_presence() {
local VBSD_HONOR_VIRT_DEV_SWITCH="0x400"
local vdat_flags="$(crossystem vdat_flags || echo 0)"
if [ "$((vdat_flags & VBSD_HONOR_VIRT_DEV_SWITCH))" = "0" ]; then
DEVSW_PRESENT=1
log "Physical developer switch is present"
else
DEVSW_PRESENT=0
log "Using virtual developer switch (no physical switch)"
fi
}
# Prints the two byte hex code of the matched char or
# exists non-zero on timeout. It reads from the current tty.
# To override, call it as follows,
# match_char_timeout args < "$STDOUT"
#
# Arguments: time_in_seconds two_byte_hex_match_1 two_byte_hex_match_2 ...
match_char_timeout() {
local delay_secs="$1"
shift
local input=''
local match=''
local start_time=$(date +%s)
local stop_time=$((start_time + delay_secs))
local tty_config=$(stty -g)
stty raw -echo
while [ $delay_secs -gt 0 ]; do
input=$(timeout -s KILL ${delay_secs}s head -c 1)
[ $? -eq 137 ] && break # Timed out.
input=$(printf "%02x" "'$input")
for char in "$@"; do
if [ "$input" = "$char" ]; then
match="$input"
break
fi
done
[ -n "$match" ] && break
delay_secs=$((stop_time - $(date +%s) ))
done
# Restores the tty's settings.
stty $tty_config
[ -z "$match" ] && return 1
printf "$match"
return 0
}
#
# factory_install.sh implements two operations for assembly line
# operators: install (obviously) and reset.
#
# Somehow the way that operators switch between the two operations
# is by plugging in a Ethernet cable.
#
# The operation is:
# * Install if it is connected to Ethernet;
# * Reset if developer switch is toggled to consumer mode.
#
# So we have to detect a possible ethernet connection here.
#
main() {
if [ "$(id -u)" -ne 0 ]; then
echo "You must run this as root."
exit 1
fi
log "Starting Factory Installer."
lightup_screen
colorize "green"
test_ec_flash_presence
test_devsw_presence
reset_chromeos_device
local install_from_omaha="1"
if [ "$(findLSBValue FACTORY_INSTALL_FROM_USB)" = "1" ]; then
install_from_omaha=""
fi
# Check for any configuration overrides.
overrides
if [ -n "$install_from_omaha" ]; then
colorize "yellow"
log "Waiting for ethernet connectivity to install"
if [ $DEVSW_PRESENT -eq 1 ]; then
log "Or disable developer mode to factory reset."
elif [ $DEVSW_PRESENT -eq 0 ]; then
log "Or press Ctrl-R to factory reset."
fi
while true; do
if check_ethernet_status; then
break
elif [ $DEVSW_PRESENT -eq 1 -a "$(crossystem devsw_cur)" = "0" ]; then
factory_reset
elif [ $DEVSW_PRESENT -eq 0 ]; then
local input=$(match_char_timeout 1 12 < "$TTY")
if [ "$input" = "12" ]; then
factory_reset
fi
else
sleep 1
fi
done
# TODO(hungte) how to set time in RMA?
set_time || die "Please check if the server is configured correctly."
fi
colorize "green"
get_dst_drive
prepare_disk
select_board
if [ -n "$install_from_omaha" ]; then
factory_install_omaha
else
factory_install_usb
fi
log "Factory Installer Complete."
factory_on_complete
# Default action after installation: reboot.
trap - EXIT
sleep 3
shutdown -r now
# sleep indefinitely to avoid re-spawning rather than shutting down
sleep 1d
}
main "$@"