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_ */