blob: f44e0d51c9a62d4082d868aa1f845dc37faeb2b3 [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.
I2C_DEVICES_PATH="/sys/bus/i2c/devices"
# powerd will check this lock file and block device suspension.
POWERD_LOCK_FILE="/run/lock/power_override/touch_updater.lock"
# Get the sysfs paths of all I2C and HID devices that might be a touch device.
get_dev_list() {
# Filter failed glob expansion.
local failed_glob_pattern='/\*$'
# Filter Pixart HID because it also shows up in I2C list.
local pixart_pattern=':'"${PIXART_VENDOR_ID}"':[0-9A-F]{4}\.'
# Filter camera devices to avoid activating them.
# https://issuetracker.google.com/132838666 and
# https://issuetracker.google.com/231502693
local camera_pattern='/i2c-(INT3478|INT3499|INT3537|OVTI5675):'
# Include the psmouse/trackpoint devices in serio bus driver.
find /sys/bus/i2c/devices /sys/bus/hid/devices /sys/bus/serio/devices -mindepth 1 -maxdepth 1 \
| grep --invert-match -E \
"(${failed_glob_pattern}|${pixart_pattern}|${camera_pattern})"
}
# The function will run the cmd and block powerd from suspending the system.
# It's done by creating the lock file while the cmd is running. Remove the lock
# file once it returns.
run_cmd_and_block_powerd() {
trap 'rm -f "${POWERD_LOCK_FILE}"' EXIT
echo $$ > "${POWERD_LOCK_FILE}"
"$@"
rm -f "${POWERD_LOCK_FILE}"
trap - EXIT
}
# Find touchscreen/pad path in /sys/bus/i2c/devices given its device name and a
# list of the sysfs entries it is expect to have (this can help skip over some
# bogus devices that have already been disconnected).
# The required sysfs filenames should be supplied as a space-delimited string.
find_i2c_device_by_name() {
local dev=""
local name_to_find="$1"
local required_sysfs="$2"
for dev in "${I2C_DEVICES_PATH}"/*/name; do
local dev_name="$(cat "${dev}")"
if [ "${name_to_find}" = "${dev_name}" ]; then
local missing_sysfs_entry=0
local path="${dev%/name}"
for sysfs in $required_sysfs; do
if [ ! -e "${path}/${sysfs}" ]; then
missing_sysfs_entry=1
fi
done
if [ "${missing_sysfs_entry}" -eq 0 ]; then
echo "${path}"
return 0
fi
fi
done
return 1
}
find_i2c_hid_device() {
local dev=""
local path=""
local vid="${1%_*}"
local pid="${1#*_}"
local preferred_search_path="${2}"
for dev in ${preferred_search_path} "${I2C_DEVICES_PATH}"/*/; do
local driver_name="$(readlink -f ${dev}/driver | xargs basename)"
case "${driver_name}" in
i2c_hid*)
local hid_path=$(echo ${dev}/*:${vid}:${pid}.*)
if [ -d "${hid_path}" ]; then
local hidraw_sysfs_path=$(echo ${hid_path}/hidraw/hidraw*)
path="/dev/${hidraw_sysfs_path##*/}"
break
fi
;;
esac
done
echo "${path}"
}
# Given a generic name and one or two hardware versions (or product IDs
# depending on the device), determine which firmware symlink in /lib/firmware
# we should try to load. Checks for symlinks in this order:
# <fw_link_name>_<hw_ver1>.<extension>
# <fw_link_name>_<hw_ver2>.<extension>
# <fw_link_name>.<extension>
#
# For example, `find_fw_link_path acme.bin alpha beta` will look for the
# following symlinks, in order:
# /lib/firmware/acme_alpha.bin
# /lib/firmware/acme_beta.bin
# /lib/firmware/acme.bin
#
# It will then output the full path of the first of those which exists.
find_fw_link_path() {
local fw_link_name="$1"
local hw_ver1="$2"
local hw_ver2="$3"
local specific_fw_link_path=""
local specific_fw_link_path2=""
local generic_fw_link_path=""
local fw_link_name_extension="`expr "$fw_link_name" : ".*\(\..*\)"`"
local fw_link_name_base="${fw_link_name%$fw_link_name_extension}"
case ${fw_link_name_base} in
/*) fw_link_path="${fw_link_name_base}" ;;
*) fw_link_path="/lib/firmware/${fw_link_name_base}" ;;
esac
specific_fw_link_path="${fw_link_path}_${hw_ver1}${fw_link_name_extension}"
specific_fw_link_path2="${fw_link_path}_${hw_ver2}${fw_link_name_extension}"
generic_fw_link_path="${fw_link_path}${fw_link_name_extension}"
if [ -e "${specific_fw_link_path}" ]; then
echo "${specific_fw_link_path}"
elif [ -n "${hw_ver2}" ] && [ -e "${specific_fw_link_path2}" ]; then
echo "${specific_fw_link_path2}"
else
echo "${generic_fw_link_path}"
fi
}
standard_update_firmware() {
# Update a touch device by piping a "1" into a sysfs entry called update_fw
# This is a common method for triggering a FW update, and it used by several
# different touch vendors.
local touch_device_path="$1"
local fw_version="$2"
local i=""
local ret=""
for i in $(seq 5); do
printf 1 > "${touch_device_path}/update_fw"
ret=$?
if [ ${ret} -eq 0 ]; then
return 0
fi
log_msg "update_firmware try #${i} failed... retrying."
done
report_update_result "${touch_device_path}" "${REPORT_RESULT_FAILURE}" \
"${fw_version}"
die "Error updating touch firmware. ${ret}"
}
get_active_firmware_version_from_sysfs() {
local sysfs_name="$1"
local touch_device_path="$2"
local fw_version_sysfs_path="${touch_device_path}/${sysfs_name}"
if [ -e "${fw_version_sysfs_path}" ]; then
cat "${fw_version_sysfs_path}"
else
die "No firmware version sysfs at ${fw_version_sysfs_path}."
fi
}
# Unbind and then re-bind the driver for this touch device, to ensure consistent
# state between the device and OS.
rebind_driver() {
local touch_device_path=
local driver_path=
local bus_id=
local ret=
# Required args
# POSIX allows, but does not require, shift to exit a non-interactive shell.
touch_device_path="$1"; [ "$#" -gt 0 ] || return; shift
# Optional: Specify the /sys driver path. This is **REQUIRED** unless your
# touch device is guaranteed to enumerate even with corrupted or erased
# firmware! Without driver_path specified, this function can only determine
# it if the device is currently bound to its driver.
driver_path="$1"; [ "$#" -eq 0 ] || shift
# Fallback for updaters which do not specify the driver_path arg.
if [ -z "$driver_path" ]; then
driver_path="$(readlink -f "${touch_device_path}/driver")"
fi
bus_id="$(basename "${touch_device_path}")"
log_msg "Attempting to re-bind '${bus_id}' to driver '${driver_path}'"
echo "${bus_id}" > "${driver_path}/unbind"
ret="$?"
if [ "$ret" -ne 0 ]; then
log_msg "Unable to unbind the device from the driver, error $ret. This "\
"is sometimes expected when recovering from corrupted firmware. Continuing "\
"with attempt to bind the device to the driver."
fi
echo "${bus_id}" > "${driver_path}/bind"
ret="$?"
if [ "$ret" -ne 0 ]; then
log_msg "Unable to bind the device to the driver."
return 1
fi
log_msg "Device (re)bind to driver success."
}
hex_to_decimal() {
printf "%d" "0x""$1"
}
log_msg() {
local script_filename="$(basename $0)"
logger -t "${script_filename%.*}" --id=$$ "${FLAGS_device} $@"
echo "$@"
}
die() {
log_msg "error: $*"
exit 1
}
# These flags can be used as "update types" to indicate updater state
UPDATE_NEEDED_OUT_OF_DATE="1"
UPDATE_NEEDED_RECOVERY="2"
UPDATE_NOT_NEEDED_UP_TO_DATE="3"
UPDATE_NOT_NEEDED_AHEAD_OF_DATE="4"
log_update_type() {
# Given the update type, print a corresponding message into the logs.
local update_type="$1"
if [ "${update_type}" -eq "${UPDATE_NEEDED_OUT_OF_DATE}" ]; then
log_msg "Update needed, firmware out of date."
elif [ "${update_type}" -eq "${UPDATE_NEEDED_RECOVERY}" ]; then
log_msg "Recovery firmware update. Rolling back."
elif [ "${update_type}" -eq "${UPDATE_NOT_NEEDED_UP_TO_DATE}" ]; then
log_msg "Firmware up to date."
elif [ "${update_type}" -eq "${UPDATE_NOT_NEEDED_AHEAD_OF_DATE}" ]; then
log_msg "Active firmware is ahead of updater, no update needed."
else
log_msg "Unknow update type '${update_type}'."
fi
}
is_update_needed() {
# Given an update type, this function returns a boolean indicating if the
# updater should trigger an update at all.
local update_type="$1"
if [ "${update_type}" -eq "${UPDATE_NEEDED_OUT_OF_DATE}" ] ||
[ "${update_type}" -eq "${UPDATE_NEEDED_RECOVERY}" ]; then
echo ${FLAGS_TRUE}
else
echo ${FLAGS_FALSE}
fi
}
# This supports hexadecimal numbers (0x / 0X prefix) and decimal numbers (no
# prefix). Currently leading zero (0) prefix is treated as decimal for
# compatibility with old updater scripts that might output them.
_canonicalize_ver_num() {
echo "$(( $(echo "$1" | sed 's/^0\+\([^Xx]\)/\1/') ))"
}
compare_multipart_version() {
# Compare firmware versions that are of the form A.B...Y.Z
# The numbers may be hexadecimal (0x / 0X prefix) or decimal (no prefix).
# octal (0 prefix). Currently leading zero (0) prefix is treated as decimal
# for compatibility with old updater scripts that might output them.
#
# To call this function interleave the two version strings by
# importance, starting with the active FW version.
# eg: If your active version is A.B.C and the fw updater is X.Y.Z
# you should call compare_multipart_version A X B Y C Z
local update_type=
local num_parts=
local active_component=
local updater_component=
num_parts="$(($# / 2))"
# Starting with the most significant values, compare them one
# by one until we find a difference or run out of values.
for i in $(seq "$num_parts"); do
active_component="$(_canonicalize_ver_num "$1")"; shift
updater_component="$(_canonicalize_ver_num "$1")"; shift
if [ "${active_component}" -lt "${updater_component}" ]; then
update_type="${UPDATE_NEEDED_OUT_OF_DATE}"
elif [ "${active_component}" -gt "${updater_component}" ]; then
update_type="${UPDATE_NOT_NEEDED_AHEAD_OF_DATE}"
else
continue
fi
break
done
if [ -z "${update_type}" ]; then
update_type="${UPDATE_NOT_NEEDED_UP_TO_DATE}"
elif [ "${FLAGS_recovery}" -eq "${FLAGS_TRUE}" ] &&
[ "${update_type}" = "${UPDATE_NOT_NEEDED_AHEAD_OF_DATE}" ]; then
update_type="${UPDATE_NEEDED_RECOVERY}"
fi
echo "${update_type}"
}
get_chassis_id() {
# Get an identifier of chassis that may need different touchpad firmware on
# devices sharing same image, even same main logic board.
# Note: chassis id is just the model name in uppercase.
cros_config / name | tr '[:lower:]' '[:upper:]'
}
get_platform_ver() {
# Get platform version, since different boards (EVT, DVT etc) may need
# different touchpad firmware on devices sharing same image.
local version="$(mosys platform version)"
echo "${version#rev}"
}
i2c_chardev_present() {
# This function tests to see if there are any i2c char devices on the system.
# It returns 0 iff /dev/i2c-* matches at least one file.
local f=""
for f in /dev/i2c-*; do
if [ -e "${f}" ]; then
return 0
fi
done
return 1
}
check_i2c_chardev_driver() {
# Check to make sure the required drivers are already availible.
if ! i2c_chardev_present; then
modprobe i2c-dev
log_msg "Please compile I2C_CHARDEV into the kernel"
log_msg "Sleeping 15s to wait for them to show up"
# Without this the added delay is small enough that a human might not
# notice that they had just slowed down the boot time by removing the
# driver from the kernel.
sleep 15
fi
}
REPORT_FILE="/run/touch-fw-versions"
# Adds an entry to the report file for a touch device. Update scripts should
# generally use the report_* functions below instead.
# Parameters (in order):
# sysfs_path: the sysfs path for the entry, beginning either /sys/devices or
# /sys/bus/i2c/devices.
# json_fields: a string containing the JSON fields to include in the object,
# without curly braces (e.g. "\"foo\": \"bar\"")
# extra_json_fields: more JSON fields to be added to the object. Optional.
add_report_entry() {
local sysfs_path="$1"
local json_fields="$2"
local extra_json_fields="$3"
case "${sysfs_path}" in
/sys/bus/i2c/devices/*) sysfs_path=$(readlink -f "${sysfs_path}");;
esac
if [ -n "${extra_json_fields}" ]; then
json_fields="${json_fields}, ${extra_json_fields}"
fi
echo "${sysfs_path}" '{'"${json_fields}"'}' >> $REPORT_FILE
}
# Makes an untrusted string safe to include in a JSON object (by removing
# quotes and newlines).
make_json_safe() {
echo "$1" | tr --delete '\n"'
}
# The version value that represents a device being in recovery mode.
REPORT_VERSION_RECOVERY="(recovery)"
# Adds a report entry giving the initial state of a touch device.
# Parameters:
# sysfs_path: a path to the device in sysfs (see add_report_entry).
# updater: a name identifying the updater in use.
# initial_version: the firmware version found on the touch device.
# optional_extra_fields: extra JSON fields to be added to the object. **When
# creating, use make_json_safe on any value from an
# untrusted binary (e.g. an updater).** Optional.
report_initial_version() {
local sysfs_path="$1"
local updater
local initial_version
local optional_extra_fields="$4"
updater="$(make_json_safe "$2")"
initial_version="$(make_json_safe "$3")"
local json_fields="\"updater\": \"${updater}\","
json_fields="${json_fields} \"initial_version\": \"${initial_version}\""
add_report_entry "${sysfs_path}" "${json_fields}" "${optional_extra_fields}"
}
REPORT_RESULT_FAILURE="FAILURE"
REPORT_RESULT_SUCCESS="SUCCESS"
# Adds a report entry describing the result of an update attempt.
# Parameters:
# sysfs_path: a path to the device in sysfs (see add_report_entry).
# update_status: one of $REPORT_RESULT_{FAILURE,SUCCESS}
# flashed_version: the firmware version that the updater attempted to flash
# (whether or not it was successful).
report_update_result() {
local sysfs_path="$1"
local update_status
local flashed_version
update_status="$(make_json_safe "$2")"
flashed_version="$(make_json_safe "$3")"
local json_fields="\"update_status\": \"${update_status}\","
json_fields="${json_fields} \"flashed_version\": \"${flashed_version}\""
add_report_entry "${sysfs_path}" "${json_fields}"
}
# Adds a report entry stating the config version found on a touch device.
# Parameters:
# sysfs_path: a path to the device in sysfs (see add_report_entry).
# initial_config: the config version or hash on the touch device initially.
report_initial_config() {
local sysfs_path="$1"
local initial_config
initial_config="$(make_json_safe "$2")"
add_report_entry "${sysfs_path}" "\"initial_config\": \"${initial_config}\""
}
# Adds a report entry describing the result of a config update attempt.
# Parameters:
# sysfs_path: a path to the device in sysfs (see add_report_entry).
# update_status: one of $REPORT_RESULT_{FAILURE,SUCCESS}
# flashed_config: the config version that the updater attempted to flash
# (whether or not it was successful).
report_config_result() {
local sysfs_path="$1"
local update_status
local flashed_config
update_status="$(make_json_safe "$2")"
flashed_config="$(make_json_safe "$3")"
local extra_fields=""
if [ "${update_status}" = "${REPORT_RESULT_FAILURE}" ]; then
# We only want to override the update_status if the config flash failed (so
# as not to hide firmware update failures with config update successes).
extra_fields="\"update_status\": \"${REPORT_RESULT_FAILURE}\""
fi
add_report_entry "${sysfs_path}" "\"flashed_config\": \"${flashed_config}\"" \
"${extra_fields}"
}