Porting Chameleon package to chrome os devices

This primarily includes the following tasks:
- Deploy a chameleond config file as an upstart service.
  Do not use update-rc.d on chrome os devices.
- Detect if the machine is already in USB host mode.
  No need to enable USB OTG driver in that case.
- In the fpga_tio driver, the instantiation of various devices is
  performed in a lazy manner according to the platform, chromeos
  or fpga.
- The memory singletons are instantiated in a lazy manner too.
  Otherwise, the system may crash due to the incompatibility of
  different platforms.

BUG=chromium:859796
TEST=Perform the following steps.

Install "make" on the chrome os device as "make" is required to
setup the package on the device.
(cr) $ emerge-${BOARD} make
     where ${BOARD} represents the chromeos board replacing
     chameleon board.
(cr) $ cros deploy "$CHROMEOS_IP" make
     where ${CHROMEOS_IP} represents the IP address of the
     chrome os device replacing chameleon board.

Next step is to make a tarball of the chameleon package and
install it on the chrome os device. This will install a few more
packages at the first time, including setuptools, pip, and wheel.
(cr) $ make
(cr) $ make remote-install CHAMELEON_HOST="$CHROMEOS_IP"

Run a bluetooth test to verify that the package is installed properly.
Remember to insert an RN42 kit to the chrome os device.
(cr) test_that --autotest_dir ~/trunk/src/third_party/autotest/files/
     --args "chameleon_host=${CHROMEOS_IP}" "${DUT_IP}"
     bluetooth_AdapterPairing.mouse
where DUT_IP is the chromebook under test.

Also verify that the patch works well on a chameleon board.

