blob: ffd1b9e5a9385d2e5758f06705011740653ef882 [file] [log] [blame]
#!/bin/sh
# Copyright (c) 2013 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.
#
# Chrome OS Disk Firmware Update Script
# This script checks whether if the root device needs to be upgraded.
#
. /usr/share/misc/shflags
. /usr/share/misc/chromeos-common.sh
# Temperaray directory to put device information
DEFINE_string 'tmp_dir' '' "Use existing temporary directory."
DEFINE_string 'fw_package_dir' '' "Location of the firmware package."
DEFINE_string 'hdparm' '/sbin/hdparm' "hdparm binary to use."
DEFINE_string 'status' '' "Status file to write to."
DEFINE_boolean 'test' ${FLAGS_FALSE} "For unit testing."
# list global variables
# disk_model
# disk_fw_rev
# disk_fw_file
# disk_exp_fw_rev
# disk_fw_opt
log_msg() {
logger -t "chromeos-disk-firmware-update[${PPID}]" "$@"
echo "$@"
}
die() {
log_msg "error: $*"
exit 1
}
# Parse command line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# disk_fw_select - Select the proper disk firmware to use.
#
# This code reuse old installer disk firmware upgrade code.
#
# inputs:
# disk_rules -- the file containing the list of rules.
# disk_model -- the model from hdparm -I
# disk_fw_rev -- the firmware version of the device.
#
# outputs:
# disk_fw_file -- name of the DISK firmware image file for this machine
# disk_exp_fw_rev -- the revision code of the firmware
# disk_fw_opt -- the options for this update
#
disk_fw_select() {
local disk_rules="$1"
local rule_model
local rule_fw_rev
local rule_exp_fw_rev
local rule_fw_opt
local rule_fw_file
disk_fw_file=""
disk_exp_fw_rev=""
disk_fw_opt=""
# Check for obvious misconfiguration problems:
if [ -z "${disk_rules}" ]; then
log_msg "Warning: disk_rules not specified"
return 1
fi
if [ ! -r "${disk_rules}" ]; then
log_msg "Warning: cannot read config file ${disk_rules}"
return 1
fi
# Read through the config file, looking for matches:
while read -r rule_model rule_fw_rev rule_exp_fw_rev rule_fw_opt rule_fw_file; do
if [ -z "${rule_fw_file}" ]; then
log_msg "${disk_rules}: incorrect number of items in file"
continue
fi
# Check for match:
if [ "${disk_model}" != "${rule_model}" ]; then
continue
fi
if [ "${disk_fw_rev}" != "${rule_fw_rev}" ]; then
continue
fi
disk_exp_fw_rev=${rule_exp_fw_rev}
disk_fw_opt=${rule_fw_opt}
disk_fw_file=${rule_fw_file}
done < "${disk_rules}"
# If we got here, then no DISK firmware matched.
if [ -z "${disk_fw_file}" ]; then
return 1
else
return 0
fi
}
# disk_hdparm_info - Shime for calling hdparm
#
# Useful for testing overide.
#
# inputs:
# device -- the device name [sda,...]
#
# echo the output of hdparm.
#
disk_hdparm_info() {
local device="$1"
# use -I option to be sure the drive is accessed:
# will fail if the drive is not up
# sure that the firmware version is up to date if the
# disk upgrade without reset.
"${FLAGS_hdparm}" -I "/dev/${device}"
}
# disk_info - Retrieve information from hdparm
#
# inputs:
# device -- the device name [sda,...]
#
# outputs:
# disk_model -- name of the DISK firmware image file for this machine
# disk_fw_rev -- the revision code of the firmware
#
# returns non 0 on error
#
disk_info() {
local device="$1"
local rc=0
local hdparm_out="${FLAGS_tmp_dir}/${device}"
disk_model=""
disk_model=""
disk_hdparm_info "${device}" > "${hdparm_out}"
rc=$?
if [ ${rc} -ne 0 ]; then
return ${rc}
fi
if [ ! -s "${hdparm_out}" ]; then
log_msg "hdparm did not produced any output"
return 1
fi
disk_model=$(sed -nre \
'/^\t+Model/s|\t+Model Number: +(.*)|\1|p' "${hdparm_out}" \
| sed -re 's/ +$//' -e 's/[ -]/_/g')
disk_fw_rev=$(sed -nre \
'/^\t+Firmware/s|\t+Firmware Revision: +(.*)|\1|p' "${hdparm_out}" \
| sed -re 's/ +$//' -e 's/[ -]/_/g')
if [ -z "${disk_model}" -o -z "${disk_fw_rev}" ]; then
return 1
fi
return 0
}
# disk_hdparm_upgrade - Upgrade the firmware on the dsik
#
# Update the firmware on the disk.
# TODO(gwendal): We assume the device can be updated in one shot.
# In a future version, we may place a
# a deep charge and reboot the machine.
#
# inputs:
# device -- the device name [sda,...]
# fw_file -- the firmware image
# fw_options -- the options from the rule file.
#
# returns non 0 on error
#
disk_hdparm_upgrade() {
local device="$1"
local fw_file="$2"
local fw_options="$3"
"${FLAGS_hdparm}" --fwdownload-mode7 "${fw_file}" \
--yes-i-know-what-i-am-doing --please-destroy-my-drive \
"/dev/${device}"
}
# disk_upgrade_devices - Look for firmware upgrades
#
# major function: look for a rule match and upgrade.
# updated in one shot. In a future version, we may place a
# a deep charge and reboot the machine.
#
# input:
# list of devices to upgrade.
# retuns 0 on sucess
# The error code of hdparm or other functions that fails
# 120 if no rules is provided
# 121 when the disk works but the firmware was not applied.
#
disk_upgrade_devices() {
local disk_rules="$1"
local device
local fw_file
local success
local disk_old_fw_rev=""
local rc=0
local tries=0
shift # skip disk rules parameters.
for device in "$@"; do
sucess=""
while true; do
disk_info "${device}" # sets disk_model, disk_fw_rev
rc=$?
if [ ${rc} -ne 0 ]; then
log_msg "Can not get info on this device. skip."
break
fi
disk_fw_select "${disk_rules}" # sets disk_fw_file, disk_exp_fw_rev, disk_fw_opt
rc=$?
if [ ${rc} -ne 0 ]; then
# Nothing to do, go to next drive if any.
: ${success:="No need to upgrade ${device}:${disk_model}"}
log_msg "${success}"
rc=0
break
fi
fw_file="${FLAGS_tmp_dir}/${disk_fw_file}"
bzcat "${FLAGS_fw_package_dir}/${disk_fw_file}.bz2" > "${fw_file}" 2> /dev/null
rc=$?
if [ ${rc} -ne 0 ]; then
log_msg "${disk_fw_file} in ${FLAGS_fw_package_dir} could not be extracted: ${rc}"
break
fi
disk_old_fw_rev="${disk_fw_rev}"
disk_hdparm_upgrade "${device}" "${fw_file}" "${disk_fw_opt}"
rc=$?
if [ ${rc} -ne 0 ]; then
# Will change in the future if we need to power cycle, reboot...
log_msg "Unable to upgrade ${device} from ${disk_fw_rev} to ${disk_exp_fw_rev}"
break
else
# Allow the kernel to recover
tries=4
rc=1
# Verify that's the firmware upgrade stuck It may take some time.
while [ ${tries} -ne 0 -a ${rc} -ne 0 ]; do
: $(( tries -= 1 ))
# Allow the error handler to block the scsi queue if it is working.
if [ ${FLAGS_test} -eq ${FLAGS_FALSE} ]; then
sleep 1
fi
disk_info "${device}"
rc=$?
done
if [ ${rc} -ne 0 ]; then
# We are in trouble. The disk was expected to come back but did not.
# TODO(gwendal): Shall we have a preemptive message to ask to
# powercycle?
break
fi
if [ "${disk_exp_fw_rev}" = "${disk_fw_rev}" ]; then
# We are good, go to the next drive if any.
if [ -n "${success}" ]; then
success="${success}
"
fi
success="${success}Upgraded ${device}:${disk_model} from"
success="${success} ${disk_old_fw_rev} to ${disk_fw_rev}"
# Continue, in case we need upgrade in several steps.
continue
else
# The upgrade did not stick, we will retry later.
rc=121
break
fi
fi
done
done
# Leave a trace of a successful run.
if [ ${rc} -eq 0 -a -n "${FLAGS_status}" ]; then
echo ${success} > "${FLAGS_status}"
fi
return ${rc}
}
main() {
local disk_rules_raw="${FLAGS_fw_package_dir}"/rules
local rc=0
local erase_tmp_dir=${FLAGS_FALSE}
if [ ! -d "${FLAGS_tmp_dir}" ]; then
erase_tmp_dir=${FLAGS_TRUE}
FLAGS_tmp_dir=$(mktemp -d)
fi
if [ ! -f "${disk_rules_raw}" ]; then
log_msg "Unable to find rules file in ${FLAGS_fw_package_dir}"
return 120
fi
disk_rules=${FLAGS_tmp_dir}/rules
# remove unnecessary lines
sed '/^#/d;/^[[:space:]]*$/d' "${disk_rules_raw}" > "${disk_rules}"
disk_upgrade_devices "${disk_rules}" $(list_fixed_ata_disks)
rc=$?
if [ ${erase_tmp_dir} -eq ${FLAGS_TRUE} ]; then
rm -rf "${FLAGS_tmp_dir}"
fi
# Append a cksum to prevent multiple calls to this script.
if [ ${rc} -eq 0 -a -n "${FLAGS_status}" ]; then
cksum "${disk_rules_raw}" >> "${FLAGS_status}"
fi
return ${rc}
}
# invoke main if not in test mode, otherwise let the test code call.
if [ ${FLAGS_test} -eq ${FLAGS_FALSE} ]; then
main "$@"
fi