Logitech firmware updater for PTZPro2
This CL adds generic logitech firmware updater to handle Logitech PTZ
Pro2 camera firmware update.
Refer to CL:583714 for PTZPro2 binary firmwares.
BUG=chromium:750297
TEST=Run logitech-updater --update or /sbin/minijail0 -u
cfm-firmware-updaters -g video -e -i -l -r -n -p -v
-S /usr/share/policy/logitech-updater-seccomp.policy
/usr/sbin/logitech-updater --update
Change-Id: Ie397e74c99c476298f0a380538f49e8313ffd8bb
Reviewed-on: https://chromium-review.googlesource.com/583727
Commit-Ready: Zhongze Hu <frankhu@google.com>
Tested-by: Zhongze Hu <frankhu@google.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
diff --git a/.clang-format b/.clang-format
new file mode 120000
index 0000000..c2257f2
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1 @@
+../../platform2/.clang-format
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9248f4d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+# ignore object files
+*.o
+*.o.depends
+*.pic.*
+*.pie.*
+*.*~
+
+# ignore executable
+/logitech-updater
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..55f376d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017 Logitech. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..ee4f6cc
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+# 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.
+
+include common.mk
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..aeb66c6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+logitech-updater is a utility to upgrade logitech camera firmwares.
+
+## Requirements
+The GNU C/C++ library is required.
+
+## Building
+At the top level of the directory.
+```
+$ make
+```
+Alternatively at Chromium OS development environment,
+```
+$ emerge-${BOARD} logitech-updater
+```
+
+## How to use
+```
+logitech updater currently includes the follow executables: ptzpro2-updater
+$ ptzpro2-updater -h
+```
+
diff --git a/common.mk b/common.mk
new file mode 100644
index 0000000..26c0940
--- /dev/null
+++ b/common.mk
@@ -0,0 +1,953 @@
+# 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.
+#
+# If this file is part of another source distribution, it's license may be
+# stored in LICENSE.makefile or LICENSE.common.mk.
+#
+# NOTE NOTE NOTE
+# The authoritative common.mk is located in:
+# https://chromium.googlesource.com/chromiumos/platform2/+/master/common-mk
+# Please make all changes there, then copy into place in other repos.
+# NOTE NOTE NOTE
+#
+# This file provides a common architecture for building C/C++ source trees.
+# It uses recursive makefile inclusion to create a single make process which
+# can be built in the source tree or with the build artifacts placed elsewhere.
+#
+# It is fully parallelizable for all targets, including static archives.
+#
+# To use:
+# 1. Place common.mk in your top source level
+# 2. In your top-level Makefile, place "include common.mk" at the top
+# 3. In all subdirectories, create a 'module.mk' file that starts with:
+# include common.mk
+# And then contains the remainder of your targets.
+# 4. All build targets should look like:
+# relative/path/target: relative/path/obj.o
+#
+# See existing makefiles for rule examples.
+#
+# Exported macros:
+# - cc_binary, cxx_binary provide standard compilation steps for binaries
+# - cxx_library, cc_library provide standard compilation steps for
+# shared objects.
+# All of the above optionally take an argument for extra flags.
+# - update_archive creates/updates a given .a target
+#
+# Instead of using the build macros, most users can just use wrapped targets:
+# - CXX_BINARY, CC_BINARY, CC_STATIC_BINARY, CXX_STATIC_BINARY
+# - CXX_LIBRARY, CC_LIBRARY, CC_STATIC_LIBRARY, CXX_STATIC_LIBRARY
+# - E.g., CXX_BINARY(mahbinary): foo.o
+# - object.depends targets may be used when a prerequisite is required for an
+# object file. Because object files result in multiple build artifacts to
+# handle PIC and PIE weirdness. E.g.
+# foo.o.depends: generated/dbus.h
+# - TEST(binary) or TEST(CXX_BINARY(binary)) may be used as a prerequisite
+# for the tests target to trigger an automated test run.
+# - CLEAN(file_or_dir) dependency can be added to 'clean'.
+#
+# If source code is being generated, rules will need to be registered for
+# compiling the objects. This can be done by adding one of the following
+# to the Makefile:
+# - For C source files
+# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/b.o,CC,c,CFLAGS))
+# - For C++ source files
+# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/b.o,CXX,cc,CXXFLAGS))
+#
+# Exported targets meant to have prerequisites added to:
+# - all - Your desired targets should be given
+# - tests - Any TEST(test_binary) targets should be given
+# - FORCE - force the given target to run regardless of changes
+# In most cases, using .PHONY is preferred.
+#
+# Possible command line variables:
+# - COLOR=[0|1] to set ANSI color output (default: 1)
+# - VERBOSE=[0|1] to hide/show commands (default: 0)
+# - MODE=[opt|dbg|profiling] (default: opt)
+# opt - Enable optimizations for release builds
+# dbg - Turn down optimization for debugging
+# profiling - Turn off optimization and turn on profiling/coverage
+# support.
+# - ARCH=[x86|arm|supported qemu name] (default: from portage or uname -m)
+# - SPLITDEBUG=[0|1] splits debug info in target.debug (default: 0)
+# If NOSTRIP=1, SPLITDEBUG will never strip the final emitted objects.
+# - NOSTRIP=[0|1] determines if binaries are stripped. (default: 1)
+# NOSTRIP=0 and MODE=opt will also drop -g from the CFLAGS.
+# - VALGRIND=[0|1] runs tests under valgrind (default: 0)
+# - OUT=/path/to/builddir puts all output in given path (default: $PWD)
+# - VALGRIND_ARGS="" supplies extra memcheck arguments
+#
+# Per-target(-ish) variable:
+# - NEEDS_ROOT=[0|1] allows a TEST() target to run with root.
+# Default is 0 unless it is running under QEmu.
+# - NEEDS_MOUNTS=[0|1] allows a TEST() target running on QEmu to get
+# setup mounts in the $(SYSROOT)
+#
+# Caveats:
+# - Directories or files with spaces in them DO NOT get along with GNU Make.
+# If you need them, all uses of dir/notdir/etc will need to have magic
+# wrappers. Proceed at risk to your own sanity.
+# - External CXXFLAGS and CFLAGS should be passed via the environment since
+# this file does not use 'override' to control them.
+# - Our version of GNU Make doesn't seem to support the 'private' variable
+# annotation, so you can't tag a variable private on a wrapping target.
+
+# Behavior configuration variables
+SPLITDEBUG ?= 0
+NOSTRIP ?= 1
+VALGRIND ?= 0
+COLOR ?= 1
+VERBOSE ?= 0
+MODE ?= opt
+CXXEXCEPTIONS ?= 0
+RUN_TESTS ?= 1
+ARCH ?= $(shell uname -m)
+
+# Put objects in a separate tree based on makefile locations
+# This means you can build a tree without touching it:
+# make -C $SRCDIR # will create ./build-$(MODE)
+# Or
+# make -C $SRCDIR OUT=$PWD
+# This variable is extended on subdir calls and doesn't need to be re-called.
+OUT ?= $(PWD)/
+
+# Make OUT now so we can use realpath.
+$(shell mkdir -p "$(OUT)")
+
+# TODO(wad) Relative paths are resolved against SRC and not the calling dir.
+# Ensure a command-line supplied OUT has a slash
+override OUT := $(realpath $(OUT))/
+
+# SRC is not meant to be set by the end user, but during make call relocation.
+# $(PWD) != $(CURDIR) all the time.
+export SRC ?= $(CURDIR)
+
+# If BASE_VER is not set, read the libchrome revision number from
+# common-mk/BASE_VER file.
+ifeq ($(strip $(BASE_VER)),)
+BASE_VER := $(shell cat $(SRC)/../common-mk/BASE_VER)
+endif
+$(info Using BASE_VER=$(BASE_VER))
+
+# Re-start in the $(OUT) directory if we're not there.
+# We may be invoked using -C or bare and we need to ensure behavior
+# is consistent so we check both PWD vs OUT and PWD vs CURDIR.
+override RELOCATE_BUILD := 0
+ifneq (${PWD}/,${OUT})
+override RELOCATE_BUILD := 1
+endif
+# Make sure we're running with no builtin targets. They cause
+# leakage and mayhem!
+ifneq (${PWD},${CURDIR})
+override RELOCATE_BUILD := 1
+# If we're run from the build dir, don't let it get cleaned up later.
+ifeq (${PWD}/,${OUT})
+$(shell touch "$(PWD)/.dont_delete_on_clean")
+endif
+endif # ifneq (${PWD},${CURDIR}
+
+# "Relocate" if we need to restart without implicit rules.
+ifeq ($(subst r,,$(MAKEFLAGS)),$(MAKEFLAGS))
+override RELOCATE_BUILD := 1
+endif
+
+ifeq (${RELOCATE_BUILD},1)
+# By default, silence build output. Reused below as well.
+QUIET = @
+ifeq ($(VERBOSE),1)
+ QUIET=
+endif
+
+# This target will override all targets, including prerequisites. To avoid
+# calling $(MAKE) once per prereq on the given CMDGOAL, we guard it with a local
+# variable.
+RUN_ONCE := 0
+MAKECMDGOALS ?= all
+# Keep the rules split as newer make does not allow them to be declared
+# on the same line. But the way :: rules work, the _all here will also
+# invoke the %:: rule while retaining "_all" as the default.
+_all::
+%::
+ $(if $(filter 0,$(RUN_ONCE)), \
+ cd "$(OUT)" && \
+ $(MAKE) -r -I "$(SRC)" -f "$(CURDIR)/Makefile" \
+ SRC="$(CURDIR)" OUT="$(OUT)" $(foreach g,$(MAKECMDGOALS),"$(g)"),)
+ $(eval RUN_ONCE := 1)
+pass-to-subcall := 1
+endif
+
+ifeq ($(pass-to-subcall),)
+
+# Only call MODULE if we're in a submodule
+MODULES_LIST := $(filter-out Makefile %.d,$(MAKEFILE_LIST))
+ifeq ($(words $(filter-out Makefile common.mk %.d $(SRC)/Makefile \
+ $(SRC)/common.mk,$(MAKEFILE_LIST))),0)
+
+# All the top-level defines outside of module.mk.
+
+#
+# Helper macros
+#
+
+# Create the directory if it doesn't yet exist.
+define auto_mkdir
+ $(if $(wildcard $(dir $1)),$2,$(QUIET)mkdir -p "$(dir $1)")
+endef
+
+# Creates the actual archive with an index.
+# The target $@ must end with .pic.a or .pie.a.
+define update_archive
+ $(call auto_mkdir,$(TARGET_OR_MEMBER))
+ $(QUIET)# Create the archive in one step to avoid parallel use accessing it
+ $(QUIET)# before all the symbols are present.
+ @$(ECHO) "AR $(subst \
+$(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o)) \
+-> $(subst $(SRC)/,,$(TARGET_OR_MEMBER))"
+ $(QUIET)$(AR) rcs $(TARGET_OR_MEMBER) \
+ $(subst $(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o))
+endef
+
+# Default compile from objects using pre-requisites but filters out
+# subdirs and .d files.
+define cc_binary
+ $(call COMPILE_BINARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+define cxx_binary
+ $(call COMPILE_BINARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+# Default compile from objects using pre-requisites but filters out
+# subdirs and .d files.
+define cc_library
+ $(call COMPILE_LIBRARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+define cxx_library
+ $(call COMPILE_LIBRARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS))
+endef
+
+# Deletes files silently if they exist. Meant for use in any local
+# clean targets.
+define silent_rm
+ $(if $(wildcard $(1)),
+ $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANFILE$(COLOR_RESET) ' && \
+ $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \
+ $(RM) $(1) 2>/dev/null) || true,)
+endef
+define silent_rmdir
+ $(if $(wildcard $(1)),
+ $(if $(wildcard $(1)/*),
+ $(QUIET)# $(1) not empty [$(wildcard $(1)/*)]. Not deleting.,
+ $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANDIR$(COLOR_RESET) ' && \
+ $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \
+ $(RMDIR) $(1) 2>/dev/null) || true),)
+endef
+
+#
+# Default variable values
+#
+
+# Only override toolchain vars if they are from make.
+CROSS_COMPILE ?=
+define override_var
+ifneq ($(filter undefined default,$(origin $1)),)
+$1 = $(CROSS_COMPILE)$2
+endif
+endef
+$(eval $(call override_var,AR,ar))
+$(eval $(call override_var,CC,gcc))
+$(eval $(call override_var,CXX,g++))
+$(eval $(call override_var,OBJCOPY,objcopy))
+$(eval $(call override_var,PKG_CONFIG,pkg-config))
+$(eval $(call override_var,RANLIB,ranlib))
+$(eval $(call override_var,STRIP,strip))
+
+RMDIR ?= rmdir
+ECHO = /bin/echo -e
+
+ifeq ($(lastword $(subst /, ,$(CC))),clang)
+CDRIVER = clang
+else
+CDRIVER = gcc
+endif
+
+ifeq ($(lastword $(subst /, ,$(CXX))),clang++)
+CXXDRIVER = clang
+else
+CXXDRIVER = gcc
+endif
+
+# Internal macro to support check_XXX macros below.
+# Usage: $(call check_compile, [code], [compiler], [code_type], [c_flags],
+# [extra_c_flags], [library_flags], [success_ret], [fail_ret])
+# Return: [success_ret] if compile succeeded, otherwise [fail_ret]
+check_compile = $(shell printf '%b\n' $(1) | \
+ $($(2)) $($(4)) -x $(3) $(LDFLAGS) $(5) - $(6) -o /dev/null > /dev/null 2>&1 \
+ && echo "$(7)" || echo "$(8)")
+
+# Helper macro to check whether a test program will compile with the specified
+# compiler flags.
+# Usage: $(call check_compile_cc, [code], [flags], [alternate_flags])
+# Return: [flags] if compile succeeded, otherwise [alternate_flags]
+check_compile_cc = $(call check_compile,$(1),CC,c,CFLAGS,$(2),,$(2),$(3))
+check_compile_cxx = $(call check_compile,$(1),CXX,c++,CXXFLAGS,$(2),,$(2),$(3))
+
+# Helper macro to check whether a test program will compile with the specified
+# libraries.
+# Usage: $(call check_compile_cc, [code], [library_flags], [alternate_flags])
+# Return: [library_flags] if compile succeeded, otherwise [alternate_flags]
+check_libs_cc = $(call check_compile,$(1),CC,c,CFLAGS,,$(2),$(2),$(3))
+check_libs_cxx = $(call check_compile,$(1),CXX,c++,CXXFLAGS,,$(2),$(2),$(3))
+
+# Helper macro to check whether the compiler accepts the specified flags.
+# Usage: $(call check_compile_cc, [flags], [alternate_flags])
+# Return: [flags] if compile succeeded, otherwise [alternate_flags]
+check_cc = $(call check_compile_cc,'int main() { return 0; }',$(1),$(2))
+check_cxx = $(call check_compile_cxx,'int main() { return 0; }',$(1),$(2))
+
+# Choose the stack protector flags based on whats supported by the compiler.
+SSP_CFLAGS := $(call check_cc,-fstack-protector-strong)
+ifeq ($(SSP_CFLAGS),)
+ SSP_CFLAGS := $(call check_cc,-fstack-protector-all)
+endif
+
+# To update these from an including Makefile:
+# CXXFLAGS += -mahflag # Append to the list
+# CXXFLAGS := -mahflag $(CXXFLAGS) # Prepend to the list
+# CXXFLAGS := $(filter-out badflag,$(CXXFLAGS)) # Filter out a value
+# The same goes for CFLAGS.
+COMMON_CFLAGS-gcc := -fvisibility=internal -ggdb3 -Wa,--noexecstack
+COMMON_CFLAGS-clang := -fvisibility=hidden -ggdb
+COMMON_CFLAGS := -Wall -Wunused -Wno-unused-parameter -Werror -Wformat=2 \
+ -fno-strict-aliasing $(SSP_CFLAGS) -O1
+CXXFLAGS += $(COMMON_CFLAGS) $(COMMON_CFLAGS-$(CXXDRIVER)) -std=gnu++11
+CFLAGS += $(COMMON_CFLAGS) $(COMMON_CFLAGS-$(CDRIVER)) -std=gnu11
+CPPFLAGS += -D_FORTIFY_SOURCE=2
+
+# Enable large file support.
+CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+
+# Disable exceptions based on the CXXEXCEPTIONS setting.
+ifeq ($(CXXEXCEPTIONS),0)
+ CXXFLAGS := $(CXXFLAGS) -fno-exceptions -fno-unwind-tables \
+ -fno-asynchronous-unwind-tables
+endif
+
+ifeq ($(MODE),opt)
+ # Up the optimizations.
+ CFLAGS := $(filter-out -O1,$(CFLAGS)) -O2
+ CXXFLAGS := $(filter-out -O1,$(CXXFLAGS)) -O2
+ # Only drop -g* if symbols aren't desired.
+ ifeq ($(NOSTRIP),0)
+ # TODO: do we want -fomit-frame-pointer on x86?
+ CFLAGS := $(filter-out -ggdb3,$(CFLAGS))
+ CXXFLAGS := $(filter-out -ggdb3,$(CXXFLAGS))
+ endif
+endif
+
+ifeq ($(MODE),profiling)
+ CFLAGS := $(CFLAGS) -O0 -g --coverage
+ CXXFLAGS := $(CXXFLAGS) -O0 -g --coverage
+ LDFLAGS := $(LDFLAGS) --coverage
+endif
+
+LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now
+
+# Fancy helpers for color if a prompt is defined
+ifeq ($(COLOR),1)
+COLOR_RESET = \x1b[0m
+COLOR_GREEN = \x1b[32;01m
+COLOR_RED = \x1b[31;01m
+COLOR_YELLOW = \x1b[33;01m
+endif
+
+# By default, silence build output.
+QUIET = @
+ifeq ($(VERBOSE),1)
+ QUIET=
+endif
+
+#
+# Implementation macros for compile helpers above
+#
+
+# Useful for dealing with pie-broken toolchains.
+# Call make with PIE=0 to disable default PIE use.
+OBJ_PIE_FLAG = -fPIE
+COMPILE_PIE_FLAG = -pie
+ifeq ($(PIE),0)
+ OBJ_PIE_FLAG =
+ COMPILE_PIE_FLAG =
+endif
+
+# Favor member targets first for CXX_BINARY(%) magic.
+# And strip out nested members if possible.
+LP := (
+RP := )
+TARGET_OR_MEMBER = $(lastword $(subst $(LP), ,$(subst $(RP),,$(or $%,$@))))
+
+# Default compile from objects using pre-requisites but filters out
+# all non-.o files.
+define COMPILE_BINARY_implementation
+ @$(ECHO) "LD$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))"
+ $(call auto_mkdir,$(TARGET_OR_MEMBER))
+ $(QUIET)$($(1)) $(COMPILE_PIE_FLAGS) -o $(TARGET_OR_MEMBER) \
+ $(2) $(LDFLAGS) \
+ $(filter %.o %.a,$(^:.o=.pie.o)) \
+ $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \
+ $(LDLIBS)
+ $(call conditional_strip)
+ @$(ECHO) -n "BIN "
+ @$(ECHO) "$(COLOR_GREEN)$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)"
+ @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)"
+endef
+
+# TODO: add version support extracted from PV environment variable
+#ifeq ($(PV),9999)
+#$(warning PV=$(PV). If shared object versions matter, please force PV=.)
+#endif
+# Then add -Wl,-soname,$@.$(PV) ?
+
+# Default compile from objects using pre-requisites but filters out
+# all non-.o values. (Remember to add -L$(OUT) -llib)
+COMMA := ,
+define COMPILE_LIBRARY_implementation
+ @$(ECHO) "SHARED$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))"
+ $(call auto_mkdir,$(TARGET_OR_MEMBER))
+ $(QUIET)$($(1)) -shared -Wl,-E -o $(TARGET_OR_MEMBER) \
+ $(2) $(LDFLAGS) \
+ $(if $(filter %.a,$^),-Wl$(COMMA)--whole-archive,) \
+ $(filter %.o ,$(^:.o=.pic.o)) \
+ $(foreach a,$(filter %.a,$^),-L$(dir $(a)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(a))))) \
+ $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \
+ -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \
+ $(LDLIBS)
+ $(call conditional_strip)
+ @$(ECHO) -n "LIB $(COLOR_GREEN)"
+ @$(ECHO) "$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)"
+ @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)"
+endef
+
+define conditional_strip
+ $(if $(filter 0,$(NOSTRIP)),$(call strip_artifact))
+endef
+
+define strip_artifact
+ @$(ECHO) "STRIP $(subst $(OUT)/,,$(TARGET_OR_MEMBER))"
+ $(if $(filter 1,$(SPLITDEBUG)), @$(ECHO) -n "DEBUG "; \
+ $(ECHO) "$(COLOR_YELLOW)\
+$(subst $(OUT)/,,$(TARGET_OR_MEMBER)).debug$(COLOR_RESET)")
+ $(if $(filter 1,$(SPLITDEBUG)), \
+ $(QUIET)$(OBJCOPY) --only-keep-debug "$(TARGET_OR_MEMBER)" \
+ "$(TARGET_OR_MEMBER).debug")
+ $(if $(filter-out dbg,$(MODE)),$(QUIET)$(STRIP) --strip-unneeded \
+ "$(TARGET_OR_MEMBER)",)
+endef
+
+#
+# Global pattern rules
+#
+
+# Below, the archive member syntax is abused to create fancier
+# syntactic sugar for recipe authors that avoids needed to know
+# subcall options. The downside is that make attempts to look
+# into the phony archives for timestamps. This will cause the final
+# target to be rebuilt/linked on _every_ call to make even when nothing
+# has changed. Until a better way presents itself, we have helpers that
+# do the stat check on make's behalf. Dodgy but simple.
+define old_or_no_timestamp
+ $(if $(realpath $%),,$(1))
+ $(if $(shell find $^ -cnewer "$%" 2>/dev/null),$(1))
+endef
+
+define check_deps
+ $(if $(filter 0,$(words $^)),\
+ $(error Missing dependencies or declaration of $@($%)),)
+endef
+
+# Build a cxx target magically
+CXX_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_binary))
+clean: CLEAN(CXX_BINARY*)
+
+CC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_binary))
+clean: CLEAN(CC_BINARY*)
+
+CXX_STATIC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_binary,-static))
+clean: CLEAN(CXX_STATIC_BINARY*)
+
+CC_STATIC_BINARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_binary,-static))
+clean: CLEAN(CC_STATIC_BINARY*)
+
+CXX_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cxx_library))
+clean: CLEAN(CXX_LIBRARY*)
+
+CXX_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call cc_library))
+clean: CLEAN(CC_LIBRARY*)
+
+CC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CXX_STATIC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call update_archive))
+clean: CLEAN(CXX_STATIC_LIBRARY*)
+
+CXX_STATIC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+CC_STATIC_LIBRARY(%):
+ $(call check_deps)
+ $(call old_or_no_timestamp,$(call update_archive))
+clean: CLEAN(CC_STATIC_LIBRARY*)
+
+CC_STATIC_LIBARY(%):
+ $(error Typo alert! LIBARY != LIBRARY)
+
+
+TEST(%): %
+ $(call TEST_implementation)
+ifneq ($(RUN_TESTS),0)
+TEST(%): qemu_chroot_install
+endif
+.PHONY: TEST
+
+# multiple targets with a wildcard need to share an directory.
+# Don't use this directly it just makes sure the directory is removed _after_
+# the files are.
+CLEANFILE(%):
+ $(call silent_rm,$(TARGET_OR_MEMBER))
+.PHONY: CLEANFILE
+
+CLEAN(%): CLEANFILE(%)
+ $(QUIET)# CLEAN($%) meta-target called
+ $(if $(filter-out $(PWD)/,$(dir $(abspath $(TARGET_OR_MEMBER)))), \
+ $(call silent_rmdir,$(dir $(abspath $(TARGET_OR_MEMBER)))),\
+ $(QUIET)# Not deleting $(dir $(abspath $(TARGET_OR_MEMBER))) yet.)
+.PHONY: CLEAN
+
+#
+# Top-level objects and pattern rules
+#
+
+# All objects for .c files at the top level
+C_OBJECTS = $(patsubst $(SRC)/%.c,%.o,$(wildcard $(SRC)/*.c))
+
+
+# All objects for .cxx files at the top level
+CXX_OBJECTS = $(patsubst $(SRC)/%.cc,%.o,$(wildcard $(SRC)/*.cc))
+
+# Note, the catch-all pattern rules don't work in subdirectories because
+# we're building from the $(OUT) directory. At the top-level (here) they will
+# work, but we go ahead and match using the module form. Then we can place a
+# generic pattern rule to capture leakage from the main Makefile. (Later in the
+# file.)
+#
+# The reason target specific pattern rules work well for modules,
+# MODULE_C_OBJECTS, is because it scopes the behavior to the given target which
+# ensures we get a relative directory offset from $(OUT) which otherwise would
+# not match without further magic on a per-subdirectory basis.
+
+# Creates object file rules. Call with eval.
+# $(1) list of .o files
+# $(2) source type (CC or CXX)
+# $(3) source suffix (cc or c)
+# $(4) compiler flag name (CFLAGS or CXXFLAGS)
+# $(5) source dir: _only_ if $(SRC). Leave blank for obj tree.
+define add_object_rules
+$(patsubst %.o,%.pie.o,$(1)): %.pie.o: $(5)%.$(3) %.o.depends
+ $$(call auto_mkdir,$$@)
+ $$(call OBJECT_PATTERN_implementation,$(2),\
+ $$(basename $$@),$$($(4)) $$(CPPFLAGS) $$(OBJ_PIE_FLAG))
+
+$(patsubst %.o,%.pic.o,$(1)): %.pic.o: $(5)%.$(3) %.o.depends
+ $$(call auto_mkdir,$$@)
+ $$(call OBJECT_PATTERN_implementation,$(2),\
+ $$(basename $$@),$$($(4)) $$(CPPFLAGS) -fPIC)
+
+# Placeholder for depends
+$(patsubst %.o,%.o.depends,$(1)):
+ $$(call auto_mkdir,$$@)
+ $$(QUIET)touch "$$@"
+
+$(1): %.o: %.pic.o %.pie.o
+ $$(call auto_mkdir,$$@)
+ $$(QUIET)touch "$$@"
+endef
+
+define OBJECT_PATTERN_implementation
+ @$(ECHO) "$(1) $(subst $(SRC)/,,$<) -> $(2).o"
+ $(call auto_mkdir,$@)
+ $(QUIET)$($(1)) -c -MD -MF $(2).d $(3) -o $(2).o $<
+ $(QUIET)# Wrap all the deps in $$(wildcard) so a missing header
+ $(QUIET)# won't cause weirdness. First we remove newlines and \,
+ $(QUIET)# then wrap it.
+ $(QUIET)sed -i -e :j -e '$$!N;s|\\\s*\n| |;tj' \
+ -e 's|^\(.*\s*:\s*\)\(.*\)$$|\1 $$\(wildcard \2\)|' $(2).d
+endef
+
+# Now actually register handlers for C(XX)_OBJECTS.
+$(eval $(call add_object_rules,$(C_OBJECTS),CC,c,CFLAGS,$(SRC)/))
+$(eval $(call add_object_rules,$(CXX_OBJECTS),CXX,cc,CXXFLAGS,$(SRC)/))
+
+# Disable default pattern rules to help avoid leakage.
+# These may already be handled by '-r', but let's keep it to be safe.
+%: %.o ;
+%.a: %.o ;
+%.o: %.c ;
+%.o: %.cc ;
+
+# NOTE: A specific rule for archive objects is avoided because parallel
+# update of the archive causes build flakiness.
+# Instead, just make the objects the prerequisites and use update_archive
+# To use the foo.a(obj.o) functionality, targets would need to specify the
+# explicit object they expect on the prerequisite line.
+
+#
+# Architecture detection and QEMU wrapping
+#
+
+HOST_ARCH ?= $(shell uname -m)
+override ARCH := $(strip $(ARCH))
+override HOST_ARCH := $(strip $(HOST_ARCH))
+# emake will supply "x86" or "arm" for ARCH, but
+# if uname -m runs and you get x86_64, then this subst
+# will break.
+ifeq ($(subst x86,i386,$(ARCH)),i386)
+ QEMU_ARCH := $(subst x86,i386,$(ARCH)) # x86 -> i386
+else ifeq ($(subst amd64,x86_64,$(ARCH)),x86_64)
+ QEMU_ARCH := $(subst amd64,x86_64,$(ARCH)) # amd64 -> x86_64
+else
+ QEMU_ARCH = $(ARCH)
+endif
+override QEMU_ARCH := $(strip $(QEMU_ARCH))
+
+# If we're cross-compiling, try to use qemu for running the tests.
+ifneq ($(QEMU_ARCH),$(HOST_ARCH))
+ ifeq ($(SYSROOT),)
+ $(info SYSROOT not defined. qemu-based testing disabled)
+ else
+ # A SYSROOT is assumed for QEmu use.
+ USE_QEMU ?= 1
+
+ # Allow 64-bit hosts to run 32-bit without qemu.
+ ifeq ($(HOST_ARCH),x86_64)
+ ifeq ($(QEMU_ARCH),i386)
+ USE_QEMU = 0
+ endif
+ endif
+ endif
+else
+ USE_QEMU ?= 0
+endif
+
+# Normally we don't need to run as root or do bind mounts, so only
+# enable it by default when we're using QEMU.
+NEEDS_ROOT ?= $(USE_QEMU)
+NEEDS_MOUNTS ?= $(USE_QEMU)
+
+SYSROOT_OUT = $(OUT)
+ifneq ($(SYSROOT),)
+ SYSROOT_OUT = $(subst $(SYSROOT),,$(OUT))
+else
+ # Default to / when all the empty-sysroot logic is done.
+ SYSROOT = /
+endif
+
+QEMU_NAME = qemu-$(QEMU_ARCH)
+QEMU_PATH = /build/bin/$(QEMU_NAME)
+QEMU_SYSROOT_PATH = $(SYSROOT)$(QEMU_PATH)
+QEMU_SRC_PATH = /usr/bin/$(QEMU_NAME)
+QEMU_BINFMT_PATH = /proc/sys/fs/binfmt_misc/$(QEMU_NAME)
+QEMU_REGISTER_PATH = /proc/sys/fs/binfmt_misc/register
+
+QEMU_MAGIC_arm = ":$(QEMU_NAME):M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/build/bin/qemu-arm:"
+
+
+#
+# Output full configuration at top level
+#
+
+# Don't show on clean
+ifneq ($(MAKECMDGOALS),clean)
+ $(info build configuration:)
+ $(info - OUT=$(OUT))
+ $(info - SRC=$(SRC))
+ $(info - MODE=$(MODE))
+ $(info - SPLITDEBUG=$(SPLITDEBUG))
+ $(info - NOSTRIP=$(NOSTRIP))
+ $(info - VALGRIND=$(VALGRIND))
+ $(info - COLOR=$(COLOR))
+ $(info - CXXEXCEPTIONS=$(CXXEXCEPTIONS))
+ $(info - ARCH=$(ARCH))
+ $(info - QEMU_ARCH=$(QEMU_ARCH))
+ $(info - USE_QEMU=$(USE_QEMU))
+ $(info - NEEDS_ROOT=$(NEEDS_ROOT))
+ $(info - NEEDS_MOUNTS=$(NEEDS_MOUNTS))
+ $(info - SYSROOT=$(SYSROOT))
+ $(info )
+endif
+
+#
+# Standard targets with detection for when they are improperly configured.
+#
+
+# all does not include tests by default
+all:
+ $(QUIET)(test -z "$^" && \
+ $(ECHO) "You must add your targets as 'all' prerequisites") || true
+ $(QUIET)test -n "$^"
+
+# Builds and runs tests for the target arch
+# Run them in parallel
+# After the test have completed, if profiling, run coverage analysis
+tests:
+ifeq ($(MODE),profiling)
+ @$(ECHO) "COVERAGE [$(COLOR_YELLOW)STARTED$(COLOR_RESET)]"
+ $(QUIET)FILES=""; \
+ for GCNO in `find . -name "*.gcno"`; do \
+ GCDA="$${GCNO%.gcno}.gcda"; \
+ if [ -e $${GCDA} ]; then \
+ FILES="$${FILES} $${GCDA}"; \
+ fi \
+ done; \
+ if [ -n "$${FILES}" ]; then \
+ gcov -l $${FILES}; \
+ lcov --capture --directory . \
+ --output-file=lcov-coverage.info; \
+ genhtml lcov-coverage.info \
+ --output-directory lcov-html; \
+ fi
+ @$(ECHO) "COVERAGE [$(COLOR_YELLOW)FINISHED$(COLOR_RESET)]"
+endif
+# Standard name everyone else uses.
+check: tests
+.PHONY: check tests
+
+qemu_chroot_install:
+ifeq ($(USE_QEMU),1)
+ $(QUIET)$(ECHO) "QEMU Preparing $(QEMU_NAME)"
+ @# Copying strategy
+ @# Compare /usr/bin/qemu inode to /build/$board/build/bin/qemu, if different
+ @# hard link to a temporary file, then rename temp to target. This should
+ @# ensure that once $QEMU_SYSROOT_PATH exists it will always exist, regardless
+ @# of simultaneous test setups.
+ $(QUIET)if [[ ! -e $(QEMU_SYSROOT_PATH) || \
+ `stat -c %i $(QEMU_SRC_PATH)` != `stat -c %i $(QEMU_SYSROOT_PATH)` \
+ ]]; then \
+ $(ROOT_CMD) ln -Tf $(QEMU_SRC_PATH) $(QEMU_SYSROOT_PATH).$$$$; \
+ $(ROOT_CMD) mv -Tf $(QEMU_SYSROOT_PATH).$$$$ $(QEMU_SYSROOT_PATH); \
+ fi
+
+ @# Prep the binfmt handler. First mount if needed, then unregister any bad
+ @# mappings and then register our mapping.
+ @# There may still be some race conditions here where one script de-registers
+ @# and another script starts executing before it gets re-registered, however
+ @# it should be rare.
+ -$(QUIET)[[ -e $(QEMU_REGISTER_PATH) ]] || \
+ $(ROOT_CMD) mount binfmt_misc -t binfmt_misc \
+ /proc/sys/fs/binfmt_misc
+
+ -$(QUIET)if [[ -e $(QEMU_BINFMT_PATH) && \
+ `awk '$$1 == "interpreter" {print $$NF}' $(QEMU_BINFMT_PATH)` != \
+ "$(QEMU_PATH)" ]]; then \
+ echo -1 | $(ROOT_CMD) tee $(QEMU_BINFMT_PATH) >/dev/null; \
+ fi
+
+ -$(if $(QEMU_MAGIC_$(ARCH)),$(QUIET)[[ -e $(QEMU_BINFMT_PATH) ]] || \
+ echo $(QEMU_MAGIC_$(ARCH)) | $(ROOT_CMD) tee $(QEMU_REGISTER_PATH) \
+ >/dev/null)
+endif
+.PHONY: qemu_clean qemu_chroot_install
+
+# TODO(wad) Move to -L $(SYSROOT) and fakechroot when qemu-user
+# doesn't hang traversing /proc from SYSROOT.
+SUDO_CMD = sudo
+UNSHARE_CMD = unshare
+QEMU_CMD =
+ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),$(SUDO_CMD) , )
+MOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) mount, \#)
+UMOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) umount, \#)
+QEMU_LDPATH = $(SYSROOT_LDPATH):/lib64:/lib:/usr/lib64:/usr/lib
+ROOT_CMD_LDPATH = $(SYSROOT_LDPATH):$(SYSROOT)/lib64:
+ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/lib:$(SYSROOT)/usr/lib64:
+ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/usr/lib
+ifeq ($(USE_QEMU),1)
+ export QEMU_CMD = \
+ $(SUDO_CMD) chroot $(SYSROOT) $(QEMU_PATH) \
+ -drop-ld-preload \
+ -E LD_LIBRARY_PATH="$(QEMU_LDPATH):$(patsubst $(OUT),,$(LD_DIRS))" \
+ -E HOME="$(HOME)" -E SRC="$(SRC)" --
+ # USE_QEMU conditional function
+ define if_qemu
+ $(1)
+ endef
+else
+ ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),sudo, ) \
+ LD_LIBRARY_PATH="$(ROOT_CMD_LDPATH):$(LD_DIRS)"
+ define if_qemu
+ $(2)
+ endef
+endif
+
+VALGRIND_CMD =
+ifeq ($(VALGRIND),1)
+ VALGRIND_CMD = /usr/bin/valgrind --tool=memcheck $(VALGRIND_ARGS) --
+endif
+
+ifneq ($(RUN_TESTS),0)
+define TEST_implementation
+ $(QUIET)$(call TEST_setup)
+ $(QUIET)$(call TEST_run)
+ $(QUIET)$(call TEST_teardown)
+ $(QUIET)exit $$(cat $(OUT)$(TARGET_OR_MEMBER).status.test)
+endef
+else
+define TEST_implementation
+endef
+endif
+
+define TEST_setup
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_YELLOW)SETUP$(COLOR_RESET)]"
+ $(QUIET)# Setup a target-specific results file
+ $(QUIET)(echo > $(OUT)$(TARGET_OR_MEMBER).setup.test)
+ $(QUIET)(echo 1 > $(OUT)$(TARGET_OR_MEMBER).status.test)
+ $(QUIET)(echo > $(OUT)$(TARGET_OR_MEMBER).cleanup.test)
+ $(QUIET)# No setup if we are not using QEMU
+ $(QUIET)# TODO(wad) this is racy until we use a vfs namespace
+ $(call if_qemu,\
+ $(QUIET)(echo "mkdir -p '$(SYSROOT)/proc' '$(SYSROOT)/dev' \
+ '$(SYSROOT)/mnt/host/source'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).setup.test"))
+ $(call if_qemu,\
+ $(QUIET)(echo "$(MOUNT_CMD) --bind /mnt/host/source \
+ '$(SYSROOT)/mnt/host/source'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).setup.test"))
+ $(call if_qemu,\
+ $(QUIET)(echo "$(MOUNT_CMD) --bind /proc '$(SYSROOT)/proc'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).setup.test"))
+ $(call if_qemu,\
+ $(QUIET)(echo "$(MOUNT_CMD) --bind /dev '$(SYSROOT)/dev'" \
+ >> "$(OUT)$(TARGET_OR_MEMBER).setup.test"))
+endef
+
+define TEST_teardown
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_YELLOW)TEARDOWN$(COLOR_RESET)]"
+ $(call if_qemu, $(QUIET)$(SHELL) "$(OUT)$(TARGET_OR_MEMBER).cleanup.test")
+endef
+
+# Use GTEST_ARGS.[arch] if defined.
+override GTEST_ARGS.real = \
+ $(call if_qemu,$(GTEST_ARGS.qemu.$(QEMU_ARCH)),$(GTEST_ARGS.host.$(HOST_ARCH)))
+
+define TEST_run
+ @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) "
+ @$(ECHO) "[$(COLOR_GREEN)RUN$(COLOR_RESET)]"
+ $(QUIET)(echo 1 > "$(OUT)$(TARGET_OR_MEMBER).status.test")
+ $(QUIET)(echo $(ROOT_CMD) SRC="$(SRC)" $(QEMU_CMD) $(VALGRIND_CMD) \
+ "$(strip $(call if_qemu, $(SYSROOT_OUT),$(OUT))$(TARGET_OR_MEMBER))" \
+ $(if $(filter-out 0,$(words $(GTEST_ARGS.real))),$(GTEST_ARGS.real),\
+ $(GTEST_ARGS)) >> "$(OUT)$(TARGET_OR_MEMBER).setup.test")
+ -$(QUIET)$(call if_qemu,$(SUDO_CMD) $(UNSHARE_CMD) -m) $(SHELL) \
+ $(OUT)$(TARGET_OR_MEMBER).setup.test \
+ && echo 0 > "$(OUT)$(TARGET_OR_MEMBER).status.test"
+endef
+
+# Recursive list reversal so that we get RMDIR_ON_CLEAN in reverse order.
+define reverse
+$(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1))
+endef
+
+clean: qemu_clean
+clean: CLEAN($(OUT)*.d) CLEAN($(OUT)*.o) CLEAN($(OUT)*.debug)
+clean: CLEAN($(OUT)*.test) CLEAN($(OUT)*.depends)
+clean: CLEAN($(OUT)*.gcno) CLEAN($(OUT)*.gcda) CLEAN($(OUT)*.gcov)
+clean: CLEAN($(OUT)lcov-coverage.info) CLEAN($(OUT)lcov-html)
+
+clean:
+ $(QUIET)# Always delete the containing directory last.
+ $(call silent_rmdir,$(OUT))
+
+FORCE: ;
+# Empty rule for use when no special targets are needed, like large_tests
+NONE:
+
+.PHONY: clean NONE valgrind NONE
+.DEFAULT_GOAL := all
+# Don't let make blow away "intermediates"
+.PRECIOUS: %.pic.o %.pie.o %.a %.pic.a %.pie.a %.test
+
+# Start accruing build info
+OUT_DIRS = $(OUT)
+LD_DIRS = $(OUT)
+SRC_DIRS = $(SRC)
+
+include $(wildcard $(OUT)*.d)
+SUBMODULE_DIRS = $(wildcard $(SRC)/*/module.mk)
+include $(SUBMODULE_DIRS)
+
+
+else ## In duplicate inclusions of common.mk
+
+# Get the current inclusion directory without a trailing slash
+MODULE := $(patsubst %/,%, \
+ $(dir $(lastword $(filter-out %common.mk,$(MAKEFILE_LIST)))))
+MODULE := $(subst $(SRC)/,,$(MODULE))
+MODULE_NAME := $(subst /,_,$(MODULE))
+#VPATH := $(MODULE):$(VPATH)
+
+
+# Depth first
+$(eval OUT_DIRS += $(OUT)$(MODULE))
+$(eval SRC_DIRS += $(OUT)$(MODULE))
+$(eval LD_DIRS := $(LD_DIRS):$(OUT)$(MODULE))
+
+# Add the defaults from this dir to rm_clean
+clean: CLEAN($(OUT)$(MODULE)/*.d) CLEAN($(OUT)$(MODULE)/*.o)
+clean: CLEAN($(OUT)$(MODULE)/*.debug) CLEAN($(OUT)$(MODULE)/*.test)
+clean: CLEAN($(OUT)$(MODULE)/*.depends)
+clean: CLEAN($(OUT)$(MODULE)/*.gcno) CLEAN($(OUT)$(MODULE)/*.gcda)
+clean: CLEAN($(OUT)$(MODULE)/*.gcov) CLEAN($(OUT)lcov-coverage.info)
+clean: CLEAN($(OUT)lcov-html)
+
+$(info + submodule: $(MODULE_NAME))
+# We must eval otherwise they may be dropped.
+MODULE_C_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.c,$(MODULE)/%.o,\
+ $(wildcard $(SRC)/$(MODULE)/*.c))
+$(eval $(MODULE_NAME)_C_OBJECTS ?= $(MODULE_C_OBJECTS))
+MODULE_CXX_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.cc,$(MODULE)/%.o,\
+ $(wildcard $(SRC)/$(MODULE)/*.cc))
+$(eval $(MODULE_NAME)_CXX_OBJECTS ?= $(MODULE_CXX_OBJECTS))
+
+# Note, $(MODULE) is implicit in the path to the %.c.
+# See $(C_OBJECTS) for more details.
+# Register rules for the module objects.
+$(eval $(call add_object_rules,$(MODULE_C_OBJECTS),CC,c,CFLAGS,$(SRC)/))
+$(eval $(call add_object_rules,$(MODULE_CXX_OBJECTS),CXX,cc,CXXFLAGS,$(SRC)/))
+
+# Continue recursive inclusion of module.mk files
+SUBMODULE_DIRS = $(wildcard $(SRC)/$(MODULE)/*/module.mk)
+include $(wildcard $(OUT)$(MODULE)/*.d)
+include $(SUBMODULE_DIRS)
+
+endif
+endif ## pass-to-subcall wrapper for relocating the call directory
diff --git a/conf/99-logitech-updater.rules b/conf/99-logitech-updater.rules
new file mode 100644
index 0000000..a5d30ed
--- /dev/null
+++ b/conf/99-logitech-updater.rules
@@ -0,0 +1,7 @@
+# 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.
+
+SUBSYSTEM=="video4linux", ACTION=="add", ENV{ID_VENDOR_ID}=="046d", ENV{ID_MODEL_ID}=="085f", \
+OWNER="cfm-firmware-updaters", GROUP="video", MODE="0664", \
+RUN+="/sbin/minijail0 -u cfm-firmware-updaters -g video -e -i -l -r -n -p -v -S /usr/share/policy/logitech-updater-seccomp.policy /usr/sbin/logitech-updater --update"
diff --git a/seccomp/logitech-updater-seccomp-amd64.policy b/seccomp/logitech-updater-seccomp-amd64.policy
new file mode 100644
index 0000000..4320a6e
--- /dev/null
+++ b/seccomp/logitech-updater-seccomp-amd64.policy
@@ -0,0 +1,36 @@
+# 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.
+
+access: 1
+arch_prctl: 1
+brk: 1
+close: 1
+connect: 1
+execve: 1
+exit_group: 1
+fstat: 1
+futex: 1
+getdents: 1
+getdents64: 1
+getrlimit: 1
+# ioctl: arg1 == TCGETS || arg1 == UVCIOC_CTRL_QUERY
+# arg1 == VIDIOC_QUERYCAP
+ioctl: arg1 == 0x5401 || arg1 == 0xC0107521 || arg1 == 0x80685600
+lseek: 1
+mmap: 1
+mprotect: 1
+munmap: 1
+nanosleep: 1
+open: 1
+read: 1
+readlink: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+sendto: 1
+set_robust_list: 1
+set_tid_address: 1
+# socket: arg0 == PFLOCAL
+socket: arg0 == 1
+stat: 1
+write: 1
diff --git a/seccomp/logitech-updater-seccomp-x86_64.policy b/seccomp/logitech-updater-seccomp-x86_64.policy
new file mode 100644
index 0000000..4320a6e
--- /dev/null
+++ b/seccomp/logitech-updater-seccomp-x86_64.policy
@@ -0,0 +1,36 @@
+# 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.
+
+access: 1
+arch_prctl: 1
+brk: 1
+close: 1
+connect: 1
+execve: 1
+exit_group: 1
+fstat: 1
+futex: 1
+getdents: 1
+getdents64: 1
+getrlimit: 1
+# ioctl: arg1 == TCGETS || arg1 == UVCIOC_CTRL_QUERY
+# arg1 == VIDIOC_QUERYCAP
+ioctl: arg1 == 0x5401 || arg1 == 0xC0107521 || arg1 == 0x80685600
+lseek: 1
+mmap: 1
+mprotect: 1
+munmap: 1
+nanosleep: 1
+open: 1
+read: 1
+readlink: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+sendto: 1
+set_robust_list: 1
+set_tid_address: 1
+# socket: arg0 == PFLOCAL
+socket: arg0 == 1
+stat: 1
+write: 1
diff --git a/src/composite_device.cc b/src/composite_device.cc
new file mode 100644
index 0000000..2f0688d
--- /dev/null
+++ b/src/composite_device.cc
@@ -0,0 +1,150 @@
+// 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.
+
+#include "composite_device.h"
+#include <unistd.h>
+#include "eeprom_device.h"
+#include "mcu2_device.h"
+#include "utilities.h"
+#include "video_device.h"
+
+constexpr unsigned int kLogiRebootingWaitTime = 10;
+
+CompositeDevice::CompositeDevice(std::string videoPid,
+ std::string eepromPid,
+ std::string mcu2Pid) {
+ devices.push_back(std::make_shared<VideoDevice>(videoPid));
+ devices.push_back(std::make_shared<EepromDevice>(eepromPid));
+ devices.push_back(std::make_shared<Mcu2Device>(mcu2Pid));
+ openDevices();
+}
+
+CompositeDevice::~CompositeDevice() {
+ closeDevices();
+}
+
+int CompositeDevice::openDevices() {
+ int error = kLogiErrorUnknown;
+ for (auto device : devices) {
+ // open each device
+ error = device->openDevice();
+ if (error)
+ break;
+ }
+ return error;
+}
+
+void CompositeDevice::closeDevices() {
+ for (auto device : devices)
+ device->closeDevice();
+}
+
+int CompositeDevice::getDevicesVersion(std::map<int, std::string>* versionMap) {
+ std::map<int, std::string> output;
+ int error = kLogiErrorUnknown;
+ for (auto device : devices) {
+ std::string version;
+ if ((error = device->getDeviceVersion(&version)))
+ break; // error getting version, break out and return
+ (*versionMap)[device->deviceType] = version;
+ }
+ return error;
+}
+
+int CompositeDevice::getDevicesName(std::map<int, std::string>* nameMap) {
+ int error = kLogiErrorUnknown;
+ for (auto device : devices) {
+ std::string name;
+ if ((error = device->getDeviceName(&name)))
+ break; // error getting name, break out and return
+ (*nameMap)[device->deviceType] = name;
+ }
+ return error;
+}
+
+int CompositeDevice::getImagesVersion(std::map<int, std::string>* versionMap) {
+ int error = kLogiErrorUnknown;
+ std::string version;
+
+ for (auto device : devices) {
+ std::string version;
+ std::vector<char> buffer = getImageBuffer(device->deviceType);
+
+ error = device->verifyImage(buffer);
+ if (error)
+ return error; // failed to verify image
+ if ((error = device->getImageVersion(buffer, &version)))
+ return error; // failed to get image version
+ (*versionMap)[device->deviceType] = version;
+ }
+ return error;
+}
+
+bool CompositeDevice::isDeviceUpToDate() {
+ for (auto device : devices) {
+ std::vector<char> buffer = getImageBuffer(device->deviceType);
+ std::string imageVersion;
+ std::string deviceVersion;
+ device->getDeviceVersion(&deviceVersion);
+ device->getImageVersion(buffer, &imageVersion);
+ if (CompareVersions(deviceVersion, imageVersion) < 0)
+ return false;
+ }
+ return true;
+}
+
+bool CompositeDevice::isDevicePresent() {
+ for (auto device : devices) {
+ std::vector<std::string> devicepath = device->findDevices();
+ if (devicepath.size() > 0)
+ return true;
+ }
+ return false;
+}
+
+int CompositeDevice::performUpdate() {
+ int error = kLogiErrorUnknown;
+ std::shared_ptr<USBDevice> videoDevice;
+ if (!isDeviceUpToDate()) {
+ for (auto device : devices) {
+ std::vector<char> buffer = getImageBuffer(device->deviceType);
+ error = device->performUpdate(buffer);
+ if (error)
+ return error; // failed to update
+ if (device->deviceType == kLogiDeviceVideo)
+ videoDevice = device; // get a hold of video device to do reboot later
+ }
+
+ // updated ok, reboot devices
+ for (auto device : devices) {
+ if (device->deviceType != kLogiDeviceVideo)
+ device->closeDevice(); // close all other device before rebooting
+ }
+ videoDevice->rebootDevice();
+ sleep(kLogiRebootingWaitTime); // wait for device to reboot
+ openDevices(); // make sure to reopen devices
+ }
+ return kLogiErrorNoError;
+}
+
+std::vector<char> CompositeDevice::getImageBuffer(int deviceType) {
+ std::vector<char> buffer;
+ switch (deviceType) {
+ case kLogiDeviceVideo:
+ buffer = videoImageBuffer;
+ break;
+
+ case kLogiDeviceEeprom:
+ buffer = eepromImageBuffer;
+ break;
+
+ case kLogiDeviceMcu2:
+ buffer = mcu2ImageBuffer;
+ break;
+
+ default:
+ break;
+ }
+ return buffer;
+}
diff --git a/src/composite_device.h b/src/composite_device.h
new file mode 100644
index 0000000..27d6879
--- /dev/null
+++ b/src/composite_device.h
@@ -0,0 +1,123 @@
+// 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.
+
+#ifndef SRC_COMPOSITE_DEVICE_H_
+#define SRC_COMPOSITE_DEVICE_H_
+
+#include <stdio.h>
+#include <map>
+#include <memory>
+#include "usb_device.h"
+
+/**
+ * Composite device class to hold and handle firmware update of component usb
+ * devices. For example: ptzpro2 device has 3 components: eeprom device
+ * (LogiEepromDevice) motor control unit device mcu2 (LogiMCU2Device) and video
+ * device (LogiVideoDevice). Some other Logitech products might have
+ * audio/codec/bluetooth ble components.
+ */
+class CompositeDevice {
+ public:
+ std::vector<char> videoImageBuffer;
+ std::vector<char> eepromImageBuffer;
+ std::vector<char> mcu2ImageBuffer;
+
+ private:
+ std::vector<std::shared_ptr<USBDevice>> devices; // component devices
+ public:
+ /**
+ * @brief Constructor with video pid, eeprom pid and mcu2 pid.
+ * @param videoPid Video product id.
+ * @param eepromPid Eeprom product id.
+ * @param mcu2Pid Motor control unit product id.
+ */
+ CompositeDevice(std::string videoPid,
+ std::string eepromPid,
+ std::string mcu2Pid);
+
+ virtual ~CompositeDevice();
+
+ /**
+ * @brief Opens all component devices.
+ * @return kLogiErrorNoError if successfully opened or error code if one of
+ * the components fails to open.
+ */
+ int openDevices();
+
+ /**
+ * @brief Closes all component devices.
+ */
+ void closeDevices();
+
+ /**
+ * @brief Gets version number of each component in the composite device.
+ * @param versionMap Output version map containing all component versions of
+ * the composite device. Version map is formatted as follow
+ * {
+ * kLogiDeviceVideo : "video-version",
+ * kLogiDeviceEeprom : "eeprom-version",
+ * kLogiDeviceMcu2 : "mcu2-version",
+ * }
+ * @return kLogiErrorNoError if succeeded or error code if failed to get one
+ * of the component versions.
+ */
+ int getDevicesVersion(std::map<int, std::string>* versionMap);
+
+ /**
+ * @brief Gets name of each component in the composite device.
+ * @param nameMap Output name map containing all component names of the
+ * composite device. name map is formatted as follow
+ * {
+ * kLogiDeviceVideo : "video-device-name",
+ * kLogiDeviceEeprom : "eeprom-device-name",
+ * kLogiDeviceMcu2 : "mcu2-device-name",
+ * }
+ * @return kLogiErrorNoError if succeeded or error code if failed to get one
+ * of the component version.
+ */
+ int getDevicesName(std::map<int, std::string>* nameMap);
+
+ /**
+ * @brief Verifies and gets version number of each image buffers assuming
+ * they've been set.
+ * @param versionMap Output version map containing all images' version
+ * Version map is formatted as follow
+ * {
+ * kLogiDeviceVideo : "video-image-version",
+ * kLogiDeviceEeprom : "eeprom-image-version",
+ * kLogiDeviceMcu2 : "mcu2-image-version",
+ * }
+ * @return kLogiErrorNoError if succeeded or error code if failed to verify or
+ * get version.
+ */
+ int getImagesVersion(std::map<int, std::string>* versionMap);
+
+ /**
+ * @brief Checks if device firmware is up to date.
+ */
+ bool isDeviceUpToDate();
+
+ /**
+ * @brief Checks if device is present.
+ */
+ bool isDevicePresent();
+
+ /**
+ * @brief Performs the firmware update on all component devices.
+ * @return kLogiErrorNoError if successfully updated all components, error
+ * code otherwise.
+ */
+ int performUpdate();
+
+ private:
+ /**
+ * @brief Gets image buffer from type.
+ * @param deviceType The device type: kLogiDeviceVideo, kLogiDeviceEeprom, or
+ * kLogiDeviceMcu2 ...
+ * @return image buffer based on device type or empty buffer if device type is
+ * unknown.
+ */
+ std::vector<char> getImageBuffer(int deviceType);
+};
+#endif /* SRC_COMPOSITE_DEVICE_H_ */
diff --git a/src/eeprom_device.cc b/src/eeprom_device.cc
new file mode 100644
index 0000000..11a455c
--- /dev/null
+++ b/src/eeprom_device.cc
@@ -0,0 +1,315 @@
+// 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.
+
+#include "eeprom_device.h"
+#include "utilities.h"
+
+constexpr unsigned int kLogiEepromDeviceVersionDataSize = 2;
+constexpr uint16_t kLogiEepromAddressSignatureByte0 = 0x00;
+constexpr uint16_t kLogiEepromAddressSignatureByte1 = 0x01;
+constexpr uint16_t kLogiEepromAdressMajorVersion = 0x10;
+constexpr uint16_t kLogiEepromAddressMinorVersion = 0x0f;
+constexpr unsigned int kLogiEepromMaxAttemptSendBlockData = 3;
+constexpr unsigned char kLogiUvcXuDevInfoCsEepromVersion = 3;
+constexpr unsigned char kLogiEepromUvcXuTestDbgCsEepromAddress = 3;
+constexpr unsigned char kLogiEepromUvcXuTestDbgCsEepromAccess = 4;
+constexpr unsigned int kLogiEepromImageVerificationMinSize = 6;
+
+EepromDevice::EepromDevice(std::string pid)
+ : VideoDevice(pid, kLogiDeviceEeprom) {}
+
+EepromDevice::~EepromDevice() {
+ closeDevice();
+}
+
+int EepromDevice::readDeviceVersion(std::string* deviceVersion) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ std::vector<unsigned char> outputData;
+ int unitId = GetUnitID(kLogiGuidDeviceInfo);
+ int error = getXuControl(unitId,
+ kLogiUvcXuDevInfoCsEepromVersion,
+ &outputData);
+ if (error)
+ return error;
+ if (outputData.size() < kLogiEepromDeviceVersionDataSize)
+ return kLogiErrorInvalidDeviceVersionDataSize;
+
+ // device version is valid
+ int major = (int)outputData[1];
+ int minor = (int)outputData[0];
+ *deviceVersion = GetDeviceStringVersion(major, minor);
+ return kLogiErrorNoError;
+}
+
+int EepromDevice::getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion) {
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ uint8_t major = 0;
+ uint8_t minor = 0;
+ std::vector<S19Block> imageBlocks;
+ int error = parseS19(buffer, &imageBlocks);
+ if (error)
+ return error;
+ error = readImageByte(imageBlocks, kLogiEepromAdressMajorVersion, &major);
+ if (error)
+ return error;
+ error = readImageByte(imageBlocks, kLogiEepromAddressMinorVersion, &minor);
+ if (error)
+ return error;
+
+ std::string version = GetDeviceStringVersion(major, minor);
+ *imageVersion = version;
+ return kLogiErrorNoError;
+}
+
+int EepromDevice::verifyImage(std::vector<char> buffer) {
+ if (buffer.empty() || buffer.size() < kLogiEepromImageVerificationMinSize)
+ return kLogiErrorImageBufferReadFailed;
+
+ std::vector<S19Block> imageBlocks;
+ int error = parseS19(buffer, &imageBlocks);
+ if (error)
+ return error;
+
+ // perform some sanity checks
+ uint8_t signatureAA = 0x00;
+ uint8_t signature55 = 0x00;
+ error = readImageByte(imageBlocks,
+ kLogiEepromAddressSignatureByte0,
+ &signatureAA);
+ if (error)
+ return error;
+ error = readImageByte(imageBlocks,
+ kLogiEepromAddressSignatureByte1,
+ &signature55);
+ if (error)
+ return error;
+
+ if (signatureAA != 0xAA || signature55 != 0x55)
+ return kLogiErrorImageVerificationFailed;
+
+ return kLogiErrorNoError;
+}
+
+int EepromDevice::parseS19(std::vector<char> buffer,
+ std::vector<S19Block>* imageBlocks) {
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ // Split the binary file into lines
+ std::vector<std::string> lines = splitBinaryIntoLines(buffer);
+ if (lines.empty())
+ return kLogiErrorParseS19BinaryFailed;
+
+ std::vector<S19Block> resultBlocks;
+ S19Block block;
+
+ for (const auto& line : lines) {
+ if (line[0] != 'S')
+ return kLogiErrorParseS19BinaryFailed;
+
+ int addressBytes = 0;
+ const auto commandString = line[1];
+ switch (commandString) {
+ case '0': // Block header
+ block = S19Block();
+ addressBytes = 2;
+ break;
+ case '1': // Data sequence (2-byte address)
+ case '9': // End of block (2-byte address)
+ addressBytes = 2;
+ break;
+ default: // Others are not supported by this parser
+ return kLogiErrorParseS19BinaryFailed;
+ }
+
+ const unsigned int byteCount = stoi(line.substr(2, 2), NULL, 16);
+ const uint16_t address =
+ (uint16_t)stoi(line.substr(4, addressBytes * 2), NULL, 16);
+ uint8_t runningCheckSum =
+ (uint8_t)(byteCount + (address >> 8) + (address & 0x00ff));
+
+ const auto payloadString = line.substr(1 + 1 + 2 + addressBytes * 2);
+ if (payloadString.size() != 2 * (byteCount - addressBytes))
+ return kLogiErrorParseS19BinaryFailed;
+ std::vector<uint8_t> data;
+ auto it = begin(payloadString);
+ uint8_t expectedCheckSum = 0;
+ for (;;) {
+ uint8_t byte;
+ // Most-significant nibble
+ char c = (char)(*it++);
+ if (!ConvertHexCharToUnsignedInt(c, &byte))
+ return kLogiErrorParseS19BinaryFailed;
+ byte <<= 4;
+
+ // Least-significant nibble
+ c = (char)(*it++);
+ uint8_t leastSignificantByte;
+ if (!ConvertHexCharToUnsignedInt(c, &leastSignificantByte))
+ return kLogiErrorParseS19BinaryFailed;
+ byte |= leastSignificantByte;
+
+ if (it != end(payloadString)) {
+ // Data byte
+ data.push_back(byte);
+ runningCheckSum += byte;
+ } else {
+ // Check sum (last byte)
+ expectedCheckSum = byte;
+ break;
+ }
+ }
+ if ((uint8_t)~runningCheckSum != expectedCheckSum)
+ return kLogiErrorParseS19BinaryFailed;
+
+ switch (commandString) {
+ case '0': // Block header
+ block.Header = make_pair(address, data);
+ break;
+ case '1': // Data sequence (2-byte address)
+ block.Data.push_back(make_pair(address, data));
+ break;
+ case '9': // End of block (2-byte address)
+ resultBlocks.push_back(block);
+ break;
+ default: // Others are not supported by this parser
+ return kLogiErrorParseS19BinaryFailed;
+ }
+ }
+
+ *imageBlocks = resultBlocks;
+ return kLogiErrorNoError;
+}
+
+int EepromDevice::sendImage(std::vector<S19Block> imageBlocks) {
+ if (imageBlocks.empty())
+ return kLogiErrorReadS19ImageByteFailed;
+
+ int bytesWritten = 0;
+ for (const auto& block : imageBlocks) {
+ for (const auto& dataPointer : block.Data) {
+ const auto baseAddress = dataPointer.first;
+ const auto& data = dataPointer.second;
+ for (uint16_t offset = 0; offset < data.size(); offset++) {
+ int attempts = kLogiEepromMaxAttemptSendBlockData;
+ while (attempts-- > 0) {
+ int error = writeEepromByte(baseAddress + offset, data[offset]);
+ if (error)
+ continue; // Failed this attempt, retry to send image.
+ break;
+ }
+
+ if (attempts <= 0)
+ return kLogiErrorSendImageFailed;
+ }
+ bytesWritten += data.size();
+ }
+ }
+ return kLogiErrorNoError;
+}
+
+int EepromDevice::performUpdate(std::vector<char> buffer) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ std::string deviceVersion;
+ std::string imageVersion;
+ int error = getDeviceVersion(&deviceVersion);
+ if (error)
+ return error;
+
+ error = verifyImage(buffer);
+ if (error)
+ return error;
+
+ error = getImageVersion(buffer, &imageVersion);
+ if (error)
+ return error;
+
+ // check if update available
+ int compareVersion = CompareVersions(deviceVersion, imageVersion);
+ if (compareVersion < 0) {
+ std::vector<S19Block> imageBlocks;
+ error = parseS19(buffer, &imageBlocks);
+ if (error)
+ return error;
+ error = sendImage(imageBlocks);
+ }
+ return error;
+}
+
+std::vector<std::string> EepromDevice::splitBinaryIntoLines(
+ std::vector<char> buffer) {
+ std::vector<std::string> lines;
+ std::string line;
+ for (const auto byte : buffer) {
+ if (isalnum(byte)) {
+ line.push_back(byte);
+ } else if (byte == '\r' || byte == '\n') {
+ if (!line.empty()) {
+ lines.push_back(line);
+ line.clear();
+ }
+ } else {
+ return std::vector<std::string>();
+ }
+ }
+ return lines;
+}
+
+int EepromDevice::readImageByte(std::vector<S19Block> imageBlocks,
+ uint16_t address,
+ uint8_t* value) {
+ if (imageBlocks.empty())
+ return kLogiErrorReadS19ImageByteFailed;
+ for (const auto& block : imageBlocks) {
+ for (const auto& p : block.Data) {
+ const auto baseAddress = p.first;
+ const auto& data = p.second;
+
+ const auto blockSize = data.size();
+ if (address >= baseAddress && address < (baseAddress + blockSize)) {
+ const auto offset = address - baseAddress; // Offset within this block
+
+ *value = data[offset];
+ return kLogiErrorNoError;
+ }
+ }
+ }
+ return kLogiErrorReadS19ImageByteFailed;
+}
+
+int EepromDevice::countImageBytes(std::vector<S19Block> imageBlocks) {
+ if (imageBlocks.empty())
+ return kLogiErrorReadS19ImageByteFailed;
+ int byteCount = 0;
+ for (const auto& block : imageBlocks) {
+ for (const auto& data : block.Data)
+ byteCount += data.second.size();
+ }
+ return byteCount;
+}
+
+int EepromDevice::writeEepromByte(uint16_t address, uint8_t byte) {
+ unsigned char firstByte = uint8_t(address & 0xff);
+ unsigned char secondByte = uint8_t(address >> 8);
+ std::vector<unsigned char> addressData;
+ addressData.push_back(firstByte);
+ addressData.push_back(secondByte);
+ int unitID = GetUnitID(kLogiGuidTestDebug);
+ int error = setXuControl(unitID,
+ kLogiEepromUvcXuTestDbgCsEepromAddress,
+ addressData);
+ if (error)
+ return error;
+
+ std::vector<unsigned char> byteData;
+ byteData.push_back(byte);
+ return setXuControl(unitID, kLogiEepromUvcXuTestDbgCsEepromAccess, byteData);
+}
diff --git a/src/eeprom_device.h b/src/eeprom_device.h
new file mode 100644
index 0000000..0288067
--- /dev/null
+++ b/src/eeprom_device.h
@@ -0,0 +1,110 @@
+// 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.
+
+#ifndef SRC_EEPROM_DEVICE_H_
+#define SRC_EEPROM_DEVICE_H_
+
+#include <stdio.h>
+#include "video_device.h"
+
+// structure represents S19 binary format
+struct S19Block {
+ // header block. Header block has address and data
+ std::pair<uint16_t, std::vector<uint8_t>> Header;
+ // data blocks. Each data block has the address and data
+ std::vector<std::pair<uint16_t, std::vector<uint8_t>>> Data;
+};
+
+/**
+ * Logitech eeprom device class to handle eeprom firmware update.
+ */
+class EepromDevice : public VideoDevice {
+ public:
+ /**
+ * @brief Constructor with product id
+ * @param pid Product id string
+ */
+ EepromDevice(std::string pid);
+ virtual ~EepromDevice();
+
+ /**
+ * @brief Reads the device version.
+ * @param deviceVersion Output device version string.
+ * @return kLogiErrorNoError if read ok, error code otherwise.
+ */
+ virtual int readDeviceVersion(std::string* deviceVersion);
+
+ /**
+ * @brief Gets the binary image version.
+ * @param buffer The image buffer to read from
+ * @param imageVersion Output device version string.
+ * @return kLogiErrorNoError if succeeded, error code otherwise.
+ */
+ virtual int getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion);
+
+ /**
+ * @brief Verifies the image to make sure it's secure.
+ * @param buffer The image buffer to verify.
+ * @return kLogiErrorNoError if verified ok, error code otherwise.
+ */
+ virtual int verifyImage(std::vector<char> buffer);
+
+ /**
+ * @brief Performs firmware update.
+ * @param buffer Firmware image buffer.
+ * @return kLogiErrorNoError if updated ok, error code otherwise.
+ */
+ virtual int performUpdate(std::vector<char> buffer);
+
+ private:
+ /**
+ * @brief Parses S19 binary file.
+ * @param buffer The binary buffer.
+ * @param imageBlocks Parsed image block.
+ * @return kLogiErrorNoError if parsed ok, error code otherwise.
+ */
+ int parseS19(std::vector<char> buffer, std::vector<S19Block>* imageBlocks);
+
+ /**
+ * @brief Sends image blocks to eeprom device.
+ * @param imageBlocks Parsed image block.
+ * @return kLogiErrorNoError if sent ok, error code otherwise.
+ */
+ int sendImage(std::vector<S19Block> imageBlocks);
+
+ /**
+ * @brief Splits the S19 binary file into lines.
+ * @param buffer The binary buffer.
+ * @return vector containing one string for each line.
+ */
+ std::vector<std::string> splitBinaryIntoLines(std::vector<char> buffer);
+
+ /**
+ * @brief Reads the image byte at address.
+ * @param imageBlocks The parsed S19 image blocks.
+ * @param address Address to read from.
+ * @param value Output value.
+ * @return kLogiErrorNoError if read ok, error code otherwise.
+ */
+ int readImageByte(std::vector<S19Block> imageBlocks,
+ uint16_t address,
+ uint8_t* value);
+
+ /**
+ * @brief Counts the image bytes.
+ * @param imageBlocks The parsed S19 image blocks.
+ * @return image blocks size or 0 if error.
+ */
+ int countImageBytes(std::vector<S19Block> imageBlocks);
+
+ /**
+ * @brief Writes eeprom byte to the device.
+ * @param address Address to write to.
+ * @param byte Byte to write.
+ * @return kLogiErrorNoError if succeeded, error code otherwise.
+ */
+ int writeEepromByte(uint16_t address, uint8_t byte);
+};
+#endif /* SRC_EEPROM_DEVICE_H_ */
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 0000000..bf8db98
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,152 @@
+// 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.
+
+#include <base/logging.h>
+#include <brillo/flag_helper.h>
+#include <brillo/syslog_logging.h>
+#include <iostream>
+#include "base/process/process_iterator.h"
+#include "composite_device.h"
+#include "utilities.h"
+#include "version.h"
+
+namespace {
+const char kLogitechPtzPro2Pid[] = "0x85f";
+const char kVideoImagePath[] =
+ "/lib/firmware/logitech/ptzpro2/ptzpro2_video.bin";
+const char kEepromImagePath[] =
+ "/lib/firmware/logitech/ptzpro2/ptzpro2_eeprom.s19";
+const char kMcu2ImagePath[] = "/lib/firmware/logitech/ptzpro2/ptzpro2_mcu2.bin";
+
+/**
+ * @brief Print the device version info
+ * @param infoMap The map containing device versions
+ */
+void printDevicesVersion(std::map<int, std::string> infoMap) {
+ LOG(INFO) << "Video version: " << infoMap[kLogiDeviceVideo].c_str();
+ LOG(INFO) << "Eeprom version: " << infoMap[kLogiDeviceEeprom].c_str();
+ LOG(INFO) << "Mcu2 version: " << infoMap[kLogiDeviceMcu2].c_str();
+}
+
+/**
+ * @brief Print the image version info
+ * @param infoMap The map containing image versions
+ */
+void printImagesVersion(std::map<int, std::string> infoMap) {
+ LOG(INFO) << "Video image version: " << infoMap[kLogiDeviceVideo].c_str();
+ LOG(INFO) << "Eeprom image version: " << infoMap[kLogiDeviceEeprom].c_str();
+ LOG(INFO) << "Mcu2 image version: " << infoMap[kLogiDeviceMcu2].c_str();
+}
+
+/**
+ * @brief Configures the logging system.
+ * @param log_file Specifies the log file to redirect to or stdout for console
+ * output.
+ */
+void configureLogging(std::string log_file) {
+ if (log_file.empty()) {
+ // Default log to syslog.
+ brillo::InitLog(brillo::InitFlags::kLogToSyslog |
+ brillo::InitFlags::kLogToStderrIfTty);
+ } else {
+ // Logs to file.
+ logging::LoggingSettings logging_settings;
+ logging_settings.logging_dest = logging::LOG_TO_FILE;
+ logging_settings.log_file = log_file.c_str();
+ logging_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ logging::InitLogging(logging_settings);
+ }
+}
+} // namespace
+
+/**
+ * main
+ */
+int main(int argc, char** argv) {
+ // Parse the flags
+ DEFINE_bool(binary_version, false, "Show this binary version.");
+ DEFINE_bool(device_version, false, "Show device versions.");
+ DEFINE_bool(image_version, false, "Show image versions.");
+ DEFINE_bool(update, false, "Perform firmware update.");
+ DEFINE_string(log_to, "", "Specify log file to write messages to.");
+ brillo::FlagHelper::Init(argc, argv, "logitech-updater");
+
+ // Configures log file.
+ configureLogging(FLAGS_log_to);
+
+ // If there is another instance of this executable is running, we want to exit
+ // now. We dont want to have multiple executables updating the same device at
+ // the same time. This should take care of cases where device is rebooting
+ // while updating and udev triggers the updater again.
+ if (base::GetProcessCount("logitech-updater", NULL) > 1) {
+ LOG(INFO) << "Another logitech-updater process is running. Existing now.";
+ return 0;
+ }
+
+ int error;
+ std::string videoVersion, eepromVersion, mcu2Version;
+ std::string videoImageVersion, eepromImageVersion, mcu2ImageVersion;
+ std::map<int, std::string> infoMap; // information map
+ std::shared_ptr<CompositeDevice> ptzpro2Device =
+ std::make_shared<CompositeDevice>(
+ kLogitechPtzPro2Pid, kLogitechPtzPro2Pid, kLogitechPtzPro2Pid);
+ ptzpro2Device->videoImageBuffer = ReadBinaryFileContent(kVideoImagePath);
+ ptzpro2Device->eepromImageBuffer = ReadBinaryFileContent(kEepromImagePath);
+ ptzpro2Device->mcu2ImageBuffer = ReadBinaryFileContent(kMcu2ImagePath);
+ if (ptzpro2Device->videoImageBuffer.size() == 0)
+ LOG(ERROR) << "Error reading video binary file.";
+ if (ptzpro2Device->eepromImageBuffer.size() == 0)
+ LOG(ERROR) << "Error reading eeprom binary file.";
+ if (ptzpro2Device->mcu2ImageBuffer.size() == 0)
+ LOG(ERROR) << "Error reading mcu2 binary file.";
+
+ if (FLAGS_binary_version) {
+ LOG(INFO) << kLogiBinaryVersion;
+ }
+ if (FLAGS_device_version) {
+ if (!ptzpro2Device->isDevicePresent()) {
+ LOG(ERROR) << "Device not found.";
+ } else {
+ // get device versions
+ ptzpro2Device->getDevicesName(&infoMap);
+ std::string name = infoMap[kLogiDeviceVideo];
+ error = ptzpro2Device->getDevicesVersion(&infoMap);
+ if (error) {
+ LOG(ERROR) << "Failed to read device versions.";
+ } else {
+ LOG(INFO) << "Device name: " << name.c_str();
+ printDevicesVersion(infoMap);
+ }
+ }
+ }
+ if (FLAGS_image_version) {
+ // get firmware image version
+ error = ptzpro2Device->getImagesVersion(&infoMap);
+ if (error) {
+ LOG(ERROR) << "Failed to read image versions.";
+ } else {
+ printImagesVersion(infoMap);
+ }
+ }
+ if (FLAGS_update) {
+ // check and perform firmware update
+ if (!ptzpro2Device->isDevicePresent()) {
+ LOG(ERROR) << "Device not found.";
+ } else if (ptzpro2Device->isDeviceUpToDate()) {
+ LOG(INFO) << "Firmware is up to date.";
+ } else {
+ LOG(INFO) << "Updating device ... ";
+ error = ptzpro2Device->performUpdate();
+ if (error) {
+ LOG(ERROR) << "Failed to update device.";
+ } else {
+ // print out new firmware
+ LOG(INFO) << "Done. Updated firmwares successfully.";
+ ptzpro2Device->getDevicesVersion(&infoMap);
+ printDevicesVersion(infoMap);
+ }
+ }
+ }
+ return error;
+}
diff --git a/src/mcu2_device.cc b/src/mcu2_device.cc
new file mode 100644
index 0000000..8ef3dd0
--- /dev/null
+++ b/src/mcu2_device.cc
@@ -0,0 +1,106 @@
+// 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.
+
+#include "mcu2_device.h"
+#include "utilities.h"
+
+constexpr unsigned char kLogiMCU2UvcXuDevInfoVersionCs = 11;
+constexpr unsigned int kLogiMCU2DeviceVersionDataSize = 2;
+// address to read major version from the image buffer
+constexpr unsigned int kLogiMCU2ImageReadAddressMajorVersion = 0x08;
+// address to read minor version from the image buffer
+constexpr unsigned int kLogiMCU2ImageReadAddressMinorVersion = 0x0c;
+// minimum size of image buffer to read versions
+constexpr unsigned int kLogiMCU2ImageReadAddressVersionMinSize =
+ kLogiMCU2ImageReadAddressMinorVersion + 1;
+constexpr unsigned char kLogiMCU2AitInitiateSetMMPData = 2;
+constexpr unsigned char kLogiMCU2AitFinalizeSetMMPData = 3;
+// bypass the 16 bytes information header
+constexpr unsigned int kLogiMCU2AitSendImageInitialOffset = 16;
+
+Mcu2Device::Mcu2Device(std::string pid) : VideoDevice(pid, kLogiDeviceMcu2) {}
+
+Mcu2Device::~Mcu2Device() {
+ closeDevice();
+}
+
+int Mcu2Device::readDeviceVersion(std::string* deviceVersion) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ std::vector<unsigned char> outputData;
+ int unitId = GetUnitID(kLogiGuidDeviceInfo);
+ int error = getXuControl(unitId, kLogiMCU2UvcXuDevInfoVersionCs, &outputData);
+ if (error)
+ return error;
+ if (outputData.size() < kLogiMCU2DeviceVersionDataSize)
+ return kLogiErrorInvalidDeviceVersionDataSize;
+
+ int major = (int)outputData[0];
+ int minor = (int)outputData[1];
+ *deviceVersion = GetDeviceStringVersion(major, minor);
+ return error;
+}
+
+int Mcu2Device::getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion) {
+ if (buffer.empty() || buffer.size() < kLogiMCU2ImageReadAddressVersionMinSize)
+ return kLogiErrorImageBufferReadFailed;
+ int major = buffer[kLogiMCU2ImageReadAddressMajorVersion];
+ int minor = buffer[kLogiMCU2ImageReadAddressMinorVersion];
+ *imageVersion = GetDeviceStringVersion(major, minor);
+ return kLogiErrorNoError;
+}
+
+int Mcu2Device::verifyImage(std::vector<char> buffer) {
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ // no verification here
+ return kLogiErrorNoError;
+}
+
+int Mcu2Device::aitInitiateUpdate() {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // Disclaimer: Any magic numbers come directly from the FlashGordon code.
+
+ // set the MMP
+ std::vector<unsigned char> mmpSetData = {kLogiAitSetMmpCmdFwBurning,
+ kLogiMCU2AitInitiateSetMMPData,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0};
+ return aitInitiateUpdateWithData(mmpSetData);
+}
+
+int Mcu2Device::aitSendImage(std::vector<char> buffer) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ return aitSendImageWithOffset(buffer, kLogiMCU2AitSendImageInitialOffset);
+}
+
+int Mcu2Device::aitFinalizeUpdate() {
+ // Disclaimer: any magic numbers and sleeps originate in the FlashGordon code
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // set MMP
+ std::vector<unsigned char> setMmpData = {kLogiAitSetMmpCmdFwBurning,
+ kLogiMCU2AitFinalizeSetMMPData,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0}; // always 8 bytes
+ return aitFinalizeUpdateWithData(setMmpData);
+}
diff --git a/src/mcu2_device.h b/src/mcu2_device.h
new file mode 100644
index 0000000..d52be8b
--- /dev/null
+++ b/src/mcu2_device.h
@@ -0,0 +1,65 @@
+// 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.
+
+#ifndef SRC_MCU2_DEVICE_H_
+#define SRC_MCU2_DEVICE_H_
+
+#include <stdio.h>
+#include "video_device.h"
+
+/**
+ * Logitech MCU2 device class to handle motor control unit firmware update
+ */
+class Mcu2Device : public VideoDevice {
+ public:
+ /**
+ * @brief Constructor with product id
+ * @param pid Product id string
+ */
+ Mcu2Device(std::string pid);
+ virtual ~Mcu2Device();
+
+ /**
+ * @brief Reads the device version.
+ * @param deviceVersion Output device version string.
+ * @return kLogiErrorNoError if read ok, error code otherwise.
+ */
+ virtual int readDeviceVersion(std::string* deviceVersion);
+
+ /**
+ * @brief Gets the binary image version.
+ * @param buffer The image buffer to read from.
+ * @param imageVersion Output device version string.
+ * @return kLogiErrorNoError if succeeded, error code otherwise.
+ */
+ virtual int getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion);
+
+ /**
+ * @brief Verifies the image to make sure it's secure.
+ * @param buffer The image buffer to verify.
+ * @return kLogiErrorNoError if verified ok, error code otherwise.
+ */
+ virtual int verifyImage(std::vector<char> buffer);
+
+ /**
+ * @brief Initiates the update process for the AIT chip (video device chip).
+ * @return kLogiErrorNoError if initiated ok, error code otherwise.
+ */
+ virtual int aitInitiateUpdate();
+
+ /**
+ * @brief Sends image buffer to the AIT chip (video device chip).
+ * @param buffer The image buffer.
+ * @return kLogiErrorNoError if sent ok, error code otherwise.
+ */
+ virtual int aitSendImage(std::vector<char> buffer);
+
+ /**
+ * @brief Finalizes the update process for the AIT chip (video device chip).
+ * @return kLogiErrorNoError if finalized ok, error code otherwise.
+ */
+ virtual int aitFinalizeUpdate();
+};
+#endif /* SRC_MCU2_DEVICE_H_ */
diff --git a/src/module.mk b/src/module.mk
new file mode 100644
index 0000000..4ee6ab3
--- /dev/null
+++ b/src/module.mk
@@ -0,0 +1,27 @@
+# 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.
+
+include common.mk
+
+BINARY_NAME := logitech-updater
+
+PC_DEPS = libbrillo-$(BASE_VER) libchrome-$(BASE_VER)
+PC_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PC_DEPS))
+PC_LIBS := $(shell $(PKG_CONFIG) --libs $(PC_DEPS))
+LDLIBS += $(PC_LIBS)
+
+CPPFLAGS += $(PC_CFLAGS)
+
+all: CXX_BINARY($(BINARY_NAME))
+
+CXX_BINARY($(BINARY_NAME)): \
+ src/main.o \
+ src/utilities.o \
+ src/usb_device.o \
+ src/video_device.o \
+ src/eeprom_device.o \
+ src/mcu2_device.o \
+ src/composite_device.o
+
+clean: CLEAN(CXX_BINARY($(BINARY_NAME)))
diff --git a/src/usb_device.cc b/src/usb_device.cc
new file mode 100644
index 0000000..23d7ca6
--- /dev/null
+++ b/src/usb_device.cc
@@ -0,0 +1,39 @@
+// 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.
+
+#include "usb_device.h"
+#include <thread>
+#include "utilities.h"
+
+// 500ms to retry reading device version
+constexpr unsigned int kLogiReadDeviceVersionRetryIntervalMs = 500;
+// max retry to read device version
+constexpr unsigned int kLogiReadDeviceVersionMaxRetry = 3;
+
+USBDevice::USBDevice(std::string pid, int type)
+ : usbPid(pid), deviceType(type), fileDescriptor(-1), isOpen(false) {}
+
+USBDevice::~USBDevice() {}
+
+int USBDevice::getDeviceVersion(std::string* deviceVersion) {
+ int error = kLogiErrorUnknown;
+ std::string version;
+ for (int i = 0; i < kLogiReadDeviceVersionMaxRetry; i++) {
+ error = readDeviceVersion(&version);
+ if (!error && CompareVersions(version, "0.0.0") != 0)
+ break;
+
+ // Occasionally, the device is not ready for version reading (just rebooted,
+ // or finalized update process, etc...) version 0.0.0 not actually an
+ // error, it's because internal chipset returns a valid but not correct
+ // version when it's not ready. Wait and retry will solve the problem.
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(kLogiReadDeviceVersionRetryIntervalMs));
+ }
+ if (error)
+ return error;
+
+ *deviceVersion = version;
+ return kLogiErrorNoError;
+}
diff --git a/src/usb_device.h b/src/usb_device.h
new file mode 100644
index 0000000..b13d0c6
--- /dev/null
+++ b/src/usb_device.h
@@ -0,0 +1,140 @@
+// 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.
+
+#ifndef SRC_USB_DEVICE_H_
+#define SRC_USB_DEVICE_H_
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+enum {
+ kLogiErrorNoError = 0,
+ // device errors
+ kLogiErrorUsbPidNotFound,
+ kLogiErrorMultipleDevicesFound,
+ kLogiErrorDeviceNotOpen,
+ kLogiErrorOpenDeviceFailed,
+ kLogiErrorDeviceRebootFailed,
+ kLogiErrorInvalidDeviceVersionDataSize,
+ // binary image errors
+ kLogiErrorImageBufferReadFailed,
+ kLogiErrorImageVersionNotFound,
+ kLogiErrorImageVersionExceededMaxSize,
+ kLogiErrorImageVerificationFailed,
+ kLogiErrorImageBurningFinalizeFailed,
+ kLogiErrorParseS19BinaryFailed,
+ kLogiErrorReadS19ImageByteFailed,
+ kLogiErrorSendImageFailed,
+ // I/O operation errors
+ kLogiErrorXuUnitIdInvalid,
+ kLogiErrorVideoDeviceAitInitiateGetDataFailed,
+ kLogiErrorIOControlOperationFailed,
+ kLogiErrorUnknown
+};
+
+enum {
+ kLogiDeviceGeneric = 0,
+ kLogiDeviceVideo,
+ kLogiDeviceEeprom,
+ kLogiDeviceMcu2
+};
+
+/**
+ * Generic Logitech usb device class containing common usb device information
+ * and firmware updating processes.
+ */
+class USBDevice {
+ public:
+ std::string usbPid; // USB product id string.
+ int deviceType; // Device type: video, eeprom, mcu2 or other type. This is
+ // convenient to get the versions/name from composite device.
+
+ protected:
+ int fileDescriptor;
+ bool isOpen;
+
+ protected:
+ /**
+ * @brief Constructor with product id.
+ * @param pid Product id string.
+ * @param type Device type.
+ */
+ USBDevice(std::string pid, int type);
+
+ public:
+ virtual ~USBDevice();
+
+ /**
+ * @brief Gets the device version. Implement readDeviceVersion to read the
+ * device version.
+ * @param deviceVersion Output device version string.
+ * @return kLogiErrorNoError if failed, error code otherwise.
+ */
+ virtual int getDeviceVersion(std::string* deviceVersion);
+
+ /**
+ * @brief Finds the device with usbPid & usbVid. To be implemented by
+ * subclass.
+ * @return list of device paths.
+ */
+ virtual std::vector<std::string> findDevices() = 0;
+
+ /**
+ * @brief Opens the device. To be implemented by subclass.
+ * @return kLogiErrorNoError if open ok, error code otherwise.
+ */
+ virtual int openDevice() = 0;
+
+ /**
+ * @brief Closes the device. To be implemented by subclass.
+ */
+ virtual void closeDevice() = 0;
+
+ /**
+ * @brief Gets the device name. To be implemented by subclass
+ * @param deviceName Output device name.
+ * @return kLogiErrorNoError if gets ok, error code otherwise.
+ */
+ virtual int getDeviceName(std::string* deviceName) = 0;
+
+ /**
+ * @brief Gets the binary image version. To be implemented by subclass
+ * @param buffer The image buffer to read from
+ * @param imageVersion Output device version string.
+ * @return kLogiErrorNoError if gets ok, error code otherwise.
+ */
+ virtual int getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion) = 0;
+
+ /**
+ * @brief Verifies the image to make sure it's secure. To be implemented by
+ * subclass.
+ * @param buffer The image buffer to verify.
+ * @return kLogiErrorNoError if verified ok, error code otherwise.
+ */
+ virtual int verifyImage(std::vector<char> buffer) = 0;
+
+ /**
+ * @brief Reboots the device. To be implemented by subclass
+ * @return kLogiErrorNoError if rebooted ok, error code otherwise.
+ */
+ virtual int rebootDevice() = 0;
+
+ /**
+ * @brief Performs update. To be implemented by subclass.
+ * @param buffer Firmware image buffer.
+ * @return kLogiErrorNoError if update ok, error code otherwise.
+ */
+ virtual int performUpdate(std::vector<char> buffer) = 0;
+
+ protected:
+ /**
+ * @brief Reads the device version. To be implemented by subclass.
+ * @param deviceVersion Output device version string.
+ * @return kLogiErrorNoError if gets ok, error code otherwise.
+ */
+ virtual int readDeviceVersion(std::string* deviceVersion) = 0;
+};
+#endif /* SRC_USB_DEVICE_H_ */
diff --git a/src/utilities.cc b/src/utilities.cc
new file mode 100644
index 0000000..a1c3b0d
--- /dev/null
+++ b/src/utilities.cc
@@ -0,0 +1,148 @@
+// 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.
+
+#include "utilities.h"
+#include <fstream>
+#include <sstream>
+#include "base/files/dir_reader_posix.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+
+constexpr unsigned int kLogiVendorID = 0x046D;
+constexpr unsigned int kDefaultVersionComponentCount =
+ 3; // major, minor, build
+constexpr unsigned int kLogiUnitIdCameraVersion = 8;
+constexpr unsigned int kLogiUnitIdAccessMmp = 6;
+constexpr unsigned int kLogiUnitIdTestDebug = 9;
+constexpr unsigned int kLogiUnitIdPeripheralControl = 11;
+
+bool GetDirectoryContents(std::string directory,
+ std::vector<std::string>* contents) {
+ base::DirReaderPosix reader(directory.c_str());
+ if (!reader.IsValid())
+ return false;
+
+ while (reader.Next())
+ (*contents).push_back(std::string(reader.name()));
+ return true;
+}
+
+bool IsLogitechVendorID(std::string vendorID) {
+ if (vendorID.empty())
+ return false;
+
+ int vidNum;
+
+ if (!ConvertHexStringToInt(vendorID, &vidNum))
+ return false;
+ return (vidNum == kLogiVendorID);
+}
+
+bool ReadFileContent(std::string filepath, std::string* output) {
+ if (filepath.empty())
+ return false;
+
+ base::File::Info info;
+ base::FilePath inputFilePath(filepath.c_str());
+ GetFileInfo(inputFilePath, &info);
+ if (info.size <= 0)
+ return false;
+
+ char* buffer = new char[info.size];
+ bool result = ReadFile(inputFilePath, buffer, info.size);
+ *output = buffer;
+ delete[] buffer;
+ return result;
+}
+
+bool ConvertHexStringToInt(std::string hexString, int* outputValue) {
+ if (hexString.empty())
+ return false;
+ std::stringstream stringStream;
+ stringStream << std::hex << hexString;
+ unsigned int hexNumber;
+ stringStream >> hexNumber;
+ if (outputValue)
+ *outputValue = hexNumber;
+ return true;
+}
+
+bool ConvertHexCharToUnsignedInt(const char c, uint8_t* outputValue) {
+ if (!isxdigit(c))
+ return false;
+ char value = base::HexDigitToInt(c);
+ *outputValue = value;
+ return true;
+}
+
+std::string GetDeviceStringVersion(int major, int minor, int build) {
+ std::stringstream stringStream;
+ stringStream << major << "." << minor << "." << build;
+ return stringStream.str();
+}
+
+std::string GetDeviceStringVersion(int major, int minor) {
+ std::stringstream stringStream;
+ stringStream << major << "." << minor;
+ return stringStream.str();
+}
+
+std::vector<char> ReadBinaryFileContent(std::string filepath) {
+ std::vector<char> contents;
+ if (filepath.empty())
+ return contents;
+
+ std::ifstream input(filepath.c_str(), std::ios::binary);
+ if (!input.is_open())
+ return contents;
+
+ std::vector<char> buffer((std::istreambuf_iterator<char>(input)),
+ (std::istreambuf_iterator<char>()));
+ return buffer;
+}
+
+int CompareVersions(std::string version1, std::string version2) {
+ std::vector<std::string> firstTokens = SplitString(version1, ".");
+ std::vector<std::string> secondTokens = SplitString(version2, ".");
+
+ // case major.minor, set the build to 0 so we can compare easily
+ while (firstTokens.size() < kDefaultVersionComponentCount)
+ firstTokens.push_back("0");
+ while (secondTokens.size() < kDefaultVersionComponentCount)
+ secondTokens.push_back("0");
+
+ for (int i = 0; i < kDefaultVersionComponentCount; i++) {
+ int firstVersion;
+ int secondVersion;
+ base::StringToInt(firstTokens[i], &firstVersion);
+ base::StringToInt(secondTokens[i], &secondVersion);
+ if (firstVersion > secondVersion)
+ return 1;
+ if (firstVersion < secondVersion)
+ return -1;
+ }
+ return 0;
+}
+
+std::vector<std::string> SplitString(std::string string,
+ std::string delimiters) {
+ return base::SplitString(
+ string, delimiters, base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+}
+
+int GetUnitID(std::string guid) {
+ if (guid.compare(kLogiGuidDeviceInfo) == 0)
+ return kLogiUnitIdCameraVersion;
+ if (guid.compare(kLogiGuidAITCustom) == 0)
+ return kLogiUnitIdAccessMmp;
+ if (guid.compare(kLogiGuidTestDebug) == 0)
+ return kLogiUnitIdTestDebug;
+ if (guid.compare(kLogiGuidPeripheralControl) == 0)
+ return kLogiUnitIdPeripheralControl;
+ return -1;
+}
diff --git a/src/utilities.h b/src/utilities.h
new file mode 100644
index 0000000..86af7e4
--- /dev/null
+++ b/src/utilities.h
@@ -0,0 +1,106 @@
+// 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.
+
+#ifndef SRC_UTILITIES_H_
+#define SRC_UTILITIES_H_
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+const char kLogiGuidDeviceInfo[] = "69678ee4-410f-40db-a850-7420d7d8240e";
+const char kLogiGuidAITCustom[] = "23e49ed0-1178-4f31-ae52-d2fb8a8d3b48";
+const char kLogiGuidTestDebug[] = "1f5d4ca9-de11-4487-840d-50933c8ec8d1";
+const char kLogiGuidPeripheralControl[] =
+ "ffe52d21-8030-4e2c-82d9-f587d00540bd";
+
+/**
+ * @brief Gets contents of the directory.
+ * @param directory The directory path to get contents.
+ * @param contents Ouput vector of the content paths.
+ * @return true if succeeded, false otherwise.
+ */
+bool GetDirectoryContents(std::string directory,
+ std::vector<std::string>* contents);
+
+/**
+ * @brief Checks if VendorID belongs to Logitech.
+ * @param vendorID The vendorID string to check against.
+ * @return true if it's Logitech, false otherwise.
+ */
+bool IsLogitechVendorID(std::string vendorID);
+
+/**
+ * @brief Reads file contents.
+ * @param filepath The file path to read.
+ * @param output Output string from reading.
+ * @return true if read ok, false otherwise.
+ */
+bool ReadFileContent(std::string filepath, std::string* output);
+
+/**
+ * @brief Converts hex string to int.
+ * @param hexString The input hex string.
+ * @param outputValue The output int value.
+ * @return false if convert failed, true otherwise.
+ */
+bool ConvertHexStringToInt(std::string hexString, int* outputValue);
+
+/**
+ * @brief Converts hex char to unsigned int.
+ * @param c The hex char to be converted.
+ * @param outputValue Output unsigned int value.
+ * @return false if convert failed, true otherwise.
+ */
+bool ConvertHexCharToUnsignedInt(const char c, uint8_t* outputValue);
+
+/**
+ * @brief Gets the device string version.
+ * @param major major version number.
+ * @param minor minor version number.
+ * @param build build version number.
+ * @reutrn std::string device version number.
+ */
+std::string GetDeviceStringVersion(int major, int minor, int build);
+
+/**
+ * @brief Gets the device string version
+ * @param major major version number.
+ * @param minor minor version number.
+ * @reutrn std::string device version number.
+ */
+std::string GetDeviceStringVersion(int major, int minor);
+
+/**
+ * @brief Reads binary file content to buffer.
+ * @param filepath Path to the binary file.
+ * @return buffer from reading. Buffer is empty if read failed.
+ */
+std::vector<char> ReadBinaryFileContent(std::string filepath);
+
+/**
+ * @brief Compares 2 version strings in format major.minor.build or major.minor.
+ * @param version1 The version string 1.
+ * @param version2 The version string 2.
+ * @return 0 equal, -1 if version1 < version2 or 1 if version1 > version2.
+ */
+int CompareVersions(std::string version1, std::string version2);
+
+/**
+ * @brief Splits the string with delimiter into vector.
+ * @param string String to be split.
+ * @param delimiters The delimiter characters.
+ * @return split string vector.
+ */
+std::vector<std::string> SplitString(std::string string,
+ std::string delimiters);
+
+/**
+ * @brief Gets the unit id from guid.
+ * @param guid GUID string.
+ * @return unit id or -1 if cannot find.
+ */
+int GetUnitID(std::string guid);
+
+#endif /* SRC_UTILITIES_H_ */
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..0d3a9c8
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,10 @@
+// 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.
+
+#ifndef SRC_VERSION_H_
+#define SRC_VERSION_H_
+
+const char kLogiBinaryVersion[] = "1.0.0";
+
+#endif /* SRC_VERSION_H_ */
diff --git a/src/video_device.cc b/src/video_device.cc
new file mode 100644
index 0000000..35d9ad9
--- /dev/null
+++ b/src/video_device.cc
@@ -0,0 +1,490 @@
+// 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.
+
+#include "video_device.h"
+#include <fcntl.h>
+#include <linux/usb/video.h>
+#include <linux/uvcvideo.h>
+#include <linux/videodev2.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <algorithm>
+#include <thread>
+#include "utilities.h"
+
+const char kDefaultVideoDeviceMountPoint[] = "/sys/class/video4linux";
+constexpr unsigned int kLogiDeviceVersionDataSize = 4;
+constexpr unsigned char kLogiCameraVersionSelector = 1;
+constexpr unsigned int kLogiVideoImageVersionMaxSize = 32;
+constexpr unsigned char kLogiVideoAitInitiateSetMMPData = 1;
+constexpr unsigned char kLogiVideoAitFinalizeSetMMPData = 1;
+constexpr unsigned int kDefaultUvcGetLenQueryControlSize =
+ 2; // 2 byte for get len query
+
+constexpr unsigned int kLogiVideoAitSendImageInitialOffset = 0;
+constexpr unsigned int kLogiDefaultAitSleepIntervalMs = 2000;
+constexpr unsigned char kLogiUvcXuAitCustomCsGetMmpResult = 0x05;
+constexpr unsigned char kLogiUvcXuAitCustomCsSetMmp = 0x04;
+constexpr unsigned char kLogiUvcXuAitCustomCsSetFwData = 0x03;
+constexpr unsigned char kLogiUvcXuAitCustomReboot = 0x11;
+constexpr unsigned int kLogiDefaultAitFinalizeMaxPollingDurationMs =
+ 120000; // when finalize Ait, max polling duration is 120s
+constexpr unsigned int kLogiDefaultAitFirstPassSleepIntervalMs = 8000;
+constexpr unsigned char kLogiDefaultAitSuccessValue = 0x00;
+constexpr unsigned char kLogiDefaultAitFailureValue = 0x82;
+constexpr unsigned int kLogiDefaultImageBlockSize =
+ 32; // 32 byte size for updating each trunk
+
+VideoDevice::VideoDevice(std::string pid) : USBDevice(pid, kLogiDeviceVideo) {}
+
+VideoDevice::VideoDevice(std::string pid, int type) : USBDevice(pid, type) {}
+
+VideoDevice::~VideoDevice() {
+ closeDevice();
+}
+
+std::vector<std::string> VideoDevice::findDevices() {
+ std::vector<std::string> devicePaths;
+
+ if (usbPid.empty())
+ return devicePaths;
+
+ std::vector<std::string> contents;
+ bool getOk = GetDirectoryContents(kDefaultVideoDeviceMountPoint, &contents);
+ if (!getOk)
+ return devicePaths; // failed to get contents of mount point
+
+ for (auto const& content : contents) {
+ if (content.compare(".") == 0 || content.compare("..") == 0)
+ continue; // ignore . and .. directory
+
+ std::string videoDir =
+ std::string(kDefaultVideoDeviceMountPoint) + "/" + content;
+
+ // read mount point device vid & pid here
+ std::string productIdPath = videoDir + "/device/../idProduct";
+ std::string vendorIdPath = videoDir + "/device/../idVendor";
+
+ std::string vendorID;
+ std::string productID;
+ if (!ReadFileContent(vendorIdPath, &vendorID))
+ continue; // failed to read vendor ID
+
+ if (!IsLogitechVendorID(vendorID))
+ continue; // not Logitech device
+
+ if (!ReadFileContent(productIdPath, &productID))
+ continue; // failed to read product ID
+
+ int pidNum = 0;
+ if (!ConvertHexStringToInt(productID, &pidNum))
+ continue; // failed to convert product ID to int
+
+ int usbPidNum = 0;
+ if (!ConvertHexStringToInt(usbPid, &usbPidNum))
+ continue;
+
+ if (pidNum == usbPidNum) {
+ // found device, add it to the device paths
+ std::string devPath = "/dev/" + content;
+ devicePaths.push_back(devPath);
+ }
+ }
+ return devicePaths;
+}
+
+int VideoDevice::openDevice() {
+ if (usbPid.empty())
+ return kLogiErrorUsbPidNotFound;
+ std::vector<std::string> devPaths = findDevices();
+ if (devPaths.size() == 0)
+ return kLogiErrorUsbPidNotFound;
+ if (devPaths.size() > 1)
+ return kLogiErrorMultipleDevicesFound;
+ const char* devPath = devPaths.at(0).c_str();
+ int fd = open(devPath, O_RDWR | O_NONBLOCK, 0);
+ if (fd == -1) // open() return -1 if failed....
+ return kLogiErrorOpenDeviceFailed;
+ // everything is ok
+ isOpen = true;
+ fileDescriptor = fd;
+ return kLogiErrorNoError;
+}
+
+void VideoDevice::closeDevice() {
+ if (fileDescriptor >= 0)
+ close(fileDescriptor);
+
+ fileDescriptor = -1;
+ isOpen = false;
+}
+
+int VideoDevice::getDeviceName(std::string* deviceName) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // get device name here
+ struct v4l2_capability videoCap;
+ int error = ioctl(fileDescriptor, VIDIOC_QUERYCAP, &videoCap);
+ if (error < 0)
+ return kLogiErrorIOControlOperationFailed;
+ std::string str(
+ videoCap.card,
+ videoCap.card + sizeof(videoCap.card) / sizeof(videoCap.card[0]));
+
+ *deviceName = str;
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::readDeviceVersion(std::string* deviceVersion) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+ std::vector<unsigned char> outputData;
+ int unitId = GetUnitID(kLogiGuidDeviceInfo);
+ int error = getXuControl(unitId, kLogiCameraVersionSelector, &outputData);
+ if (error)
+ return error;
+ if (outputData.size() < kLogiDeviceVersionDataSize)
+ return kLogiErrorInvalidDeviceVersionDataSize;
+
+ // little-endian data
+ int major = (int)outputData[1];
+ int minor = (int)outputData[0];
+ int build = (int)((outputData[3] << 8) | outputData[2]);
+ *deviceVersion = GetDeviceStringVersion(major, minor, build);
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion) {
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ // Find the start of the version string.
+ // The version information is located at an arbitrary position coded in the
+ // following format: VERSIONMAGIC<x.y.z> As a sanity check the length of the
+ // version "x.y.z" string may not exceed 32 characters.
+ std::vector<uint8_t> versionMagic = {
+ 'V', 'E', 'R', 'S', 'I', 'O', 'N', 'M', 'A', 'G', 'I', 'C', '<'};
+ auto iter = std::search(std::begin(buffer),
+ std::end(buffer),
+ std::begin(versionMagic),
+ std::end(versionMagic));
+ if (iter == std::end(buffer))
+ return kLogiErrorImageVersionNotFound;
+
+ // Copy all characters until the closing '>' marker.
+ std::string version;
+ std::advance(iter, versionMagic.size());
+ while (iter != std::end(buffer) && *iter != '>') {
+ version.push_back(*iter);
+ if (version.size() > kLogiVideoImageVersionMaxSize)
+ return kLogiErrorImageVersionExceededMaxSize;
+ ++iter;
+ }
+
+ *imageVersion = version;
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::verifyImage(std::vector<char> buffer) {
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+ std::vector<char> signature = {'A', 'I', 'T', '8', '4', '2'};
+ if (buffer.size() < signature.size())
+ return kLogiErrorImageVerificationFailed;
+ auto iter = std::search(std::begin(buffer),
+ std::begin(buffer) + signature.size(),
+ std::begin(signature),
+ std::end(signature));
+ if (iter != std::begin(buffer))
+ return kLogiErrorImageVerificationFailed;
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::aitInitiateUpdate() {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // Disclaimer: Any magic numbers come directly from the FlashGordon code.
+
+ // set the MMP
+ std::vector<unsigned char> mmpSetData = {kLogiAitSetMmpCmdFwBurning,
+ 0,
+ 0,
+ kLogiVideoAitInitiateSetMMPData,
+ 0,
+ 0,
+ 0,
+ 0};
+ return aitInitiateUpdateWithData(mmpSetData);
+}
+
+int VideoDevice::aitSendImage(std::vector<char> buffer) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ return aitSendImageWithOffset(buffer, kLogiVideoAitSendImageInitialOffset);
+}
+
+int VideoDevice::aitFinalizeUpdate() {
+ // Disclaimer: any magic numbers and sleeps originate in the FlashGordon code.
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // set MMP
+ std::vector<unsigned char> setData = {kLogiAitSetMmpCmdFwBurning,
+ kLogiVideoAitFinalizeSetMMPData,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0}; // always 8 bytes
+ return aitFinalizeUpdateWithData(setData);
+}
+
+int VideoDevice::rebootDevice() {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ int unitId = GetUnitID(kLogiGuidPeripheralControl);
+ std::vector<unsigned char> data = {1};
+ return setXuControl(unitId, kLogiUvcXuAitCustomReboot, data);
+}
+
+int VideoDevice::aitInitiateUpdateWithData(std::vector<unsigned char> mmpData) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // Disclaimer: Any magic numbers come directly from the FlashGordon code.
+
+ // set the MMP
+ int unitId = GetUnitID(kLogiGuidAITCustom);
+ int error = setXuControl(unitId, kLogiUvcXuAitCustomCsSetMmp, mmpData);
+ if (error)
+ return error;
+
+ // get the MMP
+ std::vector<unsigned char> mmpGetData;
+ error = getXuControl(unitId, kLogiUvcXuAitCustomCsGetMmpResult, &mmpGetData);
+ if (error)
+ return error;
+ if (mmpGetData.empty() || mmpGetData[0] != 0)
+ return kLogiErrorVideoDeviceAitInitiateGetDataFailed;
+
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::aitSendImageWithOffset(std::vector<char> buffer,
+ unsigned int offset) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+ if (buffer.empty())
+ return kLogiErrorImageBufferReadFailed;
+
+ int unitId = GetUnitID(kLogiGuidAITCustom);
+ unsigned int remainingSize = buffer.size() - offset;
+ while (remainingSize > 0) {
+ // get the blockSize
+ unsigned int blockSize =
+ std::min<unsigned int>(remainingSize, kLogiDefaultImageBlockSize);
+
+ // prepare the blockData
+ std::vector<unsigned char> data(kLogiDefaultImageBlockSize);
+ std::fill(std::begin(data), std::end(data), 0);
+ std::copy(buffer.cbegin() + offset,
+ buffer.cbegin() + offset + blockSize,
+ std::begin(data));
+
+ int error = setXuControl(unitId, kLogiUvcXuAitCustomCsSetFwData, data);
+ if (error)
+ return error;
+ offset += blockSize;
+ remainingSize -= blockSize;
+ }
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::aitFinalizeUpdateWithData(std::vector<unsigned char> mmpData) {
+ // Disclaimer: any magic numbers and sleeps originate in the FlashGordon code.
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ // set MMP
+ int unitId = GetUnitID(kLogiGuidAITCustom);
+ int error = setXuControl(unitId, kLogiUvcXuAitCustomCsSetMmp, mmpData);
+ if (error)
+ return error;
+
+ // get MMP data
+ // Poll until the device returns either success or failure.
+ auto durationMs = 0;
+ const auto doSleep = [&](int ms) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(ms));
+ durationMs += ms;
+ };
+
+ // This is the way AIT chipset is finalized (based on AIT finalizing method).
+ // Try to poll for burning fw result or return failure if hit max polling
+ // duration.
+ for (int pass = 0;; pass++) {
+ std::vector<unsigned char> data;
+ int attempts = 3;
+
+ // Sometimes on a very fast cpu, AIT chip returns error when querying
+ // extension control unit on first time. If fails, attempt to retry for 3
+ // times before returning error.
+ do {
+ error = getXuControl(unitId, kLogiUvcXuAitCustomCsGetMmpResult, &data);
+ if (error)
+ std::this_thread::sleep_for(
+ std::chrono::milliseconds(kLogiDefaultAitSleepIntervalMs));
+ attempts--;
+ } while (attempts > 0 && !error);
+
+ if (error || data.empty())
+ return kLogiErrorImageBurningFinalizeFailed;
+
+ // process the response
+ if (data.size() > 0) {
+ if (data[0] == kLogiDefaultAitSuccessValue) {
+ // If there is not positive response from the device that the flash was
+ // written estimate the time needed plus a time buffer to determine
+ // when the flash write process has completed. In this case, time
+ // buffer is about 8s for the flash write operation to complete This is
+ // done using both device spec. and stress testings.
+ if (pass == 0)
+ doSleep(kLogiDefaultAitFirstPassSleepIntervalMs);
+ break;
+ } else if (data[0] == kLogiDefaultAitFailureValue)
+ return kLogiErrorImageBurningFinalizeFailed; // Failed to finalize the
+ // image burning.
+ }
+ if (durationMs > kLogiDefaultAitFinalizeMaxPollingDurationMs) {
+ // In case device never returns 0x82 or 0x00, bail out.
+ return kLogiErrorImageBurningFinalizeFailed;
+ }
+ }
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::performUpdate(std::vector<char> buffer) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ std::string deviceVersion;
+ std::string imageVersion;
+ int error = getDeviceVersion(&deviceVersion);
+ if (error)
+ return error;
+
+ error = verifyImage(buffer);
+ if (error)
+ return error;
+
+ error = getImageVersion(buffer, &imageVersion);
+ if (error)
+ return error;
+
+ // check if update available
+ int compareVersion = CompareVersions(deviceVersion, imageVersion);
+ if (compareVersion < 0) {
+ // initiate AIT update
+ error = aitInitiateUpdate();
+ if (error)
+ return error;
+
+ // send image
+ error = aitSendImage(buffer);
+ if (error)
+ return error;
+
+ // finalize AIT update
+ error = aitFinalizeUpdate();
+ }
+ return error;
+}
+
+int VideoDevice::setXuControl(unsigned char unitId,
+ unsigned char controlSelector,
+ std::vector<unsigned char> data) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ if (unitId < 0)
+ return kLogiErrorXuUnitIdInvalid;
+
+ struct uvc_xu_control_query controlQuery;
+ controlQuery.unit = static_cast<uint8_t>(unitId);
+ controlQuery.selector = static_cast<uint8_t>(controlSelector);
+ controlQuery.query = UVC_SET_CUR;
+ controlQuery.size = data.size();
+ controlQuery.data = data.data();
+ // A few ioctl requests use return value as an output parameter
+ // and return a nonnegative value on success, so should check
+ // for real error before returning.
+ int error = ioctl(fileDescriptor, UVCIOC_CTRL_QUERY, &controlQuery);
+ if (error < 0)
+ return kLogiErrorIOControlOperationFailed;
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::getXuControl(unsigned char unitId,
+ unsigned char controlSelector,
+ std::vector<unsigned char>* data) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ if (unitId < 0)
+ return kLogiErrorXuUnitIdInvalid;
+
+ int dataLen;
+ int error = queryDataSize(unitId, controlSelector, &dataLen);
+ if (error)
+ return error;
+ uint8_t queryData[dataLen];
+ memset(queryData, '\0', sizeof(queryData));
+
+ struct uvc_xu_control_query controlQuery;
+ controlQuery.unit = unitId;
+ controlQuery.selector = controlSelector;
+ controlQuery.query = UVC_GET_CUR;
+ controlQuery.size = dataLen;
+ controlQuery.data = queryData;
+ error = ioctl(fileDescriptor, UVCIOC_CTRL_QUERY, &controlQuery);
+ if (error < 0)
+ return kLogiErrorIOControlOperationFailed;
+
+ for (int i = 0; i < dataLen; i++) {
+ data->push_back(queryData[i]);
+ }
+ return kLogiErrorNoError;
+}
+
+int VideoDevice::queryDataSize(unsigned char unitId,
+ unsigned char controlSelector,
+ int* dataSize) {
+ if (!isOpen)
+ return kLogiErrorDeviceNotOpen;
+
+ uint8_t sizeData[kDefaultUvcGetLenQueryControlSize];
+ struct uvc_xu_control_query sizeQuery;
+ sizeQuery.unit = unitId;
+ sizeQuery.selector = controlSelector;
+ sizeQuery.query = UVC_GET_LEN;
+ sizeQuery.size = kDefaultUvcGetLenQueryControlSize;
+ sizeQuery.data = sizeData;
+ int error = ioctl(fileDescriptor, UVCIOC_CTRL_QUERY, &sizeQuery);
+ if (error < 0)
+ return kLogiErrorIOControlOperationFailed;
+
+ // convert the data byte to int
+ int size = (sizeData[1] << 8) | (sizeData[0]);
+ *dataSize = size;
+ return kLogiErrorNoError;
+}
diff --git a/src/video_device.h b/src/video_device.h
new file mode 100644
index 0000000..db59f36
--- /dev/null
+++ b/src/video_device.h
@@ -0,0 +1,176 @@
+// 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.
+
+#ifndef SRC_VIDEO_DEVICE_H_
+#define SRC_VIDEO_DEVICE_H_
+
+#include <stdio.h>
+#include "usb_device.h"
+
+constexpr unsigned char kLogiAitSetMmpCmdFwBurning = 0x01;
+
+/**
+ * Logitech video device class to handle video firmware update.
+ */
+class VideoDevice : public USBDevice {
+ protected:
+ /**
+ * @brief Constructor with product id and device type.
+ * @param pid Product id string.
+ * @param type Device type.
+ */
+ VideoDevice(std::string pid, int type);
+
+ public:
+ /**
+ * @brief Constructor with product id.
+ * @param pid Product id string.
+ */
+ VideoDevice(std::string pid);
+
+ virtual ~VideoDevice();
+
+ /**
+ * @brief Finds the device with usbPid & usbVid.
+ * @return list of device paths. If list count > 0, there are multiple
+ * devices.
+ */
+ virtual std::vector<std::string> findDevices();
+
+ /**
+ * @brief Opens the device.
+ * @return kLogiErrorNoError if opened ok, error code otherwise.
+ */
+ virtual int openDevice();
+
+ /**
+ * @brief Closes the device.
+ */
+ virtual void closeDevice();
+
+ /**
+ * @brief Gets the device name.
+ * @param deviceName Output device name.
+ * @return kLogiErrorNoError if succeeded, error code otherwise.
+ */
+ virtual int getDeviceName(std::string* deviceName);
+
+ /**
+ * @brief Gets the binary image version.
+ * @param buffer The image buffer to read from.
+ * @param imageVersion Output device version string.
+ * @return kLogiErrorNoError if succeeded, error code otherwise.
+ */
+ virtual int getImageVersion(std::vector<char> buffer,
+ std::string* imageVersion);
+
+ /**
+ * @brief Verifies the image to make sure it has the secure signature.
+ * @param buffer The image buffer to verify.
+ * @return kLogiErrorNoError if verified ok, error code otherwise.
+ */
+ virtual int verifyImage(std::vector<char> buffer);
+
+ /**
+ * @brief Reads the device version.
+ * @param deviceVersion Output device version string.
+ * @return kLogiErrorNoError if read ok, error code otherwise.
+ */
+ virtual int readDeviceVersion(std::string* deviceVersion);
+
+ /**
+ * @brief Performs firmware update.
+ * @param buffer Firmware image buffer.
+ * @return kLogiErrorNoError if updated ok, error code otherwise.
+ */
+ virtual int performUpdate(std::vector<char> buffer);
+
+ /**
+ * @brief Reboots the device.
+ * @return kLogiErrorNoError if rebooted ok, error code otherwise.
+ */
+ virtual int rebootDevice();
+
+ protected:
+ /**
+ * @brief Initiates the update process for the AIT chip (video device chip).
+ * @return kLogiErrorNoError if initiated ok, error code otherwise.
+ */
+ virtual int aitInitiateUpdate();
+
+ /**
+ * @brief Sends image buffer to the AIT chip (video device chip).
+ * @param buffer The image buffer
+ * @return kLogiErrorNoError if sent ok, error code otherwise.
+ */
+ virtual int aitSendImage(std::vector<char> buffer);
+
+ /**
+ * @brief Finalizes the update process for the AIT chip (video device chip).
+ * @return kLogiErrorNoError if finalized ok, error code otherwise.
+ */
+ virtual int aitFinalizeUpdate();
+
+ /**
+ * @brief Initiates the update process for the AIT chip (video device chip).
+ * @param mmpData The data for setting initiation.
+ * @return kLogiErrorNoError if initiated ok, error code otherwise.
+ */
+ int aitInitiateUpdateWithData(std::vector<unsigned char> mmpData);
+
+ /**
+ * @brief Sends image buffer to the AIT chip (video device chip).
+ * @param buffer The image buffer.
+ * @param offset The initial offset when send image.
+ * @return kLogiErrorNoError if sent ok, error code otherwise.
+ */
+ int aitSendImageWithOffset(std::vector<char> buffer, unsigned int offset);
+
+ /**
+ * @brief Finalizes the update process for the AIT chip (video device chip).
+ * @param mmpData The data for setting finalization.
+ * @return kLogiErrorNoError if finalized ok, error code otherwise.
+ */
+ int aitFinalizeUpdateWithData(std::vector<unsigned char> mmpData);
+
+ public:
+ /**
+ * @brief Sets data to the extension control unit (XU).
+ * @param unitId XU unit id.
+ * @param controlSelector XU control selector.
+ * @param data XU data to be set.
+ * @return kLogiErrorNoError if succeeded, error otherwise.
+ */
+ int setXuControl(unsigned char unitId,
+ unsigned char controlSelector,
+ std::vector<unsigned char> data);
+
+ /**
+ * @brief Gets data from the extension control unit (XU).
+ * @param unitId XU unit id.
+ * @param controlSelector XU control selector.
+ * @param data XU data output.
+ * @return kLogiErrorNoError if succeeded, error otherwise.
+ */
+ int getXuControl(unsigned char unitId,
+ unsigned char controlSelector,
+ std::vector<unsigned char>* data);
+
+ private:
+ /**
+ * @brief Control query data size needs to be known before querying.
+ * Therefore, UVC_GET_LEN needs to be queried for the size first.
+ * UVC_GET_LEN query returns 2 bytes of data in little endian. Refers to
+ * https://linuxtv.org/downloads/v4l-dvb-api/v4l-drviers/uvcvideo.html for
+ * more info.
+ * @param unitID XU unit id.
+ * @param controlSelector XU control selector.
+ * @param dataSize Data size output.
+ * @return kLogiErrorNoError if succeeded, error code otherwise.
+ */
+ int queryDataSize(unsigned char unitId,
+ unsigned char controlSelector,
+ int* dataSize);
+};
+#endif /* SRC_VIDEO_DEVICE_H_ */