Change-Id: Ia5dd9aecf02be1bb2aefe4135311f19e04d86057
Reviewed-on: https://chromium-review.googlesource.com/1125650
Commit-Ready: Shyh-In Hwang <josephsih@chromium.org>
Tested-by: Shyh-In Hwang <josephsih@chromium.org>
Reviewed-by: Wai-Hong Tam <waihong@google.com>
diff --git a/MANIFEST.in b/MANIFEST.in
index 25af1d7..1f47e69 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -3,6 +3,7 @@
 include bin/*
 include deploy/deploy
 include deploy/deploy_pip
+include deploy/init/chameleond.conf
 include deploy/init.d/chameleond
 include deploy/init.d/displayd
 include deploy/init.d/scheduler
diff --git a/Makefile b/Makefile
index 9df5b51..287f0a6 100644
--- a/Makefile
+++ b/Makefile
@@ -11,9 +11,13 @@
 BINDIR := ./bin
 SRCDIR := ./src
 DISTDIR := ./dist
+CONFDIR := /etc/init
+INITDIR := ./deploy/init
 EGGDIR := ./chameleond.egg-info
 STREAM_SRCS = $(wildcard $(SRCDIR)/stream_server/*.c)
 STREAM_OBJS = $(patsubst $(SRCDIR)/stream_server/*.c,$(BINDIR)/%.o,$(STREAM_SRCS))
+CONFFILES = chameleond.conf
+IDENTITY_FILE := ~/trunk/src/scripts/mod_for_test_scripts/ssh_keys/testing_rsa
 
 TARGETS = directories chameleond
 
@@ -52,6 +56,11 @@
 # Get current time from the host.
 HOST_NOW := `date "+%Y-%m-%d %H:%M:%S"`
 
+# Check if this is a Chrome OS platform.
+# The '$' symbol in awk has to be doubled in Makefile.
+PLATFORM = $(shell awk -F= '/CHROMEOS_RELEASE_NAME/ {print $$2}' \
+	     /etc/lsb-release 2>/dev/null)
+
 .PHONY: install
 install:
 	@mkdir -p $(DESTDIR)
@@ -64,8 +73,16 @@
 	@NOW="`chameleond/utils/server_time`" deploy/deploy_pip
 endif
 	@python setup.py install -f
+
+ifeq ($(PLATFORM), Chrome OS)
+	@cp -f $(INITDIR)/$(CONFFILES) $(CONFDIR)
+	@echo Installing chameleon package on chrome os platform is completed.
+	@echo Please do \"\$ start chameleond\" or \"\$ restart chameleond\".
+else
 	@BUNDLE_VERSION=$(BUNDLE_VERSION) CHAMELEON_BOARD=$(CHAMELEON_BOARD) \
-	    deploy/deploy
+	deploy/deploy
+	@echo Installing chameleon package on fpga platform is completed.
+endif
 
 CHAMELEON_USER ?= root
 BUNDLE = chameleond-$(VERSION).tar.gz
@@ -78,8 +95,10 @@
 	@echo "Current host time: $(HOST_NOW)"
 ifdef CHAMELEON_HOST
 	@scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
+	    -i $(IDENTITY_FILE) \
 	    $(DISTDIR)/$(BUNDLE) $(CHAMELEON_USER)@$(CHAMELEON_HOST):/tmp
 	@ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
+	    -i $(IDENTITY_FILE) \
 	    $(CHAMELEON_USER)@$(CHAMELEON_HOST) \
 	    "cd /tmp && rm -rf $(BUNDLEDIR) && tar zxf $(BUNDLE) &&" \
 	    "cd $(BUNDLEDIR) && find -exec touch -c {} \; &&" \
diff --git a/chameleond/devices/bluetooth_hid_flow.py b/chameleond/devices/bluetooth_hid_flow.py
index 9766f94..32b9a00 100644
--- a/chameleond/devices/bluetooth_hid_flow.py
+++ b/chameleond/devices/bluetooth_hid_flow.py
@@ -1,13 +1,16 @@
+# -*- coding: utf-8 -*-
 # Copyright 2016 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.
 """The control interface of Bluetooth HID flow module driver."""
 
 import logging
+import subprocess
 
 from chameleond.devices import chameleon_device
 from chameleond.utils import common
 from chameleond.utils import serial_utils
+from chameleond.utils import system_tools
 from chameleond.utils.bluetooth_bluefruitle import BluefruitLE
 from chameleond.utils.bluetooth_hid import BluetoothHIDMouse
 from chameleond.utils.bluetooth_peripheral_kit import PeripheralKit
@@ -55,14 +58,34 @@
                                                 driver_name=self.DRIVER)
     return self._tty
 
+  def IsUSBHostMode(self):
+    """Check if the platform is in USB host mode.
+
+    Returns:
+      True if the platform is in USB host mode; otherwise, False.
+    """
+    try:
+      pci_info = system_tools.SystemTools.Output('lspci', '-v')
+    except subprocess.CalledProcessError:
+      logging.info('Failed to use lspci')
+      return False
+
+    for line in pci_info.splitlines():
+      if 'xhci_hcd' in line:
+        logging.info('USB host mode: %s', line)
+        return True
+
+    logging.info('Not in USB host mode')
+    return False
 
   def IsDetected(self):
     """Returns if the device can be detected."""
 
     # Enables Bluetooth HID port controller.
-    # Enables USB port device mode controller so USB host on the other side will
-    # not get confused when trying to enumerate this USB device.
-    self._usb_ctrl.EnableUSBOTGDriver()
+    # If the platform is 'chromeos' which always acts in the USB host mode,
+    # there is no need to enable the USB OTG driver.
+    if not self.IsUSBHostMode():
+      self._usb_ctrl.EnableUSBOTGDriver()
     self._usb_ctrl.EnableDriver()
     # Our Bluetooth HID flow differs substantially from other flows.
     # Everything needed for IsDetected does the job of InitDevice:
diff --git a/chameleond/drivers/fpga_tio.py b/chameleond/drivers/fpga_tio.py
index c991b3e..da0b919 100644
--- a/chameleond/drivers/fpga_tio.py
+++ b/chameleond/drivers/fpga_tio.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Copyright (c) 2014 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.
@@ -30,6 +31,7 @@
 from chameleond.utils import system_tools
 from chameleond.utils import usb
 from chameleond.utils import usb_printer_control
+from chameleond.utils.common import lazy
 
 
 class DriverError(Exception):
@@ -72,55 +74,41 @@
 
   def __init__(self, *args, **kwargs):
     super(ChameleondDriver, self).__init__(*args, **kwargs)
+
+    # The default platform is 'fpga', and could be 'chromeos' if specified.
+    platform = kwargs.get('platform', 'fpga')
     self._captured_params = {}
     self._process = None
 
-    main_bus = i2c.I2cBus(self._I2C_BUS_MAIN)
-    ext_board_bus = i2c.I2cBus(self._I2C_BUS_EXT_BOARD)
-    audio_codec_bus = i2c.I2cBus(self._I2C_BUS_AUDIO_CODEC)
-    fpga_ctrl = fpga.FpgaController()
-    usb_audio_ctrl = usb.USBAudioController()
-    usb_hid_ctrl = usb.USBController('g_hid')
-    usb_printer_ctrl = usb_printer_control.USBPrinterController()
-    bluetooth_hid_ctrl = usb.USBController(
+    logging.info("platform: %s", platform)
+
+    # waihong@chromium.org suggests to use a lazy wrapper which instantiates
+    # the following control objects when requested at the first time.
+    self._main_bus = lazy(i2c.I2cBus)(self._I2C_BUS_MAIN)
+    self._ext_board_bus = lazy(i2c.I2cBus)(self._I2C_BUS_EXT_BOARD)
+    self._audio_codec_bus = lazy(i2c.I2cBus)(self._I2C_BUS_AUDIO_CODEC)
+    self._fpga_ctrl = lazy(fpga.FpgaController)()
+    self._usb_audio_ctrl = lazy(usb.USBAudioController)()
+    self._usb_hid_ctrl = lazy(usb.USBController)('g_hid')
+    self._usb_printer_ctrl = lazy(usb_printer_control.USBPrinterController)()
+    self._bluetooth_hid_ctrl = lazy(usb.USBController)(
         bluetooth_hid_flow.BluetoothHIDMouseFlow.DRIVER)
-    bluetooth_hog_ctrl = usb.USBController(
+    self._bluetooth_hog_ctrl = lazy(usb.USBController)(
         bluetooth_hid_flow.BluetoothHOGMouseFlow.DRIVER)
 
-    self._devices = {
-        ids.DP1: input_flow.DpInputFlow(ids.DP1, main_bus, fpga_ctrl),
-        ids.DP2: input_flow.DpInputFlow(ids.DP2, main_bus, fpga_ctrl),
-        ids.HDMI: input_flow.HdmiInputFlow(ids.HDMI, main_bus, fpga_ctrl),
-        ids.VGA: input_flow.VgaInputFlow(ids.VGA, main_bus, fpga_ctrl),
-        ids.MIC: codec_flow.InputCodecFlow(ids.MIC, audio_codec_bus, fpga_ctrl),
-        ids.LINEIN: codec_flow.InputCodecFlow(
-            ids.LINEIN, audio_codec_bus, fpga_ctrl),
-        ids.LINEOUT: codec_flow.OutputCodecFlow(
-            ids.LINEOUT, audio_codec_bus, fpga_ctrl),
-        ids.USB_AUDIO_IN: usb_audio_flow.InputUSBAudioFlow(
-            ids.USB_AUDIO_IN, usb_audio_ctrl),
-        ids.USB_AUDIO_OUT: usb_audio_flow.OutputUSBAudioFlow(
-            ids.USB_AUDIO_OUT, usb_audio_ctrl),
-        ids.USB_KEYBOARD: usb_hid_flow.KeyboardUSBHIDFlow(
-            ids.USB_KEYBOARD, usb_hid_ctrl),
-        ids.USB_TOUCH: usb_hid_flow.TouchUSBHIDFlow(
-            ids.USB_TOUCH, usb_hid_ctrl),
-        ids.BLUETOOTH_HID_MOUSE: bluetooth_hid_flow.BluetoothHIDMouseFlow(
-            ids.BLUETOOTH_HID_MOUSE, bluetooth_hid_ctrl),
-        ids.BLUETOOTH_HOG_MOUSE: bluetooth_hid_flow.BluetoothHOGMouseFlow(
-            ids.BLUETOOTH_HOG_MOUSE, bluetooth_hog_ctrl),
-        ids.AVSYNC_PROBE: avsync_probe.AVSyncProbe(ids.AVSYNC_PROBE),
-        ids.AUDIO_BOARD: audio_board.AudioBoard(ext_board_bus),
-        ids.MOTOR_BOARD: motor_board.MotorBoard(ext_board_bus),
-        ids.USB_PRINTER: usb_printer_device.USBPrinter(usb_printer_ctrl),
-    }
+    if platform == 'chromeos':
+      self._devices = self.init_devices_for_chromeos()
+    else:
+      self._devices = self.init_devices_for_fpga()
 
     self._device_manager = device_manager.DeviceManager(self._devices)
     self._device_manager.Init()
     self._flows = self._device_manager.GetDetectedFlows()
 
-    # Allow to accees the methods through object.
+    # Allow to access the methods through object.
     # Hence, there is no need to export the methods in ChameleondDriver.
+    # An object in the following would be None if it is not instantiated
+    # in self._devices above.
     self.audio_board = self._device_manager.GetChameleonDevice(ids.AUDIO_BOARD)
     self.bluetooth_mouse = self._device_manager.GetChameleonDevice(
         ids.BLUETOOTH_HID_MOUSE)
@@ -135,6 +123,51 @@
 
     self.Reset()
 
+  def init_devices_for_chromeos(self):
+    devices = {
+        ids.BLUETOOTH_HID_MOUSE:
+            bluetooth_hid_flow.BluetoothHIDMouseFlow(
+                ids.BLUETOOTH_HID_MOUSE, self._bluetooth_hid_ctrl),
+        ids.BLUETOOTH_HOG_MOUSE:
+            bluetooth_hid_flow.BluetoothHOGMouseFlow(
+                ids.BLUETOOTH_HOG_MOUSE, self._bluetooth_hog_ctrl),
+    }
+    return devices
+
+  def init_devices_for_fpga(self):
+    devices = {
+        ids.DP1: input_flow.DpInputFlow(
+            ids.DP1, self._main_bus, self._fpga_ctrl),
+        ids.DP2: input_flow.DpInputFlow(
+            ids.DP2, self._main_bus, self._fpga_ctrl),
+        ids.HDMI: input_flow.HdmiInputFlow(
+            ids.HDMI, self._main_bus, self._fpga_ctrl),
+        ids.VGA: input_flow.VgaInputFlow(
+            ids.VGA, self._main_bus, self._fpga_ctrl),
+        ids.MIC: codec_flow.InputCodecFlow(
+            ids.MIC, self._audio_codec_bus, self._fpga_ctrl),
+        ids.LINEIN: codec_flow.InputCodecFlow(
+            ids.LINEIN, self._audio_codec_bus, self._fpga_ctrl),
+        ids.LINEOUT: codec_flow.OutputCodecFlow(
+            ids.LINEOUT, self._audio_codec_bus, self._fpga_ctrl),
+        ids.USB_AUDIO_IN: usb_audio_flow.InputUSBAudioFlow(
+            ids.USB_AUDIO_IN, self._usb_audio_ctrl),
+        ids.USB_AUDIO_OUT: usb_audio_flow.OutputUSBAudioFlow(
+            ids.USB_AUDIO_OUT, self._usb_audio_ctrl),
+        ids.USB_KEYBOARD: usb_hid_flow.KeyboardUSBHIDFlow(
+            ids.USB_KEYBOARD, self._usb_hid_ctrl),
+        ids.USB_TOUCH: usb_hid_flow.TouchUSBHIDFlow(
+            ids.USB_TOUCH, self._usb_hid_ctrl),
+        ids.AVSYNC_PROBE: avsync_probe.AVSyncProbe(ids.AVSYNC_PROBE),
+        ids.AUDIO_BOARD: audio_board.AudioBoard(self._ext_board_bus),
+        ids.MOTOR_BOARD: motor_board.MotorBoard(self._ext_board_bus),
+        ids.USB_PRINTER: usb_printer_device.USBPrinter(self._usb_printer_ctrl),
+    }
+
+    # The fpga board also supports all devices of chrome os.
+    devices.update(self.init_devices_for_chromeos())
+    return devices
+
   def Reset(self):
     """Resets Chameleon board."""
     logging.info('Execute the reset process')
diff --git a/chameleond/utils/common.py b/chameleond/utils/common.py
index 41d3f80..c9a2081 100644
--- a/chameleond/utils/common.py
+++ b/chameleond/utils/common.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Copyright (c) 2014 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.
@@ -36,3 +37,56 @@
                (func.__name__, str(value)))
     logging.warn(message)
     raise TimeoutError(message)
+
+
+def lazy(original_class):
+  """lazy instantiation of the original_class.
+
+  The original_class would be instantiated when any method or
+  data member is accessed at the first time.
+
+  Usage:
+    Assume that the orignal instantiation is as follows:
+
+      o = original_class(*args, **kwargs)
+
+    To use lazy instantiation, it would be something like
+
+      o = lazy(original_class)(*args, **kwargs)
+
+  Note:
+  - The following assignment statement would not instantiate the object.
+
+    oo = o
+
+  - However, the following statement would instantiate the object.
+
+    print o
+
+    since it invokes o.__str__()
+
+  Args:
+    original_class: the original class to be instantiated in the lazy way.
+  """
+
+  class LazyInstantiation(object):
+    """The lazy wrapper class."""
+
+    def __init__(self, *args, **kargs):
+      self._args = args
+      self._kargs = kargs
+      self._class = original_class
+      self._obj = None
+      self._loaded = False
+
+    def _load(self):
+      self._obj = self._class(*self._args, **self._kargs)
+      self._loaded = True
+
+    def __getattr__(self, name):
+      if not self._loaded:
+        logging.info('Load %s to access %s.', self._class.__name__, name)
+        self._load()
+      return getattr(self._obj, name)
+
+  return LazyInstantiation
diff --git a/chameleond/utils/mem.py b/chameleond/utils/mem.py
index 4741d6e..e1beb27 100644
--- a/chameleond/utils/mem.py
+++ b/chameleond/utils/mem.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
 # Copyright (c) 2014 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.
@@ -15,6 +16,9 @@
 import ctypes
 import time
 
+import chameleon_common  # pylint: disable=W0611
+from chameleond.utils.common import lazy
+
 
 class _Memory(object):
   """A class to abstract the memory access for IO."""
@@ -156,7 +160,10 @@
 _MMAP_START_HPS = 0xfc000000
 _MMAP_SIZE_HPS = 0x4000000
 
-# Singleton
-MemoryForController = _Memory(_MMAP_START_CONTROLLER, _MMAP_SIZE_CONTROLLER)
-MemoryForDumper = _Memory(_MMAP_START_DUMPER, _MMAP_SIZE_DUMPER)
-MemoryForHPS = _Memory(_MMAP_START_HPS, _MMAP_SIZE_HPS)
+
+# Lazy instantiation of the memory singletons since they are not supported
+# on a platform such as chromeos.
+MemoryForController = lazy(_Memory)(
+    _MMAP_START_CONTROLLER, _MMAP_SIZE_CONTROLLER)
+MemoryForDumper = lazy(_Memory)(_MMAP_START_DUMPER, _MMAP_SIZE_DUMPER)
+MemoryForHPS = lazy(_Memory)(_MMAP_START_HPS, _MMAP_SIZE_HPS)
diff --git a/chameleond/utils/system_tools.py b/chameleond/utils/system_tools.py
index 03f2d4e..153325a 100644
--- a/chameleond/utils/system_tools.py
+++ b/chameleond/utils/system_tools.py
@@ -1,8 +1,10 @@
+# -*- coding: utf-8 -*-
 # Copyright (c) 2014 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.
 """System tools required for Chameleond execution."""
 
+import logging
 import os
 import subprocess
 import threading
@@ -17,16 +19,17 @@
       'avsync': '/usr/bin/avsync',
       'chameleond': '/etc/init.d/chameleond',
       'date': '/bin/date',
+      'histogram': '/usr/bin/histogram',
+      'hpd_control': '/usr/bin/hpd_control',
       'i2cdump': '/usr/local/sbin/i2cdump',
       'i2cget': '/usr/local/sbin/i2cget',
       'i2cset': '/usr/local/sbin/i2cset',
-      'hpd_control': '/usr/bin/hpd_control',
       'lsmod': '/sbin/lsmod',
+      'lspci': 'lspci',
       'memtool': '/usr/bin/memtool',
       'modinfo': '/sbin/modinfo',
       'modprobe': '/sbin/modprobe',
       'reboot': '/sbin/reboot',
-      'histogram': '/usr/bin/histogram',
       'pixeldump': '/usr/bin/pixeldump',
       'printer': '/usr/bin/printer',
       'wget': '/usr/bin/wget',
@@ -44,7 +47,8 @@
     """
     for path in self._TOOL_PATHS.itervalues():
       if not os.path.isfile(path):
-        raise IOError('Required tool %s not existed' % path)
+        # It is okay that some tools may not exist in a particular platform.
+        logging.warning('IOError: Required tool %s not existed', path)
 
   def _MakeCommand(self, name, args):
     """Combines the system tool and its parameters into a list.
diff --git a/deploy/init/chameleond.conf b/deploy/init/chameleond.conf
new file mode 100644
index 0000000..599890d
--- /dev/null
+++ b/deploy/init/chameleond.conf
@@ -0,0 +1,16 @@
+# Copyright 2018 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.
+
+description     "Start the chameleon daemon"
+author          "chromium-os-dev@chromium.org"
+
+start on started system-services
+stop on stopping system-services
+
+respawn
+
+script
+  exec /usr/local/bin/run_chameleond --driver fpga_tio \
+    platform=chromeos >> /var/log/chameleond_init 2>&1
+end